From b24a652a7b0f4222c5f411a684b49ce77a700cdc Mon Sep 17 00:00:00 2001 From: Ivan Moroz Date: Tue, 2 Jun 2026 21:15:15 +0300 Subject: [PATCH] CONV-268: Test duplicate credit pack purchase is ignored Co-Authored-By: Claude Sonnet 4.6 --- .../Billing/CreditPackWebhookHandlerTest.php | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/Feature/Billing/CreditPackWebhookHandlerTest.php b/tests/Feature/Billing/CreditPackWebhookHandlerTest.php index e55ebb5..f7abe93 100644 --- a/tests/Feature/Billing/CreditPackWebhookHandlerTest.php +++ b/tests/Feature/Billing/CreditPackWebhookHandlerTest.php @@ -27,3 +27,54 @@ expect(app(CreditLedger::class)->balance($user))->toBe($balanceBefore + 500); }); + +it('does not grant credits twice for duplicate credit pack checkout event', function () { + config()->set('billing.credit_packs.small.stripe_price_id', 'price_small'); + + $user = User::factory()->create(); + $balanceBefore = app(CreditLedger::class)->balance($user); + + $event = fakeStripeCheckoutCompletedEvent([ + 'event_id' => 'evt_duplicate_1', + 'checkout_session_id' => 'cs_duplicate_1', + 'user_id' => $user->id, + 'pack_key' => 'small', + 'pack_credits' => 500, + 'price_id' => 'price_small', + ]); + + app(CreditPackWebhookHandler::class)->handleCheckoutSessionCompleted($event); + app(CreditPackWebhookHandler::class)->handleCheckoutSessionCompleted($event); + + expect(app(CreditLedger::class)->balance($user))->toBe($balanceBefore + 500); +}); + +it('does not grant credits twice for same checkout session with different event ids', function () { + config()->set('billing.credit_packs.small.stripe_price_id', 'price_small'); + + $user = User::factory()->create(); + $balanceBefore = app(CreditLedger::class)->balance($user); + + $event1 = fakeStripeCheckoutCompletedEvent([ + 'event_id' => 'evt_dup_a', + 'checkout_session_id' => 'cs_same_session', + 'user_id' => $user->id, + 'pack_key' => 'small', + 'pack_credits' => 500, + 'price_id' => 'price_small', + ]); + + $event2 = fakeStripeCheckoutCompletedEvent([ + 'event_id' => 'evt_dup_b', + 'checkout_session_id' => 'cs_same_session', + 'user_id' => $user->id, + 'pack_key' => 'small', + 'pack_credits' => 500, + 'price_id' => 'price_small', + ]); + + app(CreditPackWebhookHandler::class)->handleCheckoutSessionCompleted($event1); + app(CreditPackWebhookHandler::class)->handleCheckoutSessionCompleted($event2); + + expect(app(CreditLedger::class)->balance($user))->toBe($balanceBefore + 500); +});