Spring Boot 多商户支付系统完整实现【策略模式 + 工厂模式】

1. 核心依赖配置

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- 支付宝 SDK -->
    <dependency>
        <groupId>com.alipay.sdk</groupId>
        <artifactId>alipay-sdk-java</artifactId>
        <version>4.22.110.ALL</version>
    </dependency>
    
    <!-- 微信支付 SDK -->
    <dependency>
        <groupId>com.github.wechatpay-apiv3</groupId>
        <artifactId>wechatpay-java</artifactId>
        <version>0.2.12</version>
    </dependency>
    
    <!-- 银联支付 SDK -->
    <dependency>
        <groupId>com.unionpay</groupId>
        <artifactId>upmp</artifactId>
        <version>1.0.0</version>
    </dependency>
    
    <!-- 数据库相关 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
</dependencies>


2. 商户配置实体类

@Entity
@Table(name = "merchant_config")
public class MerchantConfig {
    @Id
    private String merchantId;
    
    @Enumerated(EnumType.STRING)
    private PayChannel payChannel;
    
    @ElementCollection
    @MapKeyColumn(name = "config_key")
    @Column(name = "config_value")
    @CollectionTable(name = "merchant_config_params", joinColumns = @JoinColumn(name = "merchant_id"))
    private Map<String, String> configParams = new HashMap<>();
    
    private Boolean enabled = true;
    
    private LocalDateTime createTime;
    
    private LocalDateTime updateTime;
    
    // getter and setter
}


public enum PayChannel {
    WECHAT("微信支付"),
    ALIPAY("支付宝"),
    UNIONPAY("银联支付");
    
    private String desc;
    
    PayChannel(String desc) {
        this.desc = desc;
    }
    
    public String getDesc() {
        return desc;
    }
}

3. 支付策略接口定义

public interface PaymentStrategy {
    /**
     * 统一下单
     */
    PayResponse unifiedPay(PayRequest request, MerchantConfig config);
    
    /**
     * 订单查询
     */
    PayQueryResponse queryOrder(String orderId, MerchantConfig config);
    
    /**
     * 退款处理
     */
    RefundResponse refund(RefundRequest request, MerchantConfig config);
    
    /**
     * 通知验证
     */
    boolean verifyNotify(Map<String, String> params, MerchantConfig config);
    
    /**
     * 支付渠道
     */
    PayChannel getPayChannel();
}

4. 支付请求和响应模型

@Data
public class PayRequest {
    private String merchantId;
    private String orderId;
    private BigDecimal amount;
    private String subject;
    private String body;
    private PayChannel payChannel;
    private PayType payType;
    private String notifyUrl;
    private String returnUrl;
    private Map<String, Object> extraParams;
}

@Data
public class PayResponse {
    private String code;
    private String message;
    private String orderId;
    private String tradeNo;
    private Map<String, Object> payInfo;
    private PayChannel payChannel;
}

public enum PayType {
    APP("app"),
    H5("h5"),
    JSAPI("jsapi"),
    NATIVE("native"),
    MINI_PROGRAM("mini_program");
    
    private String type;
    
    PayType(String type) {
        this.type = type;
    }
    
    public String getType() {
        return type;
    }
}

5. 微信支付策略实现

@Component
public class WechatPaymentStrategy implements PaymentStrategy {
    
    @Autowired
    private WechatPayService wechatPayService;
    
    @Override
    public PayChannel getPayChannel() {
        return PayChannel.WECHAT;
    }
    
    @Override
    public PayResponse unifiedPay(PayRequest request, MerchantConfig config) {
        try {
            // 初始化微信支付客户端
            WechatPayClient client = createClient(config);
            
            // 构建微信支付请求参数
            String appid = config.getConfigParams().get("appid");
            String mchId = config.getConfigParams().get("mch_id");
            String description = request.getBody();
            String outTradeNo = request.getOrderId();
            long amount = request.getAmount().multiply(new BigDecimal(100)).longValue();
            String notifyUrl = request.getNotifyUrl();
            
            // 根据支付类型选择不同的支付方式
            switch (request.getPayType()) {
                case JSAPI:
                    return handleJsapiPay(client, appid, mchId, description, outTradeNo, amount, notifyUrl, request.getExtraParams());
                case NATIVE:
                    return handleNativePay(client, appid, mchId, description, outTradeNo, amount, notifyUrl);
                case APP:
                    return handleAppPay(client, appid, mchId, description, outTradeNo, amount, notifyUrl);
                case H5:
                    return handleH5Pay(client, appid, mchId, description, outTradeNo, amount, notifyUrl);
                default:
                    throw new IllegalArgumentException("不支持的微信支付类型: " + request.getPayType());
            }
        } catch (Exception e) {
            log.error("微信支付下单失败", e);
            return PayResponse.builder()
                    .code("FAIL")
                    .message(e.getMessage())
                    .orderId(request.getOrderId())
                    .build();
        }
    }
    
