业务讲解-支付渠道策略的初始化
概要
在支付路程中往往对接的渠道有很多,除了像常用的支付宝/微信 外,还有银联,翼支付,网银等各种渠道,这么多的渠道怎么管理可是个麻烦,总不能来一个渠道就新加一个if分支吧,这维护起来可相当麻烦了
这时就要借助项目中可以说是最常用的设计模式:策略模式,将统一的支付进行抽象出来,接着不同的渠道相当于不同的策略,每个渠道有自己的支付流程,这样就便于管理,后期如果想增强新的支付渠道的话,只需加一个新的支付策略即可
那么具体是如何实现的呢?话不多说,直接开始讲解
支付策略顶级接口
com.damai.pay.PayStrategyHandler
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
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
@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
@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
@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
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