quickstart.md 7.2 KB

LINE Pay Online / Offline Implementation Quickstart

1. Prerequisites

  • JDK 21: C:\Users\qmj\.jdks\graalvm-jdk-21.0.7
  • MySQL schema changes from updatesql/sql.md applied manually by the developer
  • Redis/Redisson available
  • LINE Pay Sandbox Channel ID/Secret
  • 对应 LINE Pay 商户已开通 Offline API 权限;固定收款码但没有 API credentials 的商户不能联调
  • 官方台湾 Sandbox My Code 生成器;不得使用真实 LINE App 生成的 My Code
  • Public HTTPS API base URL reachable by LINE redirect
  • Platform frontend: E:\QtwCode\foodie\foodie-admin-vue

Do not execute schema migration from Codex. Do not use the deprecated TestTask, ZaloPayController, PayController, or OrderAppealController flows.

2. Runtime configuration

ruoyi-admin/src/main/resources/application.yml contains non-secret defaults:

line-pay:
  environment: sandbox
  base-url: https://sandbox-api-pay.line.me
  confirm-url: https://api.example.com/pay/line/confirm
  cancel-url: https://api.example.com/pay/line/cancel
  reconcile:
    fixed-delay-ms: 60000
    initial-delay-ms: 60000
    batch-size: 20
    row-lease-seconds: 120
    round-budget-seconds: 45
    auth-deadline-minutes: 30
    unknown-deadline-hours: 24

Channel credentials are saved per store through the platform API; they are not placed in application.yml.

3. Local verification

Use JDK 21 only in the current PowerShell process:

$env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7'
$env:PATH="$env:JAVA_HOME\bin;$env:PATH"
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

现有 Online 平台前端回归(仅当实际修改 foodie-admin-vue 时执行):

Set-Location E:\QtwCode\foodie\foodie-admin-vue
npm run lint
npm run build:prod

Repository checks:

Set-Location E:\QtwCode\foodie\foodie_server
git diff --check
git status --short

Offline 增量的定向验证命令在全部后端功能、测试源码和 SQL 调整完成后统一执行:

$env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7'
$env:PATH="$env:JAVA_HOME\bin;$env:PATH"
mvn -pl ruoyi-system -am -Dtest='*Line*Test' -Dsurefire.failIfNoSpecifiedTests=false test
mvn -pl ruoyi-admin -am -Dtest='*LinePayOffline*Test,PosOrderShOprateControllerTest,*LinePay*Test,OrderLifecycleServiceTest,OmgPaymentControllerTest' -Dsurefire.failIfNoSpecifiedTests=false test
mvn -pl ruoyi-admin -am -DskipTests package

4. Sandbox acceptance flow

  1. Platform opens Store Payment → LINE Pay.
  2. Enter store Channel ID/Secret; verify the detail shows a new current version and enabled state.
  3. Create one single-store order with payType=3.
  4. Call /pay/line/create; assert payment row is WAITING_AUTH and response uses paymentUrl.web.
  5. Repeat create before completion; assert the same paymentId/lineOrderId is returned with reusedAttempt=true.
  6. Complete Sandbox web authorization and allow confirm redirect.
  7. Confirm page returns quickly and App query eventually reports PAID.
  8. Query DB read-only and verify payment remains PAID, order pay_status=1, and gateway logs contain REQUEST/CHECK/CONFIRM/RETRIEVE events.
  9. Cancel a paid order; verify exactly one refund row is created, eventually REFUNDED, while payment remains PAID and order becomes pay_status=2.
  10. Create a second attempt only after the first is explicitly cancelled/expired/failed; verify both rows remain and only the second has active_dd_id.

5. Offline Sandbox acceptance flow

  1. 使用商家 token 调 /system/orderShOprate/createOrder 创建 paymentMethod="3" 的单门店订单。
  2. 只读检查子订单 order_source=MERCHANT;用户端订单和历史订单保持 USER
  3. 从官方台湾 Sandbox My Code 生成器取得 18 位 oneTimeKey,调用 /system/orderShOprate/linePay/offline/pay
  4. 检查支付行 payment_mode=OFFLINE,且数据库、应用日志、异常和 payment_gateway_log.payload 均不存在原始 oneTimeKey
  5. Pay 返回 1145/1169 或 Check 返回 AUTH_READY 时,状态接口返回 AUTH_REQUIRED;重复提交新的 My Code 只返回旧尝试,不再次调用 Pay。
  6. Pay 超时、响应不明确或返回未识别结果码时,状态接口返回 PROCESSING,任务只按 lineOrderId 调 Offline Check;Check 未识别结果码同样不得释放活跃键。
  7. Check 返回 COMPLETE 且订单号、交易号、金额合计、非空 paymentProvider 匹配时,状态最终为 PAID,订单结算和通知只执行一次。
  8. 使用错误商家、本人 shId 搭配他店 mdId、用户端/历史订单、多门店订单、非 payType=3 订单或无效 My Code 请求,确认在调用 LINE 前拒绝。
  9. 模拟金额不一致,确认 captured_amount 保存 LINE 实际扣款额、不触发正常履约、只创建一条以实际扣款额为金额的全额退款意图,并且原订单保持禁止再次支付。
  10. 对已支付 Offline 订单执行取消/退款,确认 URI 使用 lineOrderId;对 Online 订单回归确认仍使用 transactionId

6. Required concurrency and security tests

  • Two simultaneous create calls for the same LINE order return one attempt.
  • LINE create and OMG create for the same order cannot both claim the payment channel.
  • confirm redirect, scheduled task and admin reconcile cannot Confirm twice.
  • user cancellation before late Capture still records PAID and creates one refund intent.
  • store cancellation and late Capture produce the same result and enforce store ownership.
  • credential rotation during a waiting/paid transaction does not change its credential_id; old transaction remains queryable/refundable.
  • UNKNOWN and MANUAL_REVIEW never release active_dd_id or directly repeat side effects.
  • Online 与 Offline 同时发起时只允许一个模式创建活跃尝试。
  • 两个 Offline Pay 请求并发时只向 LINE 发送一次,后到请求的 oneTimeKey 不记录、不发送。
  • AUTH_REQUIREDPROCESSINGAMOUNT_MISMATCH 和缺失 paymentProviderMANUAL_REVIEW 均阻止重新扫码。
  • 普通商家和夜市商家按 shId=userId 鉴权,其他摊位商家按 mdId=InfoUser.storeId 鉴权。
  • Controller、审计服务和异常分支的测试均断言原始 oneTimeKey 不可见。

7. Production acceptance items

Sandbox cannot prove every App handoff behavior. Before production enablement, verify on real iOS and Android devices:

  • LINE payment browser returns to the intermediate page and the fixed Scheme handoff works.
  • com.twanmsdyh.app://payment/result is registered and handled.
  • auto-open works where allowed; the manual button works in LINE WebView and external browsers.
  • App always calls /query and never trusts the deep-link as success.
  • current production credentials complete an end-to-end TWD payment and full refund.
  • 业务方确认该 Channel 已开通 Offline API,并用真实客户 My Code 验证 AUTH_READY/COMPLETE/CANCEL/FAIL 收敛。
  • 验证 paymentProviderTSP/EPI 原值;若生产返回其他非空值仍保存并允许完成,缺失或空值进入人工核对。