策略模式+模板方法+工厂模式打造可扩展支付系统

79 阅读12分钟

设计模式实战:策略模式+模板方法+工厂模式打造可扩展支付系统

本文通过一个真实的支付系统案例,深入讲解如何将策略模式、模板方法模式、工厂模式三者结合,构建一个高扩展性、易维护的多渠道支付系统。

一、背景与需求分析

1.1 业务场景

在电商系统中,支付模块是核心功能之一。随着业务发展,需要接入多种支付渠道:

  • 支付宝:国内主流支付方式
  • 微信支付:社交场景支付首选
  • 银联支付:传统银行卡支付
  • 京东钱包:京东生态内支付

1.2 面临的挑战

+------------------+     +------------------+     +------------------+
|   支付宝支付      |     |   微信支付        |     |   银联支付        |
|   - 签名方式不同   |     |   - 签名方式不同   |     |   - 签名方式不同   |
|   - 请求参数不同   |     |   - 请求参数不同   |     |   - 请求参数不同   |
|   - 回调处理不同   |     |   - 回调处理不同   |     |   - 回调处理不同   |
+------------------+     +------------------+     +------------------+
         |                       |                       |
         +-----------------------------------------------+
                                 |
                    +------------------------+
                    |   如何优雅地扩展?       |
                    |   如何减少重复代码?     |
                    |   如何统一管理?        |
                    +------------------------+

1.3 设计目标

  1. 开闭原则:新增支付渠道时,不修改现有代码
  2. 代码复用:抽取公共流程,减少重复代码
  3. 易于维护:清晰的代码结构,便于定位问题
  4. 灵活扩展:支持运行时动态切换支付渠道

二、设计模式简介

2.1 策略模式 (Strategy Pattern)

+-------------------+         +----------------------+
|     Context       |         |   <<interface>>      |
|-------------------|         |     Strategy         |
| - strategy        |-------->|----------------------|
| + setStrategy()   |         | + execute()          |
| + executeStrategy |         +----------------------+
+-------------------+                   ^
                                        |
              +-------------------------+-------------------------+
              |                         |                         |
    +------------------+      +------------------+      +------------------+
    | ConcreteStrategyA|      | ConcreteStrategyB|      | ConcreteStrategyC|
    |------------------|      |------------------|      |------------------|
    | + execute()      |      | + execute()      |      | + execute()      |
    +------------------+      +------------------+      +------------------+

核心思想:定义一系列算法,将每个算法封装起来,使它们可以互相替换。

2.2 模板方法模式 (Template Method Pattern)

+--------------------------------+
|      AbstractClass             |
|--------------------------------|
| + templateMethod() {           |
|     step1();                   |
|     step2();     // 抽象方法    |
|     step3();     // 钩子方法    |
| }                              |
| # step1() { ... }              |
| # abstract step2();            |
| # step3() { ... }  // 可选覆盖  |
+--------------------------------+
              ^
              |
    +---------+---------+
    |                   |
+----------+       +----------+
| SubClassA|       | SubClassB|
|----------|       |----------|
| # step2()|       | # step2()|
| # step3()|       +----------+
+----------+

核心思想:定义算法骨架,将某些步骤延迟到子类实现。

2.3 工厂模式 (Factory Pattern)

+------------------+         +----------------------+
|    Client        |         |    <<interface>>     |
|------------------|         |      Product         |
| + main()         |-------->|----------------------|
+------------------+         | + operation()        |
        |                    +----------------------+
        |                              ^
        v                              |
+------------------+         +---------+---------+
|     Factory      |         |                   |
|------------------|    +----------+       +----------+
| + create(type)   |--->| ProductA |       | ProductB |
+------------------+    +----------+       +----------+

核心思想:将对象的创建与使用分离,通过工厂统一管理对象创建。

三、架构设计

3.1 整体架构

+------------------------------------------------------------------+
|                         支付系统架构                               |
+------------------------------------------------------------------+
|                                                                   |
|  +-------------------+    +-------------------+                   |
|  |   Controller层    |--->|   Service层       |                   |
|  +-------------------+    +-------------------+                   |
|                                   |                               |
|                                   v                               |
|  +----------------------------------------------------------+    |
|  |                    PaymentContext (上下文)                 |    |
|  |  +----------------------------------------------------+  |    |
|  |  |            PaymentStrategyFactory (工厂)            |  |    |
|  |  +----------------------------------------------------+  |    |
|  |                          |                               |    |
|  |                          v                               |    |
|  |  +----------------------------------------------------+  |    |
|  |  |        AbstractPaymentStrategy (模板方法)           |  |    |
|  |  |  +----------------------------------------------+  |  |    |
|  |  |  | 1. validateParams()   - 参数校验              |  |  |    |
|  |  |  | 2. buildRequest()     - 构建请求 (抽象)       |  |  |    |
|  |  |  | 3. sign()             - 签名 (抽象)           |  |  |    |
|  |  |  | 4. doRequest()        - 发送请求              |  |  |    |
|  |  |  | 5. parseResponse()    - 解析响应 (抽象)       |  |  |    |
|  |  |  | 6. afterPay()         - 后置处理 (钩子)       |  |  |    |
|  |  |  +----------------------------------------------+  |  |    |
|  |  +----------------------------------------------------+  |    |
|  |                          ^                               |    |
|  |     +--------------------+--------------------+          |    |
|  |     |          |              |               |          |    |
|  | +-------+ +--------+ +----------+ +--------+             |    |
|  | |支付宝 | | 微信   | |   银联   | | 京东   |             |    |
|  | +-------+ +--------+ +----------+ +--------+             |    |
|  +----------------------------------------------------------+    |
+------------------------------------------------------------------+

