"use strict"; const __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) {k2 = k;} let desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) {k2 = k;} o[k2] = m[k]; })); const __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); const __importStar = (this && this.__importStar) || (function () { let ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { const ar = []; for (const k in o) {if (Object.prototype.hasOwnProperty.call(o, k)) {ar[ar.length] = k;}} return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) {return mod;} const result = {}; if (mod != null) {for (let k = ownKeys(mod), i = 0; i < k.length; i++) {if (k[i] !== "default") {__createBinding(result, mod, k[i]);}}} __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const globals_1 = require("@jest/globals"); const firestore_1 = require("firebase-admin/firestore"); // Mock Firebase Admin globals_1.jest.mock('firebase-admin/firestore', () => ({ getFirestore: globals_1.jest.fn(), FieldValue: { arrayUnion: globals_1.jest.fn((value) => ({ arrayUnion: value })) } })); // Mock Stripe globals_1.jest.mock('stripe'); (0, globals_1.describe)('Stripe Connect Hardened Implementation', () => { let mockDb; let mockTransaction; let mockStripe; (0, globals_1.beforeEach)(() => { // Reset all mocks globals_1.jest.clearAllMocks(); // Mock Firestore transaction mockTransaction = { get: globals_1.jest.fn(), set: globals_1.jest.fn(), update: globals_1.jest.fn() }; // Mock Firestore database mockDb = { collection: globals_1.jest.fn(() => ({ doc: globals_1.jest.fn(() => ({ get: globals_1.jest.fn(), set: globals_1.jest.fn(), update: globals_1.jest.fn() })), where: globals_1.jest.fn(() => ({ get: globals_1.jest.fn() })) })), runTransaction: globals_1.jest.fn((callback) => callback(mockTransaction)), batch: globals_1.jest.fn(() => ({ set: globals_1.jest.fn(), update: globals_1.jest.fn(), commit: globals_1.jest.fn() })) }; firestore_1.getFirestore.mockReturnValue(mockDb); // Mock Stripe mockStripe = { webhooks: { constructEvent: globals_1.jest.fn() }, refunds: { create: globals_1.jest.fn() } }; }); (0, globals_1.describe)('Idempotency Protection', () => { (0, globals_1.test)('should skip processing if session already processed', async () => { // Mock existing processed session const mockProcessedDoc = { exists: true, data: () => ({ sessionId: 'cs_test_123', status: 'completed', processedAt: '2024-01-01T00:00:00Z' }) }; mockTransaction.get.mockResolvedValue(mockProcessedDoc); const session = { id: 'cs_test_123', metadata: { orgId: 'org_123', eventId: 'event_123', ticketTypeId: 'tt_123', quantity: '2' }, customer_details: { email: 'test@example.com' }, amount_total: 10000 }; // Import the function under test const { handleTicketPurchaseCompleted } = await Promise.resolve().then(() => __importStar(require('./stripeConnect'))); await (0, globals_1.expect)(handleTicketPurchaseCompleted(session, 'acct_123')).resolves.not.toThrow(); // Should only check for existing session, not create tickets (0, globals_1.expect)(mockTransaction.get).toHaveBeenCalledTimes(1); (0, globals_1.expect)(mockTransaction.set).not.toHaveBeenCalled(); (0, globals_1.expect)(mockTransaction.update).not.toHaveBeenCalled(); }); (0, globals_1.test)('should process new session and mark as processing', async () => { // Mock non-existing processed session const mockProcessedDoc = { exists: false }; const mockTicketTypeDoc = { exists: true, data: () => ({ inventory: 10, sold: 5, price: 5000 }) }; mockTransaction.get .mockResolvedValueOnce(mockProcessedDoc) // processedSessions check .mockResolvedValueOnce(mockTicketTypeDoc); // ticketTypes check const session = { id: 'cs_test_new', metadata: { orgId: 'org_123', eventId: 'event_123', ticketTypeId: 'tt_123', quantity: '2' }, customer_details: { email: 'test@example.com', name: 'Test User' }, amount_total: 10000, currency: 'usd', payment_intent: 'pi_123' }; const { handleTicketPurchaseCompleted } = await Promise.resolve().then(() => __importStar(require('./stripeConnect'))); await (0, globals_1.expect)(handleTicketPurchaseCompleted(session, 'acct_123')).resolves.not.toThrow(); // Should mark session as processing (0, globals_1.expect)(mockTransaction.set).toHaveBeenCalledWith(globals_1.expect.any(Object), globals_1.expect.objectContaining({ sessionId: 'cs_test_new', status: 'processing' })); }); }); (0, globals_1.describe)('Inventory Concurrency Control', () => { (0, globals_1.test)('should prevent overselling with insufficient inventory', async () => { const mockProcessedDoc = { exists: false }; const mockTicketTypeDoc = { exists: true, data: () => ({ inventory: 1, // Only 1 ticket available sold: 9, price: 5000 }) }; mockTransaction.get .mockResolvedValueOnce(mockProcessedDoc) .mockResolvedValueOnce(mockTicketTypeDoc); const session = { id: 'cs_test_oversell', metadata: { orgId: 'org_123', eventId: 'event_123', ticketTypeId: 'tt_123', quantity: '3' // Requesting 3 tickets but only 1 available }, customer_details: { email: 'test@example.com' } }; const { handleTicketPurchaseCompleted } = await Promise.resolve().then(() => __importStar(require('./stripeConnect'))); await (0, globals_1.expect)(handleTicketPurchaseCompleted(session, 'acct_123')).resolves.not.toThrow(); // Should not throw, but handle gracefully // Should not create any tickets (0, globals_1.expect)(mockTransaction.set).toHaveBeenCalledTimes(1); // Only the processing marker }); (0, globals_1.test)('should update inventory atomically on successful purchase', async () => { const mockProcessedDoc = { exists: false }; const mockTicketTypeDoc = { exists: true, data: () => ({ inventory: 10, sold: 5, price: 5000 }) }; mockTransaction.get .mockResolvedValueOnce(mockProcessedDoc) .mockResolvedValueOnce(mockTicketTypeDoc); const session = { id: 'cs_test_success', metadata: { orgId: 'org_123', eventId: 'event_123', ticketTypeId: 'tt_123', quantity: '2' }, customer_details: { email: 'test@example.com', name: 'Test User' }, amount_total: 10000, currency: 'usd', payment_intent: 'pi_123' }; const { handleTicketPurchaseCompleted } = await Promise.resolve().then(() => __importStar(require('./stripeConnect'))); await (0, globals_1.expect)(handleTicketPurchaseCompleted(session, 'acct_123')).resolves.not.toThrow(); // Should update inventory: 10 - 2 = 8, sold: 5 + 2 = 7 (0, globals_1.expect)(mockTransaction.update).toHaveBeenCalledWith(globals_1.expect.any(Object), globals_1.expect.objectContaining({ inventory: 8, sold: 7 })); }); }); (0, globals_1.describe)('Platform Fee Configuration', () => { (0, globals_1.test)('should calculate platform fee using configurable BPS', () => { // Mock environment variables process.env.PLATFORM_FEE_BPS = '250'; // 2.5% process.env.PLATFORM_FEE_FIXED = '25'; // $0.25 const totalAmount = 10000; // $100.00 // Expected: (10000 * 250 / 10000) + 25 = 250 + 25 = 275 cents const expectedFee = Math.round(totalAmount * (250 / 10000)) + 25; (0, globals_1.expect)(expectedFee).toBe(275); // $2.75 }); (0, globals_1.test)('should use default platform fee when env vars not set', () => { delete process.env.PLATFORM_FEE_BPS; delete process.env.PLATFORM_FEE_FIXED; const totalAmount = 10000; // $100.00 // Expected: (10000 * 300 / 10000) + 30 = 300 + 30 = 330 cents const expectedFee = Math.round(totalAmount * (300 / 10000)) + 30; (0, globals_1.expect)(expectedFee).toBe(330); // $3.30 }); }); (0, globals_1.describe)('Refund Safety', () => { (0, globals_1.test)('should validate organization ownership before refund', async () => { const mockOrgDoc = { exists: true, data: () => ({ payment: { stripe: { accountId: 'acct_123' } } }) }; const mockOrderDocs = { empty: false, docs: [{ ref: { update: globals_1.jest.fn() }, data: () => ({ id: 'order_123', orgId: 'org_123', totalAmount: 10000, metadata: { paymentIntentId: 'pi_123' }, ticketIds: ['ticket_1', 'ticket_2'] }) }] }; mockDb.collection.mockImplementation((collection) => { if (collection === 'orgs') { return { doc: () => ({ get: () => Promise.resolve(mockOrgDoc) }) }; } if (collection === 'orders') { return { where: () => ({ where: () => ({ get: () => Promise.resolve(mockOrderDocs) }) }) }; } return { doc: () => ({}) }; }); const mockRefund = { id: 'ref_123', status: 'succeeded', amount: 10000 }; mockStripe.refunds.create.mockResolvedValue(mockRefund); // Test would require importing and calling the refund function // This demonstrates the validation logic structure (0, globals_1.expect)(mockOrgDoc.exists).toBe(true); (0, globals_1.expect)(mockOrderDocs.empty).toBe(false); }); }); (0, globals_1.describe)('Connect Webhook Account Handling', () => { (0, globals_1.test)('should extract account ID from event.account property', () => { const mockEvent = { id: 'evt_123', type: 'checkout.session.completed', account: 'acct_from_event_123', data: { object: { id: 'cs_test_123', metadata: { type: 'ticket_purchase' } } } }; mockStripe.webhooks.constructEvent.mockReturnValue(mockEvent); // Test would verify that account ID is correctly extracted from event.account (0, globals_1.expect)(mockEvent.account).toBe('acct_from_event_123'); }); (0, globals_1.test)('should fallback to stripe-account header when event.account missing', () => { const mockEvent = { id: 'evt_123', type: 'checkout.session.completed', account: null, // No account in event data: { object: { id: 'cs_test_123', metadata: { type: 'ticket_purchase' } } } }; mockStripe.webhooks.constructEvent.mockReturnValue(mockEvent); const mockHeaders = { 'stripe-account': 'acct_from_header_123' }; // Test would verify that header fallback works const accountId = mockEvent.account || mockHeaders['stripe-account']; (0, globals_1.expect)(accountId).toBe('acct_from_header_123'); }); }); (0, globals_1.describe)('Structured Logging', () => { (0, globals_1.test)('should log with proper context structure', () => { const consoleSpy = globals_1.jest.spyOn(console, 'log').mockImplementation(); // Mock the logWithContext function behavior const logContext = { sessionId: 'cs_test_123', accountId: 'acct_123', orgId: 'org_123', eventId: 'event_123', action: 'test_action' }; const expectedLog = { timestamp: globals_1.expect.any(String), level: 'info', message: 'Test message', ...logContext }; // Test would verify structured logging format (0, globals_1.expect)(expectedLog).toMatchObject(logContext); consoleSpy.mockRestore(); }); }); (0, globals_1.afterEach)(() => { // Clean up environment variables delete process.env.PLATFORM_FEE_BPS; delete process.env.PLATFORM_FEE_FIXED; }); }); // # sourceMappingURL=stripeConnect.test.js.map