extractMetadata($data); $intentId = $metadata['addon_intent'] ?? null; $addonKey = $metadata['addon_key'] ?? null; if (! $intentId || ! $addonKey) { return false; } $transactionId = $data['id'] ?? $data['transaction_id'] ?? null; $checkoutId = $data['checkout_id'] ?? null; $addon = EventPackageAddon::query() ->where('addon_key', $addonKey) ->where(function ($query) use ($intentId, $checkoutId, $transactionId) { $query->where('metadata->addon_intent', $intentId) ->orWhere('checkout_id', $checkoutId) ->orWhere('transaction_id', $transactionId); }) ->latest('id') ->first(); if (! $addon) { Log::info('[AddonWebhook] Add-on intent not found', [ 'intent' => $intentId, 'addon_key' => $addonKey, 'transaction_id' => $transactionId, ]); return false; } if ($addon->status === 'completed') { return true; // idempotent } $increments = $this->catalog->resolveIncrements($addonKey); DB::transaction(function () use ($addon, $transactionId, $checkoutId, $data, $increments) { $addon->forceFill([ 'transaction_id' => $transactionId, 'checkout_id' => $addon->checkout_id ?: $checkoutId, 'status' => 'completed', 'amount' => Arr::get($data, 'totals.grand_total') ?? Arr::get($data, 'amount'), 'currency' => Arr::get($data, 'currency_code') ?? Arr::get($data, 'currency'), 'metadata' => array_merge($addon->metadata ?? [], ['webhook_payload' => $data]), 'receipt_payload' => Arr::get($data, 'receipt_url') ? ['receipt_url' => Arr::get($data, 'receipt_url')] : null, 'purchased_at' => now(), ])->save(); /** @var EventPackage $eventPackage */ $eventPackage = EventPackage::query() ->lockForUpdate() ->find($addon->event_package_id); if (! $eventPackage) { return; } $eventPackage->forceFill([ 'extra_photos' => ($eventPackage->extra_photos ?? 0) + (int) ($increments['extra_photos'] ?? 0) * $addon->quantity, 'extra_guests' => ($eventPackage->extra_guests ?? 0) + (int) ($increments['extra_guests'] ?? 0) * $addon->quantity, 'extra_gallery_days' => ($eventPackage->extra_gallery_days ?? 0) + (int) ($increments['extra_gallery_days'] ?? 0) * $addon->quantity, ]); if (($increments['extra_gallery_days'] ?? 0) > 0) { $base = $eventPackage->gallery_expires_at ?? now(); $eventPackage->gallery_expires_at = $base->copy()->addDays((int) ($increments['extra_gallery_days'] ?? 0) * $addon->quantity); } $eventPackage->save(); $tenant = $addon->tenant; if ($tenant) { Notification::route('mail', [$tenant->contact_email ?? $tenant->user?->email]) ->notify(new AddonPurchaseReceipt($addon)); $opsEmail = config('mail.ops_address'); if ($opsEmail) { Notification::route('mail', $opsEmail)->notify(new AddonPurchased($addon)); } } }); return true; } /** * @param array $data * @return array */ private function extractMetadata(array $data): array { $metadata = []; if (isset($data['metadata']) && is_array($data['metadata'])) { $metadata = $data['metadata']; } if (isset($data['custom_data']) && is_array($data['custom_data'])) { $metadata = array_merge($metadata, $data['custom_data']); } return $metadata; } }