红魔咖啡馆

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

0%

【BudgetMatch】接入APP支付网关API

payment-rpc 已完成基础能力,但 cmd/app 还没有暴露用户侧支付接口。

# 任务

验收标准

流程

调用链

1
2
3
4
5
6
HTTP 请求
  -> AuthMiddleware
  -> Handler:解析参数
  -> Logic:编排业务、做网关校验
  -> mall-rpc:读取订单
  -> payment-rpc:创建或查询支付

其中,

  • .api用于定义HTTP协议
  • handler 负责解析请求,由goctl生成
  • logic实现业务逻辑
  • ServiceContext保存RPC客户端

实现API与Handler并生成

首先在mall.api中增加请求与响应的定义,然后在mall service种增加支付相关的handler

这里如何定义来自三部分:

  • 任务描述POST/GET的路由里只有id参数,因此req只需要接收路径参数
  • 响应相关字段来自payment.proto,决定了下游要返回哪些数据
  • 写法参考现有.api内容,决定了写法风格

使用make api-all通过goctl生成相关代码,包括handler、logic、routes和swagger生成的json

接入payment-rpc

config.go中添加rpc配置,让payment-rpc接入

再在cmd/app/etc/config.yaml中配置rpc相关参数,包括etcd地址,兜底endpoint

最后在cmd/app/internal/svc/service_context.go中初始化客户端

总体逻辑

create_payment_logic.go中实现了创建支付的逻辑

  • 首先获取已认证用户的user_id,然后使用loadPaymentOrder()查询订单,这个方法单独写在了一个helper文件中:
    • 首先从鉴权中间件写入的ctx获取用户
    • 接下来查询订单,传入请求时务必带上orderid,查询订单是否存在
    • 接下来验证订单,验证订单用户是否正确,传入金额是否合法且正确,金额不能由前端提交,而是从mall-rpc查询订单并使用服务器保存的payamount,否则用户可以篡改金额
  • 最后返回订单,调用paymentRPC创建订单

query_payment_logic.go实现了查询订单的逻辑,和创建类似,这里查询时不能直接拿URL中的id调用QueryPayment,因为RPC请求没有user_id,顺序如下:

1
2
3
4
查询本人订单
  -> 确认订单归属和金额
  -> QueryPayment(order_id)
  -> 检查返回流水仍属于当前订单和用户

返回时再次检查是否是同一订单,防止跨服务数据异常时把其他用户的流水返回

查询接口定义类似于:

1
2
GET /api/mall/orders/abc123/pay/query
Authorization: Bearer 用户A的Token

这里abc123只是用户提交的参数,而token只能证明请求者是A,不能证明abc123属于请求者A,登录认证和授权是两码事

若网关直接写orderid=req.id,则rpc只知道有人要查询订单abc123,而不能直到当前用户是谁,订单属于谁,用户有无权限查询

这是典型的IDOR(越权访问)漏洞:服务器只检查资源id存不存在,却没检查资源属于谁

所以这种处理应该读取用户id,根据mall-rpc查询orderid+用户id,确认订单属于该用户,才调用rpc,并检查返回流水是否一致

综上:

URL、请求体、查询参数中的数据都不可信。 Token 只能证明用户是谁,不能自动证明用户拥有某个资源。 操作资源前,必须用“资源 ID + 当前用户 ID”做归属校验。

返回二维码信息

目前payment-rpc的queryresp并不返回二维码,实际行为是POST发起支付时返回二维码,而query的GET只有状态

如果要通过GET返回二维码,可以在创建支付时把返回的qrcode保存到支付流水,查询再从流水返回,调用链如下:

1
2
3
4
5
6
7
CreatePayment
  -> 支付宝预下单
  -> 保存 qr_code 到 payments 表

QueryPayment
  -> 查询 payments 流水
  -> 返回 status + qr_code
  • 在model层的payments添加qrcode相应字段
  • 修改protobuf中的payment和query用到的resp
  • 修改vars中的字段,并在query的逻辑层返回的响应添加对应参数
  • 修改HTTP API类型,在mall.api的resp中添加对应字段
  • 修改网关logic映射,在payment中添加qrcode
  • 重新生成代码,并补充测试

测试

单测前三种样例成功,在cmd/app/internal/logic/test/下创建逻辑测试文件夹,测试:

接下来在生产环境中进行http层测试

/api/auth/code/send

向邮箱发送验证码,传入body内容为email,返回200 OK

/api/auth/register

注册渠道,提供邮箱,密码,用户名与验证码,传入body,返回200 OK

/api/auth/login/username

用户名登录,提供用户名和密码,可以登录获取token

/api/mall/orders/:order_id/pay

调用支付前,现从admin创建一个商品和seckill,方便用户创建订单

用户登录后,凭借token创建订单,获取订单id

调用支付接口,传入token鉴权,并传入订单id创建订单,创建成功后针对查询接口/api/mall/orders/:order_id/pay/query测试三点:

  • 当前用户是否可以查询订单
  • 当前用户是否可以查询非自己的订单
  • 能不能对已支付的订单重复支付