01_微信小程序支付
【支付流程】
1.小程序内调用登录接口,获取到用户的openid(我们这一步骤让前端去获取)
2.服务端代码这边生成订单
3.服务端调用支付统一下单的api
4.服务端将再次签名,返回5个参数(前端得到数据后可以调起支付)
5.微信后台会回调我们服务端,我们通过回调更新订单状态
6.前端也会调用服务端订单查询接口,服务端查询订单状态(防止微信回调这边的一个时间差),如果成功了,在这个接口里会向用户发送一个小程序的模板消息(会消耗一个第3步的prepay_id,后续写模板消息的时候会说)
[ 概述 ]
重点是步骤3和4,特别是签名那块的格式要求务必按照微信的要求来。
【流程详解】
【 1.统一下单 】
目的:在小程序中先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易后调起支付。
接口:
https://api.mch.weixin.qq.com/pay/unifiedorder
请求参数
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
小程序ID | appid | 是 | String(32) | wxd678efh567hg6787 | 微信分配的小程序ID |
商户号 | mch_id | 是 | String(32) | 1230000109 | 微信支付分配的商户号 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,长度要求在32位以内。推荐随机数生成算法 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 通过签名算法计算得出的签名值,详见签名生成算法 |
签名类型 | sign_type | 否 | String(32) | MD5 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。 |
商品描述 | body | 是 | String(128) | 腾讯充值中心-QQ会员充值 |
商品简单描述,该字段请按照规范传递,具体请见参数规定 |
商品详情 | detail | 否 | String(6000) | 商品详细描述,对于使用单品优惠的商户,改字段必须按照规范上传,详见“单品优惠参数说明” | |
附加数据 | attach | 否 | String(127) | 深圳分店 | 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 |
商户订单号 | out_trade_no | 是 | String(32) | 20150806125346 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。详见商户订单号 |
标价币种 | fee_type | 否 | String(16) | CNY | 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型 |
标价金额 | total_fee | 是 | Int | 88 | 订单总金额,单位为分,详见支付金额 |
终端IP | spbill_create_ip | 是 | String(16) | 123.12.12.123 | APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。 |
交易起始时间 | time_start | 否 | String(14) | 20091225091010 | 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 |
交易结束时间 | time_expire | 否 | String(14) | 20091227091010 |
订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 建议:最短失效时间间隔大于1分钟 |
订单优惠标记 | goods_tag | 否 | String(32) | WXG | 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠 |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 |
交易类型 | trade_type | 是 | String(16) | JSAPI | 小程序取值如下:JSAPI,详细说明见参数规定 |
商品ID | product_id | 否 | String(32) | 12235413214070356458058 | trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。 |
指定支付方式 | limit_pay | 否 | String(32) | no_credit | 上传此参数no_credit--可限制用户不能使用信用卡支付 |
用户标识 | openid | 否 | String(128) | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o | trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取,可参考【获取openid】。 |
[ 统一下单的注意点 ]
1.签名sign生成算法,详见https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
2.随机字符串nonce_str,可以用加入当前时间戳+随机数的方法来生成
3.交易类型trade_type,小程序是JSAPI
4.统一下单和接下来的再次签名的签名类型sign_type,都统一使用MD5加密,不然可能会出现验证签名失败的情况,注意这是一个大坑。
[简易代码示例]
//第1步:拼接对应的参数 Map<String, String> param = new TreeMap<>(); param.put("appid", Config.MINI_PROGRAM_APP_ID); //像这些值,最好有个微信config类来存 param.put("mch_id", Config.MCH_ID); param.put("openid", openId); param.put("attach", orderDesc); param.put("body", orderDesc); param.put("detail", orderDesc); param.put("limit_pay", Config.NO_CREDIT_LIMIT_PAY); param.put("nonce_str", nonceStr); param.put("notify_url", Config.UNIFIED_ORDER_NOTIFY_RELATIVE_URL); param.put("out_trade_no", outTradeNo); param.put("spbill_create_ip", clientIp); param.put("total_fee", String.valueOf(totalFee)); param.put("trade_type", tradeType); param.put("sign_type","MD5"); //第2步:生成签名 StringBuilder urlParam = new StringBuilder(); for (String paramKey : param.keySet()) { //排序 urlParam.append(paramKey).append("=").append(param.get(paramKey)).append("&"); } String preParam = urlParam.toString().substring(0, urlParam.toString().length() - 1); //拼接的urlParam最后有一个&,删除 String finalParam = preParam + "&key=" + WeiXinConfig.PRIVATE_KEY; //key不参与排序 String sign = MD5.sign(finalParam, Const.Charset.UTF8).toUpperCase(); //排序后的参数+key 进行MD5签名,并且全部大写 //第三步:方便后续生成xml格式请求体,简易用一个类来封装对应的参数,然后返回 request.setAppId(param.get("appid")); request.setSign(sign); //request.set...其他参数,但要额外加一个成员变量存储sign
[ 返回结果 ]
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
返回状态码 | return_code | 是 | String(16) | SUCCESS |
SUCCESS/FAIL 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断 |
返回信息 | return_msg | 否 | String(128) | 签名失败 |
返回信息,如非空,为错误原因 签名失败 参数格式校验错误 |
以下字段在return_code为SUCCESS的时候有返回
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
小程序ID | appid | 是 | String(32) | wx8888888888888888 | 调用接口提交的小程序ID |
商户号 | mch_id | 是 | String(32) | 1900000109 | 调用接口提交的商户号 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 自定义参数,可以为请求支付的终端设备号等 |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 微信返回的随机字符串 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 微信返回的签名值,详见签名算法 |
业务结果 | result_code | 是 | String(16) | SUCCESS | SUCCESS/FAIL |
错误代码 | err_code | 否 | String(32) | SYSTEMERROR | 详细参见下文错误列表 |
错误代码描述 | err_code_des | 否 | String(128) | 系统错误 | 错误信息描述 |
以下字段在return_code 和result_code都为SUCCESS的时候有返回
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
交易类型 | trade_type | 是 | String(16) | JSAPI | 交易类型,取值为:JSAPI,NATIVE,APP等,说明详见参数规定 |
预支付交易会话标识 | prepay_id | 是 | String(64) | wx201410272009395522657a690389285100 | 微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时 |
二维码链接 | code_url | 否 | String(64) | URl:weixin://wxpay/s/An4baqw | trade_type为NATIVE时有返回,用于生成二维码,展示给用户进行扫码支付 |
【2.再次签名】
统一下单后,需要再次签名后,才能将数据返回给前端,不然会出现前端能调起微信支付,但是在支付过程中一直报签名失败的提示。
再次签名无需请求微信后台,只是自己对统一下单的一些参数做一次生成签名而已。
[ 再起签名需要的参数 ]
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
小程序ID | appId | 是 | String | wxd678efh567hg6787 | 微信分配的小程序ID |
时间戳 | timeStamp | 是 | String | 1490840662 | 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间 |
随机串 | nonceStr | 是 | String | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,不长于32位。推荐随机数生成算法 |
数据包 | package | 是 | String | prepay_id=wx2017033010242291fcfe0db70013231072 | 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=wx2017033010242291fcfe0db70013231072 |
签名方式 | signType | 是 | String | MD5 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致 |
举例如下:(必须和这里的格式一致,红色部分的也一样!)
paySign = MD5(appId=wxd678efh567hg6787&nonceStr=5K8264ILTKCH16CQ2502SI8ZNMTM67VS&package=prepay_id=wx2017033010242291fcfe0db70013231072&signType=MD5&timeStamp=1490840662&key=qazwsxedcrfvtgbyhnujmikolp111111)
[ 简易代码示例 ]
//第1步:拼接对应的参数 Map<String, String> param = new TreeMap<>(); String nonceStr = WeiXinPayUtils.generateNonceStr(); //专门写了个方法来生成随机字符串 param.put("appId", Config.MINI_PROGRAM_APP_ID); param.put("package", "prepay_id="+prepayId); //从统一下单响应的结果中取的参数 param.put("nonceStr", nonceStr); param.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); //秒级别的时间戳,应该毫秒也OK param.put("signType","MD5"); //小程序,这里和统一下单的签名类型保持一致 //第2步:再次生成签名 StringBuilder urlParam = new StringBuilder(); for (String paramKey : param.keySet()) { urlParam.append(paramKey).append("=").append(param.get(paramKey)).append("&"); } String preParam = urlParam.toString().substring(0, urlParam.toString().length() - 1); String finalParam = preParam + "&key=" + Config.PRIVATE_KEY; String sign = MD5.sign(finalParam, Const.Charset.UTF8).toUpperCase(); //同理,这里建议做一个响应参数给前端 需要返回给前端的参数有: response.setSign(sign); response.setAppId(param.get("appId")); response.setPrepayId(weiXinUnifiedOrderResponse.getPrepayId); //统一下单的响应数据 response.setNonceStr(param.get("nonceStr")); response.setTimeStamp(param.get("timeStamp")); response.setPayWay(Const.PayWay.WEIXIN); response.setAmount(amount); response.setSignType("MD5"); response.setOrderNo(outTradeNo); response.setCodeUrl(weiXinUnifiedOrderResponse.getCodeUrl());//统一下单的响应数据
再次签名之后,前端就可以根据服务端返回的参数发起正常的微信支付了。
【3.前端调微信支付需要的参数(即服务端响应参数必传参数)】
参考文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=3
前端调用wx.requestPayment(OBJECT)发起微信支付
注意:里面的时间戳timeStamp,nonceStr,package,signType,paySign都是服务端的参数,不是前端自己生成的随机数,注意!!!
要理解微信那边也只是根据这些数据来生成签名,会与我们再次签名生成的签名(即paySign)做一次比较,而这个paySign就是由这几个参数加密生成的,自己做项目时没有清楚意识到这一点,时间戳传给前端有误导致一直签名失败,这里是个坑,要注意。
Object参数说明:
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
timeStamp | String | 是 | 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间 |
nonceStr | String | 是 | 随机字符串,长度为32个字符以下。 |
package | String | 是 | 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=* |
signType | String | 是 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致 |
paySign | String | 是 | 签名,具体签名方案参见微信公众号支付帮助文档; |
success | Function | 否 | 接口调用成功的回调函数 |
fail | Function | 否 | 接口调用失败的回调函数 |
complete | Function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
回调结果:
回调类型 | errMsg | 说明 |
---|---|---|
success | requestPayment:ok | 调用支付成功 |
fail | requestPayment:fail cancel | 用户取消支付 |
fail | requestPayment:fail (detail message) | 调用支付失败,其中 detail message 为后台返回的详细失败原因 |
示例代码(前端代码):
wx.requestPayment(
{
'timeStamp': '',
'nonceStr': '',
'package': '',
'signType': 'MD5',
'paySign': '',
'success':function(res){},
'fail':function(res){
},
'complete':function(res){}
})
【微信小程序官方参考】
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3