api.md 7.9 KB

API Contracts: 蓝新金流(NewebPay)线上支付接入

Feature: specs/011-newebpay-payment/spec.md Date: 2026-06-22

本文定义本期对外与内部接口契约。Controller 位于 ruoyi-admin;Service/Domain 位于 ruoyi-system;加密工具位于 ruoyi-admin/.../app/utils/newebpay/


A. 对外接口(蓝新服务器调用,必须匿名)

A1. NotifyURL — 支付结果背景通知

蓝新在交易完成/失败后以 Form Post 通知本接口。本接口解密验签并更新订单。

  • 路径: POST /pay/newebpay/notify
  • 鉴权: @Anonymous(蓝新无登录态,经 PermitAllUrlProperties 放行)
  • Content-Type: application/x-www-form-urlencoded
  • 请求参数(蓝新 Form Post):
参数 说明
MerchantID 商店代号
Status SUCCESS 或错误代码
Version 串接版本
TradeInfo AES-256-CBC 加密的交易结果(hex)
TradeSha SHA256 检查码(大写 hex)
EncryptType 加密模式(若发起时设 0/不传,可能不回传)
  • 处理流程:
    1. 记录原始回调到 IPN 日志(复用 IpnLog)。
    2. 用 MerchantID 查 pos_store_newebpay 取该门店 HashKey/HashIV;无凭证 → 记录并返回。
    3. 验签: SHA256("HashKey={key}&{TradeInfo}&HashIV={iv}") 大写 == TradeSha;不符 → 拒绝、记录。
    4. 解密: AES-256-CBC(PKCS7) 解 TradeInfo → 得 Status/Message/MerchantOrderNo/TradeNo/Amt/PaymentType/PayTime/Auth 等。
    5. 幂等: 按 TradeNo 查 pos_order_payment;已存在且 pay_status=1 → 直接返回成功应答,不重复处理。
    6. 金额校验: 解密所得 Amt == 订单 amount;不符 → 拒绝更新、记录告警。
    7. 订单关联: 由 MerchantOrderNo("NB"+ddId)反查 pos_order;订单不存在 → 记录待查、返回。
    8. 状态更新(仅 Status=SUCCESS 且以上校验通过):
      • pos_order_payment 写入/更新 trade_no、pay_type、auth_code、pay_time、pay_status=1、callback_raw。
      • pos_order.payStatus = 1pos_order.state 保持不变(沿用 payipn 语义)。
      • orderLogHelper.logSync(...) 记订单日志。
      • 触发推送(用户/商家/骑手,复用 pushEventService / sendAcceptRiderPush 链路)。
    9. 失败回调(Status≠SUCCESS): 记录失败原因到 pos_order_payment(pay_status=2),订单保持未支付。
  • 响应: 返回 JSON {"Status":"SUCCESS","Message":"OK"}(HTTP 200);蓝新收到非成功应答会重试。

A2. ReturnURL — 支付完成返回页(前端引导)

蓝新在用户付款后将浏览器 Form Post 回本接口(携带与 NotifyURL 相同的加密参数)。

  • 路径: GET/POST /pay/newebpay/return
  • 鉴权: @Anonymous
  • 行为: 仅作引导——重定向到前端支付结果页(带订单号);据此修改订单状态(订单状态以 NotifyURL 为准,见 spec FR-015)。
  • 响应: 302 重定向至前端结果页 URL(含 ddId 与轻量结果提示)。

B. 内部接口(前端/管理端调用)

B1. 发起蓝新支付(C 端用户下单后调用)

  • 路径: POST /pay/newebpay/create
  • 鉴权: @Anonymous + @Auth(C 端用户 token,参照 /pay/VNPay)
  • 请求参数:
参数 类型 必填 说明
orderid String V 系统订单号 ddId
  • 处理:
    1. 按 ddId 查 pos_order;校验订单存在、未支付、属于当前用户。
    2. 按订单 md_id 查 pos_store_newebpay;校验已开通(status=2)且启用(is_enabled=1),否则报错「该门店暂不支持在线支付」。
    3. 生成 merchant_order_no = "NB" + ddId(重新发起时追加时间戳避免重复)。
    4. 组装 TradeInfo 明文: MerchantID、RespondType=JSON、TimeStamp、Version=2.3、MerchantOrderNo、Amt=amount、ItemDesc、NotifyURL、ReturnURL、CREDIT/LINEPAY/APPLEPAY(按门店 enabled_payments 开关)。
    5. AES-256-CBC(PKCS7) 加密 → TradeInfo(hex);SHA256 → TradeSha。
    6. pos_order_payment(merchant_order_no、store_id、merchant_id、amount、pay_status=0、create_time)。
    7. 更新 pos_order.payType="6"pos_order.payUrl=gatewayUrl
  • 响应 AjaxResult.success:

    {
    "code": 200,
    "msg": "...",
    "data": {
    "gatewayUrl": "https://ccore.newebpay.com/MPG/mpg_gateway",
    "MerchantID": "MS12345678",
    "TradeInfo": "<hex>",
    "TradeSha": "<UPPER hex>",
    "Version": "2.3",
    "EncryptType": "0"
    }
    }
    

