#!/usr/bin/env node /** * Test script to verify payment failure handling with partial discount coupons * * This script tests the critical billing bug fix where subscriptions with partial * discount coupons were incorrectly marked as 'active' even when payment failed. * * Usage: * node tests/test_payment_failure_handling.js * * Requirements: * - Stripe test mode enabled * - Test customer created * - Discount coupon created (e.g., 50% off) */ const path = require('path'); require('dotenv').config({ path: path.resolve(__dirname, '../environment.env') }); const stripe = require('stripe')(process.env.STRIPE_SEC_KEY); const assert = require('assert'); // Test cards const TEST_CARDS = { SUCCESS: '4242424242424242', DECLINE: '4000000000000341', REQUIRES_AUTH: '4000002500003155' }; async function cleanup(customerId, subscriptionIds = []) { console.log('\nCleaning up test data...'); // Cancel subscriptions for (const subId of subscriptionIds) { try { await stripe.subscriptions.del(subId); console.log(` ✓ Deleted subscription ${subId}`); } catch (err) { console.log(` ⚠ Could not delete subscription ${subId}: ${err.message}`); } } // Delete customer if (customerId) { try { await stripe.customers.del(customerId); console.log(` ✓ Deleted customer ${customerId}`); } catch (err) { console.log(` ⚠ Could not delete customer ${customerId}: ${err.message}`); } } } async function createTestPaymentMethod(cardNumber) { const paymentMethod = await stripe.paymentMethods.create({ type: 'card', card: { number: cardNumber, exp_month: 12, exp_year: 2030, cvc: '123' } }); return paymentMethod.id; } async function testScenario(name, cardNumber, couponId, expectSuccess) { console.log(`\n${'='.repeat(60)}`); console.log(`TEST: ${name}`); console.log('='.repeat(60)); let customer, subscription; const subscriptionIds = []; try { // Create test customer customer = await stripe.customers.create({ email: `test-${Date.now()}@example.com`, metadata: { test: 'payment_failure_handling' } }); console.log(`✓ Created test customer: ${customer.id}`); // Create payment method const pmId = await createTestPaymentMethod(cardNumber); console.log(`✓ Created payment method: ${pmId}`); // Attach payment method to customer await stripe.paymentMethods.attach(pmId, { customer: customer.id }); console.log(`✓ Attached payment method to customer`); // Set as default await stripe.customers.update(customer.id, { invoice_settings: { default_payment_method: pmId } }); // Create subscription with coupon const subscriptionParams = { customer: customer.id, items: [{ price: process.env.ESS_1 }], // Use your test price ID expand: ['latest_invoice.payment_intent'], payment_behavior: 'error_if_incomplete', // CRITICAL FIX metadata: { test: 'payment_failure_test' } }; if (couponId) { subscriptionParams.coupon = couponId; console.log(`✓ Applying coupon: ${couponId}`); } try { subscription = await stripe.subscriptions.create(subscriptionParams); subscriptionIds.push(subscription.id); console.log(`\nSubscription created: ${subscription.id}`); console.log(` Status: ${subscription.status}`); console.log(` Latest Invoice Status: ${subscription.latest_invoice?.status || 'N/A'}`); if (subscription.latest_invoice?.payment_intent) { console.log(` Payment Intent Status: ${subscription.latest_invoice.payment_intent.status}`); } // Verify expectations if (expectSuccess) { assert.strictEqual(subscription.status, 'active', 'Subscription should be active'); console.log('\n✅ PASS: Subscription activated successfully'); } else { assert.strictEqual(subscription.status, 'incomplete', 'Subscription should be incomplete'); assert.strictEqual(subscription.latest_invoice.status, 'open', 'Invoice should be open'); console.log('\n✅ PASS: Subscription correctly marked as incomplete'); } } catch (err) { if (!expectSuccess && err.code === 'resource_already_exists') { console.log('\n✅ PASS: Payment failed as expected'); } else { throw err; } } } catch (error) { console.error(`\n❌ FAIL: ${error.message}`); console.error(error); return false; } finally { await cleanup(customer?.id, subscriptionIds); } return true; } async function main() { console.log('Payment Failure Handling Test Suite'); console.log('=====================================\n'); console.log('Testing critical billing bug fix:'); console.log('- Partial discount coupons with failed payments'); console.log('- Should result in INCOMPLETE status, not ACTIVE\n'); // Check if required env vars are present if (!process.env.STRIPE_SEC_KEY || !process.env.ESS_1) { console.error('❌ Missing required environment variables:'); console.error(' - STRIPE_SEC_KEY (Stripe secret key)'); console.error(' - ESS_1 (Price ID for testing)'); process.exit(1); } // Check if we're in test mode if (!process.env.STRIPE_SEC_KEY.startsWith('sk_test_')) { console.error('❌ ERROR: Not in Stripe test mode!'); console.error(' This script should only run with test keys.'); process.exit(1); } let allPassed = true; // Test 1: Successful payment with coupon console.log('\nTest 1: Successful payment with partial discount coupon'); console.log('Expected: Status = ACTIVE (payment succeeds)'); const test1 = await testScenario( 'Success with 50% coupon', TEST_CARDS.SUCCESS, null, // Set your test coupon ID here true ); allPassed = allPassed && test1; // Test 2: Failed payment with coupon (THE CRITICAL BUG) console.log('\nTest 2: Failed payment with partial discount coupon'); console.log('Expected: Status = INCOMPLETE (payment fails, subscription awaits payment)'); const test2 = await testScenario( 'Failed payment with 50% coupon', TEST_CARDS.DECLINE, null, // Set your test coupon ID here false ); allPassed = allPassed && test2; // Test 3: Failed payment without coupon console.log('\nTest 3: Failed payment without coupon'); console.log('Expected: Status = INCOMPLETE'); const test3 = await testScenario( 'Failed payment no coupon', TEST_CARDS.DECLINE, null, false ); allPassed = allPassed && test3; // Summary console.log('\n' + '='.repeat(60)); console.log('TEST SUMMARY'); console.log('='.repeat(60)); if (allPassed) { console.log('\n✅ All tests PASSED!'); console.log('\nThe fix is working correctly:'); console.log(' - Failed payments result in INCOMPLETE status'); console.log(' - Partial discount coupons do not bypass payment checks'); console.log(' - Billing security is enforced\n'); process.exit(0); } else { console.log('\n❌ Some tests FAILED!'); console.log('\nThe bug may still exist:'); console.log(' - Check that payment_behavior is set correctly'); console.log(' - Verify Stripe webhooks are configured'); console.log(' - Review error logs above\n'); process.exit(1); } } // Run tests if (require.main === module) { main().catch(err => { console.error('\n❌ Fatal error:', err); process.exit(1); }); } module.exports = { testScenario, TEST_CARDS };