--- 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 为敏感凭证:仅后端持有,日志脱敏,不下发前端