欢迎光临
我们一直在努力

适配器模式在多支付渠道集成中的实践

适配器模式在多支付渠道集成中的实践

封面信息图

在电商、SaaS 订阅与跨境出海业务中,系统通常需要对接多家外部支付渠道(如微信支付、支付宝、银联、PayPal、Stripe 等)。每个支付渠道的技术规范往往差异极大:

  • 数据格式异构:有的使用 JSON,有的使用 XML,有的使用 Form 表单编码;
  • 加密与签名算法各异:涉及 RSA2、SM2、HMAC-SHA256,证书模式或公钥模式;
  • 异步回调与错误码千差万别:微信返回 SUCCESS/FAIL,支付宝返回 TRADE_SUCCESS/TRADE_CLOSED,银联返回 00/01。

如果直接在订单业务服务中通过大量的 if (channel == WECHAT) { … } else if (channel == ALIPAY) { … } 编写代码,不出半年整个支付模块就会演变成数千行不可维护的“代码泥潭”,新增或升级一个支付渠道就会引发全量回归测试的风险。

引入适配器模式(Adapter Pattern)结合策略工厂,能够构建一套高内聚、低耦合且符合开闭原则(OCP)的统一支付网关架构。


统一支付架构模型设计

为了让上游业务系统(如收银台、订单服务、会员订阅)与底层千差万别的支付渠道解耦,我们抽象出标准的统一支付内核(Unified Payment Core):

[ 上游订单/收银台服务 ]
│ (只面向统一标准接口交互)

[ 统一支付网关服务 (PaymentGatewayService) ]
│ (基于 channelCode 动态分派)

[ 支付适配器策略工厂 (PaymentAdapterFactory) ]
├── WechatPayAdapter ──> [ 转换请求/签名 ] ──> 微信支付 API (JSON+RSA)
├── AlipayAdapter ──> [ 转换请求/签名 ] ──> 支付宝开放平台 (Form+RSA2)
├── UnionPayAdapter ──> [ 转换请求/签名 ] ──> 银联在线网关 (XML+SM2)
└── StripePayAdapter ──> [ 转换请求/签名 ] ──> Stripe API (REST)


核心接口与统一模型定义

1. 统一请求与响应标准模型

package com.example.payment.model;

import java.math.BigDecimal;
import java.util.Map;

public record UnifiedPayRequest(
String tradeOrderNo, // 系统内部唯一支付交易流水号
BigDecimal amount, // 支付金额(元)
String subject, // 订单标题
String clientIp, // 客户端发起 IP
String openId, // 渠道特定用户标识 (如微信 openid)
String returnUrl, // 同步跳转地址
String notifyUrl, // 异步回调地址
Map<String, String> extraParams // 渠道扩展附加参数
) {}

public record UnifiedPayResponse(
boolean success,
String channelTradeNo, // 渠道方交易流水号
String payData, // 支付凭据(如微信 JSAPI 调起参数、支付宝唤起 URL)
String errorCode,
String errorMessage
) {}

public record UnifiedNotifyResult(
boolean isSuccess,
String tradeOrderNo,
String channelTradeNo,
BigDecimal totalAmount,
String rawResponseToChannel // 响应给渠道的 ACK 报文
) {}

2. 支付适配器顶层接口

package com.example.payment.adapter;

import com.example.payment.model.UnifiedNotifyResult;
import com.example.payment.model.UnifiedPayRequest;
import com.example.payment.model.UnifiedPayResponse;

import java.util.Map;

public interface PaymentChannelAdapter {

/**
* 获取支持的渠道标识 (如: WECHAT_V3, ALIPAY_PAGE, STRIPE)
*/
String getChannelCode();

/**
* 发起统一统一下单/支付
*/
UnifiedPayResponse pay(UnifiedPayRequest request);

/**
* 统一解析并校验异步回调通知
*/
UnifiedNotifyResult parseAndVerifyNotify(Map<String, String> headers, String body);

/**
* 主动查询订单支付状态
*/
UnifiedNotifyResult queryOrderStatus(String tradeOrderNo);
}


适配器具体实现落地

1. 微信支付渠道适配器(WechatPayAdapter)

package com.example.payment.adapter.impl;

