spec.md 16 KB

Feature Specification: 蓝新金流(NewebPay)线上支付接入

Feature Branch: 不新建分支(在当前 test 分支开发)

Created: 2026-06-22

Status: Draft

Input: User description: "为系统接入线上支付。渠道:蓝新金流(NewebPay) MPG幕前支付(文档:線上交易─幕前支付技術串接手冊 NDNF-1.2.2)。场景:C端用户下单后跳转蓝新MPG付款页完成支付,系统通过NotifyURL背景回调接收加密支付结果并更新订单payStatus。复用现有ezPay工具类的AES-256-CBC+SHA256加密模式与PosStoreEzpay门店凭证存储模式。第一期支付方式:信用卡、LINE Pay、Apple Pay;凭证门店级存储;功能为核心闭环(下单支付+回调+单笔查询)。spec编号011,不新建分支,在当前test分支开发。"

User Scenarios & Testing (mandatory)

User Story 1 - C端用户用信用卡完成线上支付 (Priority: P1)

C端用户在门店下单(外卖/自取/堂食扫码点餐)后选择线上支付,系统按订单所属门店的蓝新凭证生成加密交易参数(商店订单号、合计金额、启用信用卡),前端以 Form Post 将用户跳转至蓝新 MPG 付款页面;用户在蓝新页输入信用卡完成付款,蓝新通过 NotifyURL 背景通知系统支付结果,系统解密验签后把订单支付状态更新为「已支付」。

Why this priority: 这是「用户能在线付钱、商家能收到钱」的核心价值,是整个支付接入的主链路;没有它,后续回调、查询、对账都无从谈起。

Independent Test: 对一个已开通蓝新的门店下一笔订单 → 选择信用卡支付 → 跳转蓝新付款页并完成 → 订单支付状态变为已支付,订单上记录蓝新交易序号与支付方式。

Acceptance Scenarios:

  1. Given 订单所属门店已开通且启用蓝新支付,When 用户下单并选择线上信用卡支付,Then 系统生成加密交易参数并跳转至蓝新 MPG 付款页,页面上出现信用卡支付选项。
  2. Given 用户已在蓝新付款页成功完成信用卡付款,When 蓝新向系统 NotifyURL 发送支付结果,Then 系统解密回调、验签通过、金额一致后,订单支付状态更新为「已支付」,记录蓝新交易序号(TradeNo)、支付方式(CREDIT)、授权码与支付时间。
  3. Given 门店未开通蓝新支付或已停用,When 用户下单,Then 不提供线上支付选项(或提示该门店暂不支持线上支付)。

User Story 2 - 系统正确处理蓝新支付结果回调 (Priority: P1)

蓝新在每笔交易支付完成(或失败)后,以 Form Post 向系统 NotifyURL 发送加密的支付结果(Status、MerchantID、TradeInfo、TradeSha 等)。系统用订单所属门店的 HashKey/HashIV 对 TradeInfo 做 AES 解密、对 TradeSha 做 SHA256 验签、用 CheckCode 校验关键字段,确认交易成功且回调金额与订单金额一致后,才更新订单支付状态。同一笔交易被蓝新重复通知时,系统只处理一次(幂等)。

Why this priority: 回调是订单状态真实性的唯一可信来源——前端跳回(ReturnURL)只代表用户走完流程,不代表付款成功。回调的解密、验签、幂等、金额校验是支付安全的底线,与 US1 同为核心。

Independent Test: 构造一笔成功的蓝新回调报文(正确加密+签名+金额)发送到 NotifyURL → 订单变为已支付;将同一报文再发一次 → 订单状态不变(幂等);篡改金额后发送 → 订单拒绝更新。

Acceptance Scenarios:

  1. Given 一笔交易成功且金额与订单一致的正确加密回调,When 蓝新发送到 NotifyURL,Then 订单支付状态更新为已支付,系统记录交易明细并向蓝新返回成功应答。
  2. Given 同一交易(同一蓝新交易序号 TradeNo)的回调被发送多次,When 系统再次收到,Then 不重复更新订单、不重复记录,仍返回成功应答。
  3. Given 回调中的金额与订单合计金额不一致,When 系统处理,Then 拒绝更新订单支付状态,记录异常告警,便于运营核查。
  4. Given 回调签名(TradeSha)校验失败或解密失败,When 系统处理,Then 拒绝更新并记录,防止伪造回调。
  5. Given 交易付款失败的回调(Status 为错误代码),When 系统处理,Then 订单保持未支付,记录失败原因。

