Skip to content

支付功能

专业版基于 yansongda/pay 包装一份非常易用的支付工厂类。只需要在后台保存支付配置之后,使用下面的调用即可

php
// 公众号支付
 \Modules\Pay\Support\PayFactory::make(\Modules\Pay\Enums\Pay::WECHAT_PAY)
        ->mp([
            'subject' => '公众号支付测试',
            'amount' => 1,
            'openid' => 'xxxxxxxxxxxxxxx'
        ]);

WARNING

支付功能提供的是最基础的支付功能,不包含任何业务。功能代码需要根据实际业务编写

支付接口

提供三个支付接口,如下

php
use Modules\Pay\Support\NotifyData\NotifyData;

interface PayInterface
{
    // 退款
    public function refund(array $params): mixed;

    // 回调
    public function notify(): mixed;
}

支付基础类

专业版在基础类实现了创建订单号逻辑,这个订单号只是默认填充的,需要根据自己的实际业务来实现

php

abstract class Pay implements PayInterface
{
     /**
     * 支付实例
     */
    abstract protected function instance(): mixed;

    /**
     * 使用代理方法支付
     *
     * @return mixed
     *
     * @throws RandomException
     */
    public function __call($name, $params)
    {
        $params = array_merge(['action' => $name], ...$params);
        $payEvent = new PayEvent($this->createTradeData($params));
        $params = Event::dispatch($payEvent);
        return $this->instance()->{$name}($params[0]);
    }

    /**
     * 回调
     *
     * @throws ContainerException
     * @throws InvalidParamsException
     */
    public function notify(): mixed
    {
        $notifyData = $this->instance()->callback()->toArray();

        $notify = $this->getNotifyData($notifyData);

        try {
            Event::dispatch(new PayNotifyEvent($notify));
        } catch (\Throwable $e) {
            // 失败如何处理
        } finally {
            return $this->instance()->success();
        }
    }

    /**
     * 获取回调数据
     */
    abstract protected function getNotifyData(array $data): NotifyData;

    /**
     * 订单号前缀
     */
    abstract protected function orderNoPrefix(): string;

    /**
     * @return PayPlatform
     */
    abstract protected function platform(): PayPlatform;

    /**
     * 创建订单号
     *
     * @throws RandomException
     */
    protected function createOrderNo(): string
    {
        $prefix = $this->orderNoPrefix();

        return $prefix.date('YmdHis').random_int(1000000, 9999999).Str::random(10);
    }

    /**
     * 创建退款订单号,退款订单号加个 R 字符
     *
     * @throws RandomException
     */
    public function createRefundOrderNo(): string
    {
        return 'R'.$this->createOrderNo();
    }

    /**
     * @return array|string[]
     *
     * @throws RandomException
     */
    protected function createTradeData(array $params): array
    {
        $params['order_no'] = $this->createOrderNo();

        $params['platform'] = $this->platform();

        return $params;

    }

    /**
     * 加载支付配置
     *
     * @throws ContainerException|DdException
     */
    protected function loadPayConfig(string $key): void
    {
        PayProvider::config(PayConfig::get($key));
    }
}

具体实现的支付

以支付宝为例,如下代码

php

/**
 * @method ResponseInterface|Rocket web(array $order) 网页支付
 * @method ResponseInterface|Rocket h5(array $order) H5 支付
 * @method ResponseInterface|Rocket app(array $order) APP 支付
 * @method Rocket|Collection mini(array $order) 小程序支付
 * @method Rocket|Collection pos(array $order) 刷卡支付
 * @method Rocket|Collection scan(array $order) 扫码支付
 * @method Rocket|Collection transfer(array $order) 账户转账
 */
class AliPay extends Pay
{
    public function refund(array $params): mixed
    {
        $refundData = $this->createRefundData([
            'refund_amount' => $params['amount'],
        ]);
    }

