Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
6 / 6 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
| StripeWebhookService | |
100.00% |
6 / 6 |
|
100.00% |
2 / 2 |
3 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| handleIncomingEvent | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace App\Domain\Stripe\Service; |
| 6 | |
| 7 | use App\Domain\Stripe\Data\WebhookEventData; |
| 8 | use 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 | */ |
| 21 | final 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 | } |