User Story 3 - C端用户使用 LINE Pay / Apple Pay 支付 (Priority: P2)

用户在蓝新 MPG 付款页除信用卡外,还可选择 LINE Pay 或 Apple Pay。选 LINE Pay 时跳转 LINE 完成付款;选 Apple Pay 时调起 Apple Pay 完成付款。两种方式付款完成后,蓝新同样通过 NotifyURL 通知系统,系统处理流程与信用卡一致。

Why this priority: 信用卡已覆盖核心付款,LINE Pay 与 Apple Pay 是台湾高频的体验增强,但依赖 US1/US2 主链路先打通,故 P2。

Independent Test: 对已开通蓝新的门店下单 → 在蓝新付款页分别选 LINE Pay 与 Apple Pay 完成支付 → 订单支付状态均变为已支付,记录对应支付方式(LINEPAY / APPLEPAY)。

Acceptance Scenarios:

  1. Given 订单已发起且启用 LINE Pay,When 用户在蓝新页选 LINE Pay 并完成,Then 回调到达后订单更新为已支付,支付方式记录为 LINE Pay。
  2. Given 订单已发起且启用 Apple Pay,When 用户在 Apple Pay 设备上调起并完成,Then 回调到达后订单更新为已支付,支付方式记录为 Apple Pay。
  3. Given 门店未在发起参数中启用某支付方式,When 用户到达蓝新付款页,Then 该支付方式不出现(按门店配置/商店设定控制)。

User Story 4 - 门店开通并管理蓝新支付凭证 (Priority: P2)

门店在平台代为向蓝新申请商店账号后,运营在平台后台录入该门店的 MerchantID/HashKey/HashIV,系统调用蓝新能力验证凭证有效性后开通;门店可启用/停用蓝新支付。该流程复用 009-ezpay-invoice-onboarding 已建立的「平台代申请 → 运营录入凭证 → 验证开通 → 可停用」管理模式。

Why this priority: 凭证是发起任何交易的前提,但属于配置态、非交易主链路,复用 009 成熟流程可快速落地,故 P2。

Independent Test: 运营在后台为某门店录入一组蓝新凭证 → 系统验证通过后状态变为「已开通」→ 该门店订单即可发起线上支付;停用后该门店订单不再提供线上支付。

Acceptance Scenarios:

  1. Given 运营在后台为门店录入蓝新 MerchantID/HashKey/HashIV,When 提交并触发验证,Then 凭证有效则状态置「已开通」并记录开通时间,无效则提示验证失败原因。
  2. Given 门店已开通蓝新,When 运营/商家停用,Then 该门店订单不再提供线上支付,凭证保留以便重新启用。
  3. Given 门店从未录入蓝新凭证,When 查看门店,Then 状态为「未申请」,订单不提供线上支付。

User Story 5 - 商家/平台通过单笔查询确认订单支付状态 (Priority: P3)

当 NotifyURL 回调因网络等原因未到达、或需要对账时,商家在商家端、运营在平台后台可对某订单主动发起蓝新单笔交易查询;系统用门店凭证向蓝新查询,返回该笔交易的支付状态(TradeStatus)、支付方式、支付时间等,并据此校正订单支付状态(回调丢失时的补单)。

Why this priority: 查询是对账与异常补救手段,依赖交易主链路稳定运行后才有意义,频率低,故 P3。

Independent Test: 对一笔用户已付款但订单仍为未支付的订单(回调丢失),运营点「查询支付状态」→ 系统向蓝新查询返回已支付 → 订单状态被校正为已支付。

