133 lines
5.1 KiB
PHP
133 lines
5.1 KiB
PHP
<?php
|
|
|
|
/**
|
|
* 文件功能:前台 VIP 支付控制器
|
|
* 负责用户发起 VIP 支付、接收同步回调、接收异步通知并驱动本地会员开通
|
|
*/
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Http\Requests\CreateVipPaymentOrderRequest;
|
|
use App\Models\VipLevel;
|
|
use App\Services\VipPaymentCenterClient;
|
|
use App\Services\VipPaymentService;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* 前台 VIP 支付控制器
|
|
* 负责接收用户选择的支付渠道,下发支付中心订单并处理回调结果。
|
|
*/
|
|
class VipPaymentController extends Controller
|
|
{
|
|
/**
|
|
* 构造函数
|
|
*
|
|
* @param VipPaymentService $vipPaymentService 本地 VIP 支付服务
|
|
* @param VipPaymentCenterClient $paymentCenterClient 远端支付中心客户端
|
|
*/
|
|
public function __construct(
|
|
private readonly VipPaymentService $vipPaymentService,
|
|
private readonly VipPaymentCenterClient $paymentCenterClient,
|
|
) {}
|
|
|
|
/**
|
|
* 创建 VIP 支付订单并跳转到 NovaLink 支付页
|
|
*
|
|
* @param CreateVipPaymentOrderRequest $request 已校验的购买请求
|
|
*/
|
|
public function store(CreateVipPaymentOrderRequest $request): RedirectResponse
|
|
{
|
|
if (! $this->paymentCenterClient->isEnabled() || ! $this->paymentCenterClient->hasValidConfig()) {
|
|
return redirect()->route('vip.center')->with('error', 'VIP 支付暂未开放,请联系管理员完成后台配置。');
|
|
}
|
|
|
|
$vipLevel = VipLevel::query()->findOrFail((int) $request->validated('vip_level_id'));
|
|
$provider = (string) $request->validated('provider');
|
|
|
|
try {
|
|
// 先创建本地订单,再向支付中心发起下单,确保回调时有本地单据可追踪。
|
|
$vipPaymentOrder = $this->vipPaymentService->createLocalOrder($request->user(), $vipLevel, $provider);
|
|
$remoteOrder = $this->vipPaymentService->createRemoteOrder($vipPaymentOrder);
|
|
$payUrl = (string) ($remoteOrder['pay_url'] ?? '');
|
|
|
|
if ($payUrl === '') {
|
|
throw new RuntimeException('支付中心未返回可用的支付地址。');
|
|
}
|
|
|
|
return redirect()->away($payUrl);
|
|
} catch (\Throwable $exception) {
|
|
return redirect()->route('vip.center')->with('error', '创建 VIP 支付订单失败:'.$exception->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 处理支付完成后的同步回调
|
|
*
|
|
* @param Request $request 支付中心跳转返回参数
|
|
*/
|
|
public function handleReturn(Request $request): RedirectResponse
|
|
{
|
|
$payload = $request->all();
|
|
$vipPaymentOrder = $this->vipPaymentService->findByPaymentOrderNo($request->string('payment_order_no')->toString())
|
|
?? $this->vipPaymentService->findByMerchantOrderNo($request->string('merchant_order_no')->toString());
|
|
|
|
if (! $vipPaymentOrder) {
|
|
return redirect()->route('vip.center')->with('error', '未找到对应的 VIP 支付订单,请稍后在后台核对。');
|
|
}
|
|
|
|
$this->vipPaymentService->recordSyncReturn($vipPaymentOrder, $payload);
|
|
|
|
try {
|
|
// 同步回调只做页面回跳,但这里补查一次可让用户尽快看到最终结果。
|
|
$vipPaymentOrder = $this->vipPaymentService->syncRemoteStatus($vipPaymentOrder);
|
|
} catch (\Throwable $exception) {
|
|
return redirect()->route('vip.center')->with('error', '支付结果正在确认中,请稍后刷新查看。');
|
|
}
|
|
|
|
if ($vipPaymentOrder->isVipOpened()) {
|
|
return redirect()->route('vip.center')->with('success', 'VIP 支付成功,会员已开通。');
|
|
}
|
|
|
|
return redirect()->route('vip.center')->with('success', '支付页面已返回,系统正在确认支付结果,请稍后刷新查看。');
|
|
}
|
|
|
|
/**
|
|
* 接收 NovaLink 支付中心的异步通知
|
|
*
|
|
* @param Request $request 支付中心回调请求
|
|
*/
|
|
public function notify(Request $request): Response
|
|
{
|
|
$rawBody = $request->getContent();
|
|
$signature = (string) $request->header('X-Payment-Signature', '');
|
|
|
|
if (! $this->paymentCenterClient->isValidWebhookSignature($signature, $rawBody)) {
|
|
return response('invalid signature', 401);
|
|
}
|
|
|
|
$payload = $request->json()->all();
|
|
$vipPaymentOrder = $this->vipPaymentService->findByPaymentOrderNo($payload['payment_order_no'] ?? null)
|
|
?? $this->vipPaymentService->findByMerchantOrderNo($payload['merchant_order_no'] ?? null);
|
|
|
|
if (! $vipPaymentOrder) {
|
|
return response('order not found', 404);
|
|
}
|
|
|
|
if (($payload['status'] ?? '') !== 'paid') {
|
|
return response('ignored', 200);
|
|
}
|
|
|
|
try {
|
|
// 异步回调才是最终支付成功依据,这里完成幂等开通 VIP 的核心逻辑。
|
|
$this->vipPaymentService->markOrderAsPaid($vipPaymentOrder, $payload, 'async');
|
|
|
|
return response('success', 200);
|
|
} catch (\Throwable $exception) {
|
|
return response('error: '.$exception->getMessage(), 500);
|
|
}
|
|
}
|
|
}
|