Laravel 用 abstract 與 interface 寫一個簡單的支付回撥 demo

php_yt發表於2020-05-13

場景

微信小程式、百度小程式、頭條小程式.. 每種小程式都有不同格式的回撥資料,不同的驗籤方式,不同的回撥應答,但業務邏輯是相同的,不可能為每種都寫一套程式碼。找了一圈沒發現這方面的元件,於是寫了一個簡單的 demo,已經調通,僅供參考。

分析

  1. 商戶業務邏輯單獨拿出來。
  2. 支付回撥歸納為五步: 非同步通知的原始資料->驗籤->提取必要資料->執行商戶邏輯->返回應答。

檔案概況

回撥處理檔案 demo

Laravel 用 abstract 與 interface 寫一個簡單的支付回撥元件

商戶業務邏輯類 demo

Laravel 用 abstract 與 interface 寫一個簡單的支付回撥元件

1. 商戶業務邏輯契約類

interface

<?php
namespace App\Library\Notify;

interface PayNotifyInterface
{
    /**
     * 非同步回撥檢驗完成後,回撥客戶端的業務邏輯
     * 業務邏輯處理,必須實現該類。
     * 
     * @param array $data 經過處理後返回的統一格式的回撥資料
     * @return boolean
     */
    public function notifyProcess(array $data);
}

商戶業務邏輯類實現契約類

<?php
namespace App\Service;
use App\Library\Notify\PayNotifyInterface;
use Illuminate\Support\Facades\Log;
use App\Model\Pay;

class PaymentNotify implements PayNotifyInterface
{
    public function notifyProcess(array $data)
    {
        $pay_type = $data['pay_type'];

        switch ($pay_type) {
            case 'case1':
                $result = $this->case1($data); // 型別1
                break;
            case 'case2':
                $result = $this->case2($data); // 型別2
                break;
            default:
                $result = false;
        }
        return $result;
    }

    public function case1($data)
    {
        // 檢查訂單狀態
        $pay = Pay::where('order_no',$data['order_no'])->first();
        if ( !$pay || $pay->pay_status != 0 || $pay->pay_amount != $data['pay_amount'] ) { return false; }
        // ...業務邏輯
        return true;
    }

    public function case2($data)
    {
        // ...
    }
}
/**
返回給商戶業務邏輯的資料
 {"notify_time":"2020-05-13 16:15:10",
    "order_no":"20200513576982804164",
    "pay_amount":"3",
    "transaction_id":"82058950219732",
    "trade_state":"2",
    "return_data":{"pay_type":"commonpub"},
    "pay_type":"commonpub",
    "raw_data"(原始資料):{"unitPrice":"3","orderId":"82058950219732","payTime":"1589357710","dealId":"431111113","tpOrderId":"20200513576982804164","count":"1","totalMoney":"3","hbBalanceMoney":"0","userId":"4211111093","promoMoney":"0","promoDetail":"","hbMoney":"0","giftCardMoney":"0","payMoney":"3","payType":"1117","returnData":{"pay_type":"commonpub"},"partnerId":"6111101","rsaSign":"TdHNFqt\/0bPwA2cQ8nbNG9iJL8GkEHG0Iwfk4iVYhUZ3lRYEhYx7qzYaL3easzoglTtVedsUv+WtrNmLx6ufcf2EcS86HXP2wmf5wPNxfZJk+XDzkgwqYHU1u9pWNcxn2hwdLB1NONpRogBHlsY112wAPpRGVZJtjtuzYf+2Kcs=","status":"2"}}
 */

2. 通用回撥處理類

<?php
namespace App\Library\Notify;
use App\Library\Notify\PayNotifyInterface;
/**
 * 各小程式支付平臺的不同實現 需要繼承該類
 * 獲取非同步通知的原始資料-》驗籤-》執行商戶邏輯-》返回應答
 */
abstract class NotifyAbstract
{

    /**
     * 入口
     * @param PayNotifyInterface $notify
     * final 不能被繼承
     */
    final public function handle(PayNotifyInterface $notify)
    {
        // 獲取非同步通知的原始資料
        $notifyData = $this->getNotifyData();
        if ($notifyData === false) {
            return $this->replyNotify(false, '獲取通知資料失敗');
        }

        // 驗籤
        $checkRet = $this->checkNotifyData($notifyData);
        if ($checkRet === false) {
            return $this->replyNotify(false, '返回資料驗籤失敗,可能資料被篡改');
        }

        // 回撥商戶的業務邏輯
        $flag = $this->callback($notify, $notifyData);
        if ($flag) {
            $msg = 'OK';
        } else {
            $msg = '商戶邏輯呼叫出錯';
        }

        // 應答
        return $this->replyNotify($flag, $msg);
    }

    /**
     * 回撥商戶的業務邏輯,根據返回的 true  或者 false
     * @param PayNotifyInterface $notify
     * @param array $notifyData 原始回撥資料
     *
     * @return boolean
     */
    protected function callback(PayNotifyInterface $notify, array $notifyData)
    {
        $data = $this->getRetData($notifyData); // 提取必要資料返回商戶

        if ($data === false) {
            return false;
        }

        return $notify->notifyProcess($data); //處理商戶業務邏輯
    }

