Feature: specs/011-newebpay-payment/spec.md Date: 2026-06-22
本文记录实现蓝新 MPG 幕前支付所需的关键技术决策、理由与备选。所有决策已基于现有系统(ezPay 发票实现、PayController 回调链路、PosOrder 字段)与蓝新官方手册 NDNF-1.2.2。
Decision: 新建 ruoyi-admin/.../app/utils/newebpay/NewebPayEncryptUtil.java + NewebPay.java(HTTP 客户端)+ NewebPayConfig.java,与 ezPay 工具类并列,不抽取公共加密套件。
Rationale:
TradeSha = SHA256("HashKey={key}&{tradeInfoHex}&HashIV={iv}") 大写(请求时带);CheckCode = SHA256("HashIV={iv}&{Amt=&MerchantID=&MerchantOrderNo=&TradeNo= 排序}&HashKey={key}") 大写(校验响应);CheckValue = SHA256("IV={iv}&{Amt=&MerchantID=&MerchantOrderNo= 排序}&Key={key}") 大写(查询请求)。与 ezPay 的 CheckValue/CheckCode 形似但字段与前缀不同。EncryptType=0(默认 AES/CBC/PKCS7Padding),不上 AES/GCM,降低复杂度。Alternatives:
PaymentCryptoUtil 公共类:被否,因填充块大小与检查码字段差异使抽象收益低、误用风险高。Decision: 新增「发起支付」接口返回 { gatewayUrl, MerchantID, TradeInfo, TradeSha, Version, EncryptType };前端用隐藏 form 自动 submit 跳转至 https://(c)core.newebpay.com/MPG/mpg_gateway。
Rationale:
payUrl(单个跳转 URL)模式不同——蓝新需 POST 多个加密字段。故不复用 PosOrder.payUrl 存 URL,而是接口返回 form 参数;payUrl 字段保留存 gatewayUrl 供前端兜底/记录。Alternatives:
Decision: 新建 pos_store_newebpay,与 pos_store 一对一,结构与状态机复用 009 pos_store_ezpay 模式,但独立表、独立字段。
Rationale:
enabled_payments:记录该门店启用的支付方式组合(信用卡/LINE Pay/Apple Pay),发起 MPG 时据此设置 CREDIT/LINEPAY/APPLEPAY 开关。Alternatives:
Decision: 录入凭证后调蓝新 QueryTradeInfo(查一个不存在的订单号),根据返回判断凭证是否有效。
Rationale:
MPG01001 系列、Status≠SUCCESS 且 Message 提示金钥);若凭证正确则返回「交易不存在」类结果(Status≠SUCCESS 但 Message 为查询无资料)。KEY1 判定金钥错误)。Alternatives:
Decision: 新建 pos_order_payment,每笔蓝新交易一条,回调按 trade_no(蓝新交易序号)幂等。
Rationale:
PosOrder.payStatus 只能存最终状态,无法承载 TradeNo、支付方式、授权码、原始回调、多次发起。蓝新要求按 TradeNo 幂等(NotifyURL 可能重试),必须有专门表。Alternatives:
Decision: merchant_order_no = "NB" + ddId(ddId 为系统订单号),保证门店内唯一、格式合法(英数+下划线)、可由 ddId 反查订单。
Rationale:
-// 等非法字符;若含则清洗(替换为 _)。Alternatives:
Decision: 新建 NewebpayPayController(/pay/newebpay/*),回调接口 /pay/newebpay/notify 加 @Anonymous;处理流程复用现有 PayController.payipn 的业务链路(IPN 日志 → 解密验签 → 金额校验 → 幂等 → 更新 payStatus → 订单日志 → 推送),仅替换加解密/字段层。
Rationale:
PayController.payipn 已实现完整的「支付成功后业务动作」:orderLogHelper.logSync 记日志、userBillingService 记账、push.apppush/shpush + pushEventService.PublisherEvent 推送用户/商家/骑手、sendAcceptRiderPush 推送可接单骑手。蓝新回调成功后必须触发相同动作,否则订单虽已支付但商家/骑手收不到通知。@Anonymous 注解经 PermitAllUrlProperties 自动加入 SecurityConfig 匿名白名单(蓝新服务器无登录态)。state 保持不变(0 待处理,支付不改业务状态)、payStatus=1(已支付)。Alternatives:
Decision: PosOrder.payType 发起时设固定值表示「蓝新在线支付」(取 "6"),具体支付方式(CREDIT/LINEPAY/APPLEPAY) 由 pos_order_payment.pay_type 在回调后承载;订单列表展示支付方式时联查 pos_order_payment。
Rationale:
"6" 表示蓝新,避免语义冲突。Alternatives:
Decision: 仿 ezpay.base-url 模式新增:
newebpay:
base-url: https://ccore.newebpay.com # 测试 ccore / 正式 core
notify-url: https://<公网域名>/pay/newebpay/notify
return-url: https://<公网域名>/pay/newebpay/return
mpg-version: "2.3"
Rationale:
@Value),便于环境切换。Decision: 使用蓝新测试环境 ccore.newebpay.com + 文档附录1测试卡号验证;本地开发用内网穿透(ngrok/frp)暴露 NotifyURL。
Rationale:
Alternatives:
Decision: 所有 DDL 写入 updatesql/sql.md(标注日期与用途),不直接执行,由开发者统一手动执行(遵循项目规范)。
涉及:pos_store_newebpay 建表、pos_order_payment 建表。(pos_order 无需改结构,payType 取值扩展不改表。)