支付系统中的设计模式01:初始需求

互联网的发展极大地方便了人们的日常生活,深刻地改变了各行各业的面貌,尤其是在线支付(包括各种移动支付、指纹支付、刷脸支付等等),在前几年还和「高铁」一起,成了推动和见证中国高速发展的一张名片。现在,不管是买菜、买票、交电费还是电商网购,都已经离不开支付系统了。而且在目前的互联网应用中,不管是开发什么类型的系统,也几乎都会对接(第三方)支付系统实现订单付款。所以,了解支付系统是如何设计和运行的就显得很有必要了。而且,支付系统如果做得很耐撕,那么再去做其他的业务系统的话,技术层面能遇到的挑战就比较少了。

但是,想要设计、开发一个支付系统其实是一件极其困难的事情,因为它涉及到的场景太多太多,需要解决的问题也不计其数。别的不说,光是防黑灰产就够让人头疼的,更别说分布式事务问题、不同系统之间的认证授权问题、利益人之间的清结算、分账、分润问题等等......。

比如像下面这幅图:

图一:某互联网金融公司业务架构图(2018年)

另外,设计模式作为一项大杀招,在工程师的江湖中被「传扬」甚广,但就笔者自身经历而言,真正能把这个葵花宝典用好,用到实际开发项目中的还真不多。而能在支付系统中有目的、有计划、系统地使用设计模式的,在目前互联网中还没有这类公开的内容(各大支付公司的代码里肯定都有这种「祖传」代码,但一是它不可能公布出来,二是谁敢轻易对百万、千万级代码行的系统搞重构?)。

所以,本着「好奇害死猫」的探索精神,笔者在这里献个丑,抛个砖,通过分享自己一点点的思考和浅薄实践,给未来更多优秀的工程师们增添一些学习的材料和前行的铺路石。如果看过之后能有人说「还算有点用」,那也算没白忙活了。

不过,笔者在这里首先需要声明的是:

1、完整的支付系统涉及到的内容极其复杂(即使是图一,在整个大支付系统中也只是九牛一毫),任何人都不可能独自把这些都给实现了,既没能力,更没精力。所以,接下来要做的,就只是结合自己的工作实际把设计模式用到支付系统中;

2、只写核心代码。因为把核心问题讲清楚了,剩余的那些业务需求其实都比较通用。而且像调用支付接口,支付签名等动作,某种程度上都是可以「模板化」的,并没有什么技术含量,所以这里不会涉及到这些内容;

3、也不涉及到到抵御黑灰产的攻击,不涉及到分布式事务相关的内容。因为这两个要讲清楚的话,内容也是巨多,实在不是一个单独的专栏内容可以讲清楚的,后续笔者会接着这个主题继续讲这两部分。

不废话了,开干。

— 1 —

初始

在讲要怎么干之前,还是需要先把业务背景和基础代码讲清楚,不然都不知道是在什么基础上做的。

现在,咱们正在一家新成立不久的母婴用品的电商公司的研发部门工作。由于公司成立之初人少事多,需求迭代快速,所以导致开发工期非常紧张,电商平台的很多功能都相当于是赶工拼凑出来的,不仅问题多多,而且还经了几道手(至少有5位工程师经手开发过支付功能),稳定性、扩展性、易用性和可维护性极差,用「一团乱麻」和「命悬一线」都不为过。

为了解决这些潜在的隐患,老板下定决心要进行一次彻底的重构,一劳永逸地解决上面那些问题,让它能够满足并支撑后续业务肯能会到来的高速发展。而且老板还提出:要把现在的支付功能升格为支付系统,在新功能的开发继续进行的同时,同时还要保质保量地满足公司各种运营的需求,这有点像「给高速行驶中的汽车换轮子」——是在是太难了~

没办法,只能硬着头皮顶上去~

不过在动手之前,要先来看看现在支付功能这部分的「祖传代码」都传了些啥。

首先是账户类和订单类代码:

/**
 * 用户账户
 * 
 * @author 湘王
 */
public class Account {
   // 账户编码
   private String accid = "";
   // 账户押金
   private double deposit = 0;
   // 账户余额
   private double balance = 0;

   public String getAccid() {
      return accid;
   }

   public void setAccid(String accid) {
      this.accid = accid;
   }

   public double getDeposit() {
      return deposit;
   }

   public void setDeposit(double deposit) {
      this.deposit = deposit;
   }

   public double getBalance() {
      return balance;
   }

   public void setBalance(double balance) {
      this.balance = balance;
   }
}
 
/**
 * 用户订单
 * 
 * @author 湘王
 */
public class Order {
   // 订单编码
   private String oid = "";

   // 订单金额
   private double amount = 0;

   public String getOid() {
      return oid;
   }

   public void setOid(String oid) {
      this.oid = oid;
   }

   public double getAmount() {
      return amount;
   }

   public void setAmount(double amount) {
      this.amount = amount;
   }
}

然后是支付配置类:

/**
 * 支付接口的设置
 * 
 * @author 湘王
 */
public class Payload {
   public enum CHANNEL {
      // 余额支付
      ACCOUNT,
      // 支付宝支付
      ALIPAY,
      // 微信支付
      WEIXIN
   }

   // 支付渠道
   private CHANNEL channel;
   // 支付备注
   private String body;
   // 手续费
   private double charge;
   // 交易编码
   private String tradeNo;
   // 回调地址
   private String notifyUrl;

   public CHANNEL getChannel() {
      return channel;
   }