3.2 项目模块结构

java-pay/
├── pom.xml                          # 父POM
├── pay-common/                      # 公共模块
│   ├── pom.xml
│   └── src/main/java/
│       └── com/pay/common/
│           ├── enums/               # 枚举类
│           ├── exception/           # 异常类
│           └── utils/               # 工具类
├── pay-core/                        # 核心模块
│   ├── pom.xml
│   └── src/main/java/
│       └── com/pay/core/
│           ├── context/             # 上下文
│           ├── factory/             # 工厂
│           ├── strategy/            # 策略接口与抽象类
│           └── model/               # 数据模型
├── pay-channel-alipay/              # 支付宝渠道
├── pay-channel-wechat/              # 微信渠道
├── pay-channel-unionpay/            # 银联渠道
├── pay-channel-jdpay/               # 京东渠道
└── pay-spring-boot-starter/         # Spring Boot Starter

四、代码实现

4.1 公共模块 (pay-common)

支付渠道枚举
package com.pay.common.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 支付渠道枚举
 */
@Getter
@AllArgsConstructor
public enum PayChannelEnum {

    ALIPAY("alipay", "支付宝"),
    WECHAT("wechat", "微信支付"),
    UNIONPAY("unionpay", "银联支付"),
    JDPAY("jdpay", "京东钱包");

    private final String code;
    private final String desc;

    public static PayChannelEnum getByCode(String code) {
        for (PayChannelEnum channel : values()) {
            if (channel.getCode().equals(code)) {
                return channel;
            }
        }
        throw new IllegalArgumentException("Unknown pay channel: " + code);
    }
}
支付状态枚举
package com.pay.common.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 支付状态枚举
 */
@Getter
@AllArgsConstructor
public enum PayStatusEnum {

    WAITING("WAITING", "待支付"),
    SUCCESS("SUCCESS", "支付成功"),
    FAILED("FAILED", "支付失败"),
    CLOSED("CLOSED", "已关闭"),
    REFUND("REFUND", "已退款");

    private final String code;
    private final String desc;
}
统一异常类
package com.pay.common.exception;

import lombok.Getter;

/**
 * 支付异常
 */
@Getter
public class PayException extends RuntimeException {

    private final String code;
    private final String message;

    public PayException(String code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }

    public PayException(String code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.message = message;
    }
}

4.2 核心模块 (pay-core)

支付请求基类
package com.pay.core.model;

import lombok.Data;
import java.math.BigDecimal;

/**
 * 支付请求基类
 */
@Data
public class PayRequest {

    /** 商户订单号 */
    private String outTradeNo;

    /** 支付金额 */
    private BigDecimal amount;

    /** 商品描述 */
    private String subject;

    /** 商品详情 */
    private String body;

    /** 回调地址 */
    private String notifyUrl;

    /** 返回地址 */
    private String returnUrl;

    /** 客户端IP */
    private String clientIp;

    /** 扩展参数 */
    private Map<String, Object> extParams;
}
支付响应基类
package com.pay.core.model;

import com.pay.common.enums.PayStatusEnum;
import lombok.Data;

/**
 * 支付响应基类
 */
@Data
public class PayResponse {

    /** 是否成功 */
    private boolean success;

    /** 错误码 */
    private String code;

    /** 错误信息 */
    private String message;

    /** 商户订单号 */
    private String outTradeNo;

    /** 渠道交易号 */
    private String tradeNo;

    /** 支付状态 */
    private PayStatusEnum status;

    /** 支付链接/二维码内容 */
    private String payUrl;

    /** 原始响应 */
    private String rawResponse;

    public static PayResponse success(String outTradeNo, String tradeNo) {
        PayResponse response = new PayResponse();
        response.setSuccess(true);
        response.setOutTradeNo(outTradeNo);
        response.setTradeNo(tradeNo);
        response.setStatus(PayStatusEnum.SUCCESS);
        return response;
    }

    public static PayResponse fail(String code, String message) {
        PayResponse response = new PayResponse();
        response.setSuccess(false);
        response.setCode(code);
        response.setMessage(message);
        response.setStatus(PayStatusEnum.FAILED);
        return response;
    }
}
支付策略接口 (策略模式)
package com.pay.core.strategy;

import com.pay.common.enums.PayChannelEnum;
import com.pay.core.model.PayRequest;
import com.pay.core.model.PayResponse;

/**
 * 支付策略接口 - 策略模式核心
 */
public interface PaymentStrategy {

