前段时间对接了微信支付,于是乎,从网上找了一下别人写过的一顿copy后,修修改改终于实现完成了。
本以为万事大吉, 但是项目经理review代码时候,发现我写的支付功能和系统业务功能高度耦合, 搅和在一起结果就扣了我一部分绩效(mmp…) 。
为了避免以后扣绩效,所以决定研究一下,怎么设计支付接口比较合理。末尾附上 git传送门代码
1. if 编码方式
支付渠道暂时虽然只有微信,但是保不齐后面再加支付宝,银联啥的。到时候,代码就会像这样
if (payType.equals ("微信")) { //dosomething }else if (payType.equals ("支付宝")) { //dosomething }else if(payType.equals ("银联")) { //dosomething }
每多一个支付渠道,改动的地方包括:支付接口、支付配置、退款、统计业务。
2.聚合支付
万能的百度上搜索了一下对接多个系统的方案(参考末尾的链接),其中聚合支付的方案比较合理。虽然可能用不到其中一部分的功能,比如结算功能。
1) 什么是聚合支付呢?
说白了就是一个项目接入了多个支付渠道,而且能够使用任意一个渠道进行支付、退款等操作,而且任何渠道之间没有任何关系,彼此不会互相干扰。
2) 简单梳理一下聚合支付的业务
•需要对接多个支付渠道
• 所有的支付能够兼容任意渠道
• 所有的退款能够兼容任何渠道
• 任何渠道都能需要独立进行配置
• 任何渠道都有统计功能
• 渠道之间能够无缝进行切换(比如某个渠道奔溃了,能够切换到其他渠道)
如果想满足上面的功能,又不影响原有的业务的情况下,就需要将原有的支付模块独立抽离开来,单独作为一个服务,也就是聚合支付,凡是项目里面的任何支付、退款、查询、统计等都要通过聚合支付来处理。
3) 如何设计
设计模式是面试时候经常问的,那么,大胆地使用合理的设计模式对功能进行设计吧!
工厂模式: 每个支付渠道可以看成一个工厂
适配器模式: 不同的支付渠道使用的API,参数或者返回结果都可能不一样
策略模式: 根据支付类型创建对应的支付通道
3.工厂模式
1) 创建一个支付的统一接口 , 这里列举几个接口
/** * 支付接口, 所有支付类的接口,系统所有支付功能类都需要实现它; */ public interface Pay { /** * 下单 */ DoOrderVo doOrder(DoOrderSo so); /** * 支付或者退款通知 */ PayNotifyVo payNotify(PayNotifySo so); /** * 查询退款 */ QueryRefundVo queryRefund(QueryRefundSo so); }
2) 支付方式枚举类
/** * 支付类型枚举类 * <p> * 每增加一种支付渠道,需要同时在工厂类{@link PayFactory}里面配置 * </p> */ public enum PayWayEnum { /** * WX_APP */ WX_APP("WX_APP"), /** * WX_NATIVE */ WX_NATIVE("WX_NATIVE"), /** * ALI_APP */ ALI_APP("ALI_APP"), /** * ALI_WEB */ ALI_WEB("ALI_WEB"); PayWayEnum(String key) { } }
3) 实现类
支付渠道有微信,支付宝,银联或者第三方支付等, 而微信又有Native支付,native支付,手机支付等方式….
使用工厂方式可以根据支付方式枚举出来具体实现, 结构如下
图一 支付接口
4) 工厂获取实例对象 (又要用if…或者switch….,不过后面再优化….)
@Service public class PayFactory { //根据类型获取结果处理的实现类 public Pay getPayImpl(PayWayEnum payWayEnum) { Assert.notNull(payWayEnum, "付款渠道不能为空"); if (payWayEnum.equals(PayWayEnum.WX_APP)) { return new WxPayAppImpl(); } else if (payWayEnum.equals(PayWayEnum.WX_NATIVE)) { return new WxPayNativeImpl(); } else if (payWayEnum.equals(PayWayEnum.ALI_APP)) { return new AliPayAppImpl(); } else if (payWayEnum.equals(PayWayEnum.ALI_WEB)) { return new AliPayWebImpl(); } else { return null; } } }
4.适配器模式
1) 多个实现Pay接口问题
图一里面,通过观察发现:
①4个实现类都统一实现了pay的接口,如果再在pay接口中增加一个接口void doAction(),那么4个接口都得重新加上
②微信支付的API有些是通用的,比如统一下单,签名和验签,没必要在每个实现类里面都加上
2) 接口适配器模式 (缺省适配器模式)
适用场景: 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
微信支付抽象类:
/** * 微信支付底层抽象类 * <p>为对接第三方支付接口的支付抽象类,需要实现第三方支付接口的所有API交互,为支付功能类提供功能方法</p> * <p>每一种支付方法,都可以继承该抽象类,并拥有自己的独立的支付流程,</p> */ public abstract class WxPay implements Pay { //=============================下面是支付的业务功能接口================== @Override public WxDoOrderVo doOrder(DoOrderSo so) { //子类实现 return null; } @Override public PayNotifyVo payNotify(PayNotifySo so) { //子类实现 return null; } @Override public QueryRefundVo queryRefund(QueryRefundSo so) { //子类实现 return null; } //=============================下面是微信支付的基础API和相关方法================== /** * 统一下单接口 * * @param so */ public WxUnifiedOrderVo unifiedOrder(WxUnifiedOrderSo so) { System.out.println("WxPay->unifiedOrder :" + so.toString()); /** * 这里调用微信支付API 发送下单请求,返回二维码链接等信息 , */ // 创建签名 createSign(so.toString()); //发送请求 String resultXml = PaymenUtils.doPost("www.weixinpay/unifiedOrder", so.toString()); //解析结果成实体,并返回 WxUnifiedOrderVo wxUnifiedOrderVo = PaymenUtils.parseWxUnifiedOrderResult(resultXml); return wxUnifiedOrderVo; } /** * 生成签名 */ public void createSign(String params) { System.out.println("WxPay->createSign :" + params); } /** * 验证签名 */ public void checkSign(String params) { System.out.println("WxPay->checkSign :" + params); } }
微信Native支付方式实现类 (假如我只想使用native下单,其他功能不需要,只需要重写一下doOrder方法,其他的不需要重写)
@Service public class WxPayNativeImpl extends WxPay { @Override public WxDoOrderVo doOrder(DoOrderSo so) { System.out.println("------微信-APP方式-------"); //调用统一下单逻辑 WxUnifiedOrderSo unifiedOrderSo = new WxUnifiedOrderSo(); WxUnifiedOrderVo unifiedOrderVo = super.unifiedOrder(unifiedOrderSo); WxNativeDoOrderVo wxNativeDoOrderVo = new WxNativeDoOrderVo(); wxNativeDoOrderVo.setNativeFlag("------nativeflag------"); wxNativeDoOrderVo.setWxUnifiedOrderVo(unifiedOrderVo); return wxNativeDoOrderVo; } }
接口设计如下图
5.策略模式
1) 策略模式的定义
策略模式是对算法的包装,把使用算法的责任和算法本身分隔开,委派给不同的对象管理。策略模式通常把一系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。
2) PayFactory 工厂优化
前面工厂类,是根据if判断各种支付类型来new xxxPayImpl() 实例创建对象,
根据类型来实例化不同的对象,可以看做是多种实现策略,可以使用策略模式来优化一下;
3) 建立支付方式->实现类的对应关系
/** * 具体支付方式的配置 * key 表示支付方式, * value 表示支付具体实现类,** 注意这里类名小写 */ public static final Map<PayWayEnum, String> PAY_MAP = new HashMap<>(8); static { PAY_MAP.put(PayWayEnum.WX_APP, "wxPayAppImpl"); PAY_MAP.put(PayWayEnum.WX_NATIVE, "wxPayNativeImpl"); PAY_MAP.put(PayWayEnum.ALI_APP, "aliPayAppImpl"); PAY_MAP.put(PayWayEnum.ALI_WEB, "aliPayWebImpl"); }
4) Spring实例化bean(而不是手动new xxx)
spring工具类
/** * 直接通过Spring 上下文获取SpringBean,用于多线程环境 */ @Component public class SpringContextUtil implements ApplicationContextAware { // Spring应用上下文环境 private static ApplicationContext applicationContext; /** * 实现ApplicationContextAware接口的回调方法。设置上下文环境 */ @Override public void setApplicationContext(ApplicationContext applicationContext) { SpringContextUtil.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() { return applicationContext; } /** * 获取对象 * * @param name * @return Object * @throws BeansException */ public static Object getBean(String name) throws BeansException { return applicationContext.getBean(name); } public static void main(String[] args) { //具体使用: // AliPay aliPayImpl =(AliPay) SpringContextUtil.getBean("aliPayImpl"); // aliPayImpl.pay(); } }
根据类型获取支付渠道的实现类
//根据类型获取结果处理的实现类 public Pay getPay(PayWayEnum payWayEnum) { Assert.notNull(payWayEnum, "付款渠道不能为空"); return (Pay) SpringContextUtil.getBean(PAY_AFTER_MAP.get(payWayEnum)); }
6.Controller调用
1) 请求接口
@Autowired
private PayFactory payFactory;
@GetMapping("/hellopay/{typeEnum}") public String hello(@PathVariable("typeEnum") String typeEnumStr) { PayWayEnum typeEnum = PayWayEnum.valueOf(typeEnumStr); //由工厂获取具体的pay实现类 Pay pay = payFactory.getPay(typeEnum); System.out.println("pay:" + pay); DoOrderSo doOrderSo = new DoOrderSo(); doOrderSo.setOrderNo("下单的单号OrderNo_00001"); doOrderSo.setTradeNo("下单的外部单号TradeNo_111111"); //获取处理结果,这里实际转换成了具体的结果实现类 DoOrderVo doOrderVo = pay.doOrder(doOrderSo); // 这里其实是具体的vo System.out.println("调用doOrder返回结果:doOrderVo :" + doOrderVo); return null; }
说明: 这里的xxxSo 表示参数, xxxVo表示返回值, (可以自行定义), So和So之间存在父子关系,Vo和Vo之间也存在父子关系,
这样设计主要是: 方便支付的API处理逻辑有一个统一的返回,然后再交给系统进行DB等业务处理~
2) 测试请求
http://localhost:8088/demo/testpay/hellopay/WX_NATIVE
http://localhost:8088/demo/testpay/hellopay/ZFB_WEB
请求会看到不同的效果,(ZFB_WEB的实现类需要按照上面那样写一点逻辑就可以看到效果)
7.支付结果后处理
DoOrderVo 是调用支付的API后,统一处理的实体,我们需要根据不同的类型,转发到不同的后处理service的具体业务实现类中,
主要是在系统中记录一些DB信息和订单等信息,仿照上面的接口设计,
//工厂获取支付后处理的实现类 PayAfter payAfter = payFactory.getPayAfter(typeEnum); System.out.println("payAfter:" + payAfter); payAfter.doOrderAfter(doOrderVo);
PayAfter是后处理统一接口,doOrderAfter是doOrder支付接口的后处理逻辑;
8.调用效果
请求:
http://localhost:8088/demo/testpay/hellopay/WX_NATIVE
http://localhost:8088/demo/testpay/hellopay/ALI_WEB
结果:
git传送门代码: https://github.com/ColoZhu/paydemo
参考:
https://blog.csdn.net/think2017/article/details/79820786?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param ,
https://www.cnblogs.com/lyc94620/p/13055116.html ,