● 微信支付服务商(一、踩坑记录)

文章索引: 微信支付服务商(一、踩坑记录) 微信支付服务商(二、分账功能)

1、服务商账户注册

详细步骤可以参考接入流程: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/open/pay/chapter7_1_2.shtml


由于微信支付的产品体系全部搭载于微信的社交体系之上,所以直连商户或服务商商户接入微信支付之前,都需要有一个微信社交载体,该载体对应的ID即为APPID。 目前服务商的社交载体只能是公众号,服务商可通过公众平台完成公众号注册申请。 公众号申请成功后,服务商可登录公众号平台即可获取对应的APPID,具体查阅目录为 【开发-> 基本配置-> 公众号开发信息】,如下图所示。通常情况下,一个服务商只需申请一个公众号即可。

以上所述,注册微信支付服务商,要先注册公众号。

2、业务场景

小程序的开发:第三方服务商帮商户开发小程序,有三种业务场景:` 1、第三方自己申请账号,自己开发,生成指定内页给特约商户用,该模式简称中心化模式。 2、以特约商户身份申请小程序appid,第三方完成开发,该模式简称外包模式。 3、通过开放平台第三方开发者代特约商户进行小程序的开发,该模式简称第三方模式。 无论哪种模式开发的小程序,在使用面对用户的微信支付能力时,都以该小程序appid为主体id来调用微信开放平台提供的api。 以在某小程序中发起微信支付为例,分后台下单和前端js拉起收银台两部分。 其中,后台下单对应微信支付的三大类开放模式,前端js拉起收银台需通过该小程序的appid,且该appid参与后台下单。即,根据不同的开放模式和业务场景枚举,第三方开发小程序使用微信支付时,可分为9种(3*3)不同的组合模式,结合实际业务诉求选择对应的组合模式进行开发。

注意: 以上3*3共计9种组合,均能成功接入微信支付。但红色连接线为二清模式,政策上不允许采用该模式。有需求的第三方开发者可通过银行渠道商模式接入。

一共9种组合,我只用其中两种:外包模式+普通服务商模式中心化模式+普通服务商模式

参考资料: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/open/pay/chapter2_8_0.shtml https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_10&index=1

3、外包模式+普通服务商模式

外包模式:以特约商户主体注册微信支付,以特约商户主体申请小程序。简单归纳:服务商提供支付功能,每个客户各自注册小程序,每个小程序要单独提交上线。客户多的话比较麻烦,需要同时维护很多小程序。如果每个特约商户小程序完全一样(比如商城、充电桩、电动车等有代理的小程序),不适合用这种模式。

左侧为普通商户接入模式,一般费率为0.6%,也就是用户充值100元,实际到账99.4元;右侧为外包模式+普通服务商模式,服务商申请的特约商户费率一般为0.6%,但是可以申请优惠,最低为0.2%,如下所示:

坑1. openid的获取

统一下单接口: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_sl_api.php?chapter=9_1

微信支付普通模式下,商户直接接入微信支付,openid是小程序的openid;微信普通服务商模式下openid可以「选传」,如下图所示:

按图中的描述,sub_openid才是小程序的openid。

这里的appid也不是「服务商商户的小程序ID(APPID)」,而是「服务商所在载体的公众号appid」。

整理一下,「统一下单接口」需要以下参数:

变量名 描述
appid 服务商公众号APP ID
mch_id 服务商商户id
nonce_str 随机字符串
sign 签名,详见签名生成算法
body 商品简单描述
out_trade_no 商户系统内部订单号
total_fee 订单总金额,单位:分
spbill_create_ip 终端IP
notify_url 接收微信支付异步通知回调地址
trade_type JSAPI
sub_appid 特约商户小程序APP ID
sub_mch_id 特约商户微信商户id
sub_openid 用户在特约商户小程序的openid

比普通微信支付,少了open_id,多了最后三项sub_***。这样「统一下单接口」就调通了。

坑2.支付验证签名失败

跳过了上面的坑,支付的时候会提示“支付验证签名失败”。使用(签名校验工具)没有任何问题。经过查找资料才想明白:「统一下单接口」的appid是服务商公众号APP ID,而不是小程序的APP ID,导致支付验证签名失败

2.1 服务商特约商户小程序调起支付API文档:

https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_sl_api.php?chapter=7_7&index=5

2.2 普通商户小程序调起支付API V2文档:

https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=3

(V2在线文档截图,signType默认为MD5加密)

这一页文档有个精神分裂的地方,appId明明是必填,示例代码却没有appId,实际项目中也可不填,所以appId在微信支付普通商户中并不是文档所说的必填项。

(V2在线文档示例代码截图,appId可不填)

2.3 普通商户小程序调起支付API V3文档:

https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml

(V3在线文档截图,signType仅支持RSA加密,appId可不填)

如上所述,服务商特约商户小程序调起支付API,比普通商户小程序调起支付API多了一个参数appId。补上特约商户小程序appId,再用MD5算法重新生成paySign,而不是用「统一下单接口」返回的paySign,微信支付就成功了。代码如下:

paySign = MD5(appId=小程序appid&nonceStr=统一下单接口返回nonceStr&package=prepay_id=统一下单接口返回package&signType=MD5&timeStamp=统一下单接口返回timeStamp&key=特约商户微信支付秘钥)

wx.requestPayment({
      appId: '小程序appid',
      timeStamp: '统一下单接口返回timeStamp',
      nonceStr: '统一下单接口返回nonceStr',
      package: '统一下单接口返回package',
      signType: 'MD5',
      paySign: '上面重新生成的paySign',
      "success": function (res) {   // 支付成功的状态
        console.log(res);
      },
      "fail": function (res) {      // 支付失败的状态
        console.log(res);
      }
    });

paySign算法建议通过后台计算后返回,避免微信支付秘钥写在小程序里。

4、中心化模式+普通服务商模式

中心化模式:以特约商户主体注册微信支付,以服务商主体申请小程序,如图最右侧所示。简单归纳:服务商提供支付功能,并注册小程序,只给客户开通特约商户的为微信支付子账户。只维护服务商小程序即可,比较简单方便。这种模式适合代理模式,各个代理不考虑技术问题,只负责推广销售、按照不同子账户分红提成,服务商负责技术支撑、统一维护。

统一下单接口依旧需要openid和sub_mch_id,openid从小程序获取,不再需要sub_appid和sub_openid。

整理一下,「统一下单接口」需要以下参数:

变量名 描述
appid 服务商小程序APP ID(外包模式是公众号APP ID)
mch_id 服务商商户id
openid 服务商小程序的openid
nonce_str 随机字符串
sign 签名,详见签名生成算法
body 商品简单描述
out_trade_no 商户系统内部订单号
total_fee 订单总金额,单位:分
spbill_create_ip 终端IP
notify_url 接收微信支付异步通知回调地址
trade_type JSAPI
sub_mch_id 特约商户微信商户id

小程序中,中心化模式与外包模式小程序调起支付API的区别是:paySign无需再次计算,直接使用「统一下单接口」返回的paySign即可。代码如下:

wx.requestPayment({
      timeStamp: '统一下单接口返回timeStamp',
      nonceStr: '统一下单接口返回nonceStr',
      package: '统一下单接口返回package',
      signType: 'MD5',
      paySign: '统一下单接口返回paySign',
      "success": function (res) {   // 支付成功的状态
        console.log(res);
      },
      "fail": function (res) {      // 支付失败的状态
        console.log(res);
      }
    });

appId省略也可以正常支付。