"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