前端用隐藏 form 自动 submit 到 gatewayUrl,字段名对应 data 内 key。


B2. 单笔交易查询(商家/运营,回调补单与对账)

  • 路径: POST /pay/newebpay/query
  • 鉴权: @Auth(商家/运营 token)
  • 请求参数: orderid(ddId)
  • 处理:
    1. 查 pos_order 与其门店 pos_store_newebpay 凭证。
    2. 组装 CheckValue = SHA256("IV={iv}&Amt=&MerchantID=&MerchantOrderNo= 排序&Key={key}")
    3. 调蓝新 POST https://(c)core.newebpay.com/API/QueryTradeInfo(MerchantID/Version=1.3/RespondType=JSON/CheckValue/TimeStamp/MerchantOrderNo/Amt)。
    4. 解析 TradeStatus(0未付/1成功/2失败/3取消/6退款)、PaymentType、PayTime、CheckCode。
    5. 补单: 若蓝新返回 TradeStatus=1 且订单 payStatus 仍=0(回调丢失),则更新 pos_order.pay_status=1、写 pos_order_payment、触发推送(同 A1 步骤 8)。
  • 响应: AjaxResult.success 含 TradeStatus、PaymentType、PayTime、订单当前 payStatus。

C. 门店蓝新凭证管理(平台后台)

复用 009 PosStoreEzpayController 模式,路径 /system/storeNewebpay@PreAuthorize("@ss.hasPermi('chanting:storeNewebpay:xxx')")。Service 纯 DB,联网验证在 Controller 层。

方法 路径 权限后缀 说明
GET /system/storeNewebpay/list :list 开通管理列表(分页+筛选)
GET /system/storeNewebpay/{storeId} :query 门店蓝新详情
PUT /system/storeNewebpay/apply/{storeId} :apply 发起申请 0→1
PUT /system/storeNewebpay/saveCredentials :saveCredentials 录入凭证+验证(调 QueryTradeInfo 探测)→2
PUT /system/storeNewebpay/toggleEnable/{storeId} :toggleEnable 停用/恢复
PUT /system/storeNewebpay/reset/{storeId} :query 重置状态(凭证作废)
PUT /system/storeNewebpay/enabledPayments/{storeId} :toggleEnable 设置启用的支付方式 CREDIT/LINEPAY/APPLEPAY

saveCredentials 请求体 StoreNewebpayCredentialDto:

{ "storeId": 1, "merchantId": "MS...", "hashKey": "...", "hashIv": "...", "enabledPayments": "CREDIT,LINEPAY,APPLEPAY" }

验证:Controller 调 NewebPay QueryTradeInfo(查虚构订单);返回金钥/商店错误 → 视为无效(拒绝、状态不变);否则通过置 status=2、is_enabled=1。


D. NewebPay 工具类(内部,非 HTTP 接口)

位于 ruoyi-admin/.../app/utils/newebpay/,仿 ezPay 结构:

  • NewebPayEncryptUtil: encrypt(query,key,iv)→hex(AES-256-CBC/PKCS7)、decrypt(hex,key,iv)→明文、genTradeSha(tradeInfoHex,key,iv)→大写、genCheckValue(amt,mid,orderNo,key,iv)→大写、genCheckCode(fields,key,iv)→大写。含 main 自测(用 NDNF §4.1 示例 Key=Fs5cX1TGqYM2PpdbE14a9H83YQSQF5jn/IV=C6AcmfqJILwgnhIP/MID=MS127874575 验证加解密 round-trip 与 TradeSha 规则)。
  • NewebPay: 端点常量(URL_MPG_GATEWAYURL_QUERY_TRADE_INFO)、createMpgForm(baseUrl,cfg,params) 返回加密后的 form 字段、queryTrade(baseUrl,cfg,amt,orderNo) 调查询。
  • NewebPayConfig: merchantId/hashKey/hashIV(运行时由 Service 从 pos_store_newebpay 构造)。

配置(application.yml,新增)

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"