    @Override
    public PayQueryResponse queryOrder(String orderId, MerchantConfig config) {
        // 微信订单查询实现
        WechatPayClient client = createClient(config);
        // 实现订单查询逻辑
        return null;
    }
    
    @Override
    public RefundResponse refund(RefundRequest request, MerchantConfig config) {
        // 微信退款实现
        WechatPayClient client = createClient(config);
        // 实现退款逻辑
        return null;
    }
    
    @Override
    public boolean verifyNotify(Map<String, String> params, MerchantConfig config) {
        // 微信通知验证实现
        return false;
    }
    
    private WechatPayClient createClient(MerchantConfig config) {
        // 创建微信支付客户端
        String mchId = config.getConfigParams().get("mch_id");
        String serialNo = config.getConfigParams().get("serial_no");
        String privateKey = config.getConfigParams().get("private_key");
        String certPath = config.getConfigParams().get("cert_path");
        
        // 实现客户端创建逻辑
        return new WechatPayClient(mchId, serialNo, privateKey, certPath);
    }
    
    private PayResponse handleJsapiPay(WechatPayClient client, String appid, String mchId, 
                                     String description, String outTradeNo, long amount, 
                                     String notifyUrl, Map<String, Object> extraParams) {
        // JSAPI支付处理逻辑
        return null;
    }
    
    private PayResponse handleNativePay(WechatPayClient client, String appid, String mchId, 
                                      String description, String outTradeNo, long amount, 
                                      String notifyUrl) {
        // NATIVE支付处理逻辑
        return null;
    }
    
    private PayResponse handleAppPay(WechatPayClient client, String appid, String mchId, 
                                   String description, String outTradeNo, long amount, 
                                   String notifyUrl) {
        // APP支付处理逻辑
        return null;
    }
    
    private PayResponse handleH5Pay(WechatPayClient client, String appid, String mchId, 
                                  String description, String outTradeNo, long amount, 
                                  String notifyUrl) {
        // H5支付处理逻辑
        return null;
    }
}

6. 支付宝支付策略实现

@Component
public class AlipayPaymentStrategy implements PaymentStrategy {
    
    @Override
    public PayChannel getPayChannel() {
        return PayChannel.ALIPAY;
    }
    
    @Override
    public PayResponse unifiedPay(PayRequest request, MerchantConfig config) {
        try {
            // 初始化支付宝客户端
            AlipayClient client = createAlipayClient(config);
            
            String appId = config.getConfigParams().get("app_id");
            String returnUrl = request.getReturnUrl();
            String notifyUrl = request.getNotifyUrl();
            
            // 根据支付类型构建不同请求
            AlipayTradeCreateRequest tradeCreateRequest = new AlipayTradeCreateRequest();
            tradeCreateRequest.setReturnUrl(returnUrl);
            tradeCreateRequest.setNotifyUrl(notifyUrl);
            
            // 设置业务参数
            AlipayTradeCreateModel model = new AlipayTradeCreateModel();
            model.setOutTradeNo(request.getOrderId());
            model.setTotalAmount(request.getAmount().toString());
            model.setSubject(request.getSubject());
            model.setBody(request.getBody());
            
            tradeCreateRequest.setBizModel(model);
            
            // 执行请求
            AlipayTradeCreateResponse response = client.execute(tradeCreateRequest);
            
            if (response.isSuccess()) {
                return PayResponse.builder()
                        .code("SUCCESS")
                        .message("支付创建成功")
                        .orderId(request.getOrderId())
                        .tradeNo(response.getTradeNo())
                        .payInfo(buildPayInfo(response, request.getPayType()))
                        .payChannel(PayChannel.ALIPAY)
                        .build();
            } else {
                return PayResponse.builder()
                        .code("FAIL")
                        .message(response.getMsg())
                        .orderId(request.getOrderId())
                        .build();
            }
        } catch (Exception e) {
            log.error("支付宝支付下单失败", e);
            return PayResponse.builder()
                    .code("FAIL")
                    .message(e.getMessage())
                    .orderId(request.getOrderId())
                    .build();
        }
    }
    
    @Override
    public PayQueryResponse queryOrder(String orderId, MerchantConfig config) {
        // 支付宝订单查询实现
        return null;
    }
    
