research.md 7.4 KB

LINE Pay Research and Decisions

Date: 2026-08-12
API baseline: LINE Pay Online API v4

R1. LINE 请求字段与内部 payType 分离

Decision: 项目订单 payType="3" 仅表示 LINE Pay 渠道,不发送给 LINE。普通付款请求发送 capture=true,省略当前公开 v4 Request 页面未明确保证的 payType/confirmUrlType/appPackageName,使用默认浏览器回跳。

Rationale: 避免依赖当前公开契约未列出的枚举和字段;内部枚举始终与 LINE 请求字段无关。

Rejected: 把数字 3 写入 LINE 请求;这会违反官方契约。

Source: https://developers-pay.line.me/zh/online-api-v4/request-payment

R2. 凭证验证与自动启用

Decision: LINE 没有专门的无副作用 credential validation API。保存前使用候选 Channel ID/Secret 对随机、不存在的 orderId 调 Retrieve;Sandbox 接受签名且返回预期 1150(或结构完整的 0000)视为“凭证已被环境接受”,随后原子创建并切换为当前启用版本。网络异常、9000 或认证错误不切换当前版本。

Rationale: 1150 的官方含义只是无交易记录,不能证明币种、门店归属或真实付款能力。用户已批准把该探测作为自动启用门禁,Sandbox 端到端付款仍为上线验收项。

Rejected: 保存后不校验;使用 Request 创建真实付款做凭证探测(有副作用)。

Source: https://developers-pay.line.me/zh/online-api-v4/retrieve-payment-details

R3. 凭证采用不可变版本

Decision: 每次换 Channel ID 或 Secret 且验证成功后新建版本;旧版本不覆盖并由支付行保存 credential_id。相同值重复提交采用幂等返回,不额外生成版本。每门店仅一个 current_store_id=store_id 的当前版本。

旧版本仍优先用于交易恢复;若其返回明确凭证鉴权失败,系统只允许尝试同门店、同环境且 Channel ID 完全相同的当前版本,并必须先用 Retrieve 严格匹配 transactionId、orderId、currency、amount 与退款证据。只读证据成立后,本轮才可使用当前版本继续相应支付事实恢复或退款;不同 Channel、不同环境或证据不完整一律保持 UNKNOWN/人工核对。

Rationale: Confirm/Retrieve/Refund 必须知道付款发起时的渠道身份;版本关系也提供完整审计。若 LINE 后台使旧 Secret 立即失效,原凭证调用失败后只允许在相同 Channel ID 的当前版本上先做只读 Retrieve 证明交易归属,再使用当前版本继续恢复;不同 Channel ID 不自动替代。

Rejected: 一店一行直接覆盖;在每笔支付快照 Secret(重复敏感数据且难轮换)。

R4. 支付尝试为追加式账本

Decision: pos_order_line_payment 对订单是 1:N。重复点击复用唯一活跃行;只有网关明确取消/过期/失败后才把 active_dd_id 置 NULL,重新支付插入新行。PAID、UNKNOWN、MANUAL_REVIEW 都继续阻断新尝试。

Rationale: LINE 回跳和网关恢复依赖旧 line_order_id/transaction_id;覆盖会导致迟到事件写到新交易并让退款不可追溯。

Rejected: 每订单固定一行覆盖失败/取消尝试;仅 UNIQUE(dd_id,is_active) 且历史写 0(会限制只能一条 inactive)。

R5. 浏览器回跳快速返回

Decision: /pay/line/confirm 只持久化回跳事实并唤醒异步处理,快速返回自包含 HTML;页面尝试固定 App Scheme 并提供点击兜底,App 通过 /query 读取最终状态。

Rationale: 官方重定向页面的 confirmURL read timeout 约 20 秒,而 Confirm read timeout 要至少 40 秒,同步等待存在确定的超时冲突。

Rejected: 回跳 Controller 内同步 Confirm 后才输出 HTML;独立部署静态安全页。