Acceptance Scenarios:

  1. Given 一笔订单用户已付款但回调未到达(订单仍为未支付),When 运营发起单笔查询,Then 系统向蓝新查询得 TradeStatus=付款成功,订单支付状态校正为已支付并记录明细。
  2. Given 一笔未支付订单,When 发起查询,Then 返回蓝新 TradeStatus=未付款,订单状态保持未支付。
  3. Given 一笔订单查询返回付款失败,When 系统处理,Then 订单保持未支付,记录失败原因,可重新发起支付。

Edge Cases

  • 回调丢失:用户已付款但 NotifyURL 回调未到达 → 订单停留未支付,由 US5 单笔查询补单。
  • 回调重复:蓝新对同一笔交易重试多次通知 → 系统按蓝新交易序号(TradeNo)幂等,只处理一次。
  • 金额不一致:回调金额 ≠ 订单合计金额 → 拒绝更新,记录告警,不影响订单。
  • 签名/解密失败:伪造或损坏的回调 → 拒绝更新并记录。
  • 支付中途关闭/超时:用户跳转蓝新后未完成付款即关闭 → 订单停留未支付,可重新发起支付(生成新的商店订单号 MerchantOrderNo)。
  • 门店凭证未开通/已停用:不展示线上支付入口,下单走原有到付等方式。
  • 同一订单重复发起支付:每次发起生成新的商店订单号,旧发起作废,回调按最新 TradeNo 处理。
  • 蓝新测试与正式环境切换:通过配置在 ccore(测试)与 core(正式)间切换,避免误用。
  • 回调到达早于订单发起记录落库:以商店订单号(MerchantOrderNo)关联订单,回调到达时若订单不存在则记录并跳过,等待人工/查询处理。

Requirements (mandatory)

Functional Requirements

  • FR-001: 系统 MUST 能按订单所属门店读取该门店的蓝新凭证(MerchantID/HashKey/HashIV),未开通或已停用的门店不能发起线上支付。
  • FR-002: 系统 MUST 生成符合蓝新 MPG 规范的加密交易参数:以门店 HashKey/HashIV 对交易明文做 AES-256-CBC(PKCS7) 加密生成 TradeInfo,再用 SHA256 生成 TradeSha,串接版本号 2.3。
  • FR-003: 系统 MUST 在发起交易时支持信用卡、LINE Pay、Apple Pay 三种支付方式的启用开关,并可按门店配置控制可选方式。
  • FR-004: 系统 MUST 为每笔交易生成门店内唯一的商店订单号(MerchantOrderNo),并与系统订单号(ddId)关联,同一门店内不可重复。
  • FR-005: 支付金额 MUST 取订单合计金额(amount,整数元,新台币)。
  • FR-006: 系统 MUST 提供一个对外公网(80/443)的 NotifyURL 接口接收蓝新 Form Post 回调,对 TradeInfo 解密、对 TradeSha 验签、用 CheckCode 校验关键字段。
  • FR-007: 回调处理 MUST 幂等——同一蓝新交易序号(TradeNo)的多次通知只更新订单一次。
  • FR-008: 回调 MUST 校验「回调金额 = 订单合计金额」且签名正确,任一不符则拒绝更新订单支付状态并记录。
  • FR-009: 交易成功回调 MUST 把订单支付状态(payStatus)更新为「已支付」,并记录蓝新交易序号、支付方式、授权码、支付完成时间。
  • FR-010: 系统 MUST 记录每笔蓝新支付的完整明细(商店订单号、蓝新交易序号、门店、支付方式、金额、支付状态、支付时间、回调原始结果),用于幂等与对账。
  • FR-011: 系统 MUST 支持对订单发起蓝新单笔交易查询(QueryTradeInfo),返回并展示蓝新侧支付状态,并据此校正订单支付状态。
  • FR-012: 门店 MUST 能在平台后台录入/验证/启用/停用蓝新支付凭证,复用 009-ezpay-invoice-onboarding 的开通管理流程。
  • FR-013: 未支付订单 MUST 能重新发起支付(生成新商店订单号),原发起作废。
  • FR-014: 系统 MUST 支持测试环境(ccore.newebpay.com)与正式环境(core.newebpay.com)通过配置切换。
  • FR-015: 支付完成页(ReturnURL) MUST 将用户引导回系统前端并展示支付结果,但订单状态以 NotifyURL 回调为准(前端只作引导,不触发状态变更)。