    /**
     * 獲取回撥通知資料 進行簡單處理 返回陣列
     *
     * 如果獲取資料失敗,返回false
     *
     * @return array|false
     */
    abstract public function getNotifyData();

    /**
     * 檢查非同步通知的資料是否合法
     *
     * 如果檢查失敗,返回false
     *
     * @param array $data  由 $this->getNotifyData() 返回的原始回撥陣列
     * @return boolean
     */
    abstract public function checkNotifyData(array $data);

    /**
     * 向客戶端返回必要統一的資料
     * @param array $data 由 $this->getNotifyData() 返回的原始回撥陣列
     * @return array|false
     */
    abstract protected function getRetData(array $data);

    /**
     * 根據返回結果,回答支付機構。是否回撥通知成功
     * @param boolean $flag 每次返回的bool值
     * @param string $msg 通知資訊,錯誤原因
     * @return mixed
     */
    abstract protected function replyNotify($flag, $msg = 'OK');

}

各支付平臺繼承 NotifyAbstract 以百度小程式為例

<?php
namespace App\Library\Notify;
use App\Library\Notify\NotifyAbstract;
use App\Library\BaiduSign;
use Illuminate\Support\Facades\Log;
/**
回撥原始資料
{"unitPrice":"3","orderId":"82058950219732","payTime":"1589357710","dealId":"43111113","tpOrderId":"20200513576982804164","count":"1","totalMoney":"3","hbBalanceMoney":"0","userId":"4226669093","promoMoney":"0","promoDetail":"","hbMoney":"0","giftCardMoney":"0","payMoney":"3","payType":"1117","returnData":"{\"pay_type\":\"commonpub\"}","partnerId":"6011111","rsaSign":"TdHNFqt\/0bPwA2cQ8nbNG9iJL8GkEHG0Iwfk4iVYhUZ3lRYEhYx7qzYaL3easzoglTtVedsUv+WtrNmLx6ufcf2EcS86HXP2wmf5wPNxfZJk+XDzkgwqYHU1u9pWNcxn2hwdLB1NONpRogBHlsY112wAPpRGVZJtjtuzYf+2Kcs=","status":"2"} 
 */
class BdNotify extends NotifyAbstract
{
    // 獲取原始回撥資料 轉成陣列 array | false
    public function getNotifyData()
    {
        $data =  $_POST;

        if (empty($data) || ! is_array($data)) {
            return false;
        }

        return $data;
    }

    // 支付狀態 及 驗籤  bool
    public function checkNotifyData(array $data)
    {
        if( $data['status'] != 2 ){
            Log::error('百度支付回撥資料檢查:支付狀態不為2');
            return false;
        }

        $flag = (new BaiduSign() )->checkSignWithRsa($data);
        return $flag;
    }

    // 獲取必要欄位 返回商戶業務邏輯 array | false
    protected function getRetData(array $data)
    {
        $data['returnData'] = isset($data['returnData']) ? json_decode($data['returnData'],true) : [];

        if( !isset($data['returnData']['pay_type']) ){
            Log::error('百度支付獲取必要欄位:缺少 pay_type 引數');
            return false;
        }

        // 注意 這裡向業務邏輯返回統一的格式
        $retData = [
            'notify_time'   => date('Y-m-d H:i:s',$data['payTime']),
            'order_no'      => $data['tpOrderId'],
            'pay_amount'    => $data['totalMoney'],
            'transaction_id'=> $data['orderId'],
            'trade_state'   => $data['status'],
            'return_data'   => $data['returnData'],
            'pay_type'      => $data['returnData']['pay_type'],
            'raw_data'      => $data, //原始資料
        ];

        return $retData;
    }

    // 應答支付平臺
    protected function replyNotify($flag, $msg = 'OK')
    {
        Log::error('百度支付回撥應答:'.$msg);
        $ret['errno'] = 0;
        if ($flag) {
            $ret['msg'] = 'success';
            $ret['data'] = ['isConsumed'=>2];
        } else {
            $ret['msg'] = 'fail';
            $ret['data'] = ['isConsumed'=>2,'isErrorOrder'=>1];
        }
        return $ret;
    }
}

在回撥方法中呼叫

<?php
namespace App\Http\Controllers\Api\V1;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

use App\Library\Notify\BdNotify; // 百度小程式回撥類
use App\Service\PaymentNotify; // 商戶業務邏輯類
use Illuminate\Support\Facades\Log;

class NotifyController extends Controller
{
    public function index($platform)
    {
        if (!in_array($platform, ['baidu'])) {
            return '暫不支援';
        }
        $callback = new PaymentNotify();
        switch ($platform) {
            case 'baidu':
                $ret = (new BdNotify())->handle($callback);
                break;
            default:
                $ret = false;
                break;
        }
        return $ret;
    }

}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

簡潔略帶風騷

相關文章