    @Override
    public RefundResponse refund(RefundRequest request, MerchantConfig config) {
        // 支付宝退款实现
        return null;
    }
    
    @Override
    public boolean verifyNotify(Map<String, String> params, MerchantConfig config) {
        // 支付宝通知验证实现
        return false;
    }
    
    private AlipayClient createAlipayClient(MerchantConfig config) {
        String appId = config.getConfigParams().get("app_id");
        String privateKey = config.getConfigParams().get("private_key");
        String alipayPublicKey = config.getConfigParams().get("alipay_public_key");
        String gatewayUrl = config.getConfigParams().get("gateway_url");
        
        return new DefaultAlipayClient(gatewayUrl, appId, privateKey, 
                "json", "UTF-8", alipayPublicKey, "RSA2");
    }
    
    private Map<String, Object> buildPayInfo(AlipayTradeCreateResponse response, PayType payType) {
        Map<String, Object> payInfo = new HashMap<>();
        // 根据支付类型返回不同的支付信息
        switch (payType) {
            case APP:
                // 返回APP支付信息
                break;
            case H5:
                // 返回H5支付信息
                break;
            case JSAPI:
                // 返回JSAPI支付信息
                break;
            case NATIVE:
                // 返回NATIVE支付信息
                break;
        }
        return payInfo;
    }
}

7. 银联支付策略实现

@Component
public class UnionpayPaymentStrategy implements PaymentStrategy {
    
    @Override
    public PayChannel getPayChannel() {
        return PayChannel.UNIONPAY;
    }
    
    @Override
    public PayResponse unifiedPay(PayRequest request, MerchantConfig config) {
        try {
            // 银联支付参数配置
            String merId = config.getConfigParams().get("mer_id");
            String certPath = config.getConfigParams().get("cert_path");
            String certPwd = config.getConfigParams().get("cert_pwd");
            
            // 初始化银联支付环境
            AcpService.init(merId, certPath, certPwd);
            
            // 构建银联支付请求
            Map<String, String> reqData = new HashMap<>();
            reqData.put("version", "5.1.0"); // 版本号
            reqData.put("encoding", "UTF-8"); // 编码方式
            reqData.put("signMethod", "01"); // 签名方法
            reqData.put("txnType", "01"); // 交易类型
            reqData.put("txnSubType", "01"); // 交易子类
            reqData.put("bizType", "000201"); // 业务类型
            reqData.put("channelType", "08"); // 渠道类型
            reqData.put("accessType", "0"); // 接入类型
            reqData.put("merId", merId); // 商户代码
            reqData.put("orderId", request.getOrderId()); // 订单号
            reqData.put("txnTime", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())); // 交易时间
            reqData.put("txnAmt", request.getAmount().multiply(new BigDecimal(100)).intValue() + ""); // 交易金额
            reqData.put("currencyCode", "156"); // 交易币种
            reqData.put("frontUrl", request.getReturnUrl()); // 前台通知地址
            reqData.put("backUrl", request.getNotifyUrl()); // 后台通知地址
            reqData.put("orderDesc", request.getBody()); // 订单描述
            
            // 签名
            Map<String, String> signedData = AcpService.sign(reqData);
            
            // 发送请求
            String url = config.getConfigParams().get("front_trans_url");
            Map<String, String> rspData = AcpService.post(url, signedData, "UTF-8");
            
            if (null != rspData && !rspData.isEmpty()) {
                if (AcpService.validate(rspData, "UTF-8")) {
                    String respCode = rspData.get("respCode");
                    if ("00".equals(respCode)) {
                        return PayResponse.builder()
                                .code("SUCCESS")
                                .message("支付创建成功")
                                .orderId(request.getOrderId())
                                .tradeNo(rspData.get("queryId"))
                                .payInfo(rspData)
                                .payChannel(PayChannel.UNIONPAY)
                                .build();
                    } else {
                        return PayResponse.builder()
                                .code("FAIL")
                                .message("银联支付失败:" + rspData.get("respMsg"))
                                .orderId(request.getOrderId())
                                .build();
                    }
                }
            }
            
            return PayResponse.builder()
                    .code("FAIL")
                    .message("银联支付验证失败")
                    .orderId(request.getOrderId())
                    .build();
        } catch (Exception e) {
            log.error("银联支付下单失败", e);
            return PayResponse.builder()
                    .code("FAIL")
                    .message(e.getMessage())
                    .orderId(request.getOrderId())
                    .build();
        }
    }
    
    @Override
    public PayQueryResponse queryOrder(String orderId, MerchantConfig config) {
        // 银联订单查询实现
        return null;
    }
    
    @Override
    public RefundResponse refund(RefundRequest request, MerchantConfig config) {
        // 银联退款实现
        return null;
    }
    
    @Override
    public boolean verifyNotify(Map<String, String> params, MerchantConfig config) {
        // 银联通知验证实现
        return AcpService.validate(params, "UTF-8");
    }
}

