Skip to content

业务讲解-支付渠道策略的初始化

概要

在支付路程中往往对接的渠道有很多,除了像常用的支付宝/微信 外,还有银联,翼支付,网银等各种渠道,这么多的渠道怎么管理可是个麻烦,总不能来一个渠道就新加一个if分支吧,这维护起来可相当麻烦了

这时就要借助项目中可以说是最常用的设计模式:策略模式,将统一的支付进行抽象出来,接着不同的渠道相当于不同的策略,每个渠道有自己的支付流程,这样就便于管理,后期如果想增强新的支付渠道的话,只需加一个新的支付策略即可

那么具体是如何实现的呢?话不多说,直接开始讲解

支付策略顶级接口

com.damai.pay.PayStrategyHandler

java
public interface PayStrategyHandler {
    /**
     * 支付
     * @param outTradeNo 订单号
     * @param price 支付价格
     * @param subject 标题
     * @param notifyUrl 回调地址
     * @param returnUrl 支付后返回地址
     * @return 结果
     * */
    PayResult pay(String outTradeNo, BigDecimal price, String subject, String notifyUrl, String returnUrl);
    
    /**
     * 验签
     * @param params 参数
     * @return 结果
     * */
    boolean signVerify(Map<String, String> params);
    
    /**
     * 数据验证
     * @param params 参数
     * @param payBill 支付账单
     * @return 结果
     * */
    boolean dataVerify(Map<String, String> params, PayBill payBill);
    
    /**
     * 状态查询
     * @param outTradeNo 订单号
     * @return 结果
     * */
    TradeResult queryTrade(String outTradeNo);
    
    /**
     * 支付渠道
     * @return 结果
     * */
    String getChannel();
}

此接口统一定义了执行的方法,有支付、验签、状态查询等方法。getChannel()方法是支付渠道的分类

支付渠道

com.damai.enums.PayChannel

java
public enum PayChannel {
    /**
     * 支付渠道
     * */
    ALIPAY(1,"alipay","支付宝"),
    
    WX(2,"wx","微信"),
    ;

    private Integer code;
    
    private String value;

    private String msg;

    PayChannel(Integer code, String value, String msg) {
        this.code = code;
        this.value = value;
        this.msg = msg;
    }

    //get set省略...
}

支付渠道是在PayChannel枚举中保存管理,如果后续以后有新的支付渠道,直接追加即可

支付宝的策略实现

com.damai.pay.alipay.AlipayStrategyHandler

java
@Slf4j
@AllArgsConstructor
public class AlipayStrategyHandler implements PayStrategyHandler {

    /**
     * 支付宝的SDK
     * */
    private final AlipayClient alipayClient;
    
    /**
     * 支付宝相关配置
     * */
    private final AlipayProperties aliPayProperties;
    
    @Override
    public PayResult pay(String outTradeNo, BigDecimal price, String subject, String notifyUrl, String returnUrl){
        try {
            AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
            //异步接收地址,仅支持http/https,公网可访问
            request.setNotifyUrl(notifyUrl);
            //同步跳转地址,仅支持http/https
            request.setReturnUrl(returnUrl);
            //必传参数
            JSONObject bizContent = new JSONObject();
            //商户订单号,商家自定义,保持唯一性
            bizContent.put("out_trade_no", outTradeNo);
            //支付金额,最小值0.01元
            bizContent.put("total_amount", price);
            //订单标题,不可使用特殊符号
            bizContent.put("subject", subject);
            //电脑网站支付场景固定传值FAST_INSTANT_TRADE_PAY
            bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
            request.setBizContent(bizContent.toString());
            AlipayTradePagePayResponse response = alipayClient.pageExecute(request,"POST");
            return new PayResult(response.isSuccess(),response.getBody());
        }catch (Exception e) {
           log.error("alipay pay error",e);
           throw new DaMaiFrameException(BaseCode.PAY_ERROR);
        }
    }
    