    /**
     * 获取支付渠道
     */
    PayChannelEnum getChannel();

    /**
     * 执行支付
     */
    PayResponse pay(PayRequest request);

    /**
     * 查询支付结果
     */
    PayResponse query(String outTradeNo);

    /**
     * 关闭订单
     */
    PayResponse close(String outTradeNo);

    /**
     * 退款
     */
    PayResponse refund(String outTradeNo, BigDecimal refundAmount);
}
抽象支付策略 (模板方法模式)
package com.pay.core.strategy;

import com.pay.common.exception.PayException;
import com.pay.core.model.PayRequest;
import com.pay.core.model.PayResponse;
import lombok.extern.slf4j.Slf4j;

/**
 * 抽象支付策略 - 模板方法模式核心
 * 定义支付流程骨架,具体步骤由子类实现
 */
@Slf4j
public abstract class AbstractPaymentStrategy implements PaymentStrategy {

    /**
     * 模板方法 - 定义支付流程
     */
    @Override
    public final PayResponse pay(PayRequest request) {
        try {
            // Step 1: 参数校验
            validateParams(request);
            log.info("[{}] 参数校验通过, outTradeNo={}", getChannel(), request.getOutTradeNo());

            // Step 2: 构建请求参数 (抽象方法)
            Map<String, String> params = buildRequestParams(request);
            log.info("[{}] 构建请求参数完成", getChannel());

            // Step 3: 签名 (抽象方法)
            String sign = sign(params);
            params.put("sign", sign);
            log.info("[{}] 签名完成", getChannel());

            // Step 4: 发送请求
            String response = doRequest(params);
            log.info("[{}] 请求发送完成", getChannel());

            // Step 5: 解析响应 (抽象方法)
            PayResponse payResponse = parseResponse(response);
            payResponse.setOutTradeNo(request.getOutTradeNo());

            // Step 6: 后置处理 (钩子方法)
            afterPay(request, payResponse);

            return payResponse;

        } catch (PayException e) {
            log.error("[{}] 支付异常: {}", getChannel(), e.getMessage(), e);
            throw e;
        } catch (Exception e) {
            log.error("[{}] 支付系统异常", getChannel(), e);
            throw new PayException("SYSTEM_ERROR", "系统异常", e);
        }
    }

    /**
     * 参数校验 - 通用实现,子类可覆盖
     */
    protected void validateParams(PayRequest request) {
        if (request == null) {
            throw new PayException("PARAM_ERROR", "请求参数不能为空");
        }
        if (StringUtils.isBlank(request.getOutTradeNo())) {
            throw new PayException("PARAM_ERROR", "商户订单号不能为空");
        }
        if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new PayException("PARAM_ERROR", "支付金额必须大于0");
        }
    }

    /**
     * 构建请求参数 - 抽象方法,子类必须实现
     */
    protected abstract Map<String, String> buildRequestParams(PayRequest request);

    /**
     * 签名 - 抽象方法,子类必须实现
     */
    protected abstract String sign(Map<String, String> params);

    /**
     * 发送请求 - 通用实现
     */
    protected String doRequest(Map<String, String> params) {
        // 这里使用HTTP客户端发送请求
        // 实际项目中可以使用OkHttp、HttpClient等
        return HttpUtils.post(getPayUrl(), params);
    }

    /**
     * 获取支付网关地址 - 抽象方法
     */
    protected abstract String getPayUrl();

    /**
     * 解析响应 - 抽象方法,子类必须实现
     */
    protected abstract PayResponse parseResponse(String response);

    /**
     * 后置处理 - 钩子方法,子类可选覆盖
     */
    protected void afterPay(PayRequest request, PayResponse response) {
        // 默认空实现,子类可以覆盖实现日志记录、消息通知等
        log.info("[{}] 支付完成, outTradeNo={}, success={}",
                getChannel(), request.getOutTradeNo(), response.isSuccess());
    }
}
支付策略工厂 (工厂模式)
package com.pay.core.factory;

import com.pay.common.enums.PayChannelEnum;
import com.pay.common.exception.PayException;
import com.pay.core.strategy.PaymentStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 支付策略工厂 - 工厂模式核心
 * 负责创建和管理所有支付策略实例
 */
@Slf4j
@Component
public class PaymentStrategyFactory {

    /** 策略映射表 */
    private final Map<PayChannelEnum, PaymentStrategy> strategyMap = new HashMap<>();

    /** 自动注入所有PaymentStrategy实现类 */
    @Autowired
    private List<PaymentStrategy> strategies;

    /**
     * 初始化策略映射
     */
    @PostConstruct
    public void init() {
        for (PaymentStrategy strategy : strategies) {
            strategyMap.put(strategy.getChannel(), strategy);
            log.info("注册支付策略: {}", strategy.getChannel());
        }
        log.info("支付策略工厂初始化完成, 共注册{}个策略", strategyMap.size());
    }

