Files
amb_wechatapp/application/common/library/pay/PayService.php
T

307 lines
11 KiB
PHP

<?php
namespace app\common\library\pay;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Log;
use app\common\exception\Exception;
class PayService
{
protected $config;
protected $platform;
protected $payment;
protected $notify_url;
public $method;
/**
* 当前使用的支付账户池商户号ID(仅在账户池启用时有值)
* @var int|null
*/
public $poolAccountId = null;
/**
* 指定使用账户池中的某个商户号ID(用于支付失败切换重试 / 退款)
* @var int|null
*/
protected $forceAccountId = null;
public function __construct($payment, $platform = '', $notify_url = '', $forceAccountId = null)
{
$this->platform = $platform;
$this->payment = $payment;
$this->notify_url = $notify_url;
$this->forceAccountId = $forceAccountId;
// 清理 Yansongda Pay SDK 的单例缓存
// 重要:SDK 内部对 Support 类使用了单例模式,非 CLI 模式下会复用第一次的配置
// 切换商户号时必须清理,否则会一直使用旧商户号的配置(如私钥)导致签名失败
$this->clearPaySdkInstance();
$this->setPaymentConfig();
}
/**
* 清理 Yansongda Pay SDK 的单例缓存
* 通过反射访问 private static $instance 并置空
* @return void
*/
private function clearPaySdkInstance()
{
try {
if ($this->payment === 'alipay') {
$supportClass = '\\Yansongda\\Pay\\Gateways\\Alipay\\Support';
} elseif ($this->payment === 'wechat') {
$supportClass = '\\Yansongda\\Pay\\Gateways\\Wechat\\Support';
} else {
return;
}
if (!class_exists($supportClass)) {
return;
}
$reflection = new \ReflectionClass($supportClass);
if ($reflection->hasProperty('instance')) {
$instanceProp = $reflection->getProperty('instance');
$instanceProp->setAccessible(true);
$instanceProp->setValue(null, null);
}
} catch (\Throwable $e) {
// 清理失败不影响主流程
}
}
private function setPaymentConfig()
{
$paymentConfig = \app\common\model\config\System::getConfig($this->payment);
if ($this->payment === 'virtual_pay') {
$this->config = $paymentConfig;
$this->setPaymentMethod();
return;
}
// 接入支付账户池:当插件启用且支付类型为 wechat / alipay 时,
// 使用账户池中"使用中"商户号配置覆盖系统配置(账户池优先)。
if (in_array($this->payment, ['wechat', 'alipay'], true)
&& \app\admin\library\project\App::isInstall('paymentpool')) {
$poolAccount = null;
if ($this->forceAccountId) {
// 指定使用某个商户号(场景:自动切换 / 退款时按订单查商户号)
$poolAccount = \app\admin\model\app\paymentpool\Account::where([
'id' => $this->forceAccountId,
'uniacid' => UNIACID,
])->find();
if ($poolAccount) {
$poolAccount = $poolAccount->toArray();
$poolAccount['config'] = \app\admin\model\app\paymentpool\Account::where('id', $this->forceAccountId)->find()->getAttr('config');
}
} elseif (\app\common\library\app\paymentpool\Service::isEnable()) {
$poolAccount = \app\common\library\app\paymentpool\Service::getActiveAccount($this->payment);
}
if ($poolAccount && !empty($poolAccount['id'])) {
$poolConfig = \app\common\library\app\paymentpool\Service::toPaymentConfig($poolAccount);
// 仅覆盖账户池负责管理的字段
$paymentConfig = array_merge((array)$paymentConfig, $poolConfig);
$this->poolAccountId = intval($poolAccount['id']);
}
}
// 平台可用性校验已移除:支付账户池不再单独配置可用平台,统一使用系统规则
$this->config = $paymentConfig;
if ($this->payment === 'wechat') {
$platformConfig = \app\common\model\config\System::getConfig($this->platform);
switch ($this->platform) {
case 'wxOfficialAccount':
$this->config['app_id'] = $platformConfig['app_id'];
break;
case 'wxMiniProgram':
$this->config['miniapp_id'] = $platformConfig['app_id'];
break;
case 'H5':
$this->config['app_id'] = $platformConfig['app_id'];
break;
case 'App':
$this->config['appid'] = $platformConfig['app_id']; // 微信 App 支付使用这个
$this->config['app_id'] = $platformConfig['app_id']; // 微信 App 支付退款使用的是这个
break;
}
}
// 处理证书路径
if ($this->payment == 'wechat') {
// 微信支付证书
$this->config['cert_client'] = ROOT_PATH . $this->config['cert_client'];
$this->config['cert_key'] = ROOT_PATH . $this->config['cert_key'];
$this->config['notify_url'] = $this->notify_url;
}elseif($this->payment == 'douyinpay'){
$this->config['notify_url'] = $this->notify_url;
}elseif($this->payment == 'alipay'){
$this->config['ali_public_key'] = $this->config['alipay_public_key'] ?? '';
$this->config['private_key'] = $this->config['alipay_private_key'] ?? '';
$this->config['notify_url'] = $this->notify_url;
}
$this->setPaymentMethod();
}
private function setPaymentMethod()
{
$method = [
'wechat' => [
'wxOfficialAccount' => 'mp', //公众号支付 Collection
'wxMiniProgram' => 'miniapp', //小程序支付
'H5' => 'wap', //手机网站支付 Response
'App' => 'app' //APP 支付 JsonResponse
],
'alipay' => [
'wxOfficialAccount' => 'wap', //公众号H5网页支付
'H5' => 'wap', //手机网站支付
'url' => 'wap', //独立页面支付
'pc' => 'web', //电脑网页支付
],
'douyinpay'=>[
'dyMiniProgram' => 'douyinpay', //小程序支付
],
'virtual_pay'=>[
'wxMiniProgram' => 'virtual_pay', //虚拟支付
]
];
$this->method = $method[$this->payment][$this->platform];
}
public function create($order)
{
$method = $this->method;
switch ($this->payment) {
case 'wechat':
$order['total_fee'] = $order['total_fee'] * 100;
try{
$pay = Pay::wechat($this->config)->$method($order);
}catch (\Throwable $e){
// 注意:必须抛出 PHP 原生 \Exception,不能用 app\common\exception\Exception
// 因为 app\common\exception\Exception 不是 Throwable 子类,
// 且构造函数内直接 send 响应并 die,会导致错误信息直接返回客户端,外层 catch 拦不到。
throw new \Exception($e->getMessage() ?: '微信支付下单失败');
}
break;
case 'alipay':
if (!isset($order['total_amount'])) {
$order['total_amount'] = $order['total_fee'];
unset($order['total_fee']);
}
try{
$pay = Pay::alipay($this->config)->$method($order);
}catch (\Throwable $e){
// 注意:必须抛出 PHP 原生 \Exception,不能用 app\common\exception\Exception
// 因为 app\common\exception\Exception 不是 Throwable 子类,
// 且构造函数内直接 send 响应并 die,会导致错误信息直接返回客户端,外层 catch 拦不到。
throw new \Exception($e->getMessage() ?: '支付宝支付下单失败');
}
break;
case 'douyinpay':
$pay = [];
$payParams = (new \app\common\model\order\Pay())->getDouyinPayParams($order['out_trade_no'],$this->notify_url);
if($payParams === false){
// 使用 PHP 原生 \Exception,让外层 catch 能拦截并切换商户号
throw new \Exception("获取订单参数失败,请刷新重试");
}
$pay['authorization'] = \douyin\Byte::init($this->config)->getByteAuthorization($payParams);
$pay['data'] = json_encode($payParams);
break;
case 'virtual_pay':
$virtualPayService = new VirtualPayService();
$pay = $virtualPayService->getClientPayConfig(
$order['openid'],
$order['session_key'],
$order['out_trade_no'],
$order['total_fee'],
$order['mode'] ?? 'short_series_coin',
$order['product_id'] ?? ''
);
break;
}
return $pay;
}
/**
* 支付成功回调事件
* @param $callback
* @return mixed
*/
public function notify($callback)
{
$pay = $this->getPay();
$result = false;
try {
$data = $pay->verify();
$result = $callback($data, $pay);
} catch (\Exception $e) {
}
return $result;
}
public function refund($order_data) {
$pay = $this->getPay();
if ($this->payment === 'wechat') {
$order_data['type'] = $this->platform == 'wxMiniProgram' ? 'miniapp' : '';
}
$result = $pay->refund($order_data);
return $result;
}
public function notifyRefund($callback) {
$pay = $this->getPay();
$result = false;
try {
$data = $pay->verify(null, true); // 是的,验签就这么简单!
$result = $callback($data, $pay);
// Log::debug('Wechat notify', $data->all());
} catch (\Exception $e) {
// $e->getMessage();
}
return $result;
}
private function getPay () {
switch ($this->payment) {
case 'wechat':
$pay = Pay::wechat($this->config);
break;
case 'alipay':
$pay = Pay::alipay($this->config);
break;
default :
// 使用 PHP 原生 \Exception,让外层 catch 能拦截并切换商户号
throw new \Exception('支付方式不支持');
}
return $pay;
}
}