支付宝与微信的支付和退款功能引发的血案
接手前辈项目,在一次退款操作后发现,用户余额没减反增。正常应该是余额清零的,结果却是余额比退款前都多。我想说一句MMP,但问题还是要解决。带着这个问题,我又去好好看了下支付宝与微信支付和退款的相关文档,再结合项目逻辑终于将BUG修复。
支付宝与微信的支付功能都是在回调中确定成功与否,这一点没什么好质疑的,但是在退款功能上就有诸多不同之处。
共同点是支付宝与微信的退款,可以在退款请求的返回中得到退款成功与否的答案。也就是退款操作是同步操作,可以直接在请求结果中判断是否成功。
并且双方都支持异步操作,但支持的方式却又不同。
微信在退款请求中可以自定义退款回调的地址,这样你可以随意去写代码逻辑。这一点上微信值得表扬,还有在回调的响应上也不必非要返回XML格式的,直接返回字符串 success 也是没问题的。
支付宝却不支持在退款的请求中定义回调地址,它把支付时的回调地址作为退款地址,对于支付宝来说,这样多一层安全。但是对与开发者来说却需要在支付回调接口中做更多的操作。
支付宝的退款回调是不可控的,你不能和微信退款一样可以自己决定需不需要,它认为你需要,所以你就需要。
更骚是,在退款回调的数据与支付回调的数据上没有明确的区分。调同一个接口,若不能区分场景,想想支付宝也绝不能这样。
我在查阅资料时,看到有网友说 notify_type 作为判断依据的,真希望他程序依然坚挺。
官方文档也不是绝对的靠谱,不信自己将数据打印出来看看就知道,这里我打印了支付、全额退款、部分退款三种情况的数据:
// 充值
{
"gmt_create":"2020-11-10 15:23:12",
"charset":"UTF-8",
"seller_email":"收款账号",
"subject":"充值",
"sign":"签名",
"body":"充值",
"buyer_id":"208880123456789",
"invoice_amount":"0.10",
"notify_id":"2020111000222123456789123456789",
"fund_bill_list":"[{"amount":"0.10","fundChannel":"PCREDIT"}]",
"notify_type":"trade_status_sync",
"trade_status":"TRADE_SUCCESS",
"receipt_amount":"0.10",
"app_id":"201900000000000",
"buyer_pay_amount":"0.10",
"sign_type":"RSA2",
"seller_id":"2088631234567890",
"gmt_payment":"2020-11-10 15:23:12",
"notify_time":"2020-11-10 15:23:13",
"passback_params":"80",
"version":"1.0",
"out_trade_no":"P20201110152305602862",
"total_amount":"0.10",
"trade_no":"2020111022001428471425207380",
"auth_app_id":"201900000000000",
"buyer_logon_id":"支付账号",
"point_amount":"0.00"
}
// 全部退款
{
"gmt_create":"2020-11-10 15:23:12",
"charset":"UTF-8",
"seller_email":"收款账号",
"subject":"退款",
"sign":"签名",
"body":"退款",
"buyer_id":"208880123456789",
"notify_id":"2020111000222123456789123456788",
"notify_type":"trade_status_sync",
"trade_status":"TRADE_CLOSED",
"app_id":"201900000000000",
"sign_type":"RSA2",
"seller_id":"2088631234567890",
"gmt_payment":"2020-11-10 15:23:12",
"notify_time":"2020-11-10 15:23:57",
"gmt_refund":"2020-11-10 15:23:56.491",
"out_biz_no":"R162020111099505648",
"passback_params":"80",
"version":"1.0",
"out_trade_no":"R20201110152305602862",
"total_amount":"0.10",
"refund_fee":"0.10",
"trade_no":"2020111022001428471425207380",
"auth_app_id":"201900000000000",
"gmt_close":"2020-11-10 15:23:56",
"buyer_logon_id":"支付账号"
}
// 部分退款
{
"gmt_create":"2020-11-10 15:27:56",
"charset":"UTF-8",
"seller_email":"收款账号",
"subject":"退款",
"sign":"签名",
"body":"退款",
"buyer_id":"208880123456789",
"notify_id":"2020111000222123456789123456787",
"notify_type":"trade_status_sync",
"trade_status":"TRADE_SUCCESS",
"app_id":"201900000000000",
"sign_type":"RSA2",
"seller_id":"2088631234567890",
"gmt_payment":"2020-11-10 15:27:57",
"notify_time":"2020-11-10 15:28:44",
"trade_refund_time":"2020-11-10 15:28:43.451",
"out_biz_no":"R162020111098485753",
"passback_params":"80",
"version":"1.0",
"out_trade_no":"R20201110152751284779",
"total_amount":"0.20",
"refund_fee":"0.10",
"trade_no":"2020111022001428471425190999",
"auth_app_id":"201900000000000",
"buyer_logon_id":"支付账号"
}
要区分支付与退款,我们肯定在请求时的数据就不会一样的,这样返回的部分数据也会随之改变。但这毕竟是我们去做自定义数据,最靠谱的还是官方数据上做区分。
经过多次在各种情况的退款数据对比,不难发现在退款时:
- 退款金额 refund_fee 一定存在且大于0(偶尔支付时存在该字段且值为空)。
- 交易退款时间 gmt_refund 或 trade_refund_time 必定存在一个且不为空。
依据以上两点应该可以区分是支付或退款。
项目原来的支付回调中没有考虑退款的问题,当用户提交退款申请,订单状态变为待退款状态。然而当管理员操作退款后,支付宝再次访问回调,更致命的是,里面同样的字段依旧是那个订单号。得到订单号之后判断订单状态是否是支付成功,只有支付成功才会被拦击,退款中也就继续充值了,悲剧一次次上演。
对于支付宝与微信研究尚浅,若有不当之处,还请有经验的大牛不吝赐教,感激不尽!