    /**
     * 获取支付策略
     */
    public PaymentStrategy getStrategy(PayChannelEnum channel) {
        PaymentStrategy strategy = strategyMap.get(channel);
        if (strategy == null) {
            throw new PayException("CHANNEL_NOT_SUPPORT",
                    "不支持的支付渠道: " + channel.getDesc());
        }
        return strategy;
    }

    /**
     * 根据渠道编码获取策略
     */
    public PaymentStrategy getStrategy(String channelCode) {
        return getStrategy(PayChannelEnum.getByCode(channelCode));
    }

    /**
     * 获取所有支持的渠道
     */
    public List<PayChannelEnum> getSupportedChannels() {
        return new ArrayList<>(strategyMap.keySet());
    }
}
支付上下文 (Context)
package com.pay.core.context;

import com.pay.common.enums.PayChannelEnum;
import com.pay.core.factory.PaymentStrategyFactory;
import com.pay.core.model.PayRequest;
import com.pay.core.model.PayResponse;
import com.pay.core.strategy.PaymentStrategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;

/**
 * 支付上下文 - 策略模式的上下文角色
 * 对外提供统一的支付接口
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class PaymentContext {

    private final PaymentStrategyFactory strategyFactory;

    /**
     * 执行支付
     */
    public PayResponse pay(PayChannelEnum channel, PayRequest request) {
        log.info("开始支付, channel={}, outTradeNo={}", channel, request.getOutTradeNo());
        PaymentStrategy strategy = strategyFactory.getStrategy(channel);
        return strategy.pay(request);
    }

    /**
     * 查询订单
     */
    public PayResponse query(PayChannelEnum channel, String outTradeNo) {
        log.info("查询订单, channel={}, outTradeNo={}", channel, outTradeNo);
        PaymentStrategy strategy = strategyFactory.getStrategy(channel);
        return strategy.query(outTradeNo);
    }

    /**
     * 关闭订单
     */
    public PayResponse close(PayChannelEnum channel, String outTradeNo) {
        log.info("关闭订单, channel={}, outTradeNo={}", channel, outTradeNo);
        PaymentStrategy strategy = strategyFactory.getStrategy(channel);
        return strategy.close(outTradeNo);
    }

    /**
     * 退款
     */
    public PayResponse refund(PayChannelEnum channel, String outTradeNo, BigDecimal amount) {
        log.info("申请退款, channel={}, outTradeNo={}, amount={}", channel, outTradeNo, amount);
        PaymentStrategy strategy = strategyFactory.getStrategy(channel);
        return strategy.refund(outTradeNo, amount);
    }
}

4.3 支付宝渠道实现 (pay-channel-alipay)

package com.pay.channel.alipay;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.pay.common.enums.PayChannelEnum;
import com.pay.common.enums.PayStatusEnum;
import com.pay.core.model.PayRequest;
import com.pay.core.model.PayResponse;
import com.pay.core.strategy.AbstractPaymentStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * 支付宝支付策略实现
 */
@Slf4j
@Component
public class AlipayStrategy extends AbstractPaymentStrategy {

    @Value("${pay.alipay.app-id}")
    private String appId;

    @Value("${pay.alipay.private-key}")
    private String privateKey;

    @Value("${pay.alipay.gateway-url:https://openapi.alipay.com/gateway.do}")
    private String gatewayUrl;

    @Override
    public PayChannelEnum getChannel() {
        return PayChannelEnum.ALIPAY;
    }

    @Override
    protected Map<String, String> buildRequestParams(PayRequest request) {
        // 公共参数
        Map<String, String> params = new HashMap<>();
        params.put("app_id", appId);
        params.put("method", "alipay.trade.page.pay");
        params.put("charset", "utf-8");
        params.put("sign_type", "RSA2");
        params.put("timestamp", LocalDateTime.now()
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        params.put("version", "1.0");
        params.put("notify_url", request.getNotifyUrl());
        params.put("return_url", request.getReturnUrl());

        // 业务参数
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", request.getOutTradeNo());
        bizContent.put("total_amount", request.getAmount().toString());
        bizContent.put("subject", request.getSubject());
        bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");

        params.put("biz_content", bizContent.toJSONString());

        return params;
    }

    @Override
    protected String sign(Map<String, String> params) {
        // 按字母顺序排序
        TreeMap<String, String> sortedParams = new TreeMap<>(params);

        // 构建待签名字符串
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            if (StringUtils.isNotBlank(entry.getValue()) && !"sign".equals(entry.getKey())) {
                sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
        }
        String signContent = sb.substring(0, sb.length() - 1);

        // RSA2签名 (SHA256WithRSA)
        return AlipaySignature.rsaSign(signContent, privateKey, "utf-8", "RSA2");
    }

    @Override
    protected String getPayUrl() {
        return gatewayUrl;
    }

    @Override
    protected PayResponse parseResponse(String response) {
        JSONObject json = JSON.parseObject(response);
        JSONObject result = json.getJSONObject("alipay_trade_page_pay_response");

        if ("10000".equals(result.getString("code"))) {
            PayResponse payResponse = new PayResponse();
            payResponse.setSuccess(true);
            payResponse.setTradeNo(result.getString("trade_no"));
            payResponse.setStatus(PayStatusEnum.WAITING);
            payResponse.setRawResponse(response);
            return payResponse;
        } else {
            return PayResponse.fail(
                    result.getString("code"),
                    result.getString("msg")
            );
        }
    }

    @Override
    public PayResponse query(String outTradeNo) {
        // 查询接口实现...
        log.info("[支付宝] 查询订单: {}", outTradeNo);
        return PayResponse.success(outTradeNo, null);
    }

    @Override
    public PayResponse close(String outTradeNo) {
        // 关闭订单实现...
        log.info("[支付宝] 关闭订单: {}", outTradeNo);
        return PayResponse.success(outTradeNo, null);
    }

    @Override
    public PayResponse refund(String outTradeNo, BigDecimal refundAmount) {
        // 退款接口实现...
        log.info("[支付宝] 退款: {}, amount={}", outTradeNo, refundAmount);
        return PayResponse.success(outTradeNo, null);
    }
}

4.4 微信支付渠道实现 (pay-channel-wechat)

package com.pay.channel.wechat;

import com.pay.common.enums.PayChannelEnum;
import com.pay.common.enums.PayStatusEnum;
import com.pay.core.model.PayRequest;
import com.pay.core.model.PayResponse;
import com.pay.core.strategy.AbstractPaymentStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

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

/**
 * 微信支付策略实现
 */
@Slf4j
@Component
public class WechatPayStrategy extends AbstractPaymentStrategy {

