sync($request->user())); } public function webhook(Request $request, RevenueCatService $revenueCat): JsonResponse { $expectedAuthorization = trim((string) config('billing.revenuecat.webhook_authorization')); $providedAuthorization = (string) $request->header('Authorization'); abort_if( $expectedAuthorization === '' || ! hash_equals($expectedAuthorization, $providedAuthorization), 401, ); $event = $request->input('event'); abort_unless(is_array($event), 422); $eventId = $this->stringValue($event['id'] ?? null); $eventType = $this->stringValue($event['type'] ?? null); abort_unless($eventId && $eventType, 422); $webhookEvent = RevenueCatWebhookEvent::query()->firstOrCreate([ 'event_id' => $eventId, ], [ 'type' => $eventType, 'app_user_id' => $this->stringValue($event['app_user_id'] ?? null), 'product_id' => $this->stringValue($event['product_id'] ?? null), 'entitlement_ids' => $this->stringList($event['entitlement_ids'] ?? []), 'environment' => $this->stringValue($event['environment'] ?? null), 'payload' => $event, ]); if ($webhookEvent->processed_at !== null) { return response()->json(['received' => true]); } $user = $this->userFromEvent($event); if ($user) { $revenueCat->sync($user); if ($eventType === 'BILLING_ISSUE') { $user->notify( (new PaymentFailedNotification)->locale($user->preferredLocale()), ); } } $webhookEvent->forceFill(['processed_at' => now()])->save(); return response()->json(['received' => true]); } /** * @param array $event */ private function userFromEvent(array $event): ?User { $identifiers = collect([ $event['app_user_id'] ?? null, $event['original_app_user_id'] ?? null, ]) ->merge(Arr::wrap($event['aliases'] ?? [])) ->merge(Arr::wrap($event['transferred_to'] ?? [])) ->filter(fn (mixed $identifier): bool => is_string($identifier) && $identifier !== '') ->unique() ->values(); return User::query()->whereIn('id', $identifiers)->first(); } /** * @return list */ private function stringList(mixed $value): array { return collect(Arr::wrap($value)) ->filter(fn (mixed $item): bool => is_string($item) && $item !== '') ->values() ->all(); } private function stringValue(mixed $value): ?string { return is_string($value) && $value !== '' ? $value : null; } }