聚合支付系统:基于设计模式的详细设计与代码示例
目标:实现一个可扩展、可维护、易于接入新支付渠道的聚合支付系统,使用多种经典设计模式(策略、工厂、模板方法、责任链、适配器等)。
##记得点赞加收藏哦😁😁😁
一、业务场景与核心需求
- 支持多种支付渠道:微信、支付宝、银联、某三方支付等。
- 支持多种支付方式:扫码支付、H5 支付、App 支付等。
- 上层业务只关注“聚合支付”的统一接口,不关心具体渠道细节。
- 后续新增渠道时尽量 不改老代码,只加新代码(开闭原则)。
- 支持扩展“风控、限流、风控、风控”等前置/后置处理逻辑。
抽象一句话:对外一个“统一收单接口”,对内多渠道策略 + 可插拔的前置/后置处理。
二、总体设计模式选型
| 场景 | 使用的设计模式 | 作用 |
|---|---|---|
| 根据渠道编码选择不同实现 | 策略模式 Strategy | 运行时选择不同支付实现 |
| 创建具体渠道处理器 | 工厂模式 + 简单工厂 | 统一创建渠道实现,方便扩展 |
| 统一下单流程(校验、落库、请求渠道、更新状态……) | 模板方法 Template Method | 抽取通用流程,保留渠道差异点 |
| 风控、参数校验、限流等可插拔前置逻辑 | 责任链 Chain of Responsibility | 可自由组合与扩展 |
| 封装第三方支付 SDK | 适配器 Adapter | 把第三方 SDK 适配到统一接口 |
| 支付结果回调通知业务 | 观察者 / 发布订阅 | 解耦业务与支付模块 |
核心思想:Controller -> 统一 Service -> 选择策略 -> 调用模板方法 -> 内部走责任链 & 适配器。
三、核心领域模型设计
1. 核心枚举
public enum PayChannelEnum {
WECHAT("WECHAT", "微信支付"),
ALIPAY("ALIPAY", "支付宝"),
UNIONPAY("UNIONPAY", "银联");
private final String code;
private final String desc;
PayChannelEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() { return code; }
public String getDesc() { return desc; }
public static PayChannelEnum of(String code) {
for (PayChannelEnum value : values()) {
if (value.code.equalsIgnoreCase(code)) {
return value;
}
}
throw new IllegalArgumentException("不支持的支付渠道: " + code);
}
}
2. 统一下单请求 / 响应对象
@Data
public class UnifiedPayRequest {
/** 商户订单号 */
private String bizOrderNo;
/** 支付金额(单位:分) */
private Long amount;
/** 支付渠道编码 */
private String channelCode;
/** 支付标题 */
private String title;
/** 支付成功回调地址 */
private String notifyUrl;
/** 前端跳转地址 */
private String returnUrl;
/** 额外扩展参数(不同渠道个性化参数) */
private Map<String, String> extParams;
}
@Data
public class UnifiedPayResponse {
/** 聚合支付平台订单号 */
private String payOrderNo;
/** 支付二维码链接 / H5 链接 / 跳转链接等 */
private String payUrl;
/** 渠道返回的原始内容 */
private String rawBody;
}
四、策略模式:不同渠道不同实现
1. 渠道统一接口
public interface PayChannelService {
/**
* 统一下单
*/
UnifiedPayResponse unifiedPay(UnifiedPayRequest request);
/**
* 查询订单
*/
PayQueryResponse query(String payOrderNo);
/**
* 关闭订单
*/
void close(String payOrderNo);
}
2. 微信渠道实现(示例)
@Service
public class WechatPayChannelService implements PayChannelService {
@Override
public UnifiedPayResponse unifiedPay(UnifiedPayRequest request) {
// 真实场景:这里会调用微信的下单接口
UnifiedPayResponse resp = new UnifiedPayResponse();
resp.setPayOrderNo("WECHAT_" + System.currentTimeMillis());
resp.setPayUrl("weixin://wxpay/bizpayurl?pr=XXXXXX");
resp.setRawBody("{\"mock\": \"wechat unified order\"}");
return resp;
}
@Override
public PayQueryResponse query(String payOrderNo) {
// 真实场景:调用微信订单查询接口
return new PayQueryResponse();
}
@Override
public void close(String payOrderNo) {
// 真实场景:调用微信关单接口
}
}
3. 支付宝渠道实现(示例)
@Service
public class AlipayChannelService implements PayChannelService {
@Override
public UnifiedPayResponse unifiedPay(UnifiedPayRequest request) {
UnifiedPayResponse resp = new UnifiedPayResponse();
resp.setPayOrderNo("ALIPAY_" + System.currentTimeMillis());
resp.setPayUrl("https://openapi.alipay.com/gateway.do?order=XXXX");
resp.setRawBody("{\"mock\": \"alipay unified order\"}");
return resp;
}
@Override
public PayQueryResponse query(String payOrderNo) {
return new PayQueryResponse();
}
@Override
public void close(String payOrderNo) {
}
}
五、工厂 + 策略:根据渠道选择实现
1. 工厂类
@Component
public class PayChannelFactory {
private final Map<String, PayChannelService> channelServiceMap = new HashMap<>();
public PayChannelFactory(List<PayChannelService> channelServices) {
// 假设每个实现类上都使用 @PayChannelType 注解标记渠道
for (PayChannelService service : channelServices) {
PayChannelType annotation = service.getClass().getAnnotation(PayChannelType.class);
if (annotation != null) {
channelServiceMap.put(annotation.value().getCode(), service);
}
}
}
public PayChannelService getChannel(String channelCode) {
PayChannelService service = channelServiceMap.get(channelCode);
if (service == null) {
throw new IllegalArgumentException("不支持的支付渠道: " + channelCode);
}
return service;
}
}
2. 自定义注解标记渠道类型
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PayChannelType {
PayChannelEnum value();
}
在具体实现类上标注:
@Service
@PayChannelType(PayChannelEnum.WECHAT)
public class WechatPayChannelService implements PayChannelService {
// ...
}
@Service
@PayChannelType(PayChannelEnum.ALIPAY)
public class AlipayChannelService implements PayChannelService {
// ...
}
这样新增渠道 = 新增一个实现类 + 加一个注解即可。
六、模板方法:统一收单流程
上面是“针对某个渠道”的实现。再往外一层,我们需要一个 统一入口服务:
- 做参数校验、风控等前置逻辑;
- 生成本地支付订单并落库;
- 调用渠道策略;
- 更新本地订单状态;
- 返回统一响应。
这些步骤在所有支付渠道中基本一致,只是 “第 3 步调用哪个渠道” 不同。因此适合用模板方法。
1. 抽象模板类
public abstract class AbstractPayTemplate {
@Resource
private PayOrderRepository payOrderRepository;
@Resource
private PayPreHandlerChain payPreHandlerChain;
public UnifiedPayResponse doUnifiedPay(UnifiedPayRequest request) {
// 1. 前置处理:参数校验、风控、限流等
payPreHandlerChain.handle(request);
// 2. 构建并落库支付订单
PayOrder payOrder = buildAndSavePayOrder(request);
try {
// 3. 调用渠道统一下单(交给子类实现)
UnifiedPayResponse resp = doChannelPay(request, payOrder);
// 4. 更新订单渠道返回信息
updatePayOrderAfterChannel(payOrder, resp);
// 5. 返回响应
resp.setPayOrderNo(payOrder.getPayOrderNo());
return resp;
} catch (Exception e) {
// 失败时更新订单状态
markPayOrderFail(payOrder, e);
throw e;
}
}
protected abstract UnifiedPayResponse doChannelPay(UnifiedPayRequest request, PayOrder payOrder);
protected PayOrder buildAndSavePayOrder(UnifiedPayRequest request) {
PayOrder order = new PayOrder();
order.setPayOrderNo("PAY_" + System.currentTimeMillis());
order.setBizOrderNo(request.getBizOrderNo());
order.setAmount(request.getAmount());
order.setChannelCode(request.getChannelCode());
order.setStatus(PayStatusEnum.INIT);
payOrderRepository.save(order);
return order;
}
protected void updatePayOrderAfterChannel(PayOrder payOrder, UnifiedPayResponse resp) {
payOrder.setStatus(PayStatusEnum.PROCESSING);
payOrder.setChannelResp(resp.getRawBody());
payOrderRepository.update(payOrder);
}
protected void markPayOrderFail(PayOrder payOrder, Exception e) {
payOrder.setStatus(PayStatusEnum.FAIL);
payOrder.setErrorMsg(e.getMessage());
payOrderRepository.update(payOrder);
}
}
2. 具体实现:聚合支付统一入口
@Service
public class AggregatedPayService extends AbstractPayTemplate {
@Resource
private PayChannelFactory payChannelFactory;
@Override
protected UnifiedPayResponse doChannelPay(UnifiedPayRequest request, PayOrder payOrder) {
PayChannelService channelService = payChannelFactory.getChannel(request.getChannelCode());
// 实际调用具体渠道
return channelService.unifiedPay(request);
}
}
Controller 中就非常简洁:
@RestController
@RequestMapping("/pay")
public class PayController {
@Resource
private AggregatedPayService aggregatedPayService;
@PostMapping("/unified")
public UnifiedPayResponse unifiedPay(@RequestBody UnifiedPayRequest request) {
return aggregatedPayService.doUnifiedPay(request);
}
}
七、责任链:参数校验、风控、限流等前置处理
1. 抽象处理器
public interface PayPreHandler {
/**
* 返回 true 表示继续后续责任链;false 表示中断
*/
boolean handle(UnifiedPayRequest request);
}
2. 责任链实现类
@Component
public class PayPreHandlerChain {
private final List<PayPreHandler> handlers;
public PayPreHandlerChain(List<PayPreHandler> handlers) {
// 可根据 order 排序
this.handlers = handlers.stream()
.sorted(Comparator.comparingInt(PayPreHandlerChain::getOrder))
.collect(Collectors.toList());
}
public void handle(UnifiedPayRequest request) {
for (PayPreHandler handler : handlers) {
boolean goOn = handler.handle(request);
if (!goOn) {
break;
}
}
}
private static int getOrder(PayPreHandler handler) {
Order order = handler.getClass().getAnnotation(Order.class);
return order == null ? 0 : order.value();
}
}
3. 示例:参数校验处理器
@Component
@Order(1)
public class ParamValidateHandler implements PayPreHandler {
@Override
public boolean handle(UnifiedPayRequest request) {
if (request.getAmount() == null || request.getAmount() <= 0) {
throw new IllegalArgumentException("金额必须大于 0");
}
if (StringUtils.isBlank(request.getBizOrderNo())) {
throw new IllegalArgumentException("商户订单号不能为空");
}
return true;
}
}
4. 示例:简单风控处理器
@Component
@Order(2)
public class SimpleRiskHandler implements PayPreHandler {
@Override
public boolean handle(UnifiedPayRequest request) {
// Demo:单笔金额超过 10w 暂停支付
if (request.getAmount() != null && request.getAmount() > 100_000_00L) {
throw new IllegalStateException("风控拦截:金额过大");
}
return true;
}
}
后续需要新增任何前置逻辑,只需要实现 PayPreHandler + @Component 即可,责任链会自动组装。
八、适配器模式:对接第三方 SDK
很多三方支付 SDK 的接口风格和我们系统不一致:
- 入参是乱七八糟的 Map 或者 XML;
- 出参也是复杂对象或者字符串。
我们可以使用适配器模式,将 SDK 封装成统一接口:
1. 第三方 SDK 示例(伪代码)
// 假设三方 SDK 提供的接口是这样的:
public class ThirdPartyWechatSdk {
public String pay(Map<String, String> params) {
// 调用微信
return "{...}"; // 返回 JSON
}
}
2. 适配器接口
public interface ThirdPayClient {
/**
* 统一下单
*/
ThirdPayResponse pay(ThirdPayRequest request);
}
3. 微信适配器实现
public class WechatPayClientAdapter implements ThirdPayClient {
private final ThirdPartyWechatSdk sdk;
public WechatPayClientAdapter(ThirdPartyWechatSdk sdk) {
this.sdk = sdk;
}
@Override
public ThirdPayResponse pay(ThirdPayRequest request) {
Map<String, String> params = new HashMap<>();
params.put("amount", String.valueOf(request.getAmount()));
params.put("orderNo", request.getOrderNo());
// ... 组装微信需要的参数
String raw = sdk.pay(params);
// 解析微信返回
ThirdPayResponse resp = new ThirdPayResponse();
resp.setRaw(raw);
// 解析出 payUrl 等
return resp;
}
}
4. 在渠道实现中使用适配器
@Service
@PayChannelType(PayChannelEnum.WECHAT)
public class WechatPayChannelService implements PayChannelService {
private final ThirdPayClient wechatPayClient;
public WechatPayChannelService(ThirdPayClient wechatPayClient) {
this.wechatPayClient = wechatPayClient;
}
@Override
public UnifiedPayResponse unifiedPay(UnifiedPayRequest request) {
ThirdPayRequest thirdReq = new ThirdPayRequest();
thirdReq.setAmount(request.getAmount());
thirdReq.setOrderNo(request.getBizOrderNo());
// ...
ThirdPayResponse thirdResp = wechatPayClient.pay(thirdReq);
UnifiedPayResponse resp = new UnifiedPayResponse();
resp.setPayUrl(thirdResp.getPayUrl());
resp.setRawBody(thirdResp.getRaw());
return resp;
}
// 其他方法省略
}
这样未来如果 SDK 升级或更换供应商,我们只改适配器,不动上层聚合逻辑。
九、观察者模式:异步通知业务系统
支付成功后,往往需要:
- 通知订单系统更新状态;
- 发短信、发站内信、发优惠券等。
如果这些逻辑都写在支付模块里,会非常臃肿且耦合。可以使用 观察者模式 / 发布订阅 解耦。
1. 领域事件对象
@Data
@AllArgsConstructor
public class PaySuccessEvent {
private String payOrderNo;
private String bizOrderNo;
private Long amount;
private String channelCode;
}
2. 发布事件(可以用 Spring 事件 / MQ 等)
@Component
public class PayEventPublisher {
@Resource
private ApplicationEventPublisher publisher;
public void publishPaySuccess(PayOrder payOrder) {
PaySuccessEvent event = new PaySuccessEvent(
payOrder.getPayOrderNo(),
payOrder.getBizOrderNo(),
payOrder.getAmount(),
payOrder.getChannelCode()
);
publisher.publishEvent(event);
}
}
在支付回调处理逻辑里:
public void handlePayNotify(PayNotifyRequest notifyRequest) {
// 1. 校验签名、校验金额…
// 2. 更新支付订单状态为成功
payOrderRepository.markSuccess(payOrder);
// 3. 发布支付成功事件
payEventPublisher.publishPaySuccess(payOrder);
}
3. 业务侧订阅事件
@Component
public class OrderModuleListener {
@EventListener
public void onPaySuccess(PaySuccessEvent event) {
// 更新业务订单状态
// 发送短信 / 消息等
}
}
这样支付模块和业务模块之间通过“事件”解耦。
十、状态模式(可选):支付订单状态流转
支付订单的一生:INIT -> PROCESSING -> SUCCESS / FAIL / CLOSED,
如果状态流转复杂(多种中间态、重试、部分退款等),可以引入 状态模式 来管理。
这里给出一个简单骨架:
public interface PayOrderState {
void handle(PayOrderContext ctx);
}
public class InitState implements PayOrderState {
@Override
public void handle(PayOrderContext ctx) {
// 触发下单,更新为 PROCESSING
}
}
public class ProcessingState implements PayOrderState {
@Override
public void handle(PayOrderContext ctx) {
// 根据渠道回调,更新为 SUCCESS / FAIL
}
}
通过上下文 + 状态对象管理复杂状态机,这里不展开细节。
十一、整体调用流程串起来
- 上层业务调用
/pay/unified接口; PayController接收到UnifiedPayRequest,调用AggregatedPayService.doUnifiedPay;AbstractPayTemplate:- 调用
PayPreHandlerChain.handle走责任链(参数校验、风控等); - 构建并持久化
PayOrder; - 调用抽象方法
doChannelPay;
- 调用
AggregatedPayService.doChannelPay:- 使用
PayChannelFactory根据channelCode获取对应PayChannelService策略实现; - 调用
PayChannelService.unifiedPay;
- 使用
- 某个渠道实现(如
WechatPayChannelService):- 内部调用
ThirdPayClient适配器,对接三方 SDK; - 返回统一的
UnifiedPayResponse;
- 内部调用
- 模板方法内部更新订单渠道响应信息,并返回给 Controller;
- 前端根据
payUrl等信息拉起支付。
支付回调:
- 渠道异步通知回调接口;
- 校验签名、金额等;
- 更新
PayOrder状态为 SUCCESS; - 发布
PaySuccessEvent事件,业务模块监听并处理。
十二、如何落地到真实项目中的步骤建议
- 先抽象统一请求 / 响应模型,把所有渠道的公共字段梳理清楚;
- 先实现最小闭环:一个渠道 + 一种支付方式,代码跑通;
- 抽出:
PayChannelService接口 & 实现PayChannelFactory+@PayChannelTypeAbstractPayTemplate统一流程
- 把原来散落在 Controller/Service 里的校验逻辑迁移到
PayPreHandlerChain; - 把第三方 SDK 的调用封装到
ThirdPayClient适配器中; - 引入事件机制,将“支付成功后”的业务逻辑挪出支付模块;
- 当新增渠道时:
- 新建一个
XXXPayChannelService+@PayChannelType; - 如果 SDK 接口不一样,再加一个适配器实现即可。
- 新建一个
做到这一步,你就有了一个:结构清晰、易扩展、设计模式运用合理的聚合支付框架。
你可以直接把这个 Markdown 文件放到项目的
docs/目录里,作为“聚合支付设计说明文档”,或者继续在此基础上补充类图、时序图、表结构说明等内容。
1990

被折叠的 条评论
为什么被折叠?



