144 lines
5.9 KiB
JavaScript
144 lines
5.9 KiB
JavaScript
// 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); |