    @Override
    public boolean signVerify(final Map<String, String> params) {
        try {
            return AlipaySignature.rsaCheckV1(
                    params,
                    aliPayProperties.getAlipayPublicKey(),
                    AlipayConstants.CHARSET_UTF8,
                    //调用SDK验证签名
                    AlipayConstants.SIGN_TYPE_RSA2);
        }catch (Exception e) {
            log.error("alipay sign verify error",e);
            return false;
        }
        
    }
    
    @Override
    public boolean dataVerify(final Map<String, String> params, PayBill payBill) {
        //2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)
        BigDecimal notifyPayAmount = new BigDecimal(params.get("total_amount"));
        BigDecimal payAmount = payBill.getPayAmount();
        if (notifyPayAmount.compareTo(payAmount) != 0) {
            log.error("回调金额和账单支付金额不一致 回调金额 : {}, 账单支付金额 : {}",notifyPayAmount,payAmount);
            return false;
        }
        //3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方
        String notifySellerId = params.get("seller_id");
        String alipaySellerId = aliPayProperties.getSellerId();
        if (!notifySellerId.equals(alipaySellerId)) {
            log.error("回调商户pid和已配置商户pid不一致 回调商户pid : {}, 已配置商户pid : {}",notifySellerId,alipaySellerId);
            return false;
        }
        //4 验证 app_id 是否为该商户本身
        String notifyAppId = params.get("app_id");
        String alipayAppId = aliPayProperties.getAppId();
        if(!notifyAppId.equals(alipayAppId)){
            log.error("回调appId和已配置appId不一致 回调appId : {}, 已配置appId : {}",notifyAppId,alipayAppId);
            return false;
        }
        //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时,支付宝才会认定为买家付款成功
        String tradeStatus = params.get("trade_status");
        if(!AlipayTradeStatus.TRADE_SUCCESS.getValue().equals(tradeStatus)){
            log.error("支付未成功 tradeStatus : {}",tradeStatus);
            return false;
        }
        return true;
    }
    
    @Override
    public TradeResult queryTrade(String outTradeNo) {
        String successCode = "10000";
        String successMsg = "Success";
        TradeResult tradeResult = new TradeResult();
        tradeResult.setSuccess(false);
        try {
            //构建查询参数,将订单号放入,调用SDK查询
            AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no", outTradeNo);
            request.setBizContent(bizContent.toString());
            AlipayTradeQueryResponse response = alipayClient.execute(request);
            if (response.isSuccess()) {
                JSONObject jsonResponse = JSON.parseObject(response.getBody());
                JSONObject alipayTradeQueryResponse = jsonResponse.getJSONObject("alipay_trade_query_response");
                String code = alipayTradeQueryResponse.getString("code");
                String msg = alipayTradeQueryResponse.getString("msg");
                //如果调用成功
                if (successCode.equals(code) && successMsg.equals(msg)) {
                    tradeResult.setSuccess(true);
                    //订单编号
                    tradeResult.setOutTradeNo(alipayTradeQueryResponse.getString("out_trade_no"));
                    //支付金额
                    tradeResult.setTotalAmount(new BigDecimal(alipayTradeQueryResponse.getString("total_amount")));
                    //账单状态,需将支付的状态转换为对应的支付服务中账单状态
                    tradeResult.setPayBillStatus(convertPayBillStatus(alipayTradeQueryResponse.getString("trade_status")));
                    return tradeResult;
                }
            }
        }catch (Exception e) {
            log.error("alipay trade query error",e);
        }
        return tradeResult;
    }
    
    @Override
    public String getChannel() {
        return PayChannel.ALIPAY.getValue();
    }
    
    /**
     * 转换账单状态
     * */
    private Integer convertPayBillStatus(String tradeStatus){
        if (AlipayTradeStatus.WAIT_BUYER_PAY.getValue().equals(tradeStatus)) {
            return PayBillStatus.NO_PAY.getCode();
        } else if (AlipayTradeStatus.TRADE_CLOSED.getValue().equals(tradeStatus)) {
            return PayBillStatus.CANCEL.getCode();
        } else if (AlipayTradeStatus.TRADE_SUCCESS.getValue().equals(tradeStatus) || 
                AlipayTradeStatus.TRADE_FINISHED.getValue().equals(tradeStatus)) {
            return PayBillStatus.PAY.getCode();
        }
        throw new DaMaiFrameException(BaseCode.ALIPAY_TRADE_STATUS_NOT_EXIST);
    }
}

