统一支付接口,无限扩展不同支付服务工具包实现。【开箱即用】(可用于生产环境中)

前言

在实际生产环境中,我们经常面临多个支付厂商的选择和变更,这可能导致支付代码的不断修改和维护。为了更好地应对产品需求的变化,我们采用了策略模式,通过统一的支付接口将各个支付服务进行实现。这样一来,支付厂商的变化对业务代码没有任何影响,使得我们能够更灵活地应对支付服务的变化和新增,而无需频繁修改核心业务逻辑。这种设计不仅提高了代码的可维护性,还为未来的扩展和变更提供了便利。

实现方案

支付 PayService(统一的支付Service方便将来扩展)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.github.ji.service;


import com.github.ji.dto.PayDTO;
import com.github.ji.dto.RefundDTO;
import com.github.ji.enums.PayCode;
import com.github.ji.vo.PayVO;

/**
* 支付 PayService(统一的支付Service方便将来扩展)
*
* @author jishenglong on 2023/3/27 10:02
**/
public interface PayService {


PayCode getPayCode();

/**
* 支付
*
* @param payDTO payDTO
* @return com.github.ji.vo.PayVO
* @author jisl on 2023/11/16 11:18
**/
PayVO pay(PayDTO payDTO);


/**
* 交易是否成功
*
* @param merchantId 门店id
* @param outTradeNo 交易订单号
* @param payAmount 支付金额
*/
Boolean isPaySuccess(Integer merchantId, String outTradeNo, Integer payAmount);

/**
* 退款
*
* @param refundDTO RefundDTO
* @author jishenglong on 2023/4/19 15:23
**/
void refund(RefundDTO refundDTO);

/**
* 退款是否成功
*
* @param outRefundNo 退款单号
* @param refundAmount 退款金额
*/
Boolean isRefundSuccess(Integer merchantId, String outRefundNo, Integer refundAmount);

/**
* 关闭订单
*
* @param outTradeNo 交易订单号
*/
void payClose(Integer merchantId, String outTradeNo);
}

微信支付抽象类

引入抽象类是为了更好地封装微信支付的退款 SDK,尤其是在不同的微信支付方式下,调用方式保持一致。通过这一抽象,我们能够将共同的退款操作逻辑集中到一个代码库中,从而降低了具体实现类的重复程度。这种设计不仅提高了代码的重用性,也使得对微信支付退款的变更更为集中和方便。通过抽象类,我们有效地管理了微信支付的共享代码,简化了具体实现类的维护工作,确保了系统的一致性和可维护性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.github.ji.service.impl;


import com.github.ji.config.MerchantMatchWeChatConfig;
import com.github.ji.config.WxPayProperties;
import com.github.ji.dto.RefundDTO;
import com.github.ji.service.PayService;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;

/**
* 微信支付抽象类(退款是相同的sdk放在这里)
*
* @author jishenglong on 2023/3/27 10:02
**/
@Component
@Slf4j
public abstract class WeChatPayService implements PayService {

@Resource
private MerchantMatchWeChatConfig matchWeChatConfig;


@Resource
private WxPayProperties wxPayProperties;


@Override
public void refund(RefundDTO refundDTO) {
final RefundService refundService = new RefundService.Builder().config(matchWeChatConfig.getCertificateConfig(refundDTO.getMerchantId())).build();
CreateRequest request = new CreateRequest();
request.setOutTradeNo(refundDTO.getOutTradeNo());
request.setOutRefundNo(refundDTO.getOutRefundNo());
request.setNotifyUrl(wxPayProperties.getDomainUrl() + "/payNotify/weChatPay/refundOrder/" + refundDTO.getMerchantId());
final AmountReq amountReq = new AmountReq();
amountReq.setRefund(Long.valueOf(refundDTO.getRefundAmount()));
amountReq.setTotal(Long.valueOf(refundDTO.getAmount()));
amountReq.setCurrency("CNY");
request.setAmount(amountReq);
refundService.create(request);
}

@Override
public Boolean isRefundSuccess(Integer merchantId, String outRefundNo, Integer refundAmount) {
final RefundService refundService = new RefundService.Builder().config(matchWeChatConfig.getCertificateConfig(merchantId)).build();
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
request.setOutRefundNo(outRefundNo);
final Refund refund = refundService.queryByOutRefundNo(request);
return Objects.equals(refund.getStatus(), Status.SUCCESS) && Objects.equals(refund.getAmount().getRefund().intValue(), refundAmount);
}


}

