Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
StripeWebhookService
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
2 / 2
3
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
 handleIncomingEvent
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace App\Domain\Stripe\Service;
6
7use App\Domain\Stripe\Data\WebhookEventData;
8use App\Domain\Stripe\Repository\StripeWebhookRepository;
9
10/**
11 * Receives a verified Stripe webhook event and routes it through the
12 * persist + dispatch pipeline.
13 *
14 *   1. Verify signature via the StripeClient (caller passes raw body + sig)
15 *   2. Persist the event (PRIMARY KEY collision means duplicate delivery)
16 *   3. Dispatch to type-specific handlers (none implemented yet — every
17 *      received event is recorded but left unprocessed for ops review)
18 *
19 * Adding handlers later is a one-liner switch on {@see WebhookEventData::$type}.
20 */
21final readonly class StripeWebhookService
22{
23    public function __construct(
24        private StripeClientInterface $stripe,
25        private StripeWebhookRepository $repository,
26    ) {}
27
28    /**
29     * Returns the verified event so the action can log + return its id.
30     * @param string $payload
31     * @param string $signature
32     */
33    public function handleIncomingEvent(string $payload, string $signature): WebhookEventData
34    {
35        // Throws WebhookSignatureException on bad sig — caller renders 400.
36        $event = $this->stripe->parseWebhookEvent($payload, $signature);
37
38        $isFresh = $this->repository->recordReceived($event->id, $event->type, $event->payload);
39        if (!$isFresh) {
40            // Duplicate delivery (Stripe at-least-once). Already received
41            // and (presumably) handled. Idempotent no-op.
42            return $event;
43        }
44
45        // No handlers wired yet — future PRs can dispatch here based on
46        // $event->type. For now we leave processed_at NULL so an ops
47        // person can see that the event was received but not consumed.
48
49        return $event;
50    }
51}