红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【BudgetMatch】增加支付宝异步通知网关路由

支付宝异步通知需要一个无鉴权 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 == true

RPC内部

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记录,并统计调用FindByOutTradeNoUpdate的次数

TestHandleNotify包含三个子测试:

  • 首先传入一个格式正确但是内容无效的base64签名,这样会走到验签失败部分,此时fakemodel中的find和update仍然为0,表示验签在查询和更新流水前完成
  • 接着调用signedNotifyParams生成可以被支付宝sdk验证的签名,这里的逻辑是将sign相关的三个签名使用WithIgnore忽略,验签端会忽略这三个字段,所以签名和验签输入一致,成功后可以断言
  • 最后调用两遍HandleNotify,第二次仍可以验签,但是markPaid已经发现成功,直接返回,则find调用两次,但update只有一次,实现幂等性