- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8 - Actualizaciones de configuracion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1091 lines
50 KiB
JavaScript
1091 lines
50 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const testing_1 = require("@nestjs/testing");
|
|
const typeorm_1 = require("@nestjs/typeorm");
|
|
const config_1 = require("@nestjs/config");
|
|
const common_1 = require("@nestjs/common");
|
|
const stripe_service_1 = require("../services/stripe.service");
|
|
const subscription_entity_1 = require("../entities/subscription.entity");
|
|
const invoice_entity_1 = require("../entities/invoice.entity");
|
|
const payment_method_entity_1 = require("../entities/payment-method.entity");
|
|
const stripe_webhook_dto_1 = require("../dto/stripe-webhook.dto");
|
|
jest.mock('stripe', () => {
|
|
const mockStripe = {
|
|
customers: {
|
|
create: jest.fn(),
|
|
retrieve: jest.fn(),
|
|
update: jest.fn(),
|
|
search: jest.fn(),
|
|
},
|
|
subscriptions: {
|
|
create: jest.fn(),
|
|
retrieve: jest.fn(),
|
|
update: jest.fn(),
|
|
cancel: jest.fn(),
|
|
},
|
|
checkout: {
|
|
sessions: {
|
|
create: jest.fn(),
|
|
},
|
|
},
|
|
billingPortal: {
|
|
sessions: {
|
|
create: jest.fn(),
|
|
},
|
|
},
|
|
paymentMethods: {
|
|
attach: jest.fn(),
|
|
detach: jest.fn(),
|
|
list: jest.fn(),
|
|
},
|
|
prices: {
|
|
list: jest.fn(),
|
|
retrieve: jest.fn(),
|
|
},
|
|
setupIntents: {
|
|
create: jest.fn(),
|
|
},
|
|
webhooks: {
|
|
constructEvent: jest.fn(),
|
|
},
|
|
};
|
|
return jest.fn(() => mockStripe);
|
|
});
|
|
describe('StripeService', () => {
|
|
let service;
|
|
let configService;
|
|
let subscriptionRepo;
|
|
let invoiceRepo;
|
|
let paymentMethodRepo;
|
|
let mockStripeInstance;
|
|
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
|
|
const mockCustomerId = 'cus_test123';
|
|
const mockSubscriptionId = 'sub_test123';
|
|
const mockPriceId = 'price_test123';
|
|
beforeEach(async () => {
|
|
const mockConfigService = {
|
|
get: jest.fn((key) => {
|
|
if (key === 'STRIPE_SECRET_KEY')
|
|
return 'sk_test_123';
|
|
if (key === 'STRIPE_WEBHOOK_SECRET')
|
|
return 'whsec_test123';
|
|
return null;
|
|
}),
|
|
};
|
|
const mockSubscriptionRepo = {
|
|
create: jest.fn(),
|
|
save: jest.fn(),
|
|
findOne: jest.fn(),
|
|
find: jest.fn(),
|
|
};
|
|
const mockInvoiceRepo = {
|
|
create: jest.fn(),
|
|
save: jest.fn(),
|
|
findOne: jest.fn(),
|
|
find: jest.fn(),
|
|
};
|
|
const mockPaymentMethodRepo = {
|
|
create: jest.fn(),
|
|
save: jest.fn(),
|
|
findOne: jest.fn(),
|
|
find: jest.fn(),
|
|
};
|
|
const module = await testing_1.Test.createTestingModule({
|
|
providers: [
|
|
stripe_service_1.StripeService,
|
|
{ provide: config_1.ConfigService, useValue: mockConfigService },
|
|
{ provide: (0, typeorm_1.getRepositoryToken)(subscription_entity_1.Subscription), useValue: mockSubscriptionRepo },
|
|
{ provide: (0, typeorm_1.getRepositoryToken)(invoice_entity_1.Invoice), useValue: mockInvoiceRepo },
|
|
{ provide: (0, typeorm_1.getRepositoryToken)(payment_method_entity_1.PaymentMethod), useValue: mockPaymentMethodRepo },
|
|
],
|
|
}).compile();
|
|
service = module.get(stripe_service_1.StripeService);
|
|
configService = module.get(config_1.ConfigService);
|
|
subscriptionRepo = module.get((0, typeorm_1.getRepositoryToken)(subscription_entity_1.Subscription));
|
|
invoiceRepo = module.get((0, typeorm_1.getRepositoryToken)(invoice_entity_1.Invoice));
|
|
paymentMethodRepo = module.get((0, typeorm_1.getRepositoryToken)(payment_method_entity_1.PaymentMethod));
|
|
service.onModuleInit();
|
|
mockStripeInstance = service.stripe;
|
|
});
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
describe('onModuleInit', () => {
|
|
it('should initialize Stripe client when API key is configured', () => {
|
|
expect(mockStripeInstance).toBeDefined();
|
|
});
|
|
it('should warn when Stripe API key is not configured', () => {
|
|
const warnSpy = jest.spyOn(service['logger'], 'warn');
|
|
configService.get.mockReturnValue(undefined);
|
|
service.onModuleInit();
|
|
expect(warnSpy).toHaveBeenCalledWith('STRIPE_SECRET_KEY not configured - Stripe integration disabled');
|
|
});
|
|
});
|
|
describe('ensureStripeConfigured', () => {
|
|
it('should throw BadRequestException when Stripe is not configured', () => {
|
|
service.stripe = null;
|
|
expect(() => service.ensureStripeConfigured()).toThrow(common_1.BadRequestException);
|
|
});
|
|
});
|
|
describe('createCustomer', () => {
|
|
it('should create a Stripe customer successfully', async () => {
|
|
const mockCustomer = {
|
|
id: mockCustomerId,
|
|
email: 'test@example.com',
|
|
name: 'Test User',
|
|
metadata: { tenant_id: mockTenantId },
|
|
};
|
|
mockStripeInstance.customers.create.mockResolvedValue(mockCustomer);
|
|
const result = await service.createCustomer({
|
|
tenant_id: mockTenantId,
|
|
email: 'test@example.com',
|
|
name: 'Test User',
|
|
});
|
|
expect(result).toEqual(mockCustomer);
|
|
expect(mockStripeInstance.customers.create).toHaveBeenCalledWith({
|
|
email: 'test@example.com',
|
|
name: 'Test User',
|
|
metadata: {
|
|
tenant_id: mockTenantId,
|
|
},
|
|
});
|
|
});
|
|
it('should create customer with additional metadata', async () => {
|
|
const mockCustomer = {
|
|
id: mockCustomerId,
|
|
email: 'test@example.com',
|
|
metadata: { tenant_id: mockTenantId, company: 'Acme Inc' },
|
|
};
|
|
mockStripeInstance.customers.create.mockResolvedValue(mockCustomer);
|
|
await service.createCustomer({
|
|
tenant_id: mockTenantId,
|
|
email: 'test@example.com',
|
|
metadata: { company: 'Acme Inc' },
|
|
});
|
|
expect(mockStripeInstance.customers.create).toHaveBeenCalledWith({
|
|
email: 'test@example.com',
|
|
name: undefined,
|
|
metadata: {
|
|
tenant_id: mockTenantId,
|
|
company: 'Acme Inc',
|
|
},
|
|
});
|
|
});
|
|
});
|
|
describe('getCustomer', () => {
|
|
it('should retrieve a customer by ID', async () => {
|
|
const mockCustomer = { id: mockCustomerId, email: 'test@example.com' };
|
|
mockStripeInstance.customers.retrieve.mockResolvedValue(mockCustomer);
|
|
const result = await service.getCustomer(mockCustomerId);
|
|
expect(result).toEqual(mockCustomer);
|
|
});
|
|
it('should return null when customer not found', async () => {
|
|
mockStripeInstance.customers.retrieve.mockRejectedValue({
|
|
code: 'resource_missing',
|
|
});
|
|
const result = await service.getCustomer('invalid_id');
|
|
expect(result).toBeNull();
|
|
});
|
|
it('should throw error for non-resource-missing errors', async () => {
|
|
mockStripeInstance.customers.retrieve.mockRejectedValue(new Error('API Error'));
|
|
await expect(service.getCustomer('cus_123')).rejects.toThrow('API Error');
|
|
});
|
|
});
|
|
describe('findCustomerByTenantId', () => {
|
|
it('should find customer by tenant ID metadata', async () => {
|
|
const mockCustomer = {
|
|
id: mockCustomerId,
|
|
metadata: { tenant_id: mockTenantId },
|
|
};
|
|
mockStripeInstance.customers.search.mockResolvedValue({
|
|
data: [mockCustomer],
|
|
});
|
|
const result = await service.findCustomerByTenantId(mockTenantId);
|
|
expect(result).toEqual(mockCustomer);
|
|
expect(mockStripeInstance.customers.search).toHaveBeenCalledWith({
|
|
query: `metadata['tenant_id']:'${mockTenantId}'`,
|
|
});
|
|
});
|
|
it('should return null when no customer found', async () => {
|
|
mockStripeInstance.customers.search.mockResolvedValue({ data: [] });
|
|
const result = await service.findCustomerByTenantId('unknown_tenant');
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
describe('createSubscription', () => {
|
|
it('should create subscription without trial', async () => {
|
|
const mockSubscription = {
|
|
id: mockSubscriptionId,
|
|
customer: mockCustomerId,
|
|
items: { data: [{ price: mockPriceId }] },
|
|
status: 'active',
|
|
};
|
|
mockStripeInstance.subscriptions.create.mockResolvedValue(mockSubscription);
|
|
const result = await service.createSubscription({
|
|
customer_id: mockCustomerId,
|
|
price_id: mockPriceId,
|
|
});
|
|
expect(result).toEqual(mockSubscription);
|
|
expect(mockStripeInstance.subscriptions.create).toHaveBeenCalledWith({
|
|
customer: mockCustomerId,
|
|
items: [{ price: mockPriceId }],
|
|
payment_behavior: 'default_incomplete',
|
|
payment_settings: {
|
|
save_default_payment_method: 'on_subscription',
|
|
},
|
|
expand: ['latest_invoice.payment_intent'],
|
|
metadata: undefined,
|
|
});
|
|
});
|
|
it('should create subscription with trial period', async () => {
|
|
const mockSubscription = {
|
|
id: mockSubscriptionId,
|
|
customer: mockCustomerId,
|
|
status: 'trialing',
|
|
trial_end: Math.floor(Date.now() / 1000) + 14 * 24 * 60 * 60,
|
|
};
|
|
mockStripeInstance.subscriptions.create.mockResolvedValue(mockSubscription);
|
|
const result = await service.createSubscription({
|
|
customer_id: mockCustomerId,
|
|
price_id: mockPriceId,
|
|
trial_period_days: 14,
|
|
});
|
|
expect(result.status).toBe('trialing');
|
|
expect(mockStripeInstance.subscriptions.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
trial_period_days: 14,
|
|
}));
|
|
});
|
|
it('should create subscription with custom metadata', async () => {
|
|
const mockSubscription = {
|
|
id: mockSubscriptionId,
|
|
metadata: { tenant_id: mockTenantId, plan: 'pro' },
|
|
};
|
|
mockStripeInstance.subscriptions.create.mockResolvedValue(mockSubscription);
|
|
await service.createSubscription({
|
|
customer_id: mockCustomerId,
|
|
price_id: mockPriceId,
|
|
metadata: { tenant_id: mockTenantId, plan: 'pro' },
|
|
});
|
|
expect(mockStripeInstance.subscriptions.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
metadata: { tenant_id: mockTenantId, plan: 'pro' },
|
|
}));
|
|
});
|
|
});
|
|
describe('getStripeSubscription', () => {
|
|
it('should retrieve subscription by ID', async () => {
|
|
const mockSubscription = { id: mockSubscriptionId, status: 'active' };
|
|
mockStripeInstance.subscriptions.retrieve.mockResolvedValue(mockSubscription);
|
|
const result = await service.getStripeSubscription(mockSubscriptionId);
|
|
expect(result).toEqual(mockSubscription);
|
|
});
|
|
it('should return null when subscription not found', async () => {
|
|
mockStripeInstance.subscriptions.retrieve.mockRejectedValue({
|
|
code: 'resource_missing',
|
|
});
|
|
const result = await service.getStripeSubscription('invalid_sub');
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
describe('cancelStripeSubscription', () => {
|
|
it('should cancel subscription immediately', async () => {
|
|
const mockCancelled = {
|
|
id: mockSubscriptionId,
|
|
status: 'canceled',
|
|
canceled_at: Date.now() / 1000,
|
|
};
|
|
mockStripeInstance.subscriptions.cancel.mockResolvedValue(mockCancelled);
|
|
const result = await service.cancelStripeSubscription(mockSubscriptionId, {
|
|
immediately: true,
|
|
});
|
|
expect(result.status).toBe('canceled');
|
|
expect(mockStripeInstance.subscriptions.cancel).toHaveBeenCalledWith(mockSubscriptionId);
|
|
});
|
|
it('should schedule cancellation at period end', async () => {
|
|
const mockScheduled = {
|
|
id: mockSubscriptionId,
|
|
status: 'active',
|
|
cancel_at_period_end: true,
|
|
};
|
|
mockStripeInstance.subscriptions.update.mockResolvedValue(mockScheduled);
|
|
const result = await service.cancelStripeSubscription(mockSubscriptionId, {
|
|
immediately: false,
|
|
});
|
|
expect(result.cancel_at_period_end).toBe(true);
|
|
expect(mockStripeInstance.subscriptions.update).toHaveBeenCalledWith(mockSubscriptionId, { cancel_at_period_end: true });
|
|
});
|
|
it('should default to end-of-period cancellation', async () => {
|
|
mockStripeInstance.subscriptions.update.mockResolvedValue({
|
|
id: mockSubscriptionId,
|
|
cancel_at_period_end: true,
|
|
});
|
|
await service.cancelStripeSubscription(mockSubscriptionId);
|
|
expect(mockStripeInstance.subscriptions.update).toHaveBeenCalledWith(mockSubscriptionId, { cancel_at_period_end: true });
|
|
});
|
|
});
|
|
describe('updateStripeSubscription (upgrade/downgrade)', () => {
|
|
it('should upgrade subscription with proration', async () => {
|
|
const currentSubscription = {
|
|
id: mockSubscriptionId,
|
|
items: { data: [{ id: 'si_123', price: { id: 'price_basic' } }] },
|
|
};
|
|
const upgradedSubscription = {
|
|
id: mockSubscriptionId,
|
|
items: { data: [{ id: 'si_123', price: { id: 'price_pro' } }] },
|
|
};
|
|
mockStripeInstance.subscriptions.retrieve.mockResolvedValue(currentSubscription);
|
|
mockStripeInstance.subscriptions.update.mockResolvedValue(upgradedSubscription);
|
|
const result = await service.updateStripeSubscription(mockSubscriptionId, 'price_pro');
|
|
expect(mockStripeInstance.subscriptions.update).toHaveBeenCalledWith(mockSubscriptionId, {
|
|
items: [{ id: 'si_123', price: 'price_pro' }],
|
|
proration_behavior: 'create_prorations',
|
|
});
|
|
expect(result.items.data[0].price.id).toBe('price_pro');
|
|
});
|
|
it('should downgrade subscription with proration', async () => {
|
|
const currentSubscription = {
|
|
id: mockSubscriptionId,
|
|
items: { data: [{ id: 'si_123', price: { id: 'price_pro' } }] },
|
|
};
|
|
mockStripeInstance.subscriptions.retrieve.mockResolvedValue(currentSubscription);
|
|
mockStripeInstance.subscriptions.update.mockResolvedValue({
|
|
id: mockSubscriptionId,
|
|
items: { data: [{ id: 'si_123', price: { id: 'price_basic' } }] },
|
|
});
|
|
const result = await service.updateStripeSubscription(mockSubscriptionId, 'price_basic');
|
|
expect(mockStripeInstance.subscriptions.update).toHaveBeenCalledWith(mockSubscriptionId, expect.objectContaining({
|
|
proration_behavior: 'create_prorations',
|
|
}));
|
|
});
|
|
});
|
|
describe('createCheckoutSession', () => {
|
|
it('should create checkout session successfully', async () => {
|
|
const mockSession = {
|
|
id: 'cs_test123',
|
|
url: 'https://checkout.stripe.com/...',
|
|
};
|
|
mockStripeInstance.customers.search.mockResolvedValue({
|
|
data: [{ id: mockCustomerId }],
|
|
});
|
|
mockStripeInstance.checkout.sessions.create.mockResolvedValue(mockSession);
|
|
const result = await service.createCheckoutSession({
|
|
tenant_id: mockTenantId,
|
|
price_id: mockPriceId,
|
|
success_url: 'https://app.example.com/success',
|
|
cancel_url: 'https://app.example.com/cancel',
|
|
});
|
|
expect(result).toEqual(mockSession);
|
|
expect(mockStripeInstance.checkout.sessions.create).toHaveBeenCalledWith({
|
|
customer: mockCustomerId,
|
|
mode: 'subscription',
|
|
line_items: [{ price: mockPriceId, quantity: 1 }],
|
|
success_url: 'https://app.example.com/success',
|
|
cancel_url: 'https://app.example.com/cancel',
|
|
subscription_data: {
|
|
metadata: { tenant_id: mockTenantId },
|
|
},
|
|
});
|
|
});
|
|
it('should create checkout session with trial period', async () => {
|
|
mockStripeInstance.customers.search.mockResolvedValue({
|
|
data: [{ id: mockCustomerId }],
|
|
});
|
|
mockStripeInstance.checkout.sessions.create.mockResolvedValue({
|
|
id: 'cs_test123',
|
|
});
|
|
await service.createCheckoutSession({
|
|
tenant_id: mockTenantId,
|
|
price_id: mockPriceId,
|
|
success_url: 'https://app.example.com/success',
|
|
cancel_url: 'https://app.example.com/cancel',
|
|
trial_period_days: 14,
|
|
});
|
|
expect(mockStripeInstance.checkout.sessions.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
subscription_data: expect.objectContaining({
|
|
trial_period_days: 14,
|
|
}),
|
|
}));
|
|
});
|
|
it('should throw NotFoundException when customer not found', async () => {
|
|
mockStripeInstance.customers.search.mockResolvedValue({ data: [] });
|
|
await expect(service.createCheckoutSession({
|
|
tenant_id: 'unknown_tenant',
|
|
price_id: mockPriceId,
|
|
success_url: 'https://app.example.com/success',
|
|
cancel_url: 'https://app.example.com/cancel',
|
|
})).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
});
|
|
describe('createBillingPortalSession', () => {
|
|
it('should create billing portal session successfully', async () => {
|
|
const mockSession = {
|
|
id: 'bps_test123',
|
|
url: 'https://billing.stripe.com/...',
|
|
};
|
|
mockStripeInstance.customers.search.mockResolvedValue({
|
|
data: [{ id: mockCustomerId }],
|
|
});
|
|
mockStripeInstance.billingPortal.sessions.create.mockResolvedValue(mockSession);
|
|
const result = await service.createBillingPortalSession({
|
|
tenant_id: mockTenantId,
|
|
return_url: 'https://app.example.com/billing',
|
|
});
|
|
expect(result).toEqual(mockSession);
|
|
expect(mockStripeInstance.billingPortal.sessions.create).toHaveBeenCalledWith({
|
|
customer: mockCustomerId,
|
|
return_url: 'https://app.example.com/billing',
|
|
});
|
|
});
|
|
it('should throw NotFoundException when customer not found', async () => {
|
|
mockStripeInstance.customers.search.mockResolvedValue({ data: [] });
|
|
await expect(service.createBillingPortalSession({
|
|
tenant_id: 'unknown_tenant',
|
|
return_url: 'https://app.example.com/billing',
|
|
})).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
});
|
|
describe('attachPaymentMethod', () => {
|
|
it('should attach payment method to customer', async () => {
|
|
const mockPaymentMethod = {
|
|
id: 'pm_test123',
|
|
customer: mockCustomerId,
|
|
type: 'card',
|
|
};
|
|
mockStripeInstance.paymentMethods.attach.mockResolvedValue(mockPaymentMethod);
|
|
const result = await service.attachPaymentMethod('pm_test123', mockCustomerId);
|
|
expect(result).toEqual(mockPaymentMethod);
|
|
expect(mockStripeInstance.paymentMethods.attach).toHaveBeenCalledWith('pm_test123', { customer: mockCustomerId });
|
|
});
|
|
});
|
|
describe('detachPaymentMethod', () => {
|
|
it('should detach payment method', async () => {
|
|
const mockPaymentMethod = {
|
|
id: 'pm_test123',
|
|
customer: null,
|
|
};
|
|
mockStripeInstance.paymentMethods.detach.mockResolvedValue(mockPaymentMethod);
|
|
const result = await service.detachPaymentMethod('pm_test123');
|
|
expect(result.customer).toBeNull();
|
|
});
|
|
});
|
|
describe('listPaymentMethods', () => {
|
|
it('should list customer payment methods', async () => {
|
|
const mockPaymentMethods = {
|
|
data: [
|
|
{ id: 'pm_1', type: 'card', card: { last4: '4242' } },
|
|
{ id: 'pm_2', type: 'card', card: { last4: '1234' } },
|
|
],
|
|
};
|
|
mockStripeInstance.paymentMethods.list.mockResolvedValue(mockPaymentMethods);
|
|
const result = await service.listPaymentMethods(mockCustomerId);
|
|
expect(result).toHaveLength(2);
|
|
expect(mockStripeInstance.paymentMethods.list).toHaveBeenCalledWith({
|
|
customer: mockCustomerId,
|
|
type: 'card',
|
|
});
|
|
});
|
|
});
|
|
describe('setDefaultPaymentMethod', () => {
|
|
it('should set default payment method for customer', async () => {
|
|
const mockCustomer = {
|
|
id: mockCustomerId,
|
|
invoice_settings: { default_payment_method: 'pm_test123' },
|
|
};
|
|
mockStripeInstance.customers.update.mockResolvedValue(mockCustomer);
|
|
const result = await service.setDefaultPaymentMethod(mockCustomerId, 'pm_test123');
|
|
expect(result.invoice_settings.default_payment_method).toBe('pm_test123');
|
|
expect(mockStripeInstance.customers.update).toHaveBeenCalledWith(mockCustomerId, {
|
|
invoice_settings: { default_payment_method: 'pm_test123' },
|
|
});
|
|
});
|
|
});
|
|
describe('constructWebhookEvent', () => {
|
|
it('should construct webhook event with valid signature', () => {
|
|
const mockEvent = {
|
|
id: 'evt_test123',
|
|
type: 'customer.subscription.updated',
|
|
data: { object: {} },
|
|
};
|
|
mockStripeInstance.webhooks.constructEvent.mockReturnValue(mockEvent);
|
|
const payload = Buffer.from(JSON.stringify(mockEvent));
|
|
const signature = 'test_signature';
|
|
const result = service.constructWebhookEvent(payload, signature);
|
|
expect(result).toEqual(mockEvent);
|
|
expect(mockStripeInstance.webhooks.constructEvent).toHaveBeenCalledWith(payload, signature, 'whsec_test123');
|
|
});
|
|
it('should throw BadRequestException when webhook secret not configured', () => {
|
|
configService.get.mockImplementation((key) => {
|
|
if (key === 'STRIPE_SECRET_KEY')
|
|
return 'sk_test_123';
|
|
if (key === 'STRIPE_WEBHOOK_SECRET')
|
|
return undefined;
|
|
return null;
|
|
});
|
|
const payload = Buffer.from('{}');
|
|
expect(() => service.constructWebhookEvent(payload, 'sig')).toThrow(common_1.BadRequestException);
|
|
});
|
|
it('should throw error for invalid signature', () => {
|
|
mockStripeInstance.webhooks.constructEvent.mockImplementation(() => {
|
|
throw new Error('Invalid signature');
|
|
});
|
|
const payload = Buffer.from('{}');
|
|
expect(() => service.constructWebhookEvent(payload, 'invalid_sig')).toThrow('Invalid signature');
|
|
});
|
|
});
|
|
describe('handleWebhookEvent', () => {
|
|
describe('customer.subscription.updated', () => {
|
|
it('should sync subscription on update event', async () => {
|
|
const stripeSubscription = {
|
|
id: mockSubscriptionId,
|
|
status: 'active',
|
|
current_period_start: Math.floor(Date.now() / 1000),
|
|
current_period_end: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
|
|
metadata: { tenant_id: mockTenantId },
|
|
items: { data: [{ price: { id: mockPriceId, product: 'prod_123' } }] },
|
|
customer: mockCustomerId,
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.SUBSCRIPTION_UPDATED,
|
|
data: { object: stripeSubscription },
|
|
};
|
|
subscriptionRepo.findOne.mockResolvedValue(null);
|
|
subscriptionRepo.create.mockReturnValue({});
|
|
subscriptionRepo.save.mockResolvedValue({});
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).toHaveBeenCalled();
|
|
});
|
|
it('should update existing subscription', async () => {
|
|
const stripeSubscription = {
|
|
id: mockSubscriptionId,
|
|
status: 'active',
|
|
current_period_start: Math.floor(Date.now() / 1000),
|
|
current_period_end: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
|
|
metadata: { tenant_id: mockTenantId },
|
|
items: { data: [{ price: { id: mockPriceId, product: 'prod_123' } }] },
|
|
customer: mockCustomerId,
|
|
};
|
|
const existingSubscription = {
|
|
id: 'local-sub-123',
|
|
tenant_id: mockTenantId,
|
|
external_subscription_id: mockSubscriptionId,
|
|
status: subscription_entity_1.SubscriptionStatus.TRIAL,
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.SUBSCRIPTION_UPDATED,
|
|
data: { object: stripeSubscription },
|
|
};
|
|
subscriptionRepo.findOne.mockResolvedValue(existingSubscription);
|
|
subscriptionRepo.save.mockResolvedValue({
|
|
...existingSubscription,
|
|
status: subscription_entity_1.SubscriptionStatus.ACTIVE,
|
|
});
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
status: subscription_entity_1.SubscriptionStatus.ACTIVE,
|
|
}));
|
|
});
|
|
it('should handle subscription with trial_end', async () => {
|
|
const trialEnd = Math.floor(Date.now() / 1000) + 14 * 24 * 60 * 60;
|
|
const stripeSubscription = {
|
|
id: mockSubscriptionId,
|
|
status: 'trialing',
|
|
current_period_start: Math.floor(Date.now() / 1000),
|
|
current_period_end: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
|
|
trial_end: trialEnd,
|
|
metadata: { tenant_id: mockTenantId },
|
|
items: { data: [{ price: { id: mockPriceId, product: 'prod_123' } }] },
|
|
customer: mockCustomerId,
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.SUBSCRIPTION_UPDATED,
|
|
data: { object: stripeSubscription },
|
|
};
|
|
subscriptionRepo.findOne.mockResolvedValue(null);
|
|
subscriptionRepo.create.mockReturnValue({});
|
|
subscriptionRepo.save.mockResolvedValue({});
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
trial_end: new Date(trialEnd * 1000),
|
|
}));
|
|
});
|
|
it('should skip subscription without tenant_id metadata', async () => {
|
|
const stripeSubscription = {
|
|
id: mockSubscriptionId,
|
|
status: 'active',
|
|
metadata: {},
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.SUBSCRIPTION_UPDATED,
|
|
data: { object: stripeSubscription },
|
|
};
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('customer.subscription.deleted', () => {
|
|
it('should mark subscription as cancelled', async () => {
|
|
const stripeSubscription = {
|
|
id: mockSubscriptionId,
|
|
status: 'canceled',
|
|
};
|
|
const existingSubscription = {
|
|
id: 'local-sub-123',
|
|
external_subscription_id: mockSubscriptionId,
|
|
status: subscription_entity_1.SubscriptionStatus.ACTIVE,
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.SUBSCRIPTION_DELETED,
|
|
data: { object: stripeSubscription },
|
|
};
|
|
subscriptionRepo.findOne.mockResolvedValue(existingSubscription);
|
|
subscriptionRepo.save.mockResolvedValue({});
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
status: subscription_entity_1.SubscriptionStatus.CANCELLED,
|
|
cancelled_at: expect.any(Date),
|
|
}));
|
|
});
|
|
it('should handle deletion when subscription not found locally', async () => {
|
|
const stripeSubscription = {
|
|
id: 'unknown_sub_id',
|
|
status: 'canceled',
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.SUBSCRIPTION_DELETED,
|
|
data: { object: stripeSubscription },
|
|
};
|
|
subscriptionRepo.findOne.mockResolvedValue(null);
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('invoice.paid', () => {
|
|
it('should create and mark invoice as paid', async () => {
|
|
const stripeInvoice = {
|
|
id: 'in_test123',
|
|
number: 'INV-001',
|
|
subtotal: 10000,
|
|
tax: 1600,
|
|
total: 11600,
|
|
due_date: Math.floor(Date.now() / 1000) + 15 * 24 * 60 * 60,
|
|
subscription: mockSubscriptionId,
|
|
subscription_details: { metadata: { tenant_id: mockTenantId } },
|
|
invoice_pdf: 'https://stripe.com/invoice.pdf',
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.INVOICE_PAID,
|
|
data: { object: stripeInvoice },
|
|
};
|
|
invoiceRepo.findOne.mockResolvedValue(null);
|
|
invoiceRepo.create.mockReturnValue({});
|
|
invoiceRepo.save.mockResolvedValue({});
|
|
await service.handleWebhookEvent(event);
|
|
expect(invoiceRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
status: invoice_entity_1.InvoiceStatus.PAID,
|
|
paid_at: expect.any(Date),
|
|
external_invoice_id: 'in_test123',
|
|
pdf_url: 'https://stripe.com/invoice.pdf',
|
|
}));
|
|
});
|
|
it('should update existing invoice when paid', async () => {
|
|
const stripeInvoice = {
|
|
id: 'in_test123',
|
|
number: 'INV-001',
|
|
subtotal: 10000,
|
|
total: 11600,
|
|
subscription_details: { metadata: { tenant_id: mockTenantId } },
|
|
invoice_pdf: 'https://stripe.com/invoice.pdf',
|
|
};
|
|
const existingInvoice = {
|
|
id: 'local-inv-123',
|
|
invoice_number: 'INV-001',
|
|
status: invoice_entity_1.InvoiceStatus.OPEN,
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.INVOICE_PAID,
|
|
data: { object: stripeInvoice },
|
|
};
|
|
invoiceRepo.findOne.mockResolvedValue(existingInvoice);
|
|
invoiceRepo.save.mockResolvedValue({});
|
|
await service.handleWebhookEvent(event);
|
|
expect(invoiceRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
status: invoice_entity_1.InvoiceStatus.PAID,
|
|
}));
|
|
});
|
|
it('should skip invoice without tenant_id', async () => {
|
|
const stripeInvoice = {
|
|
id: 'in_test123',
|
|
subscription_details: { metadata: {} },
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.INVOICE_PAID,
|
|
data: { object: stripeInvoice },
|
|
};
|
|
await service.handleWebhookEvent(event);
|
|
expect(invoiceRepo.save).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('invoice.payment_failed', () => {
|
|
it('should mark subscription as past_due on payment failure', async () => {
|
|
const stripeInvoice = {
|
|
id: 'in_test123',
|
|
subscription_details: { metadata: { tenant_id: mockTenantId } },
|
|
};
|
|
const existingSubscription = {
|
|
id: 'local-sub-123',
|
|
tenant_id: mockTenantId,
|
|
status: subscription_entity_1.SubscriptionStatus.ACTIVE,
|
|
metadata: {},
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.INVOICE_PAYMENT_FAILED,
|
|
data: { object: stripeInvoice },
|
|
};
|
|
subscriptionRepo.findOne.mockResolvedValue(existingSubscription);
|
|
subscriptionRepo.save.mockResolvedValue({});
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
status: subscription_entity_1.SubscriptionStatus.PAST_DUE,
|
|
metadata: expect.objectContaining({
|
|
payment_failed_at: expect.any(String),
|
|
failed_invoice_id: 'in_test123',
|
|
}),
|
|
}));
|
|
});
|
|
it('should skip when no tenant_id in invoice', async () => {
|
|
const stripeInvoice = {
|
|
id: 'in_test123',
|
|
subscription_details: {},
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.INVOICE_PAYMENT_FAILED,
|
|
data: { object: stripeInvoice },
|
|
};
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).not.toHaveBeenCalled();
|
|
});
|
|
it('should handle payment failure when subscription not found', async () => {
|
|
const stripeInvoice = {
|
|
id: 'in_test123',
|
|
subscription_details: { metadata: { tenant_id: mockTenantId } },
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.INVOICE_PAYMENT_FAILED,
|
|
data: { object: stripeInvoice },
|
|
};
|
|
subscriptionRepo.findOne.mockResolvedValue(null);
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('payment_method.attached', () => {
|
|
it('should sync payment method when attached', async () => {
|
|
const stripePaymentMethod = {
|
|
id: 'pm_test123',
|
|
customer: mockCustomerId,
|
|
type: 'card',
|
|
card: {
|
|
brand: 'visa',
|
|
last4: '4242',
|
|
exp_month: 12,
|
|
exp_year: 2025,
|
|
},
|
|
};
|
|
const mockCustomer = {
|
|
id: mockCustomerId,
|
|
metadata: { tenant_id: mockTenantId },
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.PAYMENT_METHOD_ATTACHED,
|
|
data: { object: stripePaymentMethod },
|
|
};
|
|
const mockPaymentMethodObject = {
|
|
tenant_id: mockTenantId,
|
|
external_payment_method_id: 'pm_test123',
|
|
};
|
|
mockStripeInstance.customers.retrieve.mockResolvedValue(mockCustomer);
|
|
paymentMethodRepo.findOne.mockResolvedValue(null);
|
|
paymentMethodRepo.create.mockReturnValue(mockPaymentMethodObject);
|
|
paymentMethodRepo.save.mockResolvedValue({});
|
|
await service.handleWebhookEvent(event);
|
|
expect(paymentMethodRepo.create).toHaveBeenCalledWith({
|
|
tenant_id: mockTenantId,
|
|
external_payment_method_id: 'pm_test123',
|
|
});
|
|
expect(paymentMethodRepo.save).toHaveBeenCalled();
|
|
const savedObject = paymentMethodRepo.save.mock.calls[0][0];
|
|
expect(savedObject.card_brand).toBe('visa');
|
|
expect(savedObject.card_last_four).toBe('4242');
|
|
expect(savedObject.card_exp_month).toBe(12);
|
|
expect(savedObject.card_exp_year).toBe(2025);
|
|
expect(savedObject.payment_provider).toBe('stripe');
|
|
expect(savedObject.is_active).toBe(true);
|
|
});
|
|
it('should update existing payment method', async () => {
|
|
const stripePaymentMethod = {
|
|
id: 'pm_test123',
|
|
customer: mockCustomerId,
|
|
type: 'card',
|
|
card: {
|
|
brand: 'mastercard',
|
|
last4: '5555',
|
|
exp_month: 6,
|
|
exp_year: 2026,
|
|
},
|
|
};
|
|
const existingPaymentMethod = {
|
|
id: 'local-pm-123',
|
|
external_payment_method_id: 'pm_test123',
|
|
card_brand: 'visa',
|
|
};
|
|
const mockCustomer = {
|
|
id: mockCustomerId,
|
|
metadata: { tenant_id: mockTenantId },
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.PAYMENT_METHOD_ATTACHED,
|
|
data: { object: stripePaymentMethod },
|
|
};
|
|
mockStripeInstance.customers.retrieve.mockResolvedValue(mockCustomer);
|
|
paymentMethodRepo.findOne.mockResolvedValue(existingPaymentMethod);
|
|
paymentMethodRepo.save.mockResolvedValue({});
|
|
await service.handleWebhookEvent(event);
|
|
expect(paymentMethodRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
card_brand: 'mastercard',
|
|
card_last_four: '5555',
|
|
}));
|
|
});
|
|
it('should skip when no customer on payment method', async () => {
|
|
const stripePaymentMethod = {
|
|
id: 'pm_test123',
|
|
customer: null,
|
|
type: 'card',
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.PAYMENT_METHOD_ATTACHED,
|
|
data: { object: stripePaymentMethod },
|
|
};
|
|
await service.handleWebhookEvent(event);
|
|
expect(paymentMethodRepo.save).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('payment_method.detached', () => {
|
|
it('should deactivate payment method when detached', async () => {
|
|
const stripePaymentMethod = {
|
|
id: 'pm_test123',
|
|
};
|
|
const existingPaymentMethod = {
|
|
id: 'local-pm-123',
|
|
external_payment_method_id: 'pm_test123',
|
|
is_active: true,
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.PAYMENT_METHOD_DETACHED,
|
|
data: { object: stripePaymentMethod },
|
|
};
|
|
paymentMethodRepo.findOne.mockResolvedValue(existingPaymentMethod);
|
|
paymentMethodRepo.save.mockResolvedValue({});
|
|
await service.handleWebhookEvent(event);
|
|
expect(paymentMethodRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
is_active: false,
|
|
}));
|
|
});
|
|
it('should handle detachment when payment method not found locally', async () => {
|
|
const stripePaymentMethod = {
|
|
id: 'unknown_pm_id',
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.PAYMENT_METHOD_DETACHED,
|
|
data: { object: stripePaymentMethod },
|
|
};
|
|
paymentMethodRepo.findOne.mockResolvedValue(null);
|
|
await service.handleWebhookEvent(event);
|
|
expect(paymentMethodRepo.save).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('checkout.session.completed', () => {
|
|
it('should sync subscription on checkout completion', async () => {
|
|
const checkoutSession = {
|
|
id: 'cs_test123',
|
|
subscription: mockSubscriptionId,
|
|
metadata: { tenant_id: mockTenantId },
|
|
};
|
|
const stripeSubscription = {
|
|
id: mockSubscriptionId,
|
|
status: 'active',
|
|
current_period_start: Math.floor(Date.now() / 1000),
|
|
current_period_end: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
|
|
metadata: { tenant_id: mockTenantId },
|
|
items: { data: [{ price: { id: mockPriceId, product: 'prod_123' } }] },
|
|
customer: mockCustomerId,
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.CHECKOUT_SESSION_COMPLETED,
|
|
data: { object: checkoutSession },
|
|
};
|
|
mockStripeInstance.subscriptions.retrieve.mockResolvedValue(stripeSubscription);
|
|
subscriptionRepo.findOne.mockResolvedValue(null);
|
|
subscriptionRepo.create.mockReturnValue({});
|
|
subscriptionRepo.save.mockResolvedValue({});
|
|
await service.handleWebhookEvent(event);
|
|
expect(mockStripeInstance.subscriptions.retrieve).toHaveBeenCalledWith(mockSubscriptionId);
|
|
expect(subscriptionRepo.save).toHaveBeenCalled();
|
|
});
|
|
it('should skip when no subscription in checkout session', async () => {
|
|
const checkoutSession = {
|
|
id: 'cs_test123',
|
|
subscription: null,
|
|
metadata: { tenant_id: mockTenantId },
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.CHECKOUT_SESSION_COMPLETED,
|
|
data: { object: checkoutSession },
|
|
};
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).not.toHaveBeenCalled();
|
|
});
|
|
it('should skip when no tenant_id in checkout session', async () => {
|
|
const checkoutSession = {
|
|
id: 'cs_test123',
|
|
subscription: mockSubscriptionId,
|
|
metadata: {},
|
|
};
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: stripe_webhook_dto_1.StripeWebhookEventType.CHECKOUT_SESSION_COMPLETED,
|
|
data: { object: checkoutSession },
|
|
};
|
|
await service.handleWebhookEvent(event);
|
|
expect(subscriptionRepo.save).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('unhandled events', () => {
|
|
it('should log unhandled event types', async () => {
|
|
const logSpy = jest.spyOn(service['logger'], 'log');
|
|
const event = {
|
|
id: 'evt_test123',
|
|
type: 'some.unknown.event',
|
|
data: { object: {} },
|
|
};
|
|
await service.handleWebhookEvent(event);
|
|
expect(logSpy).toHaveBeenCalledWith('Unhandled event type: some.unknown.event');
|
|
});
|
|
});
|
|
});
|
|
describe('listPrices', () => {
|
|
it('should list all active prices', async () => {
|
|
const mockPrices = {
|
|
data: [
|
|
{ id: 'price_basic', unit_amount: 999 },
|
|
{ id: 'price_pro', unit_amount: 2999 },
|
|
],
|
|
};
|
|
mockStripeInstance.prices.list.mockResolvedValue(mockPrices);
|
|
const result = await service.listPrices();
|
|
expect(result).toHaveLength(2);
|
|
expect(mockStripeInstance.prices.list).toHaveBeenCalledWith({
|
|
active: true,
|
|
expand: ['data.product'],
|
|
});
|
|
});
|
|
it('should filter prices by product ID', async () => {
|
|
mockStripeInstance.prices.list.mockResolvedValue({ data: [] });
|
|
await service.listPrices('prod_123');
|
|
expect(mockStripeInstance.prices.list).toHaveBeenCalledWith({
|
|
active: true,
|
|
expand: ['data.product'],
|
|
product: 'prod_123',
|
|
});
|
|
});
|
|
});
|
|
describe('getPrice', () => {
|
|
it('should retrieve price by ID', async () => {
|
|
const mockPrice = { id: mockPriceId, unit_amount: 2999 };
|
|
mockStripeInstance.prices.retrieve.mockResolvedValue(mockPrice);
|
|
const result = await service.getPrice(mockPriceId);
|
|
expect(result).toEqual(mockPrice);
|
|
expect(mockStripeInstance.prices.retrieve).toHaveBeenCalledWith(mockPriceId, {
|
|
expand: ['product'],
|
|
});
|
|
});
|
|
it('should return null when price not found', async () => {
|
|
mockStripeInstance.prices.retrieve.mockRejectedValue({
|
|
code: 'resource_missing',
|
|
});
|
|
const result = await service.getPrice('invalid_price');
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
describe('createSetupIntent', () => {
|
|
it('should create setup intent for customer', async () => {
|
|
const mockSetupIntent = {
|
|
id: 'seti_test123',
|
|
client_secret: 'seti_test123_secret',
|
|
};
|
|
mockStripeInstance.setupIntents.create.mockResolvedValue(mockSetupIntent);
|
|
const result = await service.createSetupIntent(mockCustomerId);
|
|
expect(result).toEqual(mockSetupIntent);
|
|
expect(mockStripeInstance.setupIntents.create).toHaveBeenCalledWith({
|
|
customer: mockCustomerId,
|
|
payment_method_types: ['card'],
|
|
});
|
|
});
|
|
});
|
|
describe('mapStripeStatus', () => {
|
|
it('should map trialing to TRIAL', () => {
|
|
const result = service.mapStripeStatus('trialing');
|
|
expect(result).toBe(subscription_entity_1.SubscriptionStatus.TRIAL);
|
|
});
|
|
it('should map active to ACTIVE', () => {
|
|
const result = service.mapStripeStatus('active');
|
|
expect(result).toBe(subscription_entity_1.SubscriptionStatus.ACTIVE);
|
|
});
|
|
it('should map past_due to PAST_DUE', () => {
|
|
const result = service.mapStripeStatus('past_due');
|
|
expect(result).toBe(subscription_entity_1.SubscriptionStatus.PAST_DUE);
|
|
});
|
|
it('should map canceled to CANCELLED', () => {
|
|
const result = service.mapStripeStatus('canceled');
|
|
expect(result).toBe(subscription_entity_1.SubscriptionStatus.CANCELLED);
|
|
});
|
|
it('should map unpaid to PAST_DUE', () => {
|
|
const result = service.mapStripeStatus('unpaid');
|
|
expect(result).toBe(subscription_entity_1.SubscriptionStatus.PAST_DUE);
|
|
});
|
|
it('should map incomplete to TRIAL', () => {
|
|
const result = service.mapStripeStatus('incomplete');
|
|
expect(result).toBe(subscription_entity_1.SubscriptionStatus.TRIAL);
|
|
});
|
|
it('should map incomplete_expired to EXPIRED', () => {
|
|
const result = service.mapStripeStatus('incomplete_expired');
|
|
expect(result).toBe(subscription_entity_1.SubscriptionStatus.EXPIRED);
|
|
});
|
|
it('should map paused to CANCELLED', () => {
|
|
const result = service.mapStripeStatus('paused');
|
|
expect(result).toBe(subscription_entity_1.SubscriptionStatus.CANCELLED);
|
|
});
|
|
it('should default to ACTIVE for unknown status', () => {
|
|
const result = service.mapStripeStatus('unknown_status');
|
|
expect(result).toBe(subscription_entity_1.SubscriptionStatus.ACTIVE);
|
|
});
|
|
});
|
|
});
|
|
//# sourceMappingURL=stripe.service.spec.js.map
|