import com.example.payment.adapter.PaymentChannelAdapter;
import com.example.payment.model.UnifiedNotifyResult;
import com.example.payment.model.UnifiedPayRequest;
import com.example.payment.model.UnifiedPayResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.Map;

@Component
public class WechatPayAdapter implements PaymentChannelAdapter {

private static final Logger log = LoggerFactory.getLogger(WechatPayAdapter.class);

@Override
public String getChannelCode() {
return "WECHAT_PAY";
}

@Override
public UnifiedPayResponse pay(UnifiedPayRequest request) {
log.info("【微信支付】构建统一下单报文, tradeOrderNo: {}", request.tradeOrderNo());

// 1. 将标准请求转换为微信特有的 V3 API JSON 结构
// 微信金额单位为分
long totalFeeFen = request.amount().multiply(new BigDecimal("100")).longValue();

try {
// 2. 调用微信 SDK / HTTP 客户端发起请求并加签 (模拟调用)
String prepayId = "wx_prepay_id_" + System.currentTimeMillis();
String jsapiJson = String.format("{\\"appId\\":\\"wx123456\\",\\"prepayId\\":\\"%s\\"}", prepayId);

return new UnifiedPayResponse(true, prepayId, jsapiJson, null, null);
} catch (Exception e) {
log.error("微信统一下单异常", e);
return new UnifiedPayResponse(false, null, null, "WECHAT_ERROR", e.getMessage());
}
}

@Override
public UnifiedNotifyResult parseAndVerifyNotify(Map<String, String> headers, String body) {
log.info("【微信支付】接收到异步通知,执行公钥验签与报文解密…");

// 1. 提取微信头部签名: Wechatpay-Signature, Wechatpay-Timestamp, Wechatpay-Nonce
// 2. 证书/平台公钥验签与 AES-256-GCM 解密 (此处简写核心逻辑)
String mockTradeOrderNo = "ORD_20260907_0001";
String mockChannelTradeNo = "WX_TRANSACTION_987654321";

return new UnifiedNotifyResult(
true,
mockTradeOrderNo,
mockChannelTradeNo,
new BigDecimal("99.00"),
"{\\"code\\":\\"SUCCESS\\",\\"message\\":\\"成功\\"}"
);
}

@Override
public UnifiedNotifyResult queryOrderStatus(String tradeOrderNo) {
// 实现微信主动查单接口
return new UnifiedNotifyResult(true, tradeOrderNo, "WX_TX_123", BigDecimal.TEN, "SUCCESS");
}
}

2. 支付宝渠道适配器(AlipayAdapter)

package com.example.payment.adapter.impl;

import com.example.payment.adapter.PaymentChannelAdapter;
import com.example.payment.model.UnifiedNotifyResult;
import com.example.payment.model.UnifiedPayRequest;
import com.example.payment.model.UnifiedPayResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.Map;

@Component
public class AlipayAdapter implements PaymentChannelAdapter {

private static final Logger log = LoggerFactory.getLogger(AlipayAdapter.class);

@Override
public String getChannelCode() {
return "ALIPAY";
}

@Override
public UnifiedPayResponse pay(UnifiedPayRequest request) {
log.info("【支付宝】构建手机网站支付请求, tradeOrderNo: {}", request.tradeOrderNo());

// 支付宝金额单位为元,支持字符串格式
String totalAmountStr = request.amount().setScale(2, java.math.RoundingMode.HALF_UP).toString();

// 构造唤起 URL 或表单 HTML
String alipayForm = "<form name='alipayment' action='https://openapi.alipay.com/gateway.do' method='POST'>…</form>";
return new UnifiedPayResponse(true, "ALI_PREPAY_" + request.tradeOrderNo(), alipayForm, null, null);
}

@Override
public UnifiedNotifyResult parseAndVerifyNotify(Map<String, String> headers, String body) {
log.info("【支付宝】接收到 Form 表单异步通知,执行 RSA2 验签…");
// 验证支付宝签名与 trade_status
return new UnifiedNotifyResult(
true,
"ORD_20260907_0002",
"20260907220014000000",
new BigDecimal("199.00"),
"success" // 支付宝要求的 ACK 响应文本
);
}

@Override
public UnifiedNotifyResult queryOrderStatus(String tradeOrderNo) {
return new UnifiedNotifyResult(true, tradeOrderNo, "ALI_TX_456", BigDecimal.ONE, "SUCCESS");
}
}