8. 支付策略工厂

@Component
public class PaymentStrategyFactory {
    
    @Autowired
    private List<PaymentStrategy> strategies;
    
    private final Map<PayChannel, PaymentStrategy> strategyMap = new EnumMap<>(PayChannel.class);
    
    @PostConstruct
    public void init() {
        for (PaymentStrategy strategy : strategies) {
            strategyMap.put(strategy.getPayChannel(), strategy);
        }
    }
    
    public PaymentStrategy getStrategy(PayChannel payChannel) {
        PaymentStrategy strategy = strategyMap.get(payChannel);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付渠道: " + payChannel);
        }
        return strategy;
    }
}

9. 统一支付服务

@Service
public class UnifiedPayService {
    
    @Autowired
    private PaymentStrategyFactory strategyFactory;
    
    @Autowired
    private MerchantConfigRepository merchantConfigRepository;
    
    public PayResponse unifiedPay(PayRequest request) {
        // 获取商户配置
        MerchantConfig config = getMerchantConfig(request.getMerchantId(), request.getPayChannel());
        
        if (config == null || !config.getEnabled()) {
            throw new BusinessException("商户配置不存在或已禁用");
        }
        
        // 获取支付策略
        PaymentStrategy strategy = strategyFactory.getStrategy(request.getPayChannel());
        
        // 执行支付
        return strategy.unifiedPay(request, config);
    }
    
    public PayQueryResponse queryOrder(String merchantId, PayChannel payChannel, String orderId) {
        MerchantConfig config = getMerchantConfig(merchantId, payChannel);
        PaymentStrategy strategy = strategyFactory.getStrategy(payChannel);
        return strategy.queryOrder(orderId, config);
    }
    
    public RefundResponse refund(RefundRequest request) {
        MerchantConfig config = getMerchantConfig(request.getMerchantId(), request.getPayChannel());
        PaymentStrategy strategy = strategyFactory.getStrategy(request.getPayChannel());
        return strategy.refund(request, config);
    }
    
    public boolean verifyNotify(String merchantId, PayChannel payChannel, Map<String, String> params) {
        MerchantConfig config = getMerchantConfig(merchantId, payChannel);
        PaymentStrategy strategy = strategyFactory.getStrategy(payChannel);
        return strategy.verifyNotify(params, config);
    }
    
    private MerchantConfig getMerchantConfig(String merchantId, PayChannel payChannel) {
        return merchantConfigRepository.findByMerchantIdAndPayChannelAndEnabledTrue(merchantId, payChannel);
    }
}

9. 对应多商户支付系统数据库设计

9.1 商户配置主表 (merchant_config)

CREATE TABLE merchant_config (
    merchant_id VARCHAR(64) NOT NULL COMMENT '商户ID',
    pay_channel VARCHAR(32) NOT NULL COMMENT '支付渠道',
    enabled TINYINT(1) DEFAULT 1 COMMENT '是否启用',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (merchant_id, pay_channel)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商户配置主表';

9.2 商户配置参数表 (merchant_config_params)

CREATE TABLE merchant_config_params (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
    merchant_id VARCHAR(64) NOT NULL COMMENT '商户ID',
    config_key VARCHAR(128) NOT NULL COMMENT '配置键',
    config_value TEXT COMMENT '配置值',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    INDEX idx_merchant_id (merchant_id),
    INDEX idx_config_key (config_key),
    FOREIGN KEY (merchant_id) REFERENCES merchant_config(merchant_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商户配置参数表';

9.3 配置参数存储方案

1. 微信支付配置参数
    appid: 微信应用ID
    mch_id: 商户ID
    api_v3_key: API V3密钥
    private_key: 私钥路径
    cert_path: 证书路径
    serial_no: 证书序列号
    notify_url: 默认通知地址
2. 支付宝配置参数
    app_id: 应用ID
    private_key: 应用私钥
    alipay_public_key: 支付宝公钥
    gateway_url: 网关地址
    charset: 字符编码
    sign_type: 签名类型
3. 银联支付配置参数
    mer_id: 商户代码
    cert_path: 证书路径
    cert_pwd: 证书密码
    front_trans_url: 前台交易URL
    back_trans_url: 后台交易URL
    single_query_url: 单笔查询URL

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值