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

**Feature Branch**: 不新建分支（在当前 test 分支开发）

**Created**: 2026-06-22

**Status**: 已废弃（2026-09-02 确认：蓝新支付方案放弃，`NewebpayPayController`、`PosStoreNewebpayController` 从未在生产启用，代码仅保留备查；相关 `sendAcceptRiderPush` 迁移顺延至接入新支付时处理）

**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）。
