- Add Stripe and Mercado Pago providers for handling subscriptions. - Create billing repository and access services for managing billing records. - Implement webhook handlers for processing events from Stripe and Mercado Pago. - Add routes for checkout, canceling subscriptions, and retrieving billing status. - Introduce billing status management with appropriate state transitions. - Create tests for billing functionalities including webhook idempotency and provider resolution. - Document the billing module architecture, environment variables, and API endpoints.
84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
import { describe, expect, it, mock } from 'bun:test';
|
|
import { prismaMock } from '../support/prisma.mock';
|
|
|
|
describe('webhook-idempotency', () => {
|
|
it('returns conflict when providerEventId already exists', async () => {
|
|
const existingEvent = { id: 'event-1' };
|
|
|
|
prismaMock.billingEvent.findUnique.mockResolvedValue(existingEvent as never);
|
|
|
|
const { findEventByProviderId } = await import(
|
|
'@/modules/billing/services/billing-repository.service'
|
|
);
|
|
|
|
const result = await findEventByProviderId('mp-duplicate-id');
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.value).toEqual({ id: 'event-1' });
|
|
}
|
|
});
|
|
|
|
it('returns null when providerEventId does not exist', async () => {
|
|
prismaMock.billingEvent.findUnique.mockResolvedValue(null as never);
|
|
|
|
const { findEventByProviderId } = await import(
|
|
'@/modules/billing/services/billing-repository.service'
|
|
);
|
|
|
|
const result = await findEventByProviderId('new-event-id');
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.value).toBeNull();
|
|
}
|
|
});
|
|
|
|
it('records a billing event successfully', async () => {
|
|
const createdEvent = { id: 'new-uuid' };
|
|
prismaMock.billingEvent.create.mockResolvedValue(createdEvent as never);
|
|
|
|
const { recordBillingEvent } = await import(
|
|
'@/modules/billing/services/billing-repository.service'
|
|
);
|
|
|
|
const result = await recordBillingEvent({
|
|
complexId: 'complex-1',
|
|
eventType: 'activated',
|
|
provider: 'STRIPE',
|
|
providerEventId: 'evt_unique',
|
|
providerData: { some: 'data' },
|
|
previousStatus: 'TRIAL',
|
|
newStatus: 'ACTIVE',
|
|
});
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.value.id).toBe('new-uuid');
|
|
}
|
|
});
|
|
|
|
it('returns conflict when billing event create has P2002 (duplicate)', async () => {
|
|
const prismaError = new Error('Unique constraint') as Error & { code: string };
|
|
prismaError.code = 'P2002';
|
|
prismaMock.billingEvent.create.mockRejectedValue(prismaError as never);
|
|
|
|
const { recordBillingEvent } = await import(
|
|
'@/modules/billing/services/billing-repository.service'
|
|
);
|
|
|
|
const result = await recordBillingEvent({
|
|
complexId: 'complex-1',
|
|
eventType: 'activated',
|
|
provider: 'STRIPE',
|
|
providerEventId: 'evt_duplicate',
|
|
providerData: null,
|
|
previousStatus: null,
|
|
newStatus: null,
|
|
});
|
|
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) {
|
|
expect(result.error.type).toBe('conflict');
|
|
}
|
|
});
|
|
});
|