适配器策略工厂与统一分发网关

利用 Spring 的依赖自动注入,将所有 PaymentChannelAdapter 统一收集至 Map 中:

package com.example.payment.factory;

import com.example.payment.adapter.PaymentChannelAdapter;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class PaymentAdapterFactory {

private final Map<String, PaymentChannelAdapter> adapterMap = new ConcurrentHashMap<>();

public PaymentAdapterFactory(List<PaymentChannelAdapter> adapters) {
for (PaymentChannelAdapter adapter : adapters) {
adapterMap.put(adapter.getChannelCode(), adapter);
}
}

public PaymentChannelAdapter getAdapter(String channelCode) {
PaymentChannelAdapter adapter = adapterMap.get(channelCode);
if (adapter == null) {
throw new IllegalArgumentException("不支持的支付渠道: " + channelCode);
}
return adapter;
}
}

统一门面网关服务:

package com.example.payment.service;

import com.example.payment.adapter.PaymentChannelAdapter;
import com.example.payment.factory.PaymentAdapterFactory;
import com.example.payment.model.UnifiedNotifyResult;
import com.example.payment.model.UnifiedPayRequest;
import com.example.payment.model.UnifiedPayResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class PaymentGatewayService {

private static final Logger log = LoggerFactory.getLogger(PaymentGatewayService.class);

private final PaymentAdapterFactory adapterFactory;

public PaymentGatewayService(PaymentAdapterFactory adapterFactory) {
this.adapterFactory = adapterFactory;
}

/**
* 统一统一下单入口
*/
public UnifiedPayResponse createPayment(String channelCode, UnifiedPayRequest request) {
PaymentChannelAdapter adapter = adapterFactory.getAdapter(channelCode);
return adapter.pay(request);
}

/**
* 统一异步回调入口
*/
public String handleCallback(String channelCode, Map<String, String> headers, String body) {
PaymentChannelAdapter adapter = adapterFactory.getAdapter(channelCode);
UnifiedNotifyResult result = adapter.parseAndVerifyNotify(headers, body);

if (!result.isSuccess()) {
log.error("渠道 [{}] 异步回调验签失败, 原始报文: {}", channelCode, body);
return "fail";
}

// 执行核心订单状态变更与分布式流水登记(通过状态机与幂等流水表)
log.info("支付成功,开始驱动订单履约: tradeOrderNo={}, channelTradeNo={}",
result.tradeOrderNo(), result.channelTradeNo());

// 返回给渠道期望的响应字符串 (如 "success" 或 {"code":"SUCCESS"})
return result.rawResponseToChannel();
}
}


生产主动查单与轮询补偿机制

在网络不稳定或渠道异步回调丢失(丢包/防火墙拦截)的情况下,不能单纯依赖回调。系统必须配备定时主动查单轮询任务:

  • 阶梯式轮询调度:当下单成功后,支付流水进入 WAITING_PAY 状态。调度器分别在 30 秒、1 分钟、3 分钟、5 分钟后调用 adapter.queryOrderStatus(tradeOrderNo)。
  • 状态终态驱动:一旦查单返回成功,立即更新订单状态为已支付;若超过 30 分钟仍未支付,调用关单接口关闭渠道交易并释放库存。

  • 工程收益总结

    在采用适配器模式重构支付中台后:

  • 符合开闭原则(OCP):后续接入新的支付渠道(如新增 PayPal 或数字人民币),完全无需修改任何现有的核心业务代码与上游接口,只需新增一个 XxxPayAdapter 实现类即可完成扩展上线。
  • 职责边界清晰:数据格式转换、签名加解密算法、第三方特有异常隔离在各自的 Adapter 内部,上游业务只感知标准的 UnifiedPayRequest 与 UnifiedPayResponse。
  • 可测性与故障隔离显著提升:单元测试只需针对单个 Adapter 的输入输出进行 Mock 测试,单渠道抖动不会波及其他渠道的稳定性。
  • 赞(0)
    未经允许不得转载:171主机测评 » 适配器模式在多支付渠道集成中的实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址