plan.md 11 KB

LINE Pay 直连支付 Implementation Plan

For agentic workers:tasks.md 顺序逐项执行;每个实现任务先补失败测试,再写最小实现并运行对应验证。不得跳过状态机 CAS、外部调用前持久化 intent 或最终 git diff 检查。

Goal:payType="3" 接入 LINE Pay Online API v4,提供门店凭证版本管理、追加式支付尝试、异步确认、主动补单、自动全额退款和平台运维能力。

Architecture: ruoyi-system 只承载实体、MyBatis Mapper 和持久化状态机;ruoyi-admin 承载 LINE HTTP/HMAC、订单编排、回跳页面、定时任务和 Controller。支付、确认、退款均采用“本地先提交 intent/claim → 外部 HTTP → 独立本地 CAS 落事实”,不依赖一个跨网络事务。支付尝试为订单 1:N 且历史永不覆盖,稳定键为 paymentIdline_order_idtransaction_id 和可空唯一 active_dd_id

Tech Stack: Java 21、Spring Boot、MyBatis/MyBatis-Plus、MySQL、Apache HttpClient 4、fastjson2、Redisson、Vue 2、Element UI、JUnit 5/Mockito。


Summary

LINE Pay 普通付款省略未被当前公开契约保证的 payType/confirmUrlType/appPackageName,仅显式 capture=true 并使用默认浏览器回跳。Request 成功只代表已创建付款请求;回跳快速登记后返回内嵌安全中间页,Confirm 由可恢复服务异步完成。任务每分钟按行租约扫描,先 Retrieve,再按需 Check;只有 Check 0110 且本地订单仍可付款时才能 Confirm。支付经严格匹配的 Retrieve/Confirm 证实后,支付行永久保持 PAID,退款事实只写退款表。

Technical Context

Language/Version: Java 21;Vue 2
Primary Dependencies: Spring Boot、MyBatis、MyBatis-Plus、Apache HttpClient 4.5.14、fastjson2、Redisson、Element UI
Storage: MySQL;新增 4 张 LINE 专用表,不执行在线迁移
Testing: Maven Surefire + JUnit 5/Mockito;前端 ESLint/production build
Target Platform: Windows 开发;Spring Boot 多实例部署;LINE Pay Sandbox/Production
Project Type: 多模块后端 + 独立平台 Vue 项目
Performance Goals: App 查询只做本地数据库读取;定时任务单轮最多 20 笔并有时间预算;关键扫描走 (status,next_reconcile_at,id)
Constraints: 不修改 OMG 表结构或迁移 OMG 日志;不修改废弃 ZaloPay/PayController/TestTask/OrderAppeal 业务;Secret 明文存储和平台详情回显为用户已接受方案;支付/退款副作用未知时不得盲重试
Scale/Scope: 门店级凭证;单门店子订单;每订单多次历史尝试但最多一条阻断性尝试;首版只支持全额退款

Constitution Check

.specify/memory/constitution.md 尚未定义项目条款,因此以下仓库规则作为强制门禁:

  • 依赖方向保持 ruoyi-admin -> ruoyi-system;LINE HTTP 集成只放 ruoyi-admin
  • Controller 使用明确 DTO、@RequestBody/@RequestParam/@RequestHeader/@PathVariable,不新增 Map 入参或 Bean Validation。
  • 所有 DDL/权限 SQL 只追加到 updatesql/sql.md,不直接执行数据库变更。
  • 不修改废弃支付与任务代码;仅在当前有效订单入口接入支付/退款门禁。
  • 平台新增文本四语同步,保留 Vue 文件 CRLF。
  • 修复/功能均先测试再实现,交付前编译、定向测试、前端校验和 diff 审核。

Project Structure

Documentation

specs/019-line-pay/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── tasks.md
├── contracts/
│   └── api.md
└── checklists/
    └── requirements.md

Source Code