支付厂商实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.github.ji.service.impl;


import com.github.ji.config.MerchantMatchWeChatConfig;
import com.github.ji.config.WxPayProperties;
import com.github.ji.dto.PayDTO;
import com.github.ji.enums.PayCode;
import com.github.ji.service.IMerchantMatchService;
import com.github.ji.vo.MerchantMatchVO;
import com.github.ji.vo.PayVO;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.util.NonceUtil;
import com.wechat.pay.java.service.payments.app.AppService;
import com.wechat.pay.java.service.payments.app.model.Amount;
import com.wechat.pay.java.service.payments.app.model.CloseOrderRequest;
import com.wechat.pay.java.service.payments.app.model.PrepayRequest;
import com.wechat.pay.java.service.payments.app.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.payments.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Instant;
import java.util.Objects;

/**
* 微信APP支付服务实现
*
* @author jishenglong on 2023/3/27 10:02
**/
@Component
@Slf4j
public class WeChatAppPayServiceImpl extends WeChatPayService {

@Resource
private MerchantMatchWeChatConfig matchWeChatConfig;

@Resource
private IMerchantMatchService merchantMatchService;

@Resource
private WxPayProperties wxPayProperties;


@Override
public PayCode getPayCode() {
return PayCode.WX_APP;
}

@Override
public PayVO pay(PayDTO payDTO) {
final MerchantMatchVO merchantMatchVO = merchantMatchService.selectByMerchantId(payDTO.getMerchantId());
final RSAAutoCertificateConfig certificateConfig = matchWeChatConfig.getCertificateConfig(merchantMatchVO);
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(payDTO.getAmount());
request.setAmount(amount);
request.setAppid(wxPayProperties.getAppid());
request.setMchid(merchantMatchVO.getMchId());
request.setDescription(payDTO.getDescription());
request.setNotifyUrl(wxPayProperties.getDomainUrl() + "/payNotify/weChatPay/notifyOrder/" + payDTO.getMerchantId());
request.setOutTradeNo(payDTO.getOutTradeNo());


String prepayId = getAppService(payDTO.getMerchantId()).prepay(request).getPrepayId();
long timestamp = Instant.now().getEpochSecond();
String nonceStr = NonceUtil.createNonce(32);
String message =
request.getAppid() + "\n" + timestamp + "\n" + nonceStr + "\n" + prepayId + "\n";
log.debug("Message for RequestPayment signatures is[{}]", message);
String sign = certificateConfig.createSigner().sign(message).getSign();
PayVO response = new PayVO();
response.setAppid(request.getAppid());
response.setPartnerId(request.getMchid());
response.setPrepayId(prepayId);
response.setPackageVal("Sign=WXPay");
response.setNonceStr(nonceStr);
response.setTimestamp(String.valueOf(timestamp));
response.setPaySign(sign);
return response;
}

private AppService getAppService(Integer merchantId) {
return new AppService.Builder().config(matchWeChatConfig.getCertificateConfig(merchantId)).build();
}


@Override
public Boolean isPaySuccess(Integer merchantId, String outTradeNo, Integer payAmount) {
final QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
request.setMchid(merchantMatchService.selectByMerchantId(merchantId).getMchId());
request.setOutTradeNo(outTradeNo);
final Transaction transaction = getAppService(merchantId).queryOrderByOutTradeNo(request);
return Objects.equals(transaction.getTradeState(), Transaction.TradeStateEnum.SUCCESS) && Objects.equals(transaction.getAmount().getTotal(), payAmount);
}


@Override
public void payClose(Integer merchantId, String outTradeNo) {
final CloseOrderRequest request = new CloseOrderRequest();
request.setMchid(merchantMatchService.selectByMerchantId(merchantId).getMchId());
request.setOutTradeNo(outTradeNo);
getAppService(merchantId).closeOrder(request);
}
}