Key Entities (include if feature involves data)

  • 门店蓝新支付凭证(PosStoreNewebpay):与 pos_store 一对一。记录该门店的蓝新申请状态(未申请/申请中/已开通)、启用开关、MerchantID、HashKey、HashIV、申请时间、开通时间、最近验证结果、备注及审计字段。门店无此记录即视为未申请。复用 009 模式。
  • 订单(PosOrder,已有实体扩展):复用 ddId(订单号)、amount(合计金额)、payType(支付方式,扩展蓝新取值)、payStatus(支付状态 0未支付/1已支付/2已退款)、payUrl(发起支付后的跳转地址)。本期扩展 payType 取值以区分蓝新信用卡/LINE Pay/Apple Pay。
  • 支付交易流水(PosOrderPayment,新增):每笔蓝新交易一条。记录系统订单号(ddId)、商店订单号(MerchantOrderNo)、蓝新交易序号(TradeNo)、门店ID、支付方式、金额、支付状态、支付完成时间、回调原始结果、创建/更新时间。作为回调幂等判重与对账的依据。

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: 已开通蓝新的门店,C端用户下单后不超过 3 次点击即可跳转至蓝新付款页。
  • SC-002: 在 NotifyURL 回调正常到达的情况下,用户付款完成后 30 秒内订单支付状态更新为「已支付」。
  • SC-003: 异常回调(重复通知、金额不符、签名错误、解密失败)不会导致订单被错误更新,此类异常误更新率为 0%。
  • SC-004: 99% 的支付回调在蓝新发出后 10 秒内被系统正确处理并应答。
  • SC-005: 回调丢失时,运营通过单笔查询可在 1 分钟内补正订单支付状态。
  • SC-006: 门店运营可在平台后台自助完成蓝新凭证的录入、验证与启用/停用,无需开发介入。
  • SC-007: 信用卡、LINE Pay、Apple Pay 三种支付方式均能完成「下单→付款→订单已支付」完整链路。

Assumptions

  • 复用现有 ezPay 工具类的「AES-256-CBC + SHA256」加密思路,但因蓝新 PKCS7 填充块为标准 16 字节(ezPay 为特殊的 32 字节)且 MPG 为幕前 Form Post(非幕后的 PostData_),需新建独立的 NewebPay 加密客户端与工具类,不复用 EzPayEncryptUtil 的加密实现。
  • 凭证按门店存储,开通管理复用 009-ezpay-invoice-onboarding 已建立的「平台代申请 → 运营录入凭证 → 验证开通 → 可停用」流程与界面模式(新建对应蓝新的实体/表/接口,不与 ezPay 发票凭证混用)。
  • 面向 C 端用户下单场景(外卖/自取/堂食扫码点餐)收款;商家端与平台后台用于凭证管理与对账查询。
  • 商店订单号 MerchantOrderNo 以系统订单号 ddId 派生(如加门店/时间前缀),保证门店内唯一、可追溯。
  • 支付金额取订单合计金额 amount(整数元,新台币),与发票口径(amount−freight 不含运费)不同;发票与支付各自独立计算。
  • NotifyURL/ReturnURL 为系统对外公网接口(仅 80/443),部署环境需可被蓝新服务器访问。
  • 测试环境使用蓝新 ccore.newebpay.com,正式环境 core.newebpay.com,通过配置切换;接入初期在测试环境验证,正式启用前更换正式凭证。
  • 在当前 test 分支开发,不新建分支;所有数据库变更写入 updatesql/sql.md,不直接执行。
  • 第一期不含退款、取消授权、信用卡定期定额/订阅(NDNP);这些作为后续迭代。
  • 现有 PosOrder 的 payStatus 字段语义(0未支付/1已支付/2已退款)沿用,本期主要写入 0/1。
  • 前端为 Vue(商家端 foodie-store / 平台端 foodie-admin-vue),支付方式相关文案须按项目规范实现四语 i18n(vi/zh/tw/en)。