單體應用中的通用支付服務設計

Euan發表於2020-03-11

背景

相較於微服務架構應用通常會有支付服務(交易結算中心),單體應用裡大多是一個支付類,甚至有些只有一個支付方法。當新增業務需要有支付場景時,則難以使用已有的支付。大多數情況是再增加一個支付類或方法(一般很少會去做相容重構的,原因有很多)。所以在開始設計支付類時我們應該有預見性的遵循開閉幕式的設計思想。去做一個對外擴充套件,對內修改關閉的服務。
    按支付支付步驟,1,獲取商品資訊,2,按要求格式組裝支付必要資料,3,請求第三方支付介面。4,處理回撥
    我們要把支付做成通用的,就要有入參,和出參的概念。所以不能把上面的123步驟放在一個介面函式中實現。
    這裡很簡單。因為是考慮到未來不同的業務支付需求。那要支付的商品資訊則肯定不在一張表中,所以獲取商品資訊要按業務單獨抽取出來。不同的支付方式,也有不同的支付引數,所以也要抽取來。有了前兩步後,向第三放發起請求則是一個通用方法了。下面用簡單的虛擬碼說明(寫完才發現程式碼還是有點多啊,沒怎麼寫過部落格,總怕寫了別人看不懂?。就多寫了點。不喜歡看程式碼的,看到這裡就可以拉到結尾了)
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 $this->retArray(-1,$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 elseswitch case語句。程式碼整潔了不少。維護,擴充套件都很容易。
    現在開始嘗試寫部落格了。不足的地方歡迎大家指出來。以前總怕自己寫的部落格誤人子弟。現在覺得還是可以分享一些自己的經驗的,大家共同學習成長。歡迎交流討論。
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章