Harden credit flows and add RevenueCat webhook

This commit is contained in:
2025-09-25 14:05:58 +02:00
parent 9248d7a3f5
commit 215d19f07e
18 changed files with 804 additions and 190 deletions

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers;
use App\Jobs\ProcessRevenueCatWebhook;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class RevenueCatWebhookController extends Controller
{
public function handle(Request $request): JsonResponse
{
$secret = (string) config('services.revenuecat.webhook', '');
if ($secret === '') {
Log::error('RevenueCat webhook secret not configured');
return response()->json(['error' => 'Webhook not configured'], 500);
}
$signature = trim((string) $request->header('X-Signature', ''));
if ($signature === '') {
return response()->json(['error' => 'Signature missing'], 400);
}
$payload = $request->getContent();
if (! $this->signatureMatches($payload, $signature, $secret)) {
return response()->json(['error' => 'Invalid signature'], 400);
}
$decoded = json_decode($payload, true);
if (json_last_error() !== JSON_ERROR_NONE || ! is_array($decoded)) {
Log::warning('RevenueCat webhook received invalid JSON', [
'error' => json_last_error_msg(),
]);
return response()->json(['error' => 'Invalid payload'], 400);
}
ProcessRevenueCatWebhook::dispatch(
$decoded,
(string) $request->header('X-Event-Id', '')
);
return response()->json(['status' => 'accepted'], 202);
}
private function signatureMatches(string $payload, string $providedSignature, string $secret): bool
{
$normalized = preg_replace('/\s+/', '', $providedSignature);
if (! is_string($normalized)) {
return false;
}
$candidates = [
base64_encode(hash_hmac('sha1', $payload, $secret, true)),
base64_encode(hash_hmac('sha256', $payload, $secret, true)),
hash_hmac('sha1', $payload, $secret),
hash_hmac('sha256', $payload, $secret),
];
foreach ($candidates as $expected) {
if (hash_equals($expected, $normalized)) {
return true;
}
}
return false;
}
}