diff --git a/app/Endpoint/Callback/Http/Controllers/WxpayController.php b/app/Endpoint/Callback/Http/Controllers/WxpayController.php new file mode 100644 index 00000000..50e582e1 --- /dev/null +++ b/app/Endpoint/Callback/Http/Controllers/WxpayController.php @@ -0,0 +1,111 @@ +payment()->handlePaidNotify(function ($message, $fail) use ($request) { + $this->log('paid notify--------'.$request->url(), $message); + + // 通信失败 + if (data_get($message, 'return_code') !== 'SUCCESS') { + return $fail('通信失败'); + } + + try { + $payLog = DB::transaction(function () use ($message) { + $payService = new PayService(); + + if (data_get($message, 'result_code') === 'SUCCESS') { + return $payService->handleSuccessByPaySerialNumber($message['out_trade_no'], [ + 'out_trade_no' => $message['transaction_id'], + 'pay_at' => Carbon::parse($message['time_end']), + ]); + } elseif (data_get($message, 'result_code') === 'FAIL') { + return $payService->handleFailedByPaySerialNumber($message['out_trade_no'], [ + 'out_trade_no' => $message['transaction_id'] ?? null, + 'failed_reason' => '['.$message['err_code'].']'.$message['err_code_des'], + ]); + } + }); + + $payable = $payLog?->payable; + + if ($payable instanceof Order) { + OrderPaid::dispatchIf($payable->isPaid(), $payable); + } + } catch (ModelNotFoundException | BizException $e) { + } catch (Throwable $e) { + throw $e; + } + + return true; + }); + } + + /** + * 订单退款结果通知 + * + * @param \App\Services\Payment\WxpayService $wxpayService + * @return \Illuminate\Http\Response + */ + public function orderRefundedNotify(WxpayService $wxpayService) + { + return $wxpayService->payment()->handleRefundedNotify(function ($message, $reqInfo, $fail) { + $this->log('refund notify', $reqInfo); + + // 通信失败 + if (data_get($message, 'return_code') !== 'SUCCESS') { + return $fail('通信失败'); + } + + // 退款失败 + if ($reqInfo['refund_status'] !== 'SUCCESS') { + $log = OrderRefundLog::where('sn', $reqInfo['out_refund_no'])->first(); + + $log?->update([ + 'status' => OrderRefundLog::STATUS_FAILED, + 'failed_reason' => $reqInfo['refund_status'] === 'CHANGE' ? '退款异常' : '退款关闭', + ]); + } + + return true; + }); + } + + /** + * 微信回调日志 + * + * @param string $message + * @param array $context + * @return void + */ + protected function log(string $message, array $context = []) + { + return Log::build([ + 'driver' => 'daily', + 'path' => storage_path('logs/wxpay-notify.log'), + ])->info($message, $context); + } +} diff --git a/app/Endpoint/Callback/routes.php b/app/Endpoint/Callback/routes.php index 871c05c5..d7958851 100644 --- a/app/Endpoint/Callback/routes.php +++ b/app/Endpoint/Callback/routes.php @@ -3,13 +3,14 @@ use App\Endpoint\Callback\Http\Controllers\AlipayController; use App\Endpoint\Callback\Http\Controllers\Kuaidi100Controller; use App\Endpoint\Callback\Http\Controllers\WeChatPayController; +use App\Endpoint\Callback\Http\Controllers\WxpayController; use App\Endpoint\Callback\Http\Controllers\YzkWeChatPayController; use Illuminate\Support\Facades\Route; //快递100物流推送 Route::post('kuaidi100', [Kuaidi100Controller::class, 'notify']); // 微信支付通知 -Route::post('wxpay/paid-notify', [WeChatPayController::class, 'paidNotify'])->name('wxpay.paid_notify'); +Route::post('wxpay/paid-notify', [WxpayController::class, 'paidNotify'])->name('wxpay.paid_notify'); Route::post('wxpay/order-refund-notify', [WeChatPayController::class, 'orderRefundedNotify'])->name('wxpay.order_refund_notify'); Route::post('alipay', AlipayController::class)->name('alipay.notify'); diff --git a/app/Enums/PayWay.php b/app/Enums/PayWay.php index 27eab37b..ed1f60cf 100644 --- a/app/Enums/PayWay.php +++ b/app/Enums/PayWay.php @@ -10,7 +10,7 @@ enum PayWay: string { case WxpayApp = 'wxpay_app'; case WxpayH5 = 'wxpay_h5'; case WxpayJs = 'wxpay_jsapi'; - case WxpayMp = 'wxpay_mini_program'; + case WxpayMp = 'wxpay_mp'; // 阿里支付 case AlipayApp = 'alipay_app'; diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index 58bcb29c..c5544955 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -17,6 +17,7 @@ use App\Models\ProductSku; use App\Models\ShippingAddress; use App\Models\User; use App\Models\UserCoupon; +use App\Services\Payment\WxpayService; use Illuminate\Database\QueryException; use Illuminate\Support\Facades\DB; @@ -780,40 +781,50 @@ class OrderService } } while ($payLog === null); - $data = [ - 'pay_sn' => $payLog->pay_sn, - ]; - // 如果支付方式为线下支付,或支付金额为 0,则按支付完成处理 if ($order->total_amount === 0 || $payLog->isOffline()) { (new PayService())->handleSuccess($payLog, [ 'pay_at' => now(), ]); - $data = null; + return [ + 'pay_sn' => $payLog->pay_sn, + 'data' => null, + ]; } elseif ($payLog->isWxpay()) { if (is_null($tradeType = WxpayTradeType::tryFromPayWay($payLog->pay_way))) { throw new BizException('支付方式 非法'); } - $data = (new WeChatPayService())->pay([ + $params = [ 'body' => app_settings('app.app_name').'-商城订单', 'out_trade_no' => $payLog->pay_sn, 'total_fee' => $order->total_amount, 'trade_type' => $tradeType->value, - ]); + ]; + + if ($tradeType === WxpayTradeType::JSAPI) { + $params['openid'] = 'o-BdO46p2hRnk-SQIXa1TEdBFtfs'; + } + + return [ + 'pay_sn' => $payLog->pay_sn, + 'data' => (new WxpayService())->pay($params), + ]; } elseif ($payLog->isAlipay()) { - $data = app(AlipayService::class)->pay([ + $params = [ 'subject' => app_settings('app.app_name').'-商城订单', 'out_trade_no' => $payLog->pay_sn, 'total_amount' => bcdiv($order->total_amount, 100, 2), - ]); + ]; + + return [ + 'pay_sn' => $payLog->pay_sn, + 'data' => app(AlipayService::class)->pay($params), + ]; } - return [ - 'pay_way' => $payLog->pay_way, - 'data' => $data, - ]; + throw new BizException('【-1】支付方式 非法'); } /** diff --git a/app/Services/Payment/WxpayService.php b/app/Services/Payment/WxpayService.php new file mode 100644 index 00000000..b9639e12 --- /dev/null +++ b/app/Services/Payment/WxpayService.php @@ -0,0 +1,108 @@ + + */ + protected $payments = []; + + /** + * 支付 + * + * @param array $params + * @return array + */ + public function pay(array $params, string $paymentName = null): array + { + if (! isset($params['notify_url'])) { + $params['notify_url'] = url(route('wxpay.paid_notify', ['payment' => $paymentName], false), [], true); + } + + if (! isset($params['trade_type'])) { + $params['trade_type'] = WxpayTradeType::App->value; + } + + $app = $this->payment($paymentName); + + $result = $app->order->unify($params); + + $this->validateResult($result, $params); + + return match ($params['trade_type']) { + PayWay::WxpayApp => $app->jssdk->appConfig($result['prepay_id']), + PayWay::WxpayH5 => Arr::only($result, ['mweb_url']), + default => $app->jssdk->bridgeConfig($result['prepay_id'], false), + }; + } + + /** + * @param string|null $name + * @return \EasyWeChat\Payment\Application + */ + public function payment(?string $name = null): Application + { + $name = $name ?: 'default'; + + return $this->payments[$name] = $this->get($name); + } + + /** + * @param string $name + * @return \EasyWeChat\Payment\Application + */ + protected function get(string $name): Application + { + return $this->payments[$name] ?? $this->resolve($name); + } + + /** + * @param string $name + * @return \EasyWeChat\Payment\Application + * + * @throws \App\Exceptions\WeChatPayException + */ + protected function resolve(string $name): Application + { + $config = config("wechat.payment.{$name}"); + + if (empty($config)) { + throw new WeChatPayException("支付 [{$name}] 配置未找到"); + } + + return Factory::payment($config); + } + + /** + * 校验响应结果 + * + * @param array $result + * @param array $raw + * @return void + * + * @throws \App\Exceptions\WeChatPayException + */ + protected function validateResult(array $result, array $raw = []) + { + if (Arr::get($result, 'return_code') !== 'SUCCESS') { + throw new WeChatPayException(Arr::get($result, 'return_msg', '请求失败'), $raw); + } + + if (Arr::get($result, 'result_code') !== 'SUCCESS') { + throw new WeChatPayException(sprintf( + '[%s] %s', + Arr::get($result, 'err_code', '-1'), + Arr::get($result, 'err_code_des', '结果异常') + ), $raw); + } + } +}