AlipayStrategyHandler是对支付宝策略的实现,封装了支付宝的SDKAlipayClient,注入了支付宝支付的配置信息AlipayProperties

AlipayProperties

java
@Data
@ConfigurationProperties(prefix = AlipayProperties.PREFIX)
public class AlipayProperties {
    
    public static final String PREFIX = "alipay";
    
    /**
     * 应用ID
     * */
    private String appId;
    
    /**
     * 商户PID
     * */
    private String sellerId;
    
    /**
     * 支付宝网关
     * */
    private String gatewayUrl;
    
    /**
     * 商户私钥,您的PKCS8格式RSA2私钥
     * */
    private String merchantPrivateKey;
    
    /**
     * 支付宝公钥,查看地址:<a href="https://openhome.alipay.com/platform/keyManage.htm"/> 对应APPID下的支付宝公钥
     * */
    private String alipayPublicKey;
    
    /**
     * 接口内容加密秘钥,对称秘钥
     * */
    private String contentKey;
    
    /**
     * 页面跳转同步通知页面路径
     * */
    private String returnUrl;
    
    /**
     * 支付宝异步回调接口  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
     * */
    private String notifyUrl;
}

AlipayProperties是配置信息,封装了支付宝需要的所有配置信息

支付策略处理器的加载

com.damai.pay.PayStrategyInitHandler

java
@AllArgsConstructor
public class PayStrategyInitHandler extends AbstractApplicationInitializingBeanHandler {
    
    private final PayStrategyContext payStrategyContext;
    
    @Override
    public Integer executeOrder() {
        return 1;
    }
    
    @Override
    public void executeInit(ConfigurableApplicationContext context) {
        Map<String, PayStrategyHandler> payStrategyHandlerMap = context.getBeansOfType(PayStrategyHandler.class);
        for (Entry<String, PayStrategyHandler> entry : payStrategyHandlerMap.entrySet()) {
            PayStrategyHandler payStrategyHandler = entry.getValue();
            payStrategyContext.put(payStrategyHandler.getChannel(),payStrategyHandler);
        }
    }
}

通过使用初始化组件damai-service-initialize,继承AbstractApplicationInitializingBeanHandler,即可在项目启动后加载此executeInit方法,executeOrder为最先执行

执行的时候从Spring上下文中获取PayStrategyHandler类型的策略集合,接着通过 getChannel() 来将不同的策略分类,把相应的渠道支付策略添加到上下文PayStrategyContext

支付策略管理上下文

com.damai.pay.PayStrategyContext

java
public class PayStrategyContext {
    
    private final Map<String,PayStrategyHandler> payStrategyHandlerMap = new HashMap<>();
    
    public void put(String channel,PayStrategyHandler payStrategyHandler){
        payStrategyHandlerMap.put(channel,payStrategyHandler);
    }
    
    public PayStrategyHandler get(String channel){
        return Optional.ofNullable(payStrategyHandlerMap.get(channel)).orElseThrow(
                () -> new DaMaiFrameException(BaseCode.PAY_STRATEGY_NOT_EXIST));
    }
}

PayStrategyContext中是靠Map结构的payStrategyHandlerMap来管理,key为支付渠道类型,value为具体的支付实现策略,在上述的初始化方法executeInit中,就会调用支付策略管理上下文的put方法,将所有支付策略添加到payStrategyHandlerMap

当涉及到调用时,直接调用PayStrategyContext.get,通过传入的渠道类型channel,找到对应的支付策略即可

更新: 2025-10-13 11:22:46
原文: https://www.yuque.com/u22210564/ykdrdh/gy0g15cn2o0crnyh

Java 后端面试知识库