api.md 6.4 KB

LINE Pay HTTP Contracts

所有 JSON 业务响应沿用项目 AjaxResult 外壳。交易号字段始终是 JSON string。业务错误文本从 MessageUtils.message(...) 获取。

1. App API

POST /pay/line/create

Annotations: @Anonymous, @Auth, @RequestHeader String token, @RequestBody LinePayOrderRequest.

Request:

{
  "ddId": "202608120001"
}

Success data:

{
  "ddId": "202608120001",
  "paymentId": 42,
  "lineOrderId": "LP20260812...",
  "transactionId": "2026081200000000001",
  "paymentUrl": "https://sandbox-web-pay.line.me/...",
  "status": "WAITING_AUTH",
  "reusedAttempt": false
}

Rules:

  • token 用户必须拥有 ddId 对应订单。
  • 只接受订单当前 payType="3",单门店、未支付、合法非终态、门店当前 LINE 版本已验证并启用。
  • 同 ddId 有阻断性活跃尝试时返回该行,reusedAttempt=true,不再 Request。
  • 只有最新尝试已明确 CANCELLED_OR_EXPIRED/FAILED 才新增行。
  • 父单跨多门店时返回业务错误,不拆分多次支付。

POST /pay/line/query

Annotations 与 create 相同。

Request:

{
  "ddId": "202608120001"
}

Success data:

{
  "ddId": "202608120001",
  "paymentId": 42,
  "orderPayStatus": 1,
  "paymentStatus": "PAID",
  "refundStatus": null,
  "transactionId": "2026081200000000001",
  "updatedAt": "2026-08-12T12:34:56+08:00"
}

该接口只读本地状态,不在 App 请求线程中调用 LINE。

2. LINE redirect endpoints

GET /pay/line/confirm?orderId={lineOrderId}&transactionId={transactionId}

  • orderId 是 LINE Request 的 line_order_id,不是 ddId。
  • 两个参数都用显式 @RequestParam;按 lineOrderId 定位并校验 transactionId。
  • 只登记回跳/触发可恢复处理,不同步等待 Confirm。
  • 返回 text/html;charset=UTF-8 和:
    • Cache-Control: no-store, no-cache, must-revalidate
    • Pragma: no-cache
    • Referrer-Policy: no-referrer
    • Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'
  • 页面尝试 com.twanmsdyh.app://payment/result?orderId=<urlEncodedDdId>,并显示手动按钮和“正在确认支付,请回 App 查询”的安全提示。

GET /pay/line/cancel

Optional explicit query parameters: orderId, transactionId.

  • 只记录浏览器事件并触发只读核对;不能把到达 cancelUrl 当成取消终态。
  • 返回与 confirm 相同安全页面/响应头。

3. Platform credential API

Base path: /system/storeLinePay

GET /list

Permission: chanting:storeLinePay:list.

Query uses explicit optional @RequestParam: posName, isStall, credentialStatus, isEnabled.

Rows include store identity/current version/status/channelId/hasCredential/verifiedTime, never include channelSecret.

GET /{storeId}

Permission: chanting:storeLinePay:query.

Returns current credential detail. Per approved product decision this privileged response may contain plaintext channelSecret.

PUT /saveCredentials

Permission: chanting:storeLinePay:saveCredentials.

{
  "storeId": 123,
  "channelId": "2000000000",
  "channelSecret": "plain-secret"
}

Business validation is in Controller/Service without @Valid. Candidate is verified before an immutable version becomes current and enabled. Failed/unknown probe leaves existing current version untouched.

PUT /toggleEnable

Permission: chanting:storeLinePay:toggleEnable.

{
  "storeId": 123,
  "enabled": false
}

Only changes whether the current verified version may serve new payments; old transactions remain queryable/refundable through their credential_id.

4. Platform order API

GET /system/order/{id}/status-context

Extend existing response with:

{
  "linePaymentId": 42,
  "linePaymentStatus": "PAID",
  "lineRefundStatus": null,
  "lineTransactionId": "2026081200000000001"
}

Only populate LINE fields when a matching pos_order_line_payment exists. Never infer LINE solely from payType="3".

POST /system/order/{id}/line-payment/reconcile

Permission: system:order:linePaymentReconcile. Uses the selected paymentId from stable server-side rules, takes a row lease, then performs Retrieve/Check recovery.

POST /system/order/{id}/line-refund

Permission: system:order:lineRefund. Creates/uses the unique full-refund row for the selected PAID payment. UNKNOWN only retrieves; it never directly repeats Refund.

5. LINE v4 outbound contracts

Request

POST /v4/payments/request, 10 second timeout.

{
  "amount": 100,
  "currency": "TWD",
  "orderId": "LP20260812...",
  "packages": [{
    "id": "202608120001",
    "amount": 100,
    "products": [{"name": "Order 202608120001", "quantity": 1, "price": 100}]
  }],
  "redirectUrls": {
    "confirmUrl": "https://api.example/pay/line/confirm",
    "cancelUrl": "https://api.example/pay/line/cancel"
  },
  "options": {
    "payment": {"capture": true}
  }
}

The total amount must equal package amount and product price * quantity sum.

Check

GET /v4/payments/requests/{transactionId}/check, 20 second timeout.

Confirm

POST /v4/payments/{transactionId}/confirm, 40 second read timeout or greater.

{"amount": 100, "currency": "TWD"}

Confirm is sent only after local CAS claim and amount/currency/order/store/credential/active checks.

Retrieve

GET /v4/payments?orderId={encodedLineOrderId} or ?transactionId={encodedTransactionId}, 20 second timeout. The exact ordered query string is included in the HMAC input.

Refund

POST /v4/payments/{transactionId}/refund, 20 second timeout, body {}. refundAmount is intentionally omitted for full refund; there is no currency request field.

Required headers

X-LINE-ChannelId: <channel id>
X-LINE-Authorization-Nonce: <unique nonce>
X-LINE-Authorization: Base64(HMAC-SHA256(channelSecret, channelSecret + URI + bodyOrQuery + nonce))
Content-Type: application/json

6. App deep-link contract

The mobile application must register and handle:

com.twanmsdyh.app://payment/result?orderId=<ddId>

On open, the App reads its normal login token and calls /pay/line/query. It must not trust Scheme parameters as payment success. Auto-open and fallback button require production-device acceptance testing; Sandbox web payment cannot prove LINE App handoff behavior.