// tests/test_multi_subscription_auth.js const path = require('path'); require('dotenv').config({ path: path.resolve(process.cwd(), 'environment.env') }); const stripe = require('stripe')(process.env.STRIPE_SEC_KEY); const { IntentStatus } = require('../model/subscription'); async function testDoubleCallAuth() { console.log('๐Ÿงช Testing Multi-Subscription 3DS Behavior\n'); // Create customer first const customer = await stripe.customers.create({ email: 'test-multi-3ds@example.com' }); console.log('โœ… Customer created:', customer.id); // Create a fresh payment method with 3DS card const paymentMethod = await stripe.paymentMethods.create({ type: 'card', card: { number: '4000002500003155', // 3DS required card exp_month: 12, exp_year: 2030, cvc: '123' } }); console.log('โœ… Payment method created:', paymentMethod.id); // Attach to customer await stripe.paymentMethods.attach(paymentMethod.id, { customer: customer.id }); console.log('โœ… Payment method attached to customer\n'); // First subscription - Package (will require 3DS) console.log('๐Ÿ“ฆ Creating FIRST subscription (Package)...'); const sub1 = await stripe.subscriptions.create({ customer: customer.id, items: [{ price: process.env.ESS_1 }], default_payment_method: paymentMethod.id, expand: ['latest_invoice.payment_intent'], payment_behavior: 'default_incomplete', metadata: { type: 'package' } }); const pi1 = sub1.latest_invoice.payment_intent; console.log(` Subscription: ${sub1.id}, Status: ${sub1.status}`); console.log(` PaymentIntent: ${pi1.id}, Status: ${pi1.status}`); console.log(` Requires 3DS: ${pi1.status === IntentStatus.REQUIRES_ACTION}\n`); // Backend confirms payment intent if requires_confirmation if (pi1.status === IntentStatus.REQUIRES_CONFIRMATION) { console.log('๐Ÿ”„ Confirming payment intent (backend logic)...'); const confirmedPi1 = await stripe.paymentIntents.confirm(pi1.id, { return_url: 'http://localhost:4100/payment-complete' }); console.log(` Status after confirmation: ${confirmedPi1.status}\n`); if (confirmedPi1.status === IntentStatus.REQUIRES_ACTION) { console.log('๐Ÿ” 3DS required for package subscription'); console.log(' Backend would return client_secret to frontend'); console.log(' (In real flow, frontend would use stripe.confirmCardPayment())'); console.log(' NOTE: Cannot simulate 3DS completion in backend-only test\n'); } } else if (pi1.status === IntentStatus.REQUIRES_ACTION) { console.log('๐Ÿ” 3DS required for package subscription'); console.log(' (In real flow, frontend would use stripe.confirmCardPayment())'); console.log(' NOTE: Cannot simulate 3DS completion in backend-only test\n'); } // Verify package is active const sub1Updated = await stripe.subscriptions.retrieve(sub1.id); console.log(`โœ… Package subscription: ${sub1Updated.status}\n`); // Second subscription - Addon (with SAME payment method, minutes later) console.log('๐Ÿ“ฆ Creating SECOND subscription (Addon) with SAME card...'); console.log(' (Simulating second /update call)\n'); const sub2 = await stripe.subscriptions.create({ customer: customer.id, items: [{ price: process.env.ADDON_1, quantity: 1 }], default_payment_method: paymentMethod.id, expand: ['latest_invoice.payment_intent'], payment_behavior: 'default_incomplete', metadata: { type: 'addon' } }); const pi2 = sub2.latest_invoice.payment_intent; console.log(` Subscription: ${sub2.id}, Status: ${sub2.status}`); console.log(` PaymentIntent: ${pi2.id}, Status: ${pi2.status}`); let addonRequires3DS = false; let confirmedPi2 = pi2; // Backend confirms payment intent if requires_confirmation if (pi2.status === 'requires_confirmation') { console.log('๐Ÿ”„ Confirming payment intent (backend logic)...'); confirmedPi2 = await stripe.paymentIntents.confirm(pi2.id, { payment_method: paymentMethod.id }); console.log(` Status after confirmation: ${confirmedPi2.status}\n`); if (confirmedPi2.status === IntentStatus.REQUIRES_ACTION) { addonRequires3DS = true; console.log('โš ๏ธ ANSWER: YES - Second subscription REQUIRES 3DS AGAIN!'); console.log(' Even though same card was just used.'); console.log(' Each PaymentIntent is independent.\n'); } else { console.log('โœ… ANSWER: NO - Authentication was reused!\n'); } } else if (pi2.status === IntentStatus.REQUIRES_ACTION) { addonRequires3DS = true; console.log('โš ๏ธ ANSWER: YES - Second subscription requires 3DS\n'); } else { console.log(`โœ… ANSWER: NO - Addon status is ${pi2.status}\n`); } // === RESULT SUMMARY === console.log('\n๐Ÿ“Š Test Results:'); console.log(` โœ… Package subscription: required 3DS`); console.log(` ${addonRequires3DS ? 'โš ๏ธ' : 'โœ…'} Addon subscription: ${addonRequires3DS ? 'REQUIRES 3DS AGAIN' : 'no 3DS needed'}`); if (addonRequires3DS) { console.log('\n๐Ÿ’ก Conclusion:'); console.log(' Each subscription creates a separate PaymentIntent.'); console.log(' Even when using the same payment method immediately after,'); console.log(' Stripe does NOT reuse 3DS authentication between PaymentIntents.'); console.log(' Frontend must handle 3DS for EACH subscription.'); } // Check the subscription statuses for both const sub1Final = await stripe.subscriptions.retrieve(sub1.id); console.log(`\nโœ… Final Package subscription status: ${sub1Final.status}`); const sub2Updated = await stripe.subscriptions.retrieve(sub2.id); console.log(`\nโœ… Addon subscription: ${sub2Updated.status}`); // Cleanup await stripe.subscriptions.del(sub1.id); await stripe.subscriptions.del(sub2.id); await stripe.customers.del(customer.id); console.log('\n๐Ÿงน Cleaned up test data'); } testDoubleCallAuth().catch(console.error);