---
description: "Task list for 蓝新金流(NewebPay)线上支付接入"
---

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

**Input**: Design documents from `/specs/011-newebpay-payment/`

**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md（均已就绪）

**Tests**: 未要求 TDD；验证以 `NewebPayEncryptUtil.main` 自测 + quickstart.md 端到端场景为准。

**Organization**: 按 user story 组织，每个 story 可独立实现与验证。MVP = US1 + US2（支付闭环）。

## Format: `[ID] [P?] [Story] Description`

- **[P]**: 可并行（不同文件、无依赖）
- **[Story]**: 归属 user story（US1~US5）
- 描述含精确文件路径；前端 CRLF 文件用 Python 脚本编辑

---

## Phase 1: Setup（基础设施）

**Purpose**: 数据库 DDL 与配置就位

- [ ] T001 追加两表建表 DDL 到 `updatesql/sql.md`（pos_store_newebpay、pos_order_payment，取自 data-model.md，标注 2026-06-22 蓝新支付接入）
- [ ] T002 [P] 在 `ruoyi-admin/src/main/resources/application.yml` 新增 newebpay 配置段（base-url=ccore.newebpay.com、notify-url、return-url、mpg-version=2.3），仿 ezpay 段

---

## Phase 2: Foundational（阻塞前置，所有 story 依赖）

**Purpose**: 加密工具、HTTP 客户端、实体、Mapper——所有 story 的公共地基

**⚠️ CRITICAL**: 本阶段完成前不得开始任何 user story

- [ ] T003 [P] 创建 `ruoyi-admin/src/main/java/com/ruoyi/app/utils/newebpay/NewebPayConfig.java`（merchantId/hashKey/hashIV，仿 EzPayConfig）
- [ ] T004 创建 `ruoyi-admin/src/main/java/com/ruoyi/app/utils/newebpay/NewebPayEncryptUtil.java`：AES-256-CBC/PKCS7Padding(块16) encrypt/decrypt(hex)、genTradeSha(`HashKey=&{enc}&HashIV=`)、genCheckValue(`IV=&{Amt/MerchantID/MerchantOrderNo 排序}&Key=`)、genCheckCode(`HashIV=&{Amt/MerchantID/MerchantOrderNo/TradeNo 排序}&HashKey=`)；含 main 自测用 NDNF §4.1 示例(Key=Fs5cX1TGqYM2PpdbE14a9H83YQSQF5jn/IV=C6AcmfqJILwgnhIP/MID=MS127874575)验证加解密 round-trip 与 TradeSha 规则
- [ ] T005 [P] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/domain/PosStoreNewebpay.java`（@TableName pos_store_newebpay，字段见 data-model.md）
- [ ] T006 [P] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrderPayment.java`（@TableName pos_order_payment，字段见 data-model.md）
- [ ] T007 [P] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosStoreNewebpayMapper.java` + `ruoyi-system/src/main/resources/mapper/chanting/PosStoreNewebpayMapper.xml`（仿 PosStoreEzpayMapper，基础 CRUD）
- [ ] T008 [P] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosOrderPaymentMapper.java` + `ruoyi-system/src/main/resources/mapper/chanting/PosOrderPaymentMapper.xml`（insert、selectByTradeNo、selectByDdId、updateById）
- [ ] T009 [P] 创建 `ruoyi-admin/src/main/java/com/ruoyi/app/utils/newebpay/NewebPay.java` HTTP 客户端：端点常量(URL_MPG_GATEWAY、URL_QUERY_TRADE_INFO、BASE_TEST=ccore/BASE_PROD=core)、createMpgForm(baseUrl,cfg,params)→加密form字段Map、queryTrade(baseUrl,cfg,amt,orderNo)→JSONObject、buildQuery(http_build_query)、postForm

**Checkpoint**: 加密自测通过、表可建、实体/Mapper 就绪 → 可开始 user story

---

## Phase 3: User Story 1 - 发起蓝新支付(信用卡) (Priority: P1) 🎯 MVP part 1

**Goal**: C端用户下单后调发起接口拿到加密form参数，前端Form Post跳转蓝新付款页

**Independent Test**: 对已开通蓝新的门店下单 → 调 /pay/newebpay/create → 返回 gatewayUrl+TradeInfo+TradeSha → 前端跳转蓝新页出现信用卡选项；pos_order_payment 落一行(pay_status=0)、pos_order.pay_type=6

