支付宝异步通知需要一个无鉴权 HTTP 入口,把表单参数透传给 payment-rpc HandleNotify,并按支付宝协议 返回纯文本 success。
任务
实现
添加路由
由于需要的是无鉴权HTTP入口,所以我们不能在原有mall.api中添加,而是新增一个payments.api重新写路径,而不经过鉴权中间件,main.go中也将这个配置为公开rpc方法
修改后再通过goctl生成api文件和handler
读取表单参数
在logic中有notify相关函数,进入对应writeAlipayNotifyResponse,这里可以返回纯文本成功或失败
支付宝通常以x-www-form-urlencoded发送通知,使用ParseForm解析字段,并读取支付宝的POST表单,都是单值字段,只取第一个值即可,读取r.PostForm
转换参数并调用rpc
使用HandleNotifyReq将读取到的参数转换为protobuf请求,并调用HandleNotify
接下来不会验签也不会处理支付状态,保证按照支付宝协议返回纯文本的success,且:
支付宝公钥等支付配置只保留在 payment-rpc。
主动查询与异步通知可以共用 payment-rpc 的幂等确认逻辑。
无论成功与否都返回200 OK,但是只有在满足以下条件的时候才会返回success:
1
2
3
4
表单解析成功
-> RPC 调用成功
-> RPC 响应非空
-> HandleNotifyResp.Ok == trueRPC内部
RPC处理逻辑位于service文件夹下相关文件handle_notify_logic.go
- 检查支付宝客户端是否配置,与通知参数是否为空
- 调用
DecodeNotify,使用支付宝公钥验签并解析通知 - 根据
out_trade_no查询本地流水,根据trade_status更新支付状态- 若为成功或完成,则调用
markPaid将支付流水标记为成功 - 若为关闭,若流水仍是pending,则标记为closed
- 若为成功或完成,则调用
测试
未登录访问
使用API路由测试,使用postman发送请求,注意返回状态码为200,但响应体是failure
(成功的响应需要等待映射到公网后使用支付宝沙箱App处理)
验签相关
notify的单测是在不启动服务的前提下,直接测试handler函数的业务
结构如下:
1
2
3
4
5
测试构造签名通知
-> HandleNotifyLogic.HandleNotify
-> 真实支付宝 SDK 验签
-> fakePaymentsModel 模拟数据库
-> 断言查询、更新次数和流水状态fakePaymentsModel实现了PaymentsModel接口,只保存一条payments记录,并统计调用FindByOutTradeNo和Update的次数
TestHandleNotify包含三个子测试:
- 首先传入一个格式正确但是内容无效的base64签名,这样会走到验签失败部分,此时fakemodel中的find和update仍然为0,表示验签在查询和更新流水前完成
- 接着调用
signedNotifyParams生成可以被支付宝sdk验证的签名,这里的逻辑是将sign相关的三个签名使用WithIgnore忽略,验签端会忽略这三个字段,所以签名和验签输入一致,成功后可以断言 - 最后调用两遍
HandleNotify,第二次仍可以验签,但是markPaid已经发现成功,直接返回,则find调用两次,但update只有一次,实现幂等性