首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

工廠模式實戰:支付系統多渠道統一接口設計與 Spring 整合最佳實踐

     編輯:Java 綜合教學

工廠模式實戰:支付系統多渠道統一接口設計與 Spring 整合最佳實踐

支付業務常面臨微信、支付寶、銀聯、數字人民幣等多渠道接入,各渠道流程相似但配置參數、簽名算法、報文格式差異巨大。本文以「工廠模式 + 策略模式」結合 Spring 依賴注入,構建可擴展、配置驅動的支付統一網關,附帶完整代碼結構與關鍵實現細節。

一、核心設計思路

  1. 統一接口:CommonPay 定義預下單標準契約,屏蔽渠道差異。
  2. 工廠管理:CommonPayFactory 持有各渠道實例,Spring 初始化時注入。
  3. 配置外部化:渠道參數 (商戶號、密鑰路徑、回調地址) 置於 properties,Spring 注入配置 Bean。
  4. 調用方零依賴:業務代碼僅依賴 CommonPayFactory,獲取渠道實例調用統一接口。

二、統一接口定義

public interface CommonPay {
    /**
     * 支付預處理:生成支付 URL 或提供給支付平台的數據
     * @param param 預支付參數 (訂單號、金額、卡號等)
     * @return 預支付結果 DTO (含表單 HTML、二維碼鏈接等)
     */
    <T> PrepayDto<T> prePay(PrepayParam param) throws Exception;
}

三、工廠類:Spring InitializingBean 校驗

public class CommonPayFactory implements InitializingBean {
    private CommonPay unionPay; // 銀聯
    // private CommonPay wechatPay; // 微信
    // private CommonPay alipay;    // 支付寶

    @Override
    public void afterPropertiesSet() {
        if (unionPay == null) {
            throw new IllegalStateException("未配置任何支付實例");
        }
    }

    public CommonPay getUnionPay() { return unionPay; }
    public void setUnionPay(CommonPay unionPay) { this.unionPay = unionPay; }
    // 其他渠道 getter/setter...
}

四、Spring XML 配置裝配 (亦可用 @Configuration + @Bean)

<beans xmlns="https://www.springframework.org/schema/beans"
       xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="https://www.springframework.org/schema/beans 
       https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 1. 銀聯配置 Bean -->
    <bean id="unionPayConfig" class="com.xxx.union.UnionPayConfig">
        <property name="payUrl" value="${pay.unionpay.payUrl}"/>
        <property name="queryUrl" value="${pay.unionpay.queryUrl}"/>
        <property name="merId" value="${pay.unionpay.merId}"/>
        <property name="signPfxFile" value="${pay.unionpay.signPfxFile}"/>
        <property name="signPfxPwd" value="${pay.unionpay.signPfxPwd}"/>
        <property name="verifySignCertFile" value="${pay.unionpay.verifySignCertFile}"/>
        <property name="payNotifyFrontUrl" value="${pay.unionpay.payNotifyFrontUrl}"/>
        <property name="payNotifyUrl" value="${pay.unionpay.payNotifyUrl}"/>
    </bean>

    <!-- 2. 工廠 Bean,注入渠道實例 -->
    <bean id="commonPayFactory" class="com.xxx.service.pay.CommonPayFactory">
        <property name="unionPay">
            <bean class="com.xxx.service.pay.UnionPay">
                <property name="unionPayConfig" ref="unionPayConfig"/>
            </bean>
        </property>
        <!-- 新增渠道只需在此添加 property -->
    </bean>
</beans>

五、銀聯實現核心邏輯 (節選關鍵點)

public class UnionPay implements CommonPay {
    private UnionPayConfig unionPayConfig;

