47 lines
1.8 KiB
PHP
47 lines
1.8 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Services\Billing\SubscriptionManager;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Receives Moneybird's webhook POSTs (see `php artisan moneybird:setup`).
|
|
* Moneybird includes the webhook's `webhook_token` in every delivery so we
|
|
* can confirm it's genuinely from the subscription we registered — that's
|
|
* verified with a constant-time comparison rather than an HMAC signature,
|
|
* since Moneybird's webhook docs describe a shared token, not a signed
|
|
* payload.
|
|
*
|
|
* The exact POST field names below (`webhook_token`, `entity_type`,
|
|
* `entity_id`) are Moneybird's documented webhook-delivery fields, but
|
|
* weren't independently confirmed against a live delivery while building
|
|
* this — the raw payload is logged so that's easy to double-check the
|
|
* first time a real event comes in, and the daily `app:sync-subscriptions`
|
|
* sweep means nothing breaks even if this needs adjusting.
|
|
*/
|
|
class MoneybirdWebhookController extends Controller
|
|
{
|
|
public function __invoke(Request $request, SubscriptionManager $subscriptions): Response
|
|
{
|
|
Log::info('Moneybird webhook received.', $request->all());
|
|
|
|
$expected = (string) config('services.moneybird.webhook_token');
|
|
$received = (string) $request->input('webhook_token', '');
|
|
|
|
if (blank($expected) || blank($received) || ! hash_equals($expected, $received)) {
|
|
abort(403);
|
|
}
|
|
|
|
$entityType = $request->input('entity_type');
|
|
$entityId = $request->input('entity_id');
|
|
|
|
if ($entityType === 'SalesInvoice' && filled($entityId)) {
|
|
$subscriptions->handleSalesInvoiceEvent((string) $entityId);
|
|
}
|
|
|
|
return response()->noContent();
|
|
}
|
|
}
|