所有 JSON 业务响应沿用项目 AjaxResult 外壳。交易号字段始终是 JSON string。业务错误文本从 MessageUtils.message(...) 获取。
POST /pay/line/createAnnotations: @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:
payType="3",单门店、未支付、合法非终态、门店当前 LINE 版本已验证并启用。reusedAttempt=true,不再 Request。CANCELLED_OR_EXPIRED/FAILED 才新增行。POST /pay/line/queryAnnotations 与 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。
POST /system/orderShOprate/createOrderRequest 继续使用现有 OrderCreateInput,其中 paymentMethod="3" 写入子订单 pay_type="3"。本增量不接受新的来源字段:
items 必须非空且恰好包含一个门店包,所有商品和金额属于同一门店。userType=1/3 同时校验 PosOrder.shId=userId 和目标 PosStore.userId=userId;其他摊位商家按 PosOrder.mdId=InfoUser.storeId 校验。PosOrder 显式写 order_source="MERCHANT";客户端不能传入或覆盖该值。POST /system/orderShOprate/linePay/offline/payAnnotations: @Anonymous, @Auth, @RequestHeader String token, @RequestBody LinePayOfflinePayRequest。
Request:
{
"ddId": "202608180001",
"oneTimeKey": "123456789012345678"
}
Success data:
{
"ddId": "202608180001",
"paymentId": 84,
"lineOrderId": "LPOFF20260818...",
"transactionId": "2026081800000000001",
"status": "AUTH_REQUIRED",
"reusedAttempt": false,
"updatedAt": "2026-08-18T12:34:56+08:00"
}
Rules:
oneTimeKey 必须是 18 位数字,只在本次内存调用链使用;响应、异常、数据库和任何日志不得返回原值。MERCHANT 新订单,且 payType="3"、未支付、未取消、未完成、金额大于零、门店凭证已启用。oneTimeKey,返回已有本地状态并令 reusedAttempt=true。line.pay.offline.attempt.blocked,不得返回 Online 跳转地址、交易上下文或发送 My Code。GET /system/orderShOprate/linePay/offline/status?ddId={ddId}Annotations: @Anonymous, @Auth, @RequestHeader String token, @RequestParam String ddId。
该接口验证 MERCHANT 来源和当前商家归属后只读本地状态,不在请求线程调用 LINE。Success data 与付款接口相同,但 reusedAttempt 可省略。规范状态:
若订单尚无 Offline 尝试(包括只有 Online 历史/活跃尝试),返回业务错误 line.pay.offline.attempt.not.found,不得回传 Online 支付上下文。
AUTH_REQUIRED 1145/1169/AUTH_READY/WAITING_AUTH,客户仍需在 LINE Pay 操作
PROCESSING Pay 结果未知或重复待核实,后端只读查单中
PAID 已严格核实订单号、交易号、金额和 paymentProvider
CANCELLED 客户取消或支付过期,可使用新的 My Code 重试
FAILED 明确无副作用失败,可使用新的 My Code 重试
MANUAL_REVIEW 恢复截止仍未知或支付事实字段不完整
AUTH_REQUIRED/PROCESSING/PAID/MANUAL_REVIEW 均阻止再次扫码。金额不一致内部状态 AMOUNT_MISMATCH 对外返回 MANUAL_REVIEW,持久化 LINE 实际扣款额,并以该金额自动创建唯一全额退款;原订单不再开放支付。
GET /pay/line/confirm?orderId={lineOrderId}&transactionId={transactionId}orderId 是 LINE Request 的 line_order_id,不是 ddId。@RequestParam;按 lineOrderId 定位并校验 transactionId。text/html;charset=UTF-8 和:
Cache-Control: no-store, no-cache, must-revalidatePragma: no-cacheReferrer-Policy: no-referrerContent-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/cancelOptional explicit query parameters: orderId, transactionId.
Base path: /system/storeLinePay
GET /listPermission: 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 /saveCredentialsPermission: 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 /toggleEnablePermission: 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.
GET /system/order/{id}/status-contextExtend 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/reconcilePermission: 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-refundPermission: system:order:lineRefund. Creates/uses the unique full-refund row for the selected PAID payment. UNKNOWN only retrieves; it never directly repeats Refund.
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.
GET /v4/payments/requests/{transactionId}/check, 20 second timeout.
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.
GET /v4/payments?orderId={encodedLineOrderId} or ?transactionId={encodedTransactionId}, 20 second timeout. The exact ordered query string is included in the HMAC input.
POST /v4/payments/{transactionId}/refund, 20 second timeout, body {}. refundAmount is intentionally omitted for full refund; there is no currency request field.
POST /v4/payments/oneTimeKeys/pay, read timeout 40 seconds or greater.
{
"amount": 100,
"currency": "TWD",
"orderId": "LPOFF20260818...",
"oneTimeKey": "<仅发送给 LINE,不记录>",
"packages": [{
"id": "202608180001",
"amount": 100,
"products": [{"name": "Order 202608180001", "quantity": 1, "price": 100}]
}]
}
0000 必须核对响应 orderId、字符串 transactionId、payInfo[].amount 合计和非空 paymentProvider 后才能落账。1145/1169 进入 WAITING_AUTH;超时、不可解析响应和不明确结果进入 REQUEST_UNKNOWN,后续不得再次调用 Pay。GET /v4/payments/orders/{encodedLineOrderId}/check, 20 second timeout. Path 中使用永久 lineOrderId,签名 URI 使用编码后的最终路径。
AUTH_READY -> WAITING_AUTH / AUTH_REQUIRED
COMPLETE -> strict fact validation -> PAID or AMOUNT_MISMATCH/MANUAL_REVIEW
CANCEL -> CANCELLED_OR_EXPIRED / CANCELLED
FAIL -> FAILED
POST /v4/payments/orders/{encodedLineOrderId}/refund, 20 second timeout, body {}。refundAmount 省略;退款响应未知时沿用 Retrieve/refundList 只读恢复,禁止直接重发。
Online 与 Offline Refund 只能根据持久化的 payment_mode 分派,不能根据 URL、transactionId 是否存在或调用者参数判断。
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
Offline 请求不得添加 X-LINE-MerchantDeviceProfileId 或 X-LINE-MerchantDeviceType。网关审计保存 Offline REQUEST 时必须删除 oneTimeKey 或固定替换为 <redacted>;捕获异常时不得拼接请求 DTO/body。
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.