    @Value("${pay.wechat.app-id}")
    private String appId;

    @Value("${pay.wechat.mch-id}")
    private String mchId;

    @Value("${pay.wechat.api-key}")
    private String apiKey;

    @Value("${pay.wechat.gateway-url:https://api.mch.weixin.qq.com/pay/unifiedorder}")
    private String gatewayUrl;

    @Override
    public PayChannelEnum getChannel() {
        return PayChannelEnum.WECHAT;
    }

    @Override
    protected Map<String, String> buildRequestParams(PayRequest request) {
        Map<String, String> params = new HashMap<>();
        params.put("appid", appId);
        params.put("mch_id", mchId);
        params.put("nonce_str", UUID.randomUUID().toString().replace("-", ""));
        params.put("body", request.getSubject());
        params.put("out_trade_no", request.getOutTradeNo());
        // 微信金额单位为分
        params.put("total_fee", request.getAmount()
                .multiply(new BigDecimal("100")).intValue() + "");
        params.put("spbill_create_ip", request.getClientIp());
        params.put("notify_url", request.getNotifyUrl());
        params.put("trade_type", "NATIVE"); // 扫码支付
        params.put("sign_type", "MD5");

        return params;
    }

    @Override
    protected String sign(Map<String, String> params) {
        // 微信签名: MD5(key1=value1&key2=value2&key=API_KEY)
        TreeMap<String, String> sortedParams = new TreeMap<>(params);

        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            if (StringUtils.isNotBlank(entry.getValue()) && !"sign".equals(entry.getKey())) {
                sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
        }
        sb.append("key=").append(apiKey);

        return DigestUtils.md5Hex(sb.toString()).toUpperCase();
    }

    @Override
    protected String getPayUrl() {
        return gatewayUrl;
    }

    @Override
    protected PayResponse parseResponse(String response) {
        // 解析XML响应
        Map<String, String> resultMap = XmlUtils.xmlToMap(response);

        if ("SUCCESS".equals(resultMap.get("return_code"))
                && "SUCCESS".equals(resultMap.get("result_code"))) {
            PayResponse payResponse = new PayResponse();
            payResponse.setSuccess(true);
            payResponse.setPayUrl(resultMap.get("code_url")); // 二维码链接
            payResponse.setTradeNo(resultMap.get("prepay_id"));
            payResponse.setStatus(PayStatusEnum.WAITING);
            payResponse.setRawResponse(response);
            return payResponse;
        } else {
            return PayResponse.fail(
                    resultMap.get("err_code"),
                    resultMap.get("err_code_des")
            );
        }
    }

    @Override
    protected void afterPay(PayRequest request, PayResponse response) {
        // 微信支付特有的后置处理: 记录prepay_id用于后续操作
        super.afterPay(request, response);
        if (response.isSuccess()) {
            log.info("[微信] 获取二维码链接: {}", response.getPayUrl());
        }
    }

    @Override
    public PayResponse query(String outTradeNo) {
        log.info("[微信] 查询订单: {}", outTradeNo);
        return PayResponse.success(outTradeNo, null);
    }

    @Override
    public PayResponse close(String outTradeNo) {
        log.info("[微信] 关闭订单: {}", outTradeNo);
        return PayResponse.success(outTradeNo, null);
    }

    @Override
    public PayResponse refund(String outTradeNo, BigDecimal refundAmount) {
        log.info("[微信] 退款: {}, amount={}", outTradeNo, refundAmount);
        return PayResponse.success(outTradeNo, null);
    }
}

4.5 银联支付渠道实现 (pay-channel-unionpay)

package com.pay.channel.unionpay;

import com.pay.common.enums.PayChannelEnum;
import com.pay.common.enums.PayStatusEnum;
import com.pay.core.model.PayRequest;
import com.pay.core.model.PayResponse;
import com.pay.core.strategy.AbstractPaymentStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * 银联支付策略实现
 */
@Slf4j
@Component
public class UnionPayStrategy extends AbstractPaymentStrategy {

