需求
- 实现一个聚合支付平台,拥有多种支付通道(直连,其他第三方公司平台间连),拥有多种支付方式(支付宝,微信,银联,京东钱包,百度钱包等),多种支付接口(扫码支付,预下单,查询,撤销,退款,代收代付等),多种支付场景(H5,App,刷卡,扫码),并且可以在后台动态切换支付通道,而对调用者透明。支付平台提供多种接入方式,如socket(ISO8583),http,webservice等。
架构思考
对外提供的入口
多种对外提供的入口应转换成统一的对象接入平台,转换的方法应由各接入方式模块自行实现,对平台内部接口透明,平台不关心接入方式。这部分很少会有什么变动,模块化即可。
对支付场景处理的思考
- 不同支付场景需要统一的接口,比如查询,退款等,因此支付场景需要在业务实现类中进行判断处理。某些不同支付场景共用的支付接口(如统一预下单)需要调用者上送支付场景字段。
关于内部接口对象的思考
方式一:抽象成统一的对象
- 旧平台的做法
优点
- 简单,清晰,所有的数据都封装在同一个对象中,有利于降低开发难度。
缺点
- 不同平台不同业务不同长度的字段强行塞到一起导致对象庞大(可通过将无共性的业务对象封装到Map中解决,但是会导致内存占用变大问题)。
- 数据对象不能放业务逻辑,否则会导致难以维护。
方式二:根据支付接口抽象对象(推荐)
- 对象实现统一的接口,并且分为扫码支付对象,预下单对象,查询对象,撤销对象,退款对象等。
优点
- 支付接口业务一致,所需要的业务参数个数类型基本一致。
- 有利于提供多渠道共用的接口,强化平台无关性概念。
- 支付业务已经成熟,未来应该很少会新增实现类。
缺点
- 多种通道多种渠道的实现,签名,公共参数,参数的意义会不一致(比如一个平台只需要商户号,而另一个平台只需要Appid)。
- 新增支付平台会可能会造成现有对象的改动,长期的改动下来同样会造成大量冗余,歧义字段。
方式三:根据支付通道+支付方式(即根据平台)抽象对象
优点
- 对于第三方公司平台间连,一般会提供多渠道统一的接口,并且其需要的公有参数验签方式风格应该会保持一致。
- 某个平台的参数变动会局限在平台内部,不会造成对其他平台的影响。
缺点
- 不同支付接口需要的业务参数不一致(同样可通过Map,或再抽象一层业务参数对象来避免)。
内部接口到业务实现类之间结构思考
方式一:普通模式
- 即在每个字段区分接口做判断
优点
- 实现简单并且可以精确控制每条业务路线。
缺点
- m个支付通道,n个支付方式,共需m*n个判断。
- 随便加什么都要改代码,只适合简单场景(原平台),已经无法满足目前的需求。
方式二:抽象工厂方式
- 使用抽象工厂创建最终的实现类,并且使用门面模式包装具体实现方法,对外只提供一个方法,由此方法将请求路由到各种交易接口中。
优点
- 可以方便的增加支付方式,而不需要改动现有代码,符合开闭原则。
缺点
- 抽象工厂共有的缺陷,产品族无法方便的添加,添加产品族(支付通道)需要修改抽象工厂接口(增加一个方法),其所有实现工厂都要改动。
- 门面需要对各个支付接口进行switch操作,并且增加新支付接口时需对门面修改。
- 创建实现工厂时同样需要switch,此缺陷可通过配合简单工厂解决,但是添加支付方式后同样需要修改代码实现。
方式三:简单工厂 + 反射机制(推荐)
- 调用者将请求渠道,交易接口传上来(类似于目前支付宝接口参数,如wechat.query代表微信查询请求),加上平台获取的支付通道参数,在简单工厂中拼接成类路径,直接将交易实现类反射出来,另外可使用缓存缓存对象信息,提高效率。
优点
- 实现简单,只需要一个简单工厂即可实现。
- 符合开闭原则,只要实现类按规定命名包和类,不需要改动任何代码即可添加任意实现。
缺点
- 反射效率问题,可以通过缓存解决。(考虑使用google工具包)
- 未来的所有增加都必须按既定的规则命名。(可以考虑将所有对应关系存入数据库,并使用缓存,通过参数直接反射到任意数据库记录的类)