状态:已确认
确认日期:2026-08-18
所属规格:019-line-pay
实现范围:仅 foodie_server 后端
在现有 LINE Pay Online API v4 支付不变的前提下,增加实体门店“商家扫描客户 LINE Pay 我的条码(My Code)”支付能力。
payType="3" 继续统一表示 LINE Pay,不为扫码支付新增支付渠道编号。payment_mode 区分并长期并存。/system/orderShOprate/createOrder 创建的新商家订单可以发起 Offline 扫码支付。shId/mdId。foodie-store 或 foodie-admin-vue,只定义后端接口契约。oneTimeKey 传给后端;金额、币种、商品、门店和 LINE orderId 均由后端生成或读取。pos_store_line_pay 凭证及其已经批准的保存策略,本增量不变更 Channel Secret 的存储和回显规则。本设计以 2026-08-18 可访问的 LINE Pay 官方文档为准:
直接影响实现的约束如下:
oneTimeKey 长度为 18,生成后五分钟内有效。POST /v4/payments/oneTimeKeys/pay 的 Read Timeout 至少为 40 秒。GET /v4/payments/orders/{orderId}/check 查询;该接口 Read Timeout 至少为 20 秒,禁止直接重复付款请求。info.status 为 AUTH_READY、COMPLETE、CANCEL 或 FAIL。AUTH_READY 以及结果码 1169 表明客户仍可能需要在 LINE Pay 选择付款方式并完成密码认证,扫码本身不等于支付成功。info.payInfo[].amount 合计与请求金额一致;不一致必须退款。POST /v4/payments/orders/{orderId}/refund,省略 refundAmount。returnCode 判断,不能把 HTTP 200 当成成功。transactionId 是 19 位整数,沿用现有字符串存储和 JSON 字符串输出。paymentProvider 可能为 TSP 或 EPI,沿用现有字段保存原值。X-LINE-MerchantDeviceProfileId 和 X-LINE-MerchantDeviceType 为成对出现的选填请求头,本期不接收或发送设备信息。采用“扩展现有支付尝试”方案:在 pos_order_line_payment 增加模式字段,复用现有凭证、审计、支付事实、退款事实、行租约和恢复任务。
未采用以下方案:
pos_order.order_source新增 order_source VARCHAR(16) NOT NULL DEFAULT 'USER':
USER:用户端或历史订单,不允许 Offline 扫码支付。MERCHANT:由商家下单接口创建,允许在通过其他门禁后发起 Offline 扫码支付。/system/orderShOprate/createOrder 必须显式写入 MERCHANT。默认值使历史订单保持不可扫码,不做历史来源推断或回填。
pos_order_line_payment.payment_mode新增 payment_mode VARCHAR(16) NOT NULL DEFAULT 'ONLINE':
ONLINE:现有 Online API v4 流水。OFFLINE:本次新增的 My Code 扫码流水。所有现有行通过默认值保持 ONLINE。新建 Online/Offline 尝试时必须显式写入模式。退款、查询和恢复按模式选择端点,不根据是否存在 paymentUrl 等可变字段猜测。
不保存 oneTimeKey,也不新增设备信息字段。
商家下单接口在创建订单前必须一次性完成以下校验:
items 非空且恰好一个门店包,所有门店字段一致。shId/mdId,禁止商家替其他门店创建可扫码订单。paymentMethod 通过现有业务规则。PosOrder 显式写入 orderSource=MERCHANT。扫码接口还必须验证:订单来源为 MERCHANT、属于当前商家、payType="3"、未支付、未取消、未完成、金额大于零、只有一个子订单/门店,并且门店存在已启用 LINE Pay 凭证。
POST /system/orderShOprate/linePay/offline/pay
Header: token
Content-Type: application/json
{
"ddId": "Foodie 业务订单号",
"oneTimeKey": "台湾 18 位 My Code"
}
@RequestBody 和 @RequestHeader String token,不使用 Map 或 Bean Validation。ddId 用于定位 Foodie 订单;提交给 LINE 的 orderId 是后端为本次支付尝试生成的永久唯一 line_order_id,两者不得混用。oneTimeKey 只允许本次方法调用链在内存中使用,接口返回前不持久化。GET /system/orderShOprate/linePay/offline/status?ddId=<Foodie业务订单号>
Header: token
查询接口先验证商家订单归属,再返回稳定的本地结果:
AUTH_REQUIRED 客户需在 LINE Pay 完成认证
PROCESSING 网关结果确认中,禁止重新扫码扣款
PAID 已核实支付成功
CANCELLED 客户取消
FAILED 明确失败,需要客户生成新的 My Code
MANUAL_REVIEW 结果长期不明确,需要人工核对
响应不返回 oneTimeKey、Channel Secret、HMAC、网关原始请求体或内部异常。
LinePayClient 增加独立方法,不能复用 Online request/check/refund 的 URI:
POST /v4/payments/oneTimeKeys/pay
GET /v4/payments/orders/{lineOrderId}/check
POST /v4/payments/orders/{lineOrderId}/refund
首版付款请求仅发送自动请款所需字段,不发送 redirect URL、Capture/Void 或设备请求头:
{
"amount": 100,
"currency": "TWD",
"oneTimeKey": "<仅发送给LINE,不记录>",
"orderId": "后端生成的lineOrderId",
"packages": [
{
"id": "单门店包标识",
"amount": 100,
"products": [
{
"name": "服务端生成的订单商品摘要",
"quantity": 1,
"price": 100
}
]
}
]
}
amount 必须等于 package 金额合计;package 金额必须等于 product 的 quantity × price 合计。签名使用实际发送的同一份 UTF-8 JSON 字符串。
Offline 流水复用现有基础状态:
REQUESTING
REQUEST_UNKNOWN
WAITING_AUTH
PAID
CANCELLED_OR_EXPIRED
FAILED
AMOUNT_MISMATCH
MANUAL_REVIEW
0000:核对 orderId、transactionId、payInfo 金额合计和 paymentProvider;全部一致后落 PAID 并且只执行一次订单支付事实与后续副作用。币种固定取本地请求事实 TWD,不要求付款响应返回该字段。1145、1169:进入 WAITING_AUTH,本地返回 AUTH_REQUIRED/PROCESSING,后续按 lineOrderId 查询。1152、1172、1198:存在相同或重复请求的可能,不直接判失败,转只读状态查询;任何未识别 Pay/Check 返回码也按未知结果处理,不能按失败释放订单。1199、190X、9000:进入 REQUEST_UNKNOWN,禁止重新调用付款接口,只能状态查询。1133:My Code 无效或过期,明确失败并提示重新生成 My Code。FAILED。AUTH_READY:保持 WAITING_AUTH。COMPLETE:核对交易号、订单号和 payInfo 金额合计后落支付事实;查询响应未返回的币种继续以本地请求事实 TWD 为准。CANCEL:进入 CANCELLED_OR_EXPIRED 并释放活跃尝试。FAIL:进入 FAILED 并释放活跃尝试。1150 或未取得终态:在恢复截止时间前继续只读查询;到期进入 MANUAL_REVIEW,不自动释放订单占用。若 payInfo 合计与订单金额不一致,支付行进入 AMOUNT_MISMATCH,记录真实交易号和 captured_amount,并在同一事务内以实际扣款额创建唯一全额退款意图;不得执行正常订单已支付/履约副作用,也不得允许再次扫码,退款结果仍不明确时进入人工核对。
active_dd_id 唯一门禁。oneTimeKey 不发送、不记录。CANCELLED_OR_EXPIRED 或明确 FAILED 释放后,才能使用客户新生成的 My Code 创建新尝试。PAID、REQUEST_UNKNOWN、WAITING_AUTH、AMOUNT_MISMATCH 和 MANUAL_REVIEW 持续阻断新支付。payStatus 和通知/结算副作用均使用现有 CAS/唯一约束保证至多一次。transactionId 调用 /v4/payments/{transactionId}/refund。lineOrderId 调用 /v4/payments/orders/{lineOrderId}/refund。refundAmount,但 URI 必须根据 payment_mode 分派。refundList,不得盲目重复 Refund。oneTimeKey 不得进入数据库、普通日志、异常文本、Controller 响应或 payment_gateway_log.payload。oneTimeKey 固定替换为 <redacted>。returnCode、returnMessage、lineOrderId、字符串 transactionId、paymentProvider 和耗时可以记录。https://sandbox-api-pay.line.me 和官方台湾 My Code 生成器。paymentProvider 原值。书面规格审批后,实施计划至少覆盖:
oneTimeKey 18 位校验、禁止落库和全链路日志脱敏测试。0000/1145/1169/1133/重复请求/未知结果 映射测试。AUTH_READY/COMPLETE/CANCEL/FAIL 状态查询测试。lineOrderId 退款、Online 仍按 transactionId 退款的回归测试。