Files
chatroom/app/Http/Controllers/VipPaymentController.php
2026-04-13 17:25:33 +08:00

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