fix(api): only finalize self-serve refunds once the provider confirms (#1731)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-19 02:57:58 +02:00
1 parent d148b4e5b7
commit 591e9fe2ba
7 files changed
+248 -23

No files matched your search

@@ -2,12 +2,14 @@
import crypto from 'node:crypto';
import {PremiumFlags, UserPremiumTypes} from '@fluxer/constants/src/UserConstants';
import {HttpResponse, http} from 'msw';
import {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest';
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
import {createUserID} from '../../BrandedTypes';
import {Config} from '../../Config';
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
import {createMockWebhookPayload, type StripeWebhookEventData} from '../../test/msw/handlers/StripeApiHandlers';
import {server} from '../../test/msw/server';
import {createBuilder} from '../../test/TestRequestBuilder';
import {UserRepository} from '../../user/repositories/UserRepository';
import {setupSyncStripeWebhookWorker} from './StripeWebhookTestUtils';
@@ -341,4 +343,90 @@ describe('Stripe Webhook Refund', () => {
expect(updatedRedeemer.premium_type).toBe(UserPremiumTypes.LIFETIME);
});
});
describe('refund.updated', () => {
test('finalizes self-serve cooldown and cancels the subscription once the refund is confirmed succeeded', async () => {
const account = await createTestAccount(harness);
const userId = createUserID(BigInt(account.userId));
const userRepository = new UserRepository();
const subscriptionId = 'sub_test_webhook_finalize';
await userRepository.patchUpsert(
userId,
{stripe_subscription_id: subscriptionId},
(await userRepository.findUnique(userId))!.toRow(),
);
server.use(
http.delete('https://api.stripe.com/v1/subscriptions/:id', ({params}) =>
HttpResponse.json({id: params.id, object: 'subscription', status: 'canceled'}),
),
);
await sendWebhook({
type: 'refund.updated',
data: {
object: {
id: 'pyr_test_webhook_finalize',
status: 'succeeded',
amount: 2499,
currency: 'brl',
metadata: {
refund_kind: 'self_serve',
user_id: account.userId.toString(),
invoice_id: 'in_test_webhook_finalize',
subscription_id: subscriptionId,
},
},
},
});
const updatedUser = await userRepository.findUnique(userId);
expect(updatedUser!.firstRefundAt).not.toBeNull();
});
test('does not finalize cooldown while the refund is still pending', async () => {
const account = await createTestAccount(harness);
const userId = createUserID(BigInt(account.userId));
const userRepository = new UserRepository();
await sendWebhook({
type: 'refund.updated',
data: {
object: {
id: 'pyr_test_webhook_pending',
status: 'pending',
amount: 2499,
currency: 'brl',
metadata: {
refund_kind: 'self_serve',
user_id: account.userId.toString(),
invoice_id: 'in_test_webhook_pending',
},
},
},
});
const updatedUser = await userRepository.findUnique(userId);
expect(updatedUser!.firstRefundAt).toBeNull();
});
});
describe('refund.failed', () => {
test('does not finalize cooldown when the refund ultimately fails', async () => {
const account = await createTestAccount(harness);
const userId = createUserID(BigInt(account.userId));
const userRepository = new UserRepository();
await sendWebhook({
type: 'refund.failed',
data: {
object: {
id: 'pyr_test_webhook_failed',
status: 'failed',
failure_reason: 'unknown',
amount: 2499,
currency: 'brl',
metadata: {
refund_kind: 'self_serve',
user_id: account.userId.toString(),
invoice_id: 'in_test_webhook_failed',
},
},
},
});
const updatedUser = await userRepository.findUnique(userId);
expect(updatedUser!.firstRefundAt).toBeNull();
});
});
});