Sources: https://developers-pay.line.me/zh/online-api-v4/merchant/redirection-pages/ ; https://developers-pay.line.me/zh/online-api-v4/confirm-payment

R6. Retrieve 是支付事实来源,Check 是恢复提示

Decision: 恢复先 Retrieve。只有 returnCode=0000info[] 唯一匹配 orderId/transactionId/currency、transactionType=PAYMENTpayInfo 金额合计才判已支付;不依赖当前公开契约未保证的 payStatus。Retrieve 1150 时再 Check:0000 等待、0110 通过本地门禁后 Confirm、0121/0122 在再次排除付款事实后终止、0123 回 Retrieve。

Rationale: Check 0123 仍要求查询实际付款详情;Retrieve 0000 仅表示查询成功,不能单独表示已 Capture。

Rejected: 把 Check 0000/0123 或 Retrieve 0000 直接映射为已支付。

Sources: https://developers-pay.line.me/zh/online-api-v4/check-payment-request-status ; https://developers-pay.line.me/zh/online-api-v4/retrieve-payment-details

R7. 全额退款单独建模

Decision: 每笔 PAID payment 最多一条退款行;全额退款调用省略 refundAmount。Refund 0000 保存 refundTransactionId;未知结果只通过 Retrieve 的 refundList 恢复。支付行永不改成 REFUNDED。

Rationale: 官方用省略 refundAmount 表达全额退款;付款和退款是两个独立资金事实。

Rejected: 发送等于订单金额的 refundAmount 并假设一定等价;Refund UNKNOWN 直接重发。

Source: https://developers-pay.line.me/zh/online-api-v4/refund

R8. 外部副作用必须有 durable intent

Decision: Request 前先提交 REQUESTING + line_order_id;Confirm/Refund 前先 CAS 到 PROCESSING 状态;HTTP 后另事务写事实。超时进入 UNKNOWN,不能回退为可直接重复副作用的状态。

Rationale: 数据库事务不能回滚 LINE 已执行的网络操作,响应丢失必须能用稳定键查询恢复。

Rejected: 把数据库写和 HTTP 包在同一个 @Transactional 方法中。

R9. 跨渠道创建门禁

Decision: LINE 与 OMG create 共用 pay:create:<ddId> watchdog 锁和 pos_order 条件更新。LINE 还必须存在匹配支付行,不能仅凭历史 payType=3 识别 LINE。

Rationale: LINE 表上的唯一活跃键只能阻止 LINE 对 LINE,无法阻止 OMG 与 LINE 并发;历史 payType=3 可能属于已下线的 ZaloPay。

Rejected: 仅在 LINE 表加唯一索引;迁移或修改 OMG 表。

R10. 安全中间页与 Sandbox 限制

Decision: Controller 返回固定模板 HTML,动态内容做 HTML/JS/URL 编码;响应包含 Cache-Control: no-storeReferrer-Policy: no-referrer 和严格 CSP。Sandbox 使用 web paymentUrl;LINE App 内唤起、appPackageName 和自定义 Scheme 在生产真机验收。

Rationale: 浏览器/WebView 和 App 安装状态决定是否能自动唤起;官方不保证自定义 Scheme 一定被放行,Sandbox 也不能完整模拟 App 环境。

Rejected: 接受请求传入 redirect URL;假设自动唤起必然成功。

Sources: https://developers-pay.line.me/zh/faq ; https://developers-pay.line.me/zh/api-change-log

R11. 日志与状态职责

Decision: payment_gateway_log 仅记录 LINE 请求/响应追加审计,OMG 不迁移;支付/退款业务表仍保存规范状态、稳定键和调度字段。Secret 明文与管理员详情回显风险按用户决定接受,不额外扩大到普通列表或 App 接口。

Rationale: 原始日志不能提供可靠的 CAS、唯一性、扫描索引或当前状态;同时普通列表不需要批量传输 Secret。

Rejected: 把全部状态只存日志;让 OMG 同步迁移到新日志表。