    @Override
    public <T> PrepayDto<T> prePay(PrepayParam param) throws Exception {
        PrepayDto<String> dto = new PrepayDto<>();
        Map<String, String> params = new TreeMap<>(); // TreeMap 保證鍵有序,利於簽名

        // 1. 填充銀聯固定業務參數 (版本、編碼、簽名方法、交易類型、渠道類型...)
        params.put("version", UnionPayConfig.VERSION);
        params.put("encoding", UnionPayConfig.CHARSET);
        params.put("signMethod", "01"); // RSA
        params.put("txnType", "01");    // 消費
        params.put("txnSubType", "01"); // 自助消費
        params.put("bizType", "000201"); // B2C 網關支付
        params.put("channelType", "07"); // PC/平板

        // 2. 填充商戶動態參數
        params.put("merId", unionPayConfig.getMerId());
        params.put("accessType", "0");
        params.put("orderId", param.getOutTradeNo()); // 商戶訂單號
        params.put("txnTime", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
        params.put("accNo", param.getCardNumber()); // 卡號 (可選)
        params.put("currencyCode", "156"); // 人民幣
        params.put("txnAmt", param.getTotalFee()); // 金額 (分)
        params.put("reserved", "{cardNumberLock=1}"); // 透傳字段

        // 3. 通知地址
        params.put("frontUrl", unionPayConfig.getPayNotifyFrontUrl()); // 前台通知
        params.put("backUrl", unionPayConfig.getPayNotifyUrl());       // 後台通知

        // 4. 簽名 (SHA-1 摘要 + RSA 私鑰簽名 + Base64)
        sign(params);

        // 5. 生成自動提交表單 HTML
        String html = createAutoFormHtml(unionPayConfig.getPayUrl(), params, UnionPayConfig.CHARSET);
        dto.setResp(html);
        dto.setOutTradeNo(param.getOutTradeNo());
        return (PrepayDto<T>) dto;
    }

    private void sign(Map<String, String> params) {
        params.put("certId", unionPayConfig.getCertId());
        String reqStr = PayUtil.mapToQueryStr(params, UnionPayConfig.CHARSET, false, true);
        byte[] signDigest = SHA1Util.sha1X16(reqStr, UnionPayConfig.CHARSET);
        byte[] byteSign = RSA.signBySoft(unionPayConfig.getPrivateKey(), signDigest);
        params.put("signature", Base64.encode(byteSign));
    }

    private String createAutoFormHtml(String url, Map<String, String> hiddens, String charset) {
        StringBuilder sb = new StringBuilder();
        sb.append("<html><head><meta charset=\"").append(charset).append("\"/></head><body>");
        sb.append("<form id=\"pay_form\" action=\"").append(url).append("\" method=\"post\">");
        hiddens.forEach((k, v) -> sb.append("<input type=\"hidden\" name=\"").append(k)
            .append("\" value=\"").append(v).append("\"/>"));
        sb.append("</form><script>document.getElementById('pay_form').submit();</script></body></html>");
        return sb.toString();
    }
}

六、業務調用方代碼

@Service
public class OrderService {
    @Autowired CommonPayFactory payFactory;

    public String createPayForm(Long orderId, String tradeNo, String cardNo, BigDecimal amount) {
        CommonPay unionPay = payFactory.getUnionPay(); // 獲取渠道實例
        PrepayParam param = new PrepayParam();
        param.setOrderId(orderId);
        param.setOutTradeNo(tradeNo);
        param.setCardNumber(cardNo);
        param.setTotalFee(amount.multiply(new BigDecimal(100)).longValue() + ""); // 元轉分

        try {
            PrepayDto dto = unionPay.prePay(param);
            return dto.getResp(); // 直接返回 HTML,前端渲染跳轉
        } catch (Exception e) {
            log.error("銀聯預下單失敗", e);
            throw new BizException("支付通道暫時不可用");
        }
    }
}

七、擴展新渠道 (如微信支付) 只需三步

  1. 實現 CommonPay 接口:WechatPay。
  2. 創建配置類 WechatPayConfig 並加入 Spring 配置。
  3. 在 CommonPayFactory 添加 wechatPay 屬性及 getter/setter,XML 中注入新 Bean。

調用方代碼零修改,僅需 payFactory.getWechatPay() 即可切換渠道。

八、總結

「工廠模式管理實例 + 策略模式封裝算法 + Spring IOC 裝配配置」構建了清晰、可擴展、可測試的支付網關架構。核心優勢:

  • 單一職責:各渠道邏輯隔離在獨立實現類。
  • 開放封閉:新增渠道不修改現有代碼,只擴展。
  • 配置驅動:參數外部化,環境切換零代碼改動。
  • 統一契約:業務層面向接口編程,渠道切換透明。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved