Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
WebhookAction
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
2 / 2
2
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __invoke
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace App\Action\Stripe;
6
7use App\Domain\Stripe\Service\StripeWebhookService;
8use App\Renderer\JsonRenderer;
9use Psr\Http\Message\ResponseInterface;
10use Psr\Http\Message\ServerRequestInterface;
11
12/**
13 * POST /api/stripe/webhook
14 *
15 * Receives Stripe webhook events. NOT auth-gated — Stripe authenticates
16 * via the `Stripe-Signature` header, which the service verifies against
17 * our configured webhook_secret. A bad signature throws
18 * {@see \App\Domain\Stripe\Exception\WebhookSignatureException}, which
19 * ExceptionMiddleware translates into a 400.
20 *
21 * On success returns 200 with the event id so Stripe stops retrying.
22 * Duplicate events are idempotent — recordReceived returns false on
23 * conflict and we still respond 200.
24 */
25final readonly class WebhookAction
26{
27    public function __construct(
28        private StripeWebhookService $service,
29        private JsonRenderer $renderer,
30    ) {}
31
32    public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
33    {
34        $payload = (string)$request->getBody();
35        $signature = $request->getHeaderLine('Stripe-Signature');
36
37        $event = $this->service->handleIncomingEvent($payload, $signature);
38
39        return $this->renderer->json($response, [
40            'success' => true,
41            'data' => [
42                'eventId' => $event->id,
43                'type' => $event->type,
44            ],
45        ]);
46    }
47}