单体应用中的通用支付服务设计

  1. 背景
  • 支付分析
  • 接口设计
  • 结尾
  • 背景

    相较于微服务架构应用通常会有支付服务(交易结算中心),单体应用里大多是一个支付类,甚至有些只有一个支付方法。当新增业务需要有支付场景时,则难以使用已有的支付。大多数情况是再增加一个支付类或方法(一般很少会去做兼容重构的,原因有很多)。所以在开始设计支付类时我们应该有预见性的遵循开闭幕式的设计思想。去做一个对外扩展,对内修改关闭的服务。

    支付分析

    按支付支付步骤,1,获取商品信息,2,按要求格式组装支付必要数据,3,请求第三方支付接口。4,处理回调
    
    我们要把支付做成通用的,就要有入参,和出参的概念。所以不能把上面的1,2,3步骤放在一个接口函数中实现。
    

    接口设计

        这里很简单。因为是考虑到未来不同的业务支付需求。那要支付的商品信息则肯定不在一张表中,所以获取商品信息要按业务单独抽取出来。不同的支付方式,也有不同的支付参数,所以也要抽取来。有了前两步后,向第三放发起请求则是一个通用方法了。下面用简单的伪代码说明(写完才发现代码还是有点多啊,没怎么写过博客,总怕写了别人看不懂😂。就多写了点。不喜欢看代码的,看到这里就可以拉到结尾了)
    
    class PaymentService extends BaseService
    {
    
        private $orderId;
    
        private $postDate = [];
    
        private $language;
    
        private $service;
    
    
    /**
         * 支付
         * @param $data
         * @param null $language
         * @return array
         */
        public function goPay($data)
        {
            $this->orderId = $data['orderId'];
            $payWay = $data['payWay'];//支付方式(支付宝,微信,信用卡等)
            $orderType =$data['orderType'];//订单类型(哪一种业务)
    
            //校验业务类型
            if (!isset(Payment::$orderType[$orderType])){
                return $this->retArray(-1, [], '订单类型不存在');
            }
            //获取该业务类型订单。这里使用可变函数(通过变量名获取对应处理函数)减少if else判断语句
            $order = $this->{Payment::$orderType[$orderType]}();  
            if (!$order) {
                return $this->retArray(-1, [], '订单不存在');
            }
            //获取支付方式
            if (!isset(Payment::$orderPay[$payWay])){
                return $this->retArray(-1, [], '支付方式不存在');
            }
            //一些参数的特殊处理
                    。。。
         
    
            //支付所需的参数   根据实际情况来
            $this->postDate = [
                'uuid' => $order->uuid,
                'order_type' => $orderType,
                'order_num' => $order->order_num,
                'pay_way' =>  $payWay,
                'pay_status' =>  0,
                'total_fee' =>  $payMoney,
                'amounts' =>   $order->amounts,
                'ip' =>   $data['ip'],
                'platform' => $data['platform'],
                'body' => $body,
                'subject' => $subject,
                ];
    
            // 创建业务支付单.很多公司是没有这一步的。直接拿订单号支付。这个看具体情况吧。好点的做法是有支付记录表的以后有时间再写。
            $payData = UserWalletRepositories::instance()->createPayment($this->postDate);
            if (!$payData) {
                return $this->retArray(-1, [], '创建支付单失败');
            }
    
            //支付单号。
            $this->postDate['uuid'] = $payData;
            //去支付
            $payResult = $this->{Payment::$orderPay[$payWay]}();
            if (isset($payResult['code'])){
              return $this->retArray(-1,$payResult['msg']);
            }
    
            return $this->retArray(0,$payResult);
            }
        }
    

    这里,总体的支付框架就已经完成了。剩下的则是对上面调用方法的实现了。下面就就接着简单说明一下
    这里使用了Payment一个类用来做相关的配置信息。这样的好处不用多说了吧。当然你也可以写在这个支付类里。看看Payment类里有那些东西

    /**
     * 支付参数设定
     * Class Payment
     * @package common
     */
    class Payment
    {
    
        //业务A
        const A = 1;
    
       //业务B
        const B = 2;
      //业务C
        const C = 3;
    
       //业务D
        const D = 4;
    
          const PAY_TYPE_WECHAT = 1;//微信公众号
          const PAY_TYPE_WECHAT_WAP = 2;//微信wap
          const PAY_TYPE_WECHAT_APP = 3;//微信app
          const PAY_TYPE_WECHAT_MINIPROGRAM = 4;//小程序
          const PAY_TYPE_STRIPE = 5;//信用卡
          const PAY_TYPE_ALI_WAP = 6;//国内支付宝wap
          const PAY_TYPE_ALI_APP = 7;//国内支付宝app
          const PAY_TYPE_ALI_HK_WAP = 8;//香港支付宝wap
          const PAY_TYPE_ALI_HK_APP = 9;//香港支付宝app
    
    
    //业务和获取对应业务订单信息的方法名的映射(通过可变函数实现)达到消除过多条件判断语句
        public static $orderType = [
            self::A => "getAOrder",
            self::B => "getBOrder",
            self::C => "getCOrder",
            self::D => "getDOrder",
        ];
    
    //支付方式,与支付方法映射
        public  static $orderPay = [
            self::PAY_TYPE_WECHAT => "payTypeWchat",
            self::PAY_TYPE_ALI_WAP => "payTypeAliWap",
            self::PAY_TYPE_STRIPE => "payTypeStripe",
            self::PAY_TYPE_WECHAT_WAP => "payTypeWchatWap",
            self::PAY_TYPE_WECHAT_APP => "payTypeWchatApp",
            self::PAY_TYPE_WECHAT_MINIPROGRAM => "payTypeWchatMiniprogram",
            self::PAY_TYPE_ALI_APP => "payTypeAliApp",
            self::PAY_TYPE_ALI_HK_WAP => "payTypeAliHkWap",
            self::PAY_TYPE_ALI_HK_APP => "payTypeAliHkApp",
        ];
        }
    
        通过上面的Payment类,我们就好比较理解下面的写法了
    
            if (!isset(Payment::$orderType[$orderType])){
                return $this->retArray(-1, [], '订单类型不存在');
            }
            //通过可变函数调用获取对应业务订单信息的函数
            $order = $this->{Payment::$orderType[$orderType]}();
            if (!$order) {
            return $this->retArray(-1, [], '订单不存在');
            }
    
    下面是是获取对应业务订单的函数。这些函数可根据需要实现
    
    
        private function getAOrder(){
            return AOrder::getOrderByID($this->orderId);
        }
        private function getBOrder(){
            return BCardService::instance()->getOrderByID($this->orderId);
        }
        private function getCOrder(){
            return COrder::getOrderByID($this->orderId);
        }
        private function getDOrder(){
            return D::getOrderByID($this->orderId);
        }
    
        到这里,就把支付流程里的获取商品信息给抽取出来了。也就完成了大半工作。接下来就是支付方式的实现了。这里同样是使用可变函数
    
             $this->postDate['id'] = $payData;
            $payResult = $this->{Payment::$orderPay[$payWay]}();
    
        对应支付方法的实现。这里只用了一个信用卡的支付例子。其它支付方式,只要根据第三方支付要求实现就好了
    
     /**
         * 信用卡支付
         * @return array
         * @throws \GuzzleHttp\Exception\GuzzleException
         */
        private function payTypeStripe(){
    
            if (empty($this->postDate['stripeId']) || empty($this->postDate['total_fee'])){
                return $this->retArray(-1, [], '支付参数异常');
            }
    
            //$post数组key的顺序是固定的
            $post = [
                'stripe_request_model' => [
                    'source' => $this->postDate['stripeId'],
                    'bill' => $this->postDate['order_num'] ,
                    'currency' => $this->postDate['currency'],
                    'amount' => $this->postDate['total_fee'],
                    'description' => '充值',
                    ]
            ];
            $url='第三方支付接口'
            $payResult = $this->send($post,$url);
            if (isset($payResult['code'])){
                return $this->retArray(-1,$payResult['msg']);
            }
            return $payResult;
        }
    
    请求支付方法实现
    
    /**
     * 发送请求 * @param $post
     * @param $headers
     * @return array
     * @throws \GuzzleHttp\Exception\GuzzleException
     */public function send($post,$url){
    
        if (empty($post)){
            return $this->retArray(-1, [], '支付参数不能为空');
        }
        try {
            $client = new Client();
            $response = $client->request('POST', $url, ['json' => $post]);
    
        } catch (RequestException $e) {
         //记录日志等其它处理
            return $this->retArray(-1, [], $e->getRequest().'st<->se'.$e->getResponse());
        }
    
        $result = $response->getBody()->getContents();
        $rs = json_decode($result, true);
    
        if (200 != $rs['code']) {
        //记录日志等其它处理
            return $this->retArray(-1, [], '支付失败');
        }
        return $rs['data'];
    }
    

    结尾

        到这里。单体应用中的通用支付类就实现了。这里主要用到的设计思想就是策略模式(当然我这里不是严格按照策略模式写的只是用其思想)。还有一个技巧就是使用了可变函数。使我们的代码减少了很多if else 或switch case语句。代码整洁了不少。维护,扩展都很容易。
        现在开始尝试写博客了。不足的地方欢迎大家指出来。以前总怕自己写的博客误人子弟。现在觉得还是可以分享一些自己的经验的,大家共同学习成长。欢迎交流讨论。
        
    

    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 lzdong@foxmail.com

    ×

    喜欢就点赞,疼爱就打赏