# LINE Pay Sandbox 接入头脑风暴记录 **日期**:2026-08-11 **状态**:暂停,待继续设计评审 **目标**:以 spec-kit 流程接入 LINE Pay Online API v4 Sandbox,并在设计批准后依次完成 `specify -> plan -> tasks -> implement`。 > 本文只记录已确认决策和待评审设计,不代表实现已获完整批准。不得在文档、源码、配置文件或日志中写入真实 Channel Secret。 ## 1. 已确认的业务决策 | 议题 | 决策 | |---|---| | 与现有 OMG 的关系 | 方案 A:LINE Pay 与 OMG 并存,不替换 OMG | | 支付方式编号 | 暂定 `payType=8`,最终在规格阶段核对现有枚举后固化 | | API 版本 | Online API v4,Request、Check、Confirm、Retrieve、Refund 全部统一使用 `/v4` | | 环境 | 首版接入 LINE Pay Sandbox | | 凭证模式 | 商家独立凭证,每个门店使用自己的 Channel ID / Channel Secret | | 凭证归属 | 与现有 OMG 一致,按 `pos_store` 一店一套;订单通过 `PosOrder.mdId` 定位凭证 | | 凭证维护者 | 平台管理员;商家端不录入、不查看 Channel Secret | | 付款确认 | `confirmUrlType=CLIENT` | | 请款方式 | Confirm 时自动请款,不实现授权/请款分离、Capture 或 Void | | 退款范围 | 首版只支持全额退款 | | 币种与金额 | 固定 `TWD`,整数金额;金额仅取服务端订单,Request/Confirm/Refund 必须一致 | | 实现范围 | `foodie_server` 后端 + `foodie-admin-vue` 平台管理前端;用户端只交付 API 契约 | | Sandbox 凭证 | 已具备;后续通过管理页面安全录入,不在本文记录 | | 公网后端地址 | `https://foodieapi.waimai-paotui.com` | | 用户端结果地址 | 暂留空占位,后续补充;不得硬编码虚假地址 | ## 2. LINE Pay 回跳地址 LINE Pay 配置使用以下两个后端 HTTPS 地址: ```text confirmUrl: https://foodieapi.waimai-paotui.com/pay/line/confirm cancelUrl: https://foodieapi.waimai-paotui.com/pay/line/cancel ``` 它们不是用户端最终页面: - `confirmUrl` 只表示用户已完成 LINE Pay 认证,后端仍须调用 Confirm 或查询 API,不能直接把订单标为已支付。 - `cancelUrl` 只结束本次支付尝试,不直接取消外卖订单。 - 后端处理完成后应 302 跳至固定配置的用户端结果地址;该地址尚未确定。 - 结果地址为空时,不进行开放重定向,也不接受请求参数提供跳转目标;实现应返回安全的中性提示页。 项目已有 App Scheme `com.twanmsdyh.app`,曾提议以后使用统一入口: ```text com.twanmsdyh.app://payment/result?status=&orderId= ``` 该路径尚未由用户端确认,当前仅作为候选,不得直接视为有效契约。 ## 3. 官方文档核验结论 ### 3.1 版本与 Sandbox - 台湾新接入优先使用 Online API v4。v4 于 2025-11 增加 `paymentProvider`(`TSP` / `EPI`)。 - 官方基础付款指南仍含 v3 示例,不能因此混用 v3;实现路径必须全部为 `/v4`。 - Sandbox 主机:`https://sandbox-api-pay.line.me`。 - Production 主机:`https://api-pay.line.me`,本阶段不启用。 - Sandbox Online 支付只能验证 Web 收银台,必须使用 `info.paymentUrl.web`;不能以 Sandbox 验证 App payment URL。 - Sandbox 的 `paymentProvider` 固定为 `TSP`,无法模拟 EPI;EPI 响应兼容必须列为生产前独立验收项。 ### 3.2 v4 核心 API ```text POST /v4/payments/request GET /v4/payments/requests/{transactionId}/check POST /v4/payments/{transactionId}/confirm GET /v4/payments POST /v4/payments/{transactionId}/refund ``` 建议最短 Read timeout:Request 10 秒、Check/Retrieve/Refund 20 秒、Confirm 40 秒。HTTP 200 不代表业务成功,必须判断 `returnCode`。 ### 3.3 HMAC 签名 请求头: ```text Content-Type: application/json X-LINE-ChannelId X-LINE-Authorization X-LINE-Authorization-Nonce ``` 签名使用 HMAC-SHA256,key 为 Channel Secret,结果 Base64: ```text GET: channelSecret + apiPath + queryString + nonce POST: channelSecret + apiPath + exactRequestBody + nonce ``` 关键约束: - POST JSON 必须只序列化一次;用于签名的字符串与实际发送内容必须完全一致。 - GET query 的参数顺序、重复参数、编码和值必须与实际 URL 完全一致。 - `apiPath` 包含准确的 `/v4` 路径和路径参数,不包含 scheme 或 host。 - nonce 使用 UUID v4;同一次请求的签名和请求头必须使用同一值。 - 必须为 POST 精确 JSON、GET query、空 body 和非 ASCII 内容编写签名契约测试。 ### 3.4 交易事实与幂等 - `confirmUrl` 是无签名的浏览器 GET 回跳,不是可信支付成功通知。 - LINE Pay 未提供可直接作为最终支付事实的签名 webhook。 - 本地必须先校验 `orderId + transactionId + 门店 + 金额 + 币种`,再由服务器 Confirm/查询取证。 - LINE Pay `orderId` 必须全局唯一;每次新的支付尝试使用独立 LINE orderId,不能直接复用外卖订单号。 - `transactionId` 为 19 位数字,全链路按字符串存储和返回,避免 JavaScript 精度丢失。 - 重复回跳、用户刷新、乱序回跳和并发 Confirm 只能有一个执行者,其余返回已有结果。 - Confirm/Refund 超时、`1198` 或临时错误后先调用 Retrieve 对账,不盲目重复有副作用的请求。 - `cancelUrl` 或 Check `0121` 不能覆盖已通过 Confirm/Retrieve 证实的支付终态。 ## 4. 对抗复核必须覆盖的风险 实现与测试至少要回答以下问题: 1. 所有 API 是否统一使用 v4,是否完整保存 v4 的 `paymentProvider`。 2. 签名 JSON/Query 是否与实际发送内容逐字节一致。 3. `orderId` 是否永久唯一,`transactionId` 是否始终按字符串处理。 4. Request、Confirm、订单和退款金额是否全部来自同一服务端事实。 5. 是否错误地把 HTTP 200、`confirmUrl` 到达或 `cancelUrl` 到达视为最终支付状态。 6. Confirm/Refund 已被 LINE 执行但响应丢失时,是否能通过 Retrieve 恢复最终事实。 7. Sandbox 无法测试 EPI 和 App payment URL 时,是否在上线清单中单独保留生产验收。 8. 用户取消订单与迟到的支付成功发生竞态时,是否先记录真实支付,再自动全额退款或进入人工核对,而不是丢弃付款事实。 9. 重复回跳、重复退款、定时对账和人工操作之间是否使用数据库条件更新/锁保证幂等。 10. 日志、接口响应和平台页面是否始终隐藏 Channel Secret、HMAC 原文及敏感 token。 ## 5. 已比较的实现路线 ### 方案 1:独立 LINE Pay 模块(已批准) 新建 LINE Pay 凭证、支付和退款流水。只在订单取消、退款和后台查询处增加很薄的支付方式分派,不重构 OMG。 优点:边界清楚、对现有 OMG 风险较低,并能完整处理幂等、对账和审计。 ### 方案 2:统一支付框架(未采用) 建立通用 `PaymentProvider` 接口,并同时迁移 OMG。长期结构整洁,但超出本次范围,会扩大已运行 OMG 的回归风险。 ### 方案 3:Controller 直接接入(拒绝) 不建独立支付/退款账本,只把交易号写入订单。无法可靠处理回跳重复、网络超时、退款恢复和审计,不满足支付安全要求。 ## 6. 已批准的架构边界 ### 6.1 `ruoyi-system` 负责纯数据库能力,不引入 LINE Pay HTTP 客户端或 `com.ruoyi.app.*`: - `pos_store_line_pay`:每个 `pos_store` 一套加密凭证。 - `pos_order_line_payment`:每次 Request/Confirm 的独立支付流水。 - `pos_order_line_refund`:每次全额退款操作的独立流水。 - 相应 Entity、Mapper XML、Service。 ### 6.2 `ruoyi-admin` - `LinePayClient`:v4 HTTP、精确 JSON/Query 签名、超时及结果码解析。 - `LinePayService`:创建、Confirm、查询、全额退款和状态机。 - `LinePayController`:用户支付接口及匿名 CLIENT 回跳接口。 - `PosStoreLinePayController`:平台管理员维护门店凭证和启用状态。 - `LinePayReconcileTask`:恢复漏回跳、Confirm 未知和 Refund 未知状态。 - 订单取消入口按 `payType` 分派至 OMG 或 LINE Pay;不引用废弃支付 Controller。 预定用户支付接口: ```text POST /pay/line/create GET /pay/line/confirm GET /pay/line/cancel POST /pay/line/query POST /pay/line/refund ``` - `create/query/refund` 使用明确 DTO;需要登录的接口通过 `@RequestHeader String token` 获取 token。 - `confirm/cancel` 使用显式 `@RequestParam`,允许匿名 GET,并按重复、乱序请求设计。 - Sandbox 的创建响应只向调用方提供 `paymentUrl.web`。 ### 6.3 `foodie-admin-vue` 新增“LINE Pay 门店管理”,交互风格参考现有 OMG 页面,但数据和状态完全独立: - 平台管理员分页查看门店开通/启用状态。 - 录入或轮换 Channel ID / Channel Secret。 - Secret 只允许写入,不允许读取回显;详情仅返回 `hasSecret` 等脱敏状态。 - 启停门店 LINE Pay。 - 新增用户可见文本必须同步 `zh/tw/en/vi` 四个 i18n 文件。 ## 7. 安全设计方向(待详细评审) - Channel Secret 使用 AES-256-GCM 加密落库,保存随机 nonce、密文和版本;主密钥只从服务端环境变量读取。 - Channel ID 可查询但默认脱敏展示;Channel Secret 永不回显。 - 不把 Secret、签名原文、Authorization、paymentAccessToken 写入日志。 - 凭证更新与启用分开;是否加入无扣款的在线探测仍需在详细设计中确定,不能依赖未被 LINE 官方保证的响应语义。 - 外部回跳只能使用服务端固定配置的结果地址,禁止请求参数控制 302 目标。 - 所有数据库迁移 SQL 只写入 `updatesql/sql.md`,不直接执行。 ## 8. 明天继续的设计评审顺序 1. 数据表字段、索引、状态枚举和凭证加密/轮换。 2. Request -> CLIENT 回跳 -> Confirm -> PAID 的事务边界与幂等。 3. Confirm/Refund 超时、迟到成功、取消竞态和定时对账。 4. Controller 契约、平台管理 API 与 `foodie-admin-vue` 页面。 5. 四语 i18n、错误映射、日志脱敏与权限。 6. Sandbox 自动化测试、真实凭证联调及生产前 EPI/App 验收。 7. 全部设计获批后创建正式 `spec.md`,自审并等待批准,再生成 `plan.md`、`tasks.md`,最后按 TDD 实现。 ## 9. 官方资料 - [LINE Pay 开发者中心](https://developers-pay.line.me/zh/) - [Sandbox](https://developers-pay.line.me/zh/sandbox) - [线上支付前置条件与签名](https://developers-pay.line.me/zh/online/prerequisites) - [Online API v4](https://developers-pay.line.me/zh/online-api-v4) - [v4 Request](https://developers-pay.line.me/zh/online-api-v4/request-payment) - [v4 Request 状态](https://developers-pay.line.me/zh/online-api-v4/check-payment-request-status) - [v4 Confirm](https://developers-pay.line.me/zh/online-api-v4/confirm-payment) - [v4 Retrieve](https://developers-pay.line.me/zh/online-api-v4/retrieve-payment-details) - [v4 Refund](https://developers-pay.line.me/zh/online-api-v4/refund) - [重定向页面](https://developers-pay.line.me/zh/online-api-v4/merchant/redirection-pages/) - [FAQ](https://developers-pay.line.me/zh/faq) - [API 变更日志](https://developers-pay.line.me/zh/api-change-log)