主题
支付功能
专业版基于 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'];
}
}
回调实现
以支付宝为例,只需要实现 RefundNotify
和 PayNotify
两个回调方法即可。对于处理本地的相同的业务逻辑,可以写在 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 [];
}
}
这是整个支付模块的思想,当然具体业务实现还需要根据实际业务功能添加。但是这已经在很大程度上屏蔽了支付功能的实际复杂度。只需要关注业务实现即可