赞
踩
在最近的工作中,引入了微信小程序支付,在开发过程中积累和整理了一些技术知识,现将其整理如下
目录
微信支付目前提供以下几种类型的支付产品
【1】付款码支付
付款码支付是用户展示微信钱包内的“刷卡条码/二维码”给商户系统扫描后直接完成支付的模式。主要应用线下面对面收银的场景。
【2】Native支付
Native支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。
【3】JSAPI支付
JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
1:用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
2:用户的好友在朋友圈、聊天窗口等分享商家页面链接,用户点击链接打开商家页面,完成支付
3:将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付。
“线下场景”
“公众号场景”
“PC网站场景”
【4】APP支付
APP支付又称移动端支付,是商户通过在移动端应用APP中集成开放SDK调起微信支付模块完成支付的模式。例如京东商城、外卖APP中选择微信支付场景。
【5】小程序支付
小程序支付是指商户通过调用微信支付小程序支付接口,在微信小程序平台内实现支付功能;用户打开商家助手小程序下单,输入支付密码并完成支付后,返回商家小程序。
【商户证书】
商户证书是微信提供的二进制文件,商户系统发起与微信支付后台服务器通信请求的时候,作为微信支付后台识别商户真实身份的凭据。
【签名】
商户后台和微信支付后台根据相同的密钥和算法生成一个结果,用于校验双方身份合法性。签名的算法由微信支付制定并公开,常用的签名方式有:MD5、SHA1、SHA256、HMAC等。
【JSAPI网页支付】
JSAPI网页支付即前文说的公众号支付,可在微信公众号、朋友圈、聊天会话中点击页面链接,或者用微信“扫一扫”扫描页面地址二维码在微信中打开商户HTML5页面,在页面内下单完成支付。
【Native原生支付】
Native原生支付即前文说的扫码支付,商户根据微信支付协议格式生成的二维码,用户通过微信“扫一扫”扫描二维码后即进入付款确认界面,输入密码即完成支付。
【支付密码】
支付密码是用户开通微信支付时单独设置的密码,用于确认支付完成交易授权。该密码与微信登录密码不同。
【Openid】
用户在公众号内的身份标识,不同公众号拥有不同的openid。商户后台系统通过登录授权、支付通知、查询订单等API可获取到用户的openid。主要用途是判断同一个用户,对用户发送客服消息、模板消息等。企业号用户需要使用企业号userid转openid接口将企业成员的userid转换成openid。
(三)微信支付商户账号配置
【1】HTTP请求提交方式使用POST
【2】提交和返回数据都为XML格式,根节点名为xml
【3】签名算法 MD5/HMAC-SHA256
【4】开发者代码判断逻辑:先判断协议字段返回,再判断业务返回,最后判断交易状态。
【5】支付交易金额单位为分,可以使用整型类型存储。
【6】交易类型trade_type:JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付,不同trade_type决定了调起支付的方式,请根据支付产品正确上传
【7】自定义的商户订单号参考:
商户支付的订单号由商户自定义生成,仅支持使用字母、数字、中划线-、下划线_、竖线|、星号*这些英文半角字符的组合,请勿使用汉字或全角等特殊字符。微信支付要求商户订单号保持唯一性(建议根据当前系统时间加随机序列来生成订单号)。
【8】重新发起一笔支付要使用原订单号,避免重复支付;已支付过或已调用关单、撤销的订单号不能重新发起支付。
参考文档:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
参考文档:
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3
商户系统(即我们自己开发的服务端程序)和微信支付系统主要交互:
1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】
2、商户server调用支付统一下单,api参见公共api【统一下单API】,此时需要生成订单号,处于预处理阶段,还未展示用户支付页面。
3、商户server调用再次签名,api参见公共api【再次签名】
4、商户server接收支付通知,api参见公共api【支付结果通知API】
5、商户server查询支付结果,如未收到支付通知的情况,商户后台系统可调用【查询订单API】 (查单实现可参考:支付回调和查单实现指引)
6、商户小程序内使用小程序调起支付API(wx.requestPayment)发起微信支付,详见小程序API文档。
【1】微信支付整体上分成两个步骤,第一步商户系统先调用该接口在微信支付服务后台生成预支付交易单,第二步用户输入支付密码完成支付。
【2】在项目开发流程中,建议开发侧需要创建一个微信支付记录表,这个表需要记录商户订单号,openId,预支付金额(商户自己保存的金额),用户实际支付金额(微信支付系统返回的金额),预支付交易单时间,实际支付时间,微信返回支付状态等等,可以根据实际情况进行添加,这个主要的作用是对支付做好记录,支付时间支付状态,以及需要对比系统支付金额和微信支付金额对账,用户是否取消付款,或者因为某些情况导致付款失败原因等等。
【3】微信支付结果仅通过微信服务器推送回调地址可能会因为某些原因(服务器重启宕机,网络延迟等)造成业务侧存在异常,所以更加可靠的做法是还需要主动通过查询接口去查询订单情况,并及时对过期的订单通过关闭订单接口及时关闭订单。
【4】上面微信接口规则提及:
判断逻辑:先判断协议字段返回,再判断业务返回,最后判断交易状态。这个放在开发侧就是,对于微信响应或者推送的数据,大致判断逻辑如下:
1:首先判断返回状态:return_code ,该结果SUCCESS或者FAIL
2:然后判断业务返回状态:result_code
3:最后判断交易状态:trade_state
注意:交易成功判断条件: return_code、result_code和trade_state都为SUCCESS
【5】订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。
【6】微信支付结果通知:
后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总共会发起多次通知,通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)这里通知发送可能会多台服务器进行发送,且发送时间可能会在几秒内,但微信不保证通知最终一定能成功。
在订单状态不明或者没有收到微信支付结果通知的情况下,建议商户主动调用微信支付【查询订单API】确认订单状态。
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_9&index=1
微信开发 Java SDK ,支持包括微信支付,开放平台,小程序,企业微信,公众号等的后端开发。
- <dependency>
-
- <groupId>com.github.binarywang</groupId>
-
- <artifactId>weixin-java-miniapp</artifactId>
-
- <version>3.3.0</version>
-
- </dependency>
-
- <dependency>
-
- <groupId>com.github.binarywang</groupId>
-
- <artifactId>weixin-java-pay</artifactId>
-
- <version>3.3.0</version>
-
- </dependency>
【发起预支付订单接口】
com.github.binarywang.wxpay.service.WxPayService#createOrder
【订单查询接口】
com.github.binarywang.wxpay.service.impl.BaseWxPayServiceImpl#queryOrder(java.lang.String, java.lang.String)
【关闭订单接口】
com.github.binarywang.wxpay.service.impl.BaseWxPayServiceImpl#closeOrder(java.lang.String)
【binarywang文档参考】
https://github.com/binarywang/weixin-java-pay-demo
下面展示了微信支付流程中创建订单、用户订单未支付、订单查询的请求和响应XML参考信息,其中隐私数据做了适当的掩码处理。
【请求地址】:https://api.mch.weixin.qq.com/pay/unifiedorder
【请求数据】:<xml>
<appid>wx123445678</appid>
<mch_id>16057493906</mch_id>
<nonce_str>16618447757287</nonce_str>
<sign>5C3D79AAC043489D78E0C0DC6EAD9DE66EF</sign>
<sign_type>MD5</sign_type>
<body>W54527491126157300</body>
<out_trade_no>ORDER3545J_27491126157300</out_trade_no>
<total_fee>1</total_fee>
<spbill_create_ip>127.0.0.1</spbill_create_ip>
<notify_url>http://xxx/wx/order/notify</notify_url>
<trade_type>JSAPI</trade_type>
<openid>okC12345556781t6VDC9OVWA</openid>
</xml>
【响应数据】:<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<result_code><![CDATA[SUCCESS]]></result_code>
<mch_id><![CDATA[160575593906]]></mch_id>
<appid><![CDATA[wx12555345678]]></appid>
<nonce_str><![CDATA[k1cwhifg55ENmTWL2w]]></nonce_str>
<sign><![CDATA[9AF0451334E07A0702F9B6CA29118AC670]]></sign>
<prepay_id><![CDATA[wx3014562dd74959d9af493e167d0000]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>
【请求地址】:https://api.mch.weixin.qq.com/pay/orderquery
【请求数据】:<xml>
<appid>wx123445455678</appid>
<mch_id>1605734553393906</mch_id>
<nonce_str>1661854547907726</nonce_str>
<sign>1F2351D4E82FB54548B917CCD7CD27AF691D</sign>
<sign_type>MD5</sign_type>
<out_trade_no>5454126157300</out_trade_no>
</xml>
【响应数据】:<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<result_code><![CDATA[SUCCESS]]></result_code>
<mch_id><![CDATA[16057334554393906]]></mch_id>
<appid><![CDATA[wx12354545678]]></appid>
<trade_state><![CDATA[NOTPAY]]></trade_state>
<out_trade_no><![CDATA[ORDE45491126157300]]></out_trade_no>
<attach><![CDATA[]]></attach>
<trade_state_desc><![CDATA[订单未支付]]></trade_state_desc>
<nonce_str><![CDATA[96aLm454545Vmgk9]]></nonce_str>
<sign><![CDATA[02337B24545EADE0990F2DF23]]></sign>
</xml>
【请求地址】:https://api.mch.weixin.qq.com/pay/orderquery 【请求数据】:<xml> <appid>wx12345678</appid> <mch_id>16057333956563906</mch_id> <nonce_str>16620065653726538</nonce_str> <sign>7B73926CD856565C81D073CB64234443FD63</sign> <sign_type>MD5</sign_type> <out_trade_no>656565180011941175300</out_trade_no> </xml> 【响应数据】:<xml><return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> <result_code><![CDATA[SUCCESS]]></result_code> <mch_id><![CDATA[160576533393906]]></mch_id> <appid><![CDATA[wx1662345678]]></appid> <openid><![CDATA[okC663232h1t6VDC9OVWA]]></openid> <is_subscribe><![CDATA[N]]></is_subscribe> <trade_type><![CDATA[JSAPI]]></trade_type> <trade_state><![CDATA[SUCCESS]]></trade_state> <bank_type><![CDATA[OTHERS]]></bank_type> <total_fee>1</total_fee> <fee_type><![CDATA[CNY]]></fee_type> <cash_fee>1</cash_fee> <cash_fee_type><![CDATA[CNY]]></cash_fee_type> <transaction_id><![CDATA[42000016112656502209010323687410]]></transaction_id> <out_trade_no><![CDATA[WH6565011941175300]]></out_trade_no> <attach><![CDATA[]]></attach> <time_end><![CDATA[20226564445]]></time_end> <trade_state_desc><![CDATA[支付成功]]></trade_state_desc> <nonce_str><![CDATA[t7mKr65653qsUxrL]]></nonce_str> <sign><![CDATA[5CCC16FE6656DCE0EDB2078CB5]]></sign> </xml> |
以下记录了微信支付开发过程中的一些注意事项和设计参考
在用户输入密码支付完成之后,我们需要及时接受微信支付服务器处理结果,但是这个过程可能不是那么及时,因为在某些设计时我们需要在前端轮询获取后端对应订单的支付结果,并根据结果进行处理。
【1】我们给支付中心下单
【2】我们调用支付中心生成微信支付二维码地址
【3】用户支付之后,微信通知回调支付中心
【4】支付中心回调通知我们后台系统
【5】我们前端页面轮询我们的后台系统获取订单的支付状态
微信支付结果通知响应字段中没有trade_state交易结果字段响应,而微信支付官方描述说:交易成功判断条件: return_code、result_code和trade_state都为SUCCESS,针对此问题,一个比较好的参考解决方式是,当接受到支付回调时检查return_code、result_code是否都是SUCCESS,当判断成功后,再去调用微信支付查询接口,然后根据查询接口的return_code、result_code和trade_state都为SUCCESS来判断最终交易是否确实成功完成。
为什么要引入定时任务?这是为了对一些异常数据做补偿处理。为了避免支付回调不成功,出现用户付款成功,却没有执行功能服务的情况。可以使用定时任务,主动去"支付系统"中查询订单的支付状态,这是一种补偿机制。
比如我们可以在每天通过定时任务去检索那么未最终完成的订单,通过查询微信服务器判断其最终状态,未支付,已关闭,超时等等,此外,通过定时任务可以定期关闭那么过期的订单。
微信支付接口的 return_code、result_code、trade_state
变量名 | 字段名 | 官方描述 |
return_code | 返回状态码 | SUCCESS/FAIL 此字段是通信标识,非交易标识,交易是否成功需要查看 trade_state 来判断 在“统一下单”和“支付结果通知”中,该描述变成了:交易是否成功需要查看 result_code 来判断 |
result_code | 业务结果 | SUCCESS/FAIL |
trade_state | 交易状态 | SUCCESS — 支付成功 REFUND — 转入退款 NOTPAY — 未支付 CLOSED — 已关闭 REVOKED — 已撤销(付款码支付) USERPAYING — 用户支付中(付款码支付) PAYERROR — 支付失败(其他原因,如银行返回失败) |
总的来说:
【1】return_code 是用来判断通信状态的,可以理解为在“结果通知”时必为 SUCCESS;
【2】result_code 是用来判断业务结果的,指一次调用接口或回调的动作是否如愿执行成功。如“关闭订单”时关闭成功为 SUCCESS,因参数配置错误、找不到订单号、订单状态不允许关闭等其它关闭失败的情况为 FAIL;
【3】trade_state 是用来判断交易状态的,“交易”是指微信支付订单。
【4】保险起见,我们在异步收到“结果通知”时,不要相信文档去判断 result_code,应调用“查询订单”,并判断 trade_state。
【5】接收和解析回调结果时检查到result_code 和return_code 为SUCCESS的时候应该主动“查询订单”然后在根据trade_state处理返回结果
【1】支付完成后,微信会把相关支付结果及用户信息通过数据流的形式发送给商户,商户需要接收处理,并按文档规范返回应答。
【2】同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。订单的处理必须要做到幂等性的。
【3】后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总共会发起多次通知,通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m),但微信不保证通知最终一定能成功。
【4】商户处理后同步返回给微信参数:
<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml> |
注意:严格遵守返回要求,否则微信会一直发送通知
参考binarywang工具:
@ApiOperation(value = "支付回调通知处理") @PostMapping("/notify/order") public String parseOrderNotifyResult(@RequestBody String xmlData) throws WxPayException { final WxPayOrderNotifyResult notifyResult = this.wxService.parseOrderNotifyResult(xmlData); // TODO 根据自己业务场景需要构造返回对象 return WxPayNotifyResponse.success("成功"); } |
查询接口这个字段
微信支付接口的数据类型都是整型,将原来值X 100的,但是我们在设计自己系统时不一定非要按照微信的来,如果自己已有设计金额存储,那么就保持原样即可,千万不要在数据库中使用多种存储方式,如果之前有存储int类型,那么统一用这个,如果用的是decimal那么就统一使用这个,不要混用类型,不然很容易出现问题,就单独在和微信端交互时转换即可,其他地方都是保持自己的统一设计。
这是我找资料时发现的一个开源的比较完整的支付系统,感兴趣或者需要对支付系统进阶开发的可以学习了解一下。
【1】对于微信支付的数据,不仅仅需要处理微信支付系统回调通知的数据,还必须要进行主动查询验证,对于异步回调可能会存在因为服务器停机或者其他情况影响未收到微信推送结果,所以相对保险的方式就是需要进行主动查询或者设置一个定时任务,对未完结的订单进行对账查询,保证支付不会出现问题。
【2】用户点击支付生成预支付订单时但是取消支付,对于这个订单该如何处理?这个订单如果取消支付了,根据实际业务进行处理,目前我的一个处理方式是订单保留,并且会查询微信支付系统对应的状态,正常情况下应该是“用户取消支付”
【3】对于订单支付类,需要在后台设置对应的功能模块进行展示,要详细记录订单支付信息。
【4】处理结果分析:
首先需要判断return_code和return_msg,只有当return_code=SUCCESS是才表示当前请求成功,如果不是则需要记录return_msg不用在处理其他信息。result_code需要记录,这是业务结果。核心字段:trade_state和trade_type和trade_state_desc
【5】微信支付回写结果该怎么处理:
1:前端先展示中间的状态,JS回调有两种,成功时显示订单处理中,具体的结果还需要后台处理,JS失败时:支付未完成
2:后台通过回调进行处理,不要主动查询和回调一起处理,避免出现重复处理,导致前后结果覆盖或者锁表。
3:前端处理成功后,跳转到一个订单处理中的页面,前端不断地轮询后台结果,此时查询的自己数据库系统的数据,持续查询两次,每次间隔2秒。
【6】订单支付表建议设置两个字段
1:订单查询状态字段
2:订单回调状态字段
上面这两个字段的意义在于是否进行了订单的主动查询以及支付结果的回调处理,上面这两个字段可以设计为数字,默认为0,每次处理增加1即可,这两个字段可以不展示,留存在数据库里面,可以作为后面排查问题。
在这里,选择微信支付中JSAPI的核心接口统一下单场景来讲述微信支付的消息保护。统一下单的使用场景为除付款码支付外,其他的支付场景下,商户系统先调用统一下单接口在微信支付服务后台生成预支付交易单,再根据后台服务返回的预支付交易会话标识调用JSAPI完成交易支付的场景。JSAPI交易支付的流程如图所示。
用户使用移动端设备(比如手机)打开商户网页选购商品,在确认微信支付后,网页调用JS getBrandWCPayRequest接口发起微信支付请求,进入支付页面。当用户输入密码成功支付后,直接进入支付成功页面。商户应用程序后台服务在收到来自微信开放平台的支付成功回调通知后,标志该笔订单支付流程结束。
微信支付使用的消息格式为自定义的XML格式,对于通信的安全性除了使用基本的身份鉴别措施,如商户注册认证、商户密钥、API证书的单双向认证、商户支付密钥,还使用了随机数算法、签名算法、HTTPS来保证消息在通信过程中的安全性。
统一下单的消息格式的主要字段及消息格式,其官方有如下定义。
【1】请求消息关键部分格式如下。在请求消息的格式定义中可以看到,包含了随机字符串、签名、签名类型等与消息保护的关键字段。
而与之对应的响应消息,其格式类似。
【2】应答消息关键部分格式如下。在应答消息的格式定义中除了与业务相关的返回状态码、服务商商户APPID、服务商商户号字段外,也可以包含随机字符串、签名等字段。
在这些字段中,与消息保护关系密切的三个字段是sign、sign_type、nonce_str,下面来详细看看其使用过程。
使用签名算法之前,先生成随机数,微信支付随机数nonce_str生成算法可以在官方文档中找到。
当对传输的消息采用签名算法时,其签名过程如下。
【1】将多个参数以键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
【2】将参数键值对按照参数名ASCII字典序排序。
【3】将stringA拼接上key得到stringSignTemp字符串,再对stringSignTemp按照指定的签名算法(MD5或HMAC-SHA256)进行运算,最后将得到的字符串中所有字符转换为大写,得到sign值。算法表示为sign=Upper(签名算法(stringA+key))。
通过上述步骤后,将生成的sign值放入请求消息或应答消息中,供通信的对方验签使用。当然,微信支付在签名过程中,还需要注意如果参数的值为空不参与签名、参数名排序时区分大小写等问题(因此处主要分析API的消息保护,故其相关注意事项不在此展开)。而通信的对方在验签时,其过程为生成sign的逆操作,首先判断消息格式中是否包含sign字段,如果包含sign字段,则对所有参数进行签名,将获得的签名值与传输过来的sign进行比对,结果一致则验签通过。
除了上述的消息签名外,对于微信支付接口的安全性还有一些其他的安全策略,比如敏感数据加密处理、外部请求数据访问必须进行鉴权操作、通过XML外部实体引用的限制来防止XXE,这些都是值得学习的地方
【1】小程序微信支付API接口文档
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
【2】微信小程序前端JS微信支付API
wx.requestPayment(Object object) | 微信开放文档
【3】小程序调起支付API
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5
【4】普通商户接入开发文档
https://pay.weixin.qq.com/wiki/doc/api/index.html
【5】Jeequan支付系统
【6】binarywang微信开发JAVA工具参考
GitHub - binarywang/weixin-java-pay-demo: 基于Spring Boot 和 WxJava 实现的微信支付Java后端Demo
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。