agmission/Development/server/tests/test_payment_failure_handling.js

239 lines
7.5 KiB
JavaScript

#!/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 };