Laravel 用 abstract 与 interface 写一个简单的支付回调 demo

场景

微信小程序、百度小程序、头条小程序.. 每种小程序都有不同格式的回调数据,不同的验签方式,不同的回调应答,但业务逻辑是相同的,不可能为每种都写一套代码。找了一圈没发现这方面的组件,于是写了一个简单的 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;
        }
        return $ret;
    }

}
本作品采用《CC 协议》,转载必须注明作者和本文链接

简洁略带风骚

《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!