price <= 0) { throw new RuntimeException('当前 VIP 等级未设置在线支付价格,暂不支持直接购买。'); } return VipPaymentOrder::create([ 'order_no' => $this->generateOrderNo(), 'merchant_order_no' => $this->generateOrderNo(), 'user_id' => $user->id, 'vip_level_id' => $vipLevel->id, 'status' => 'created', 'amount' => $vipLevel->price, 'subject' => '购买 VIP 会员 - '.$vipLevel->name, 'provider' => 'alipay', 'vip_name' => $vipLevel->name, 'vip_duration_days' => (int) $vipLevel->duration_days, 'meta' => [ 'username' => $user->username, ], ]); } /** * 调用支付中心创建远端支付单 * * @param VipPaymentOrder $vipPaymentOrder 本地 VIP 支付订单 * @return array */ public function createRemoteOrder(VipPaymentOrder $vipPaymentOrder): array { $remoteOrder = $this->paymentCenterClient->createOrder( $vipPaymentOrder, route('vip.payment.return'), route('vip.payment.notify') ); // 将远端平台支付单号回填到本地订单,后续回调和补单都依赖它。 $vipPaymentOrder->update([ 'payment_order_no' => (string) ($remoteOrder['payment_order_no'] ?? ''), 'status' => 'pending', ]); return $remoteOrder; } /** * 根据远端支付单号查找本地订单 * * @param string|null $paymentOrderNo 远端平台支付单号 */ public function findByPaymentOrderNo(?string $paymentOrderNo): ?VipPaymentOrder { if (! $paymentOrderNo) { return null; } return VipPaymentOrder::query()->where('payment_order_no', $paymentOrderNo)->first(); } /** * 根据本地业务订单号查找本地订单 * * @param string|null $merchantOrderNo 商户业务订单号 */ public function findByMerchantOrderNo(?string $merchantOrderNo): ?VipPaymentOrder { if (! $merchantOrderNo) { return null; } return VipPaymentOrder::query()->where('merchant_order_no', $merchantOrderNo)->first(); } /** * 在同步回调时主动补查远端状态 * * @param VipPaymentOrder $vipPaymentOrder 本地 VIP 支付订单 */ public function syncRemoteStatus(VipPaymentOrder $vipPaymentOrder): VipPaymentOrder { if (! $vipPaymentOrder->payment_order_no || $vipPaymentOrder->isVipOpened()) { return $vipPaymentOrder; } $remoteOrder = $this->paymentCenterClient->queryOrder($vipPaymentOrder->payment_order_no); if (($remoteOrder['status'] ?? null) === 'paid') { return $this->markOrderAsPaid($vipPaymentOrder, $remoteOrder, 'sync'); } if (($remoteOrder['status'] ?? null) === 'closed') { $vipPaymentOrder->update(['status' => 'closed']); } return $vipPaymentOrder->fresh(); } /** * 记录同步回调原始参数 * * @param VipPaymentOrder $vipPaymentOrder 本地 VIP 支付订单 * @param array $payload 同步回调参数 */ public function recordSyncReturn(VipPaymentOrder $vipPaymentOrder, array $payload): void { $vipPaymentOrder->update([ 'sync_return_payload' => $payload, ]); } /** * 根据异步通知将订单标记为已支付,并完成会员开通 * * @param VipPaymentOrder $vipPaymentOrder 本地 VIP 支付订单 * @param array $payload 支付中心回调数据 * @param string $source 触发来源:sync|async */ public function markOrderAsPaid(VipPaymentOrder $vipPaymentOrder, array $payload, string $source = 'async'): VipPaymentOrder { return DB::transaction(function () use ($vipPaymentOrder, $payload, $source) { $lockedOrder = VipPaymentOrder::query() ->with(['user', 'vipLevel']) ->lockForUpdate() ->findOrFail($vipPaymentOrder->id); $amount = number_format((float) $lockedOrder->amount, 2, '.', ''); $callbackAmount = number_format((float) ($payload['amount'] ?? 0), 2, '.', ''); if ($amount !== $callbackAmount) { throw new RuntimeException('支付金额校验失败,已拒绝开通会员。'); } // 无论是同步还是异步,都保留原始回调数据,方便后台排查问题。 $lockedOrder->fill([ 'payment_order_no' => (string) ($payload['payment_order_no'] ?? $lockedOrder->payment_order_no), 'provider' => (string) ($payload['provider'] ?? $lockedOrder->provider ?? 'alipay'), 'provider_trade_no' => (string) ($payload['provider_trade_no'] ?? $lockedOrder->provider_trade_no), 'status' => 'paid', 'paid_at' => isset($payload['paid_at']) ? Carbon::parse((string) $payload['paid_at']) : ($lockedOrder->paid_at ?? now()), ]); if ($source === 'async') { $lockedOrder->async_notify_payload = $payload; } if ($source === 'sync') { $lockedOrder->sync_return_payload = array_merge($lockedOrder->sync_return_payload ?? [], $payload); } $lockedOrder->save(); if (! $lockedOrder->isVipOpened()) { // 只在首次成功支付时开通会员,防止重复回调导致会员时长重复叠加。 $user = User::query()->lockForUpdate()->findOrFail($lockedOrder->user_id); $this->vipService->grantVip($user, $lockedOrder->vip_level_id, (int) $lockedOrder->vip_duration_days); $lockedOrder->update([ 'opened_vip_at' => now(), ]); } return $lockedOrder->fresh(['user', 'vipLevel']); }); } /** * 生成本地 VIP 订单号 */ private function generateOrderNo(): string { return 'VPO'.date('YmdHis').random_int(1000, 9999); } }