   public void setChannel(CHANNEL channel) {
      this.channel = channel;
   }

   public String getBody() {
      return body;
   }

   public void setBody(String body) {
      this.body = body;
   }

   public double getCharge() {
      return charge;
   }

   public void setCharge(double charge) {
      this.charge = charge;
   }

   public String getTradeNo() {
      return tradeNo;
   }

   public void setTradeNo(String tradeNo) {
      this.tradeNo = tradeNo;
   }

   public String getNotifyUrl() {
      return notifyUrl;
   }

   public void setNotifyUrl(String notifyUrl) {
      this.notifyUrl = notifyUrl;
   }

   @Override
   public String toString() {
      return String.format("{\"body\":\"%s\", "
            + "\"charge\":%f, \"tradeNo\":\"%s\", "
            + "\"notifyUrl\":\"%s\"}", body, 
            charge, tradeNo, notifyUrl);
   }
}

最后是支付类:

/**
 * 支付类
 *
 * @author 湘王
 */
public class Payment {
    private Payload payload;

    /**
     * 支付方法
     */
    protected boolean pay(int type, final Account account, final Order order) {
        // 支付接口的设置
        payload = new Payload();

        // 如果是支付宝
        if (CHANNEL.ALIPAY.ordinal() == type) {
            payload.setChannel(CHANNEL.ALIPAY);
            // 千六手续费
            payload.setCharge(order.getAmount() * 0.006);
            payload.setNotifyUrl("http://localhost:7070");
        }
        // 如果是微信
        if (CHANNEL.WEIXIN.ordinal() == type) {
            payload.setChannel(CHANNEL.WEIXIN);
            // 千六手续费
            payload.setCharge(order.getAmount() * 0.006);
            payload.setNotifyUrl("http://localhost:8080");
        }
        // 如果是余额
        if (CHANNEL.ALIPAY.ordinal() == type) {
            payload.setChannel(CHANNEL.ACCOUNT);
            // 无手续费
            payload.setCharge(0.0);
            payload.setNotifyUrl("http://localhost:9090");
        }
        payload.setBody("订单备注");
        payload.setTradeNo("202001230258863496515");
        System.out.println("您需要支付的金额是:" + order.getAmount());

        // 用余额支付
        if (payload.getChannel() ==CHANNEL.ACCOUNT) {
            // 如果可用余额不足
            if (account.getBalance() < order.getAmount()) {
                return false;
            } else {
                // 从可用余额中扣除
                account.setBalance(account.getBalance() - order.getAmount());
            }
        }
        return true;
    }
}

以上就是目前支付功的基础代码了(为了更聚焦于说明咱们需要解决的问题,排除干扰信息,这里对实际代码略做了些简化,但不影响学习的效果)。

现在,老板提出,要把用户在APP中的余额分成两部分(假设这么做不违规且经用户调研后可行):

1、将账户分为可用余额与押金两部分。如果可用余额不足,就从押金中扣除一部分;

2、如果可用余额+ 押金仍不足以支付,那么支付失败;

3、为了支持产品和运营活动,业务系统在支付前和支付后需要完成不同的工作,例如支付前需要锁定账户,而支付成后要给账户增加积分;

4、另外,架构师说,公司的APP既有自己的钱包,也同时对接了多种不同的第三方接口,而且不同的支付接口(支付宝和微信)、不同的服务(支付和提现),接口的设置可能完全不同,这块的代码需要改善一下。

仔细思考一下需求,可以知道:

功能「1」纯粹属于产品设计的范畴,和技术架构无关。功能「2」属于是业务多了个分支条件,目前看来也和技术改造扯不上关系。而功能「3」,很明显,要做一个面向「切面」的拦截。也就是拦截所有支付请求,在支付钱执行某些动作,然后在支付后再完成另一些动作。

至于功能「4」,因为目前APP的后台是由咱们搭建并开发的,所以理所当然改造的重任就落到了自己肩上。而且,用「if...else」的方式设置不同的支付渠道,在将来系统更新时确实是难免会有一些隐患。而且每次配置属性参数的时候代码都比较丑陋,都是一堆set()方法,可能就像下面这样:

client.setAppID(......);

client.setAppSecret(......);

client.setRequest(......);

client.setPaymentModel(......);

client.setNotifyUrl(......);

......

这种写法确实不够「优雅」。

所以,很明显「3」和「4」就是需要根据业务需求来改造技术架构的点。那么,在GoF经典的23种设计模式中,哪几个匹配「3」和「4」的需求呢?

因为我们希望客户端的设置能够用一行代码搞定,就像这样:

client.setAppID(......).setAppSecret(......).setRequest(......).setPaymentModel(......).setNotifyUrl(......);

可能有的童鞋已经猜到了,它就是构造器(Builder)模式,它可以解决支付配置属性参数的问题,让代码更优雅。

对设计模式稍稍有点了解的小伙伴,可能已经想到了哪个模式比较适用于解决众多的「if...else」——对,就是策略(Strategy)模式——把不同的分支条件放在不同的类中而不是「if...else」条件里,这样至少对修改非常有好处,再也不会出现几个工程师集中修改一个类导致出现代码冲突了。而且从现在的支付代码来看,如果再增加其他支付接口,可能需要重新设置一遍,要改代码,这很不好,而且不符合软件设计中的开闭原则。

至于解决支付前和支付后要执行额外动作的问题,其实就是一个通用模板的问题,这个用模板方法(Template Method)模式就能完美解决。

下面来一个一个来实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值