foodie_server/
├── ruoyi-system/src/main/java/com/ruoyi/system/
│   ├── domain/{PosStoreLinePay,PosOrderLinePayment,PosOrderLineRefund,PaymentGatewayLog}.java
│   ├── domain/dto/{StoreLinePayCredentialDto,StoreLinePayToggleDto}.java
│   ├── domain/vo/PosStoreLinePayVo.java
│   ├── mapper/*Line*.java
│   └── service/{I*,impl/*}.java
├── ruoyi-system/src/main/resources/mapper/chanting/*Line*.xml
├── ruoyi-admin/src/main/java/com/ruoyi/app/
│   ├── utils/linepay/{LinePayProperties,LinePaySigner,LinePayHttpTransport,ApacheLinePayHttpTransport,LinePayClient}.java
│   ├── pay/{LinePayController,LinePayService,LinePayGatewayAuditService,PaymentCreateGuardService}.java
│   ├── pay/dto/*LinePay*.java
│   ├── mendian/PosStoreLinePayController.java
│   └── task/LinePayReconcileTask.java
├── ruoyi-admin/src/test/java/com/ruoyi/app/{utils/linepay,pay,task}/*Test.java
├── ruoyi-system/src/test/java/com/ruoyi/system/service/impl/*Line*Test.java
└── updatesql/sql.md

foodie-admin-vue/
└── src/
    ├── api/chanting/storeLinePay.js
    ├── api/system/order.js
    ├── views/mendian/storePayment/{index.vue,components/LinePayTab.vue}
    ├── views/system/order/index.vue
    └── api/language/language.{zh_CN,zh_TW,en_US,vi}.js

Component Design

Persistence boundary (ruoyi-system)

  • IPosStoreLinePayService 负责版本号分配、验证成功后的当前版本原子切换、启停和列表/详情。
  • IPosOrderLinePaymentService 负责创建 REQUESTING、稳定键查询、App 选择规则、CAS 状态推进、活跃键释放、扫描和行租约。
  • IPosOrderLineRefundService 负责 UNIQUE(payment_id) 的 insert-if-absent、CAS claim、扫描和退款事实。
  • IPaymentGatewayLogService 只追加审计数据;日志不是状态机事实,日志写入失败不回滚资金事实。
  • PosOrderMapper 新增条件更新:只有未付款、合法订单态且当前 pay_type 与目标渠道一致时,才允许领取支付渠道或更新支付 URL。

LINE gateway boundary (ruoyi-admin)

  • LinePaySigner 严格按 channelSecret + URI + body/query + nonce 计算 Base64 HMAC-SHA256;GET 使用最终原始 query 字符串,POST 使用最终 UTF-8 JSON 字节。
  • LinePayClient 暴露 request/check/confirm/retrieve/refund/verifyCredential;交易号始终为字符串。端点超时分别按 Request 10 秒、Check/Retrieve/Refund 20 秒、Confirm 40 秒配置。
  • LinePayHttpTransport 隔离实际 Apache HttpClient,便于不联网测试精确 method/URI/body/header。
  • LinePayGatewayAuditService 记录 correlationId、方向、action、HTTP/LINE 结果和 payload;调用方捕获日志异常后继续处理资金事实。

Orchestration boundary (ruoyi-admin)

  • PaymentCreateGuardService 使用共享 pay:create:<ddId> Redisson watchdog 锁和订单条件更新,LINE 与 OMG create 都经过同一门禁;OMG 仅做这处最小代码调整。
  • LinePayService.create 在锁内校验用户、订单、单门店、金额、当前凭证和历史 payType=3 归属,然后复用活跃尝试或先提交新 REQUESTING。Request HTTP 在事务外执行,结果以 CAS 落为 WAITING_AUTHREQUEST_UNKNOWN
  • LinePayService.onConfirmRedirect 只按 line_order_id 登记并尝试把行推进到可处理状态,不同步等待 Confirm;Controller 返回安全 HTML。
  • LinePayService.reconcilePayment 始终优先 Retrieve;无付款事实才 Check。副作用调用前先 CAS claim,超时进入 UNKNOWN,后续只读恢复。
  • LinePayService.applyPaidFact 在一笔本地事务内写 PAID、锁定订单并更新 pay_status=1;订单已取消则 insert-if-absent 创建退款 intent。
  • LinePayService.requestFullRefund 只针对 PAID 行,省略 refundAmount;成功后退款表为 REFUNDED 且订单 pay_status=2,支付表保持 PAID
  • LinePayReconcileTask 使用进程级 watchdog 锁减少重复扫描,但每行仍通过 lease_owner/lease_until/version 领取;失败一笔不终止整批。

Platform and App boundary

  • /pay/line/create/query 使用 {ddId} DTO 和现有 @Auth + @Anonymous + token header 组合;confirm/cancel 仅为匿名 GET 回跳。
  • HTML 页面由 Controller 自包含返回,不依赖单独服务器部署;固定 App Scheme,不接受客户端 redirect 参数,并设置 no-store/no-referrer/CSP/frame 限制。
  • 平台凭证页可查看当前版本详情、验证并切换新版本、启停;普通列表不携带 Secret,拥有详情权限的页面按用户决定可回显。
  • 订单平台页显示 LINE 支付/退款状态,并提供人工查询与全额退款;资金动作使用独立权限。

Error and Recovery Policy

场景 本地处理 后续动作
Request 超时/响应丢失 REQUEST_UNKNOWN,保留活跃键 Retrieve by line_order_id,必要时 Check
Check 0000 保持等待 退避后再查
Check 0110 本地门禁通过后 CAS CONFIRMING Confirm;超时转 CONFIRM_UNKNOWN
Check 0121/0122 先 Retrieve 排除已付款 明确未付款才终止并释放活跃键
Check 0123 不直接判已付 Retrieve 验证唯一 PAYMENT + CAPTURE
Confirm 非明确结果/超时 CONFIRM_UNKNOWN 只读 Retrieve/Check,不盲 Confirm
取消后迟到付款 支付仍写 PAID 唯一退款 intent + 自动全退
Refund 超时/未知 UNKNOWN Retrieve 原支付并检查 refundList
到达恢复截止仍未知 MANUAL_REVIEW 停止自动副作用,平台人工处理

Implementation Order

  1. 先落实 SQL 模型、实体/Mapper/Service 和状态 CAS 测试。
  2. 实现 HMAC、HTTP 契约和凭证验证测试,再接凭证管理接口。
  3. 实现 create/query、回跳页面和追加式尝试选择规则。
  4. 实现 Retrieve/Check/Confirm 恢复、支付事实事务和取消竞态。
  5. 实现全额退款与定时任务,再接两个真实取消入口和接单门禁。
  6. 最小修改 OMG create 共享渠道门禁。
  7. 完成平台前端、四语和权限 SQL,最后做全栈验证。

Verification Gates

  • mvn -pl ruoyi-system -am -Dtest='*Line*Test' -Dsurefire.failIfNoSpecifiedTests=false test
  • mvn -pl ruoyi-admin -am -Dtest='*LinePay*Test,OrderLifecycleServiceTest,OmgPayControllerTest' -Dsurefire.failIfNoSpecifiedTests=false test
  • mvn -pl ruoyi-admin -am -DskipTests package
  • foodie-admin-vue 运行 npm run lintnpm run build:prod;若仓库已有无关 lint 错误,记录并对变更文件运行 ESLint。
  • git diff --checkgit status --short、逐文件核对换行/编码、确认未触碰废弃代码和用户已有 OMG SQL。

Complexity Tracking

Decision Why Needed Simpler Alternative Rejected Because
订单 1:N 支付尝试 + 可空唯一活跃键 保留每次网关 orderId/transactionId/凭证与未知状态,避免迟到回跳污染新支付 覆盖单行会丢失恢复和退款所需键值
不可变凭证版本 + payment.credential_id 轮换后历史交易仍能找到原渠道身份 一店一行覆盖会让旧交易失去凭证归属
支付与退款分表 PAID 是资金事实,退款是另一事实与状态机 在支付状态写 REFUNDED 会掩盖曾经扣款
durable intent + 外部调用 + CAS 数据库事务无法覆盖 LINE 网络副作用 单个 @Transactional 无法修复响应丢失和本地提交失败