    /**
     * @return \Yansongda\Pay\Provider\Alipay
     *
     * @throws \Yansongda\Artful\Exception\ContainerException
     */
    protected function instance(): mixed
    {
        PayProvider::config(config('pay.alipay'));

        return PayProvider::alipay();
    }

    /**
     * 包装回调数据
     */
    protected function getNotifyData(array $data): NotifyData
    {
        return new AliPayNotifyData($data);
    }

    protected function orderNoPrefix(): string
    {
        return 'A';
    }
}

支付参数

由于各个平台的支付参数都不相同,不仅平台参数有所差异,每个平台的支付方式的参数也所有差异,我们需要同意管理起来,所以也对支付参数进行了格式化。以支付宝的为例

php
// 在 modules/Pay/Support/PayParams
<?php

namespace Modules\Pay\Support\PayParams;

/**
 * @see https://pay.yansongda.cn/docs/v3/alipay/pay.html
 */
class AliPayParams extends PayParams
{

    /**
     * @return array
     */
    public function web(): array
    {
        return $this->common();
    }

    /**
     * @return array
     */
    public function h5(): array
    {
        return array_merge($this->common(), [
            'quit_url' => $this->params['quit_url'],
        ]);
    }

    /**
     * app 参数
     *
     * @return array
     */
    public function app(): array
    {
        return $this->common();
    }

    /**
     * 小程序参数
     *
     * @return array
     */
    public function mini(): array
    {
        return array_merge($this->common(), [
            'buyer_id' => $this->params['buyer_id'],
        ]);
    }

    /**
     * pos 机参数
     *
     * @return array
     */
    public function pos(): array
    {
        return array_merge($this->common(), [
            'auth_code' => $this->params['auth_code'],
        ]);
    }


    /**
     * 扫码参数
     *
     * @return array
     */
    public function scan(): array
    {
        return $this->common();
    }

    /**
     * 公共参数
     *
     * @return array
     */
    protected function common(): array
    {
        return [
            'out_trade_no' => $this->params['order_no'],
            'total_amount' => $this->params['amount'],
            'subject' => $this->params['subject'] ?? '支付',
        ];
    }
}

这样就可以统一管理参数了。将这些都散落的数据都聚合格式化之后。在对相关业务包装和后续业务维护都很有利。尽可能屏蔽上下文的依赖,独立使用它。

回调数据接口

这里为了隐藏支付回调业务处理的细节,对回调数据进行了一次包装。

php
// 回调数据接口
interface NotifyDataInterface
{
    /**
     * 是否支付成功
     *
     * @return bool
     */
    public function isPaySuccess(): bool;

    /**
     * 是否退款成功
     *
     * @return bool
     */
    public function isRefundSuccess(): bool;

    /**
     * 是否是退款
     *
     * @return bool
     */
    public function isRefund(): bool;


    /**
     * 获取支付平台的订单号
     *
     * @return string
     */
    public function getOutTradeNo(): string;


    /**
     * 获取本地订单号
     *
     * @return string
     */
    public function getTradeNo(): string;
}

例如支付宝的回调数据处理,必须实现接口的几个方法,这样在回调数据处理的时候就不需要关心具体实现了。

php
/**
 * 微信回调数据
 */
class AliPayNotifyData implements NotifyDataInterface
{
    /**
     * 是否支付成功
     */
    public function isPaySuccess(): bool
    {
        return $this->getTradeState() == 'trade_success';
    }

    /**
     * 是否退款成功
     */
    public function isRefundSuccess(): bool
    {
        return $this->getRefundStatus() == 'success';
    }

    /**
     * 获取退款状态
     */
    public function getRefundStatus(): string
    {
        return strtolower($this->data['resource']['refund_status']);
    }

    /**
     * 交易是否完成
     *
     * @return bool
     */
    public function isTradeFinished(): bool
    {
        return $this->getTradeState() == 'trade_finished';
    }

