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; } }