    @Value("${pay.unionpay.mer-id}")
    private String merId;

    @Value("${pay.unionpay.cert-path}")
    private String certPath;

    @Value("${pay.unionpay.gateway-url:https://gateway.95516.com/gateway/api/frontTransReq.do}")
    private String gatewayUrl;

    @Override
    public PayChannelEnum getChannel() {
        return PayChannelEnum.UNIONPAY;
    }

    @Override
    protected Map<String, String> buildRequestParams(PayRequest request) {
        Map<String, String> params = new HashMap<>();

        // 版本号
        params.put("version", "5.1.0");
        // 字符集编码
        params.put("encoding", "UTF-8");
        // 签名方法
        params.put("signMethod", "01");
        // 交易类型
        params.put("txnType", "01");
        // 交易子类型
        params.put("txnSubType", "01");
        // 业务类型
        params.put("bizType", "000201");
        // 渠道类型
        params.put("channelType", "07");
        // 前台通知地址
        params.put("frontUrl", request.getReturnUrl());
        // 后台通知地址
        params.put("backUrl", request.getNotifyUrl());
        // 接入类型
        params.put("accessType", "0");
        // 商户号
        params.put("merId", merId);
        // 商户订单号
        params.put("orderId", request.getOutTradeNo());
        // 订单发送时间
        params.put("txnTime", LocalDateTime.now()
                .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
        // 交易金额 (单位: 分)
        params.put("txnAmt", request.getAmount()
                .multiply(new BigDecimal("100")).intValue() + "");
        // 交易币种
        params.put("currencyCode", "156");
        // 订单描述
        params.put("orderDesc", request.getSubject());

        return params;
    }

    @Override
    protected String sign(Map<String, String> params) {
        // 银联签名使用证书签名
        return UnionPaySignature.signByCert(params, certPath);
    }

    @Override
    protected String getPayUrl() {
        return gatewayUrl;
    }

    @Override
    protected PayResponse parseResponse(String response) {
        Map<String, String> resultMap = parseQueryString(response);

        if ("00".equals(resultMap.get("respCode"))) {
            PayResponse payResponse = new PayResponse();
            payResponse.setSuccess(true);
            payResponse.setPayUrl(resultMap.get("payUrl"));
            payResponse.setTradeNo(resultMap.get("queryId"));
            payResponse.setStatus(PayStatusEnum.WAITING);
            payResponse.setRawResponse(response);
            return payResponse;
        } else {
            return PayResponse.fail(
                    resultMap.get("respCode"),
                    resultMap.get("respMsg")
            );
        }
    }

    @Override
    public PayResponse query(String outTradeNo) {
        log.info("[银联] 查询订单: {}", outTradeNo);
        return PayResponse.success(outTradeNo, null);
    }

    @Override
    public PayResponse close(String outTradeNo) {
        log.info("[银联] 关闭订单: {}", outTradeNo);
        return PayResponse.success(outTradeNo, null);
    }

    @Override
    public PayResponse refund(String outTradeNo, BigDecimal refundAmount) {
        log.info("[银联] 退款: {}, amount={}", outTradeNo, refundAmount);
        return PayResponse.success(outTradeNo, null);
    }
}

4.6 京东钱包渠道实现 (pay-channel-jdpay)

package com.pay.channel.jdpay;

import com.pay.common.enums.PayChannelEnum;
import com.pay.common.enums.PayStatusEnum;
import com.pay.core.model.PayRequest;
import com.pay.core.model.PayResponse;
import com.pay.core.strategy.AbstractPaymentStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

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

/**
 * 京东钱包支付策略实现
 */
@Slf4j
@Component
public class JdPayStrategy extends AbstractPaymentStrategy {

    @Value("${pay.jdpay.merchant}")
    private String merchant;

    @Value("${pay.jdpay.des-key}")
    private String desKey;

    @Value("${pay.jdpay.private-key}")
    private String privateKey;

    @Value("${pay.jdpay.gateway-url:https://paygate.jd.com/service/uniorder}")
    private String gatewayUrl;

    @Override
    public PayChannelEnum getChannel() {
        return PayChannelEnum.JDPAY;
    }

    @Override
    protected Map<String, String> buildRequestParams(PayRequest request) {
        Map<String, String> params = new HashMap<>();

        params.put("version", "V2.0");
        params.put("merchant", merchant);
        params.put("tradeNum", request.getOutTradeNo());
        params.put("tradeName", request.getSubject());
        params.put("tradeDesc", request.getBody());
        // 金额单位: 分
        params.put("amount", request.getAmount()
                .multiply(new BigDecimal("100")).longValue() + "");
        params.put("currency", "CNY");
        params.put("callbackUrl", request.getReturnUrl());
        params.put("notifyUrl", request.getNotifyUrl());
        params.put("tradeType", "GEN"); // 一般交易

        return params;
    }