- [ ] T010 [P] [US1] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/StoreNewebpayCredentialDto.java`（storeId/merchantId/hashKey/hashIv/enabledPayments）与 `ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/PosStoreNewebpayVo.java`（列表/详情VO）
- [ ] T011 [US1] 实现 `ruoyi-system/.../service/IPosStoreNewebpayService.java` + `impl/PosStoreNewebpayServiceImpl.java`（纯DB：getOrCreateByStoreId、getEnabledConfig(storeId)返回启用且已开通的凭证或null、基础保存），仿 PosStoreEzpayServiceImpl
- [ ] T012 [US1] 实现 `ruoyi-system/.../service/IPosOrderPaymentService.java` + `impl/PosOrderPaymentServiceImpl.java`（createPayment(ddId,merchantOrderNo,storeId,merchantId,amount)发起流水、getByTradeNo、getByMerchantOrderNo、markSuccess(tradeNo,payType,authCode,payTime,callbackRaw)、markFail）
- [ ] T013 [US1] 在 `ruoyi-system/.../domain/PosOrder.java` 附近定义 payType 蓝新取值常量（PAY_TYPE_NEWEBPAY="6"），或新建枚举类；不改表结构
- [ ] T014 [US1] 创建 `ruoyi-admin/src/main/java/com/ruoyi/app/pay/NewebpayPayController.java` 实现 `POST /pay/newebpay/create`（@Anonymous+@Auth）：校验订单存在/未支付/属当前用户 → 查门店启用凭证(无则报错) → 生成 merchant_order_no="NB"+ddId → 组装TradeInfo明文(MerchantID/RespondType=JSON/TimeStamp/Version=2.3/MerchantOrderNo/Amt=amount/ItemDesc/NotifyURL/ReturnURL/CREDIT=1) → NewebPayEncryptUtil 加密+TradeSha → 落 pos_order_payment → 更新 pos_order.payType=6/payUrl=gatewayUrl → 返回 form 字段（见 contracts/api.md B1）
- [ ] T015 [US1] 前端 `E:\QtwCode\foodie\foodie-store\` 新增"蓝新支付"入口：用户下单后调 /pay/newebpay/create，用返回字段构建隐藏 form 自动 submit 到 gatewayUrl；支付方式相关文案用 $t() 四语 i18n（zh/tw/en/vi，key 加到对应对象层级，如支付/订单对象内）

**Checkpoint**: 发起支付链路通，可跳转蓝新测试页（此时回调未接，订单状态待 US2 更新）

---

## Phase 4: User Story 2 - 支付结果回调处理 (Priority: P1) 🎯 MVP part 2

**Goal**: 蓝新NotifyURL回调到达后，解密验签+幂等+金额校验，更新订单payStatus=1并触发推送；ReturnURL仅引导

**Independent Test**: US1发起并跳转后用测试卡付款 → 蓝新回调 /pay/newebpay/notify → 订单payStatus变1、pos_order_payment写trade_no/pay_type=CREDIT、商家/骑手收到推送；重复回调不变；金额不符/伪造签名拒绝

- [ ] T016 [US2] 在 `NewebpayPayController.java` 实现 `POST /pay/newebpay/notify`（@Anonymous）：收Form参数 → 记IpnLog → 由MerchantID查门店凭证 → 无凭证记录返回
- [ ] T017 [US2] notify 验签解密：`genTradeSha(TradeInfo,key,iv)==TradeSha` 校验，不符拒绝记录；通过则 `NewebPayEncryptUtil.decrypt(TradeInfo)` 解出 Status/MerchantOrderNo/TradeNo/Amt/PaymentType/PayTime/Auth
- [ ] T018 [US2] notify 幂等+金额校验：按TradeNo查 pos_order_payment（已存在且pay_status=1→直接返回成功应答）；Amt==订单amount校验（不符→拒绝更新、记录告警）；由MerchantOrderNo反查pos_order（不存在→记录待查返回）
- [ ] T019 [US2] notify 成功业务链路（Status=SUCCESS且校验通过）：markSuccess写流水、pos_order.payStatus=1(state不变)、orderLogHelper.logSync记日志、复用 pushEventService.PublisherEvent + sendAcceptRiderPush 推送用户/商家/骑手（参照 PayController.payipn）；返回 `{"Status":"SUCCESS","Message":"OK"}`
- [ ] T020 [US2] notify 失败处理（Status≠SUCCESS）：markFail记 pos_order_payment(pay_status=2)、订单保持未支付、记失败原因、返回成功应答
- [ ] T021 [US2] 在 `NewebpayPayController.java` 实现 `GET/POST /pay/newebpay/return`（@Anonymous）：仅302重定向前端结果页(带ddId)，不改订单状态（FR-015）
- [ ] T022 [US2] 前端 `E:\QtwCode\foodie\foodie-store\` 支付结果页：展示支付状态，轮询订单payStatus（因ReturnURL不改状态，以轮询/回调后状态为准）；文案四语 i18n

**Checkpoint**: US1+US2 构成完整 MVP——下单→付款→订单已支付→推送，可端到端验证（quickstart 场景2、3）

---

## Phase 5: User Story 3 - LINE Pay / Apple Pay (Priority: P2)

**Goal**: 用户在蓝新页可选 LINE Pay / Apple Pay，回调方式记录正确

**Independent Test**: 门店enabled_payments含LINEPAY/APPLEPAY → 发起参数带LINEPAY=1/APPLEPAY=1 → 蓝新页出现选项 → 付款后回调pay_type为LINEPAY/APPLEPAY

- [ ] T023 [P] [US3] 扩展 `NewebpayPayController.create`：按门店 enabled_payments 在TradeInfo明文中设 LINEPAY=1 / APPLEPAY=1（与CREDIT并存），读取门店 pos_store_newebpay.enabled_payments
- [ ] T024 [US3] notify 解析 PaymentType 映射到 pos_order_payment.pay_type（CREDIT/LINEPAY/APPLEPAY，回调字段 PaymentType/PaymentMethod），确保 US2 的 markSuccess 正确写入方式
- [ ] T025 [US3] 前端 `E:\QtwCode\foodie\foodie-store\` 支付方式展示：按门店启用方式渲染可选标识（发起前提示），文案四语 i18n

**Checkpoint**: 三种支付方式均可完成闭环（quickstart 场景4；Apple Pay 需具备 HTTPS+Apple 配置）

---

## Phase 6: User Story 4 - 门店蓝新凭证开通管理 (Priority: P2)

**Goal**: 运营在平台后台录入/验证/启停门店蓝新凭证，复用009模式

**Independent Test**: 后台为门店录入测试凭证 → 调QueryTradeInfo验证通过 → 状态已开通 → 该门店可发起支付；停用后不可

- [ ] T026 [US4] 创建 `ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosStoreNewebpayController.java`（/system/storeNewebpay，@PreAuthorize chanting:storeNewebpay:*）：list/{storeId}/apply/saveCredentials/toggleEnable/reset/enabledPayments，仿 PosStoreEzpayController
- [ ] T027 [US4] saveCredentials 凭证验证：Controller 调 NewebPay.queryTrade(虚构订单)探测，返回金钥/商店错误(MPG01001类/Status≠SUCCESS且Message含金钥)→视为无效拒绝；否则置 newebpay_status=2/is_enabled=1、记last_verify_result
- [ ] T028 [US4] 扩展 `PosStoreNewebpayServiceImpl`：apply(0→1)、enableWithCredentials、toggleEnable、reset、setEnabledPayments、recordVerifyResult（仿 PosStoreEzpayServiceImpl 状态机）
- [ ] T029 [US4] 平台后台 `E:\QtwCode\foodie\foodie-admin-vue\` 新增门店蓝新凭证管理页（列表分页/详情/录入凭证/启停/重置/设置支付方式），文案四语 i18n
- [ ] T030 [US4] 菜单与权限：新增菜单项 + 权限标识 chanting:storeNewebpay:list/query/apply/saveCredentials/toggleEnable，SQL 写入 `updatesql/sql.md`（sys_menu 插入，仿 storeEzpay 菜单）

**Checkpoint**: 凭证可全流程管理，US1/US3 发起支付有真实凭证来源（不必手插数据）

---

## Phase 7: User Story 5 - 单笔交易查询/补单 (Priority: P3)

**Goal**: 回调丢失或对账时，主动查蓝新并校正订单状态

**Independent Test**: 回调丢失(订单未支付) → 调 /pay/newebpay/query → 蓝新返回TradeStatus=1 → 补更新payStatus=1+推送

- [ ] T031 [US5] 在 `NewebpayPayController.java` 实现 `POST /pay/newebpay/query`（@Auth）：查订单+门店凭证 → genCheckValue → NewebPay.queryTrade 调 /API/QueryTradeInfo → 解析 TradeStatus/PaymentType/PayTime
- [ ] T032 [US5] 查询补单：若蓝新TradeStatus=1且订单payStatus仍0 → 执行与US2相同的状态更新+流水+推送（抽取公共方法供 notify/query 复用，避免重复）
- [ ] T033 [US5] 商家端 `E:\QtwCode\foodie\foodie-store\` 订单详情增加"查询支付状态"入口（展示蓝新TradeStatus/支付方式/时间），文案四语 i18n

**Checkpoint**: 对账与补单可用（quickstart 场景5）

---

## Phase 8: Polish & Cross-Cutting

**Purpose**: 多 story 共享的收尾

- [ ] T034 [P] 四语 i18n 统一校验：foodie-store 与 foodie-admin-vue 的 zh.js/tw.js/en.js/vi.js 中支付相关 key 四文件齐全、命名有意义、位于正确对象层级（用 i18n-consistency-checker 或人工核）
- [ ] T035 [P] 安全复核：NotifyURL/ReturnURL 确为 @Anonymous 且仅此两接口匿名；HashKey/HashIV 不出现在任何前端响应；回调 TradeSha 验签为强制前置；金额校验不可绕过
- [ ] T036 运行 `specs/011-newebpay-payment/quickstart.md` 全部 6 场景端到端验证（测试环境 ccore + 测试卡 + 内网穿透），记录结果
- [ ] T037 文档一致性：确认 spec.md/plan.md/tasks.md 描述一致；更新 `updatesql/sql.md` 中所有 SQL 齐全（两建表 + 菜单权限）

---

## Dependencies & Execution Order

### Phase 依赖
- **Phase 1 Setup**: 无依赖，立即开始
- **Phase 2 Foundational**: 依赖 Phase 1（配置/DDL）—— **阻塞所有 user story**
- **Phase 3 US1**: 依赖 Phase 2
- **Phase 4 US2**: 依赖 US1（回调处理依赖发起产生的流水与订单关联）
- **Phase 5 US3**: 依赖 US1+US2（复用主链路，增量在参数开关与方式解析）
- **Phase 6 US4**: 依赖 Phase 2（凭证表/Mapper）；可与 US1 并行（US1 测试可临时手插凭证数据）
- **Phase 7 US5**: 依赖 US1+US2（查询补单复用状态更新逻辑）
- **Phase 8 Polish**: 依赖所有欲交付的 story 完成

### User Story 独立性
- **US1**: 依赖 Foundational，无跨 story 依赖
- **US2**: 依赖 US1（闭环下半段）
- **US3**: 依赖 US1+US2，但增量小（参数+解析）
- **US4**: 依赖 Foundational，可与 US1 并行
- **US5**: 依赖 US1+US2

### 并行机会
- Phase 2 的 T003/T005/T006/T007/T008/T009 互不冲突，可全部并行
- US4（Phase 6）可与 US1/US2（Phase 3/4）并行（不同文件、不同端）
- Phase 8 的 T034/T035 可并行

---

## Parallel Example: Foundational

```bash
# 以下任务文件互不冲突，可并行：
Task: "NewebPayConfig in ruoyi-admin/.../utils/newebpay/NewebPayConfig.java"
Task: "PosStoreNewebpay in ruoyi-system/.../domain/PosStoreNewebpay.java"
Task: "PosOrderPayment in ruoyi-system/.../domain/PosOrderPayment.java"
Task: "PosStoreNewebpayMapper + XML"
Task: "PosOrderPaymentMapper + XML"
Task: "NewebPay HTTP 客户端骨架 in ruoyi-admin/.../utils/newebpay/NewebPay.java"
# 仅 NewebPayEncryptUtil(T004) 含被他人依赖的加密契约，建议优先完成并跑通自测
```

---

## Implementation Strategy

### MVP First（US1 + US2）
1. Phase 1 Setup + Phase 2 Foundational（**加密自测必须先过**）
2. Phase 3 US1（发起支付）+ Phase 4 US2（回调处理）→ 端到端验证（quickstart 场景2、3）
3. **STOP and VALIDATE**: 下单→信用卡付款→订单已支付→推送，全链路通
4. 此时已是最小可用产品

### 增量交付
1. MVP（US1+US2）→ 验证 → 可上测试环境
2. + US3（LINE Pay/Apple Pay）→ 验证
3. + US4（凭证管理）→ 去除手插数据、运营自助
4. + US5（查询补单）→ 异常补救就绪
5. Polish（i18n/安全/全场景验证）→ 可转正式

### 并行策略
- Foundational 完成后：开发者 A 做 US1+US2（主链路），开发者 B 做 US4（凭证管理前端+后端），互不阻塞
- US3/US5 待主链路稳定后接力

---

## Notes

- [P] 任务 = 不同文件、无依赖；同文件多改动须串行
- 前端 CRLF 文件（foodie-store/foodie-admin-vue）用 Python 脚本编辑，避免 Edit 匹配失败
- 所有数据库变更写 `updatesql/sql.md`，不直接执行
- 每个任务或逻辑组完成后提交（按用户指示）；在 test 分支开发，不新建分支
- HashKey/HashIV 为敏感凭证：仅后端持有，日志脱敏，不下发前端