PayContext

通过创建 PayContext,我们成功将所有支付实现类汇聚到一个中心化的管理器中。这种设计使得支付方式的选择和切换变得更为简洁和方便。业务代码只需与 PayContext 进行交互,而无需直接与具体的支付实现类打交道。这不仅提高了代码的可读性,也使得系统更易于维护和扩展。通过 PayContext,我们实现了一种灵活且高度可扩展的支付体系,使得系统能够轻松适应支付方式的变化,同时确保业务逻辑的清晰和解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.github.ji;


import com.github.ji.service.PayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* 支付方式的PayContext,自动加入到这个PayContext
*/
@Slf4j
@Component
public class PayContext {

private Map<Integer, PayService> payServiceMap;

// spring中,在使用@Autowired注解注入list集合的时候,并不会根据List类型去容器中查找,而是根据list集合的元素类型,从spring容器中找到所有的实现类,放在list集合中,然后注入到bean中
@Resource
private List<PayService> payServiceList;


@PostConstruct
public void buildMap() {
// 将来完善下,可以加入是否禁用等
payServiceMap = payServiceList.stream()
.collect(Collectors.toMap(operationStrategy -> operationStrategy.getPayCode().getCode(), Function.identity()));
}

public PayService getPayService(Integer code) {
if (!payServiceMap.containsKey(code)) {
throw new RuntimeException("没有该支付方式");
}
return payServiceMap.get(code);
}


}

支付调用

通过使用 PayCode 来选择支付方式,我们实现了对不同支付厂商的调用,从而将支付的变化抽象到了一个统一的接口中。这种设计使得业务代码与支付实现之间完全解耦,业务逻辑无需关心具体的支付厂商,只需通过 PayCode 来选择支付方式即可。这不仅提高了代码的可扩展性,使得支付方式的变化对业务代码没有任何影响,而且使系统更容易适应未来的扩展和变更。这种解耦的设计方式为我们提供了更大的灵活性和可维护性,确保了系统的健壮性。

1
2
3
4
5
6
7
8
9
10
11
12
13
@ApiOperation("订单支付")
@PostMapping("/orderPay")
public CommonResult<PayVO> orderPay(@Validated @RequestBody OrderPayDTO orderDTO) {
final PayService payService = payContext.getPayService(orderDTO.getPayCode());
Order order = new Order();
final PayDTO payDTO = PayDTO.builder()
.description(order.getDescription()).outTradeNo(order.getOutTradeNo())
.openid(orderDTO.getOpenid()).amount(order.getAmount())
.payCode(orderDTO.getPayCode()).merchantId(order.getMerchantId())
.build();
final PayVO payVO = payService.pay(payDTO);
return CommonResult.success(payVO);
}

GitHub源码

源码获取:JCommon

总结

通过策略的封装,我们成功将支付功能解耦,摆脱了硬编码的束缚。这意味着不再需要为产品的每一个支付需求而硬编码,从而大大提高了系统的灵活性。现在,我们可以通过简单地切换策略,适应产品支付方式的变化,而不必担心影响到业务代码。这种解耦的设计使得支付模块更为可维护和可扩展,为未来的需求变更提供了更为便捷的途径

统一支付接口,无限扩展不同支付服务工具包实现。【开箱即用】(可用于生产环境中)

http://example.com/2023/11/16/统一支付接口,无限扩展不同支付服务工具包实现。【开箱即用】(可用于生产环境中)/

作者

纪东东

发布于

2023-11-16

更新于

2023-11-16

许可协议