    /**
     * 交易是否等待付款
     *
     * @return bool
     */
    public function isWaitBuyerPay(): bool
    {
        return $this->getTradeState() == 'wait_buyer_pay';
    }

    /**
     * 交易是否关闭
     *
     * @return bool
     */
    public function isTradeClose(): bool
    {
        return $this->getTradeState() == 'trade_closed';
    }

    /**
     * 获取交易状态
     *
     * @return string
     */
    public function getTradeState(): string
    {
        return strtolower($this->data['trade_status']);
    }

    /**
     * 是否是退款回调
     */
    public function isRefund(): bool
    {
        return $this->data['resource']['original_type'] == 'refund';
    }

    /**
     * 获取平台交易订单号
     *
     * @return string
     */
    public function getOutTradeNo(): string
    {
        return $this->data['trade_no'];
    }

    /**
     * 获取本地交易订单号
     *
     * @return string
     */
    public function getTradeNo(): string
    {
        return $this->data['out_trade_no'];
    }
}

回调实现

以支付宝为例,只需要实现 RefundNotifyPayNotify 两个回调方法即可。对于处理本地的相同的业务逻辑,可以写在 Notify 基础类中。这两个方法是为了区分不同平台数据处理业务逻辑。如果相同,你可以提取到基类中

php
namespace Modules\Pay\Support\Notify;

class AliPayNotify extends Notify
{

    public function refundNotify(): mixed
    {
        // 根据实际业务处理
    }

    public function payNotify(): mixed
    {
        // 根据实际业务处理
    }
}

事件

支付模块提供两个事件

  • Modules\Pay\Events\PayEvent
  • Modules\Pay\Events\PayNotifyEvent

监听者

  • Modules\Pay\Events\PayListener
  • Modules\Pay\Events\PayNotifyListener

实现

你可以看到这两个监听者的实现也很简单,因为我们已经将整个支付的差异都屏蔽到了不同的上下文中。你只需要关注那一小块业务处理上,而无需关注支付中所有细节处理。

php
class PayNotifyListener
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param PayNotifyEvent $event
     * @throws \Throwable
     */
    public function handle(PayNotifyEvent $event): void
    {
        //
        $notifyData = $event->data;

        if ($notifyData instanceof AliPayNotifyData) {
            (new AliPayNotify($notifyData))->notify();
        }

        if ($notifyData instanceof WechatPayNotifyData) {
            (new WechatPayNotify($notifyData))->notify();
        }

        if ($notifyData instanceof UniPayNotifyData) {
            (new UniPayNotify($notifyData))->notify();
        }

        if ($notifyData instanceof DouYinPayNotifyData) {
            (new DouYinPayNotify($notifyData))->notify();
        }
    }
}
php

class PayListener
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct(){}

    /**
     * Handle the event.
     *
     * @param PayEvent $event
     * @return array
     */
    public function handle(PayEvent $event): array
    {
        //
        $params = $event->params;

        $order = Order::createNew([
            'subject_id' => $params['subject_id'],
            'order_no' => $params['order_no'],
            'amount' => $params['amount'],
            'platform' => $params['platform'],
            'user_id' => $params['user_id'],
            'action' => $params['action'],
            'pay_status' => PayStatus::Pending,
        ]);

        if ($order) {
            return match ($params['platform']) {
                PayPlatform::Alipay => (new AliPayParams($params))->{$params['action']}(),
                PayPlatform::UnionPay => (new UniPayParams($params))->{$params['action']}(),
                PayPlatform::Wechat => (new WechatPayParams($params))->{$params['action']}(),
                PayPlatform::Douyin => (new DouyinPayParams($params))->{$params['action']}(),
                default => [],
            };
        }

        return [];
    }
}

这是整个支付模块的思想,当然具体业务实现还需要根据实际业务功能添加。但是这已经在很大程度上屏蔽了支付功能的实际复杂度。只需要关注业务实现即可