    @Override
    protected String sign(Map<String, String> params) {
        // 京东签名: 先DES加密,再RSA签名
        String encrypted = JdPaySignature.encrypt3DES(params, desKey);
        return JdPaySignature.rsaSign(encrypted, privateKey);
    }

    @Override
    protected String getPayUrl() {
        return gatewayUrl;
    }

    @Override
    protected PayResponse parseResponse(String response) {
        // 解析京东响应
        Map<String, String> resultMap = JdPaySignature.decrypt3DES(response, desKey);

        if ("000000".equals(resultMap.get("code"))) {
            PayResponse payResponse = new PayResponse();
            payResponse.setSuccess(true);
            payResponse.setPayUrl(resultMap.get("payUrl"));
            payResponse.setTradeNo(resultMap.get("jdTradeNum"));
            payResponse.setStatus(PayStatusEnum.WAITING);
            payResponse.setRawResponse(response);
            return payResponse;
        } else {
            return PayResponse.fail(
                    resultMap.get("code"),
                    resultMap.get("desc")
            );
        }
    }

    @Override
    public PayResponse query(String outTradeNo) {
        log.info("[京东] 查询订单: {}", outTradeNo);
        return PayResponse.success(outTradeNo, null);
    }

    @Override
    public PayResponse close(String outTradeNo) {
        log.info("[京东] 关闭订单: {}", outTradeNo);
        return PayResponse.success(outTradeNo, null);
    }

    @Override
    public PayResponse refund(String outTradeNo, BigDecimal refundAmount) {
        log.info("[京东] 退款: {}, amount={}", outTradeNo, refundAmount);
        return PayResponse.success(outTradeNo, null);
    }
}

五、Spring Boot Starter集成

5.1 自动配置类

package com.pay.starter.config;

import com.pay.core.context.PaymentContext;
import com.pay.core.factory.PaymentStrategyFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * 支付自动配置
 */
@Configuration
@ComponentScan(basePackages = {
    "com.pay.core",
    "com.pay.channel.alipay",
    "com.pay.channel.wechat",
    "com.pay.channel.unionpay",
    "com.pay.channel.jdpay"
})
public class PaymentAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public PaymentStrategyFactory paymentStrategyFactory() {
        return new PaymentStrategyFactory();
    }

    @Bean
    @ConditionalOnMissingBean
    public PaymentContext paymentContext(PaymentStrategyFactory factory) {
        return new PaymentContext(factory);
    }
}

5.2 配置属性类

package com.pay.starter.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 支付配置属性
 */
@Data
@ConfigurationProperties(prefix = "pay")
public class PaymentProperties {

    private AlipayConfig alipay = new AlipayConfig();
    private WechatConfig wechat = new WechatConfig();
    private UnionPayConfig unionpay = new UnionPayConfig();
    private JdPayConfig jdpay = new JdPayConfig();

    @Data
    public static class AlipayConfig {
        private String appId;
        private String privateKey;
        private String publicKey;
        private String gatewayUrl = "https://openapi.alipay.com/gateway.do";
    }

    @Data
    public static class WechatConfig {
        private String appId;
        private String mchId;
        private String apiKey;
        private String gatewayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    }

    @Data
    public static class UnionPayConfig {
        private String merId;
        private String certPath;
        private String gatewayUrl = "https://gateway.95516.com/gateway/api/frontTransReq.do";
    }

    @Data
    public static class JdPayConfig {
        private String merchant;
        private String desKey;
        private String privateKey;
        private String gatewayUrl = "https://paygate.jd.com/service/uniorder";
    }
}

六、使用示例

6.1 Controller层

package com.example.controller;

import com.pay.common.enums.PayChannelEnum;
import com.pay.core.context.PaymentContext;
import com.pay.core.model.PayRequest;
import com.pay.core.model.PayResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;

/**
 * 支付控制器
 */
@RestController
@RequestMapping("/api/pay")
@RequiredArgsConstructor
public class PayController {

    private final PaymentContext paymentContext;

    /**
     * 统一下单接口
     */
    @PostMapping("/create")
    public PayResponse createOrder(@RequestParam String channel,
                                   @RequestParam String orderNo,
                                   @RequestParam BigDecimal amount,
                                   @RequestParam String subject) {
        PayRequest request = new PayRequest();
        request.setOutTradeNo(orderNo);
        request.setAmount(amount);
        request.setSubject(subject);
        request.setNotifyUrl("https://example.com/notify");
        request.setReturnUrl("https://example.com/return");

        PayChannelEnum payChannel = PayChannelEnum.getByCode(channel);
        return paymentContext.pay(payChannel, request);
    }

    /**
     * 查询订单
     */
    @GetMapping("/query")
    public PayResponse queryOrder(@RequestParam String channel,
                                  @RequestParam String orderNo) {
        PayChannelEnum payChannel = PayChannelEnum.getByCode(channel);
        return paymentContext.query(payChannel, orderNo);
    }

