# 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=<success|failed|cancelled>&orderId=<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)

