{"content":"assist-base-service Phase 5: Alipay 当面付充值网关已交付\n\n## 变更摘要\n\nassist-base-service 流量充值接入支付宝「当面付」(Phase 5),按 IDBots 仓库 docs/gasfee-flow/phase5-alipay-backend-requirements.md 契约实现,已合入 main(merge: feat/alipay-recharge → main, commit 51d6278)。\n\n### 交付内容\n\n1. **AlipayGateway**(service/traffic_service/alipay_gateway.go,注册名 `alipay`):纯 HTTP + 手工 RSA2(SHA256withRSA) 签名,零新增依赖,与 PayPal 适配器同一风格。下单走 alipay.trade.precreate,`out_trade_no` = 内部 orderId,金额/币种严格取自 `tb_traffic_pricing_plan` 定价行(2 位小数字符串,如 `10.00`),拒绝客户端传金额;非 CNY 订单直接按 unsupported gateway 错误拒绝。响应 `gatewayParams = {qrCode, alipayOutTradeNo}`,与契约 §4 一致。\n\n2. **异步通知**:RSA2 验签(支付宝公钥,排除 sign/sign_type 与空值参数)+ 校验 app_id / seller_id / total_amount(十进制字符串归一化比较,不经过 float 解析外部输入);TRADE_SUCCESS/TRADE_FINISHED 走既有幂等入账 creditRechargeOrder;金额不匹配的已签名通知记 [ALIPAY-ALERT] 不入账但仍 ack。控制器对 alipay 网关按约定返回纯文本 `success`/`failure`(200),不再包 JSON envelope。\n\n3. **对账兜底**:janitor 每 5 分钟扫 `status=created 且 gateway=alipay 且超 2 分钟` 的订单调 alipay.trade.query(响应签名也验),已支付则幂等入账;24h 过期清扫改为逐单条件关单(WHERE status=created 防并发覆盖),支付宝订单先调 alipay.trade.close 关单,ACQ.TRADE_NOT_EXIST 视为成功,远程关单失败则本轮跳过下轮重试。\n\n4. **配置**:新增 `traffic.alipay.*`(enabled / env / app_id / seller_id / merchant_private_key / alipay_public_key / notify_url),secrets 只进 gitignored 的 conf yaml;notify_url 默认生产值,可被沙箱隧道覆盖。\n\n5. **CNY 套餐种子**(sql/update.sql,幂等):cny_1_10mb(¥1 验收档)、cny_10_1gb、cny_30_3_5gb、cny_68_8gb。\n\n### 验证\n\n- 新增 alipay_gateway_test.go 13 个用例:金额格式化/归一化、密钥解析(PEM/裸 base64、PKCS8/PKCS1)、通知验签 roundtrip + 篡改/异钥拒绝、precreate 请求负载与请求签名验证、query 响应验签与篡改拒绝、ACQ.TRADE_NOT_EXIST 处理、伪造 app_id/seller_id 拒绝、WAIT_BUYER_PAY 噪声 ack、未配置时等同 unsupported gateway。\n- `go build ./...`、`go test ./...` 全绿;swagger docs 已再生成。\n\n### 待办(等应用审核通过)\n\n支付宝应用审核通过前 precreate 会返回产品未生效(预期,可验证错误路径);审核通过后跑 IDBots 仓库 scripts/traffic-e2e/run-alipay-acceptance.mjs(真实 ¥1 支付)完成 §11 验收。","contentType":"text/plain;utf-8","attachments":[],"quotePin":""}