    /**
     * 关闭订单
     */
    @PostMapping("/close")
    public PayResponse closeOrder(@RequestParam String channel,
                                  @RequestParam String orderNo) {
        PayChannelEnum payChannel = PayChannelEnum.getByCode(channel);
        return paymentContext.close(payChannel, orderNo);
    }

    /**
     * 申请退款
     */
    @PostMapping("/refund")
    public PayResponse refund(@RequestParam String channel,
                              @RequestParam String orderNo,
                              @RequestParam BigDecimal amount) {
        PayChannelEnum payChannel = PayChannelEnum.getByCode(channel);
        return paymentContext.refund(payChannel, orderNo, amount);
    }
}

6.2 application.yml配置

pay:
  alipay:
    app-id: your_alipay_app_id
    private-key: your_alipay_private_key
    public-key: your_alipay_public_key
  wechat:
    app-id: your_wechat_app_id
    mch-id: your_wechat_mch_id
    api-key: your_wechat_api_key
  unionpay:
    mer-id: your_unionpay_mer_id
    cert-path: /path/to/cert
  jdpay:
    merchant: your_jd_merchant
    des-key: your_jd_des_key
    private-key: your_jd_private_key

七、设计模式对比与总结

7.1 三种模式的职责划分

+------------------------------------------------------------------+
|                     设计模式职责划分                               |
+------------------------------------------------------------------+
|                                                                   |
|  +-------------------+                                            |
|  |    工厂模式        |  负责: 创建和管理策略对象                   |
|  |  StrategyFactory  |  解决: 对象创建与使用解耦                   |
|  +-------------------+                                            |
|           |                                                       |
|           | 创建                                                  |
|           v                                                       |
|  +-------------------+                                            |
|  |    策略模式        |  负责: 定义统一的策略接口                   |
|  | PaymentStrategy   |  解决: 算法可互换,消除if-else              |
|  +-------------------+                                            |
|           ^                                                       |
|           | 实现                                                  |
|           |                                                       |
|  +-------------------+                                            |
|  |  模板方法模式       |  负责: 定义算法骨架,复用公共流程            |
|  |AbstractPayStrategy|  解决: 代码重复,流程标准化                  |
|  +-------------------+                                            |
|                                                                   |
+------------------------------------------------------------------+

7.2 模式组合的优势

设计模式解决的问题在支付系统中的应用
策略模式消除if-else,算法可互换不同支付渠道的切换
模板方法代码复用,流程标准化支付流程的公共步骤
工厂模式对象创建与使用解耦支付策略的创建管理

7.3 扩展新渠道的步骤

新增一个支付渠道(如苏宁支付)只需:

  1. 创建新模块 pay-channel-suning
  2. 实现 AbstractPaymentStrategy 抽象类
  3. 添加 @Component 注解
  4. 配置相关参数
@Component
public class SuningPayStrategy extends AbstractPaymentStrategy {

    @Override
    public PayChannelEnum getChannel() {
        return PayChannelEnum.SUNING; // 需要先在枚举中添加
    }

    @Override
    protected Map<String, String> buildRequestParams(PayRequest request) {
        // 实现苏宁特有的参数构建
    }

    @Override
    protected String sign(Map<String, String> params) {
        // 实现苏宁特有的签名方式
    }

    // ... 其他方法实现
}

无需修改任何现有代码,完美符合开闭原则!

八、与开源框架对比

8.1 IJPay

IJPay 是一款优秀的开源支付SDK,我们的设计借鉴了其部分思想:

IJPay特点:
├── 支持多种支付渠道
├── 提供丰富的工具类
├── 简化签名验签流程
└── 社区活跃,文档完善

本文设计的改进:
├── 更清晰的设计模式应用
├── 更好的扩展性(工厂+策略)
├── 更标准化的流程(模板方法)
└── 更灵活的配置方式(Spring Boot Starter)

8.2 Pay-Java

Pay-Java 同样是优秀的支付整合框架:

// Pay-Java的使用方式
PayService payService = new AliPayService(config);
PayOrder payOrder = new PayOrder("订单", "商品", 0.01);
Map<String, Object> result = payService.orderInfo(payOrder);

// 本文设计的使用方式 (更加统一)
PayResponse response = paymentContext.pay(PayChannelEnum.ALIPAY, request);

九、总结

通过本文,我们学习了如何将三种设计模式组合使用,构建一个可扩展的支付系统:

                    三模式组合优势
                         |
        +----------------+----------------+
        |                |                |
   +----v----+      +----v----+      +----v----+
   | 策略模式 |      | 模板方法 |      | 工厂模式 |
   +---------+      +---------+      +---------+
   | 渠道切换 |      | 流程复用 |      | 对象管理 |
   +---------+      +---------+      +---------+
        |                |                |
        +----------------+----------------+
                         |
                  高扩展性支付系统

核心要点:

  1. 策略模式:定义支付策略接口,各渠道实现自己的支付逻辑
  2. 模板方法:抽取公共流程(校验、签名、请求、解析),子类实现差异部分
  3. 工厂模式:统一管理策略对象的创建,支持Spring自动注入