前言
在实际生产环境中,我们经常面临多个支付厂商的选择和变更,这可能导致支付代码的不断修改和维护。为了更好地应对产品需求的变化,我们采用了策略模式,通过统一的支付接口将各个支付服务进行实现。这样一来,支付厂商的变化对业务代码没有任何影响,使得我们能够更灵活地应对支付服务的变化和新增,而无需频繁修改核心业务逻辑。这种设计不仅提高了代码的可维护性,还为未来的扩展和变更提供了便利。
实现方案
支付 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;
public interface PayService {
PayCode getPayCode();
PayVO pay(PayDTO payDTO);
Boolean isPaySuccess(Integer merchantId, String outTradeNo, Integer payAmount);
void refund(RefundDTO refundDTO);
Boolean isRefundSuccess(Integer merchantId, String outRefundNo, Integer refundAmount);
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;
@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;
@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;
@Slf4j @Component public class PayContext {
private Map<Integer, PayService> payServiceMap;
@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
总结
通过策略的封装,我们成功将支付功能解耦,摆脱了硬编码的束缚。这意味着不再需要为产品的每一个支付需求而硬编码,从而大大提高了系统的灵活性。现在,我们可以通过简单地切换策略,适应产品支付方式的变化,而不必担心影响到业务代码。这种解耦的设计使得支付模块更为可维护和可扩展,为未来的需求变更提供了更为便捷的途径