feat(stripe): add diagnostic tools for Customer Portal

- Add check-stripe-bank-account.js for bank account verification
- Add verify-stripe-portal.js for portal configuration validation
- Tools help troubleshoot bank account holder name issues
- Automated verification of portal features and requirements

Part of Stripe troubleshooting workflow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
TheFlow 2025-10-18 22:19:30 +13:00
parent 8b5a515a29
commit 5c7f0ab1e7
2 changed files with 381 additions and 0 deletions

View file

@ -0,0 +1,159 @@
#!/usr/bin/env node
/**
* Check and display Stripe bank account holder name
*
* This script helps diagnose the bank account holder name issue by:
* 1. Listing all external accounts (bank accounts) on the Stripe account
* 2. Showing the current account holder name
* 3. Providing the exact account details for verification
*
* Usage:
* node scripts/check-stripe-bank-account.js
*
* Environment:
* STRIPE_SECRET_KEY - Your Stripe secret key (test or live)
*/
require('dotenv').config();
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
async function checkBankAccount() {
console.log('\n🔍 Checking Stripe Bank Account Configuration\n');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
try {
// Get account details
const account = await stripe.account.retrieve();
console.log('📋 Account Information:');
console.log(` Type: ${account.type}`);
console.log(` Country: ${account.country}`);
console.log(` Email: ${account.email || 'Not set'}`);
if (account.type === 'standard') {
console.log(` Business Name: ${account.business_profile?.name || 'Not set'}`);
} else if (account.type === 'express' || account.type === 'custom') {
console.log(` Account Holder Name: ${account.individual?.first_name || ''} ${account.individual?.last_name || ''}`);
console.log(` Company Name: ${account.company?.name || 'Not set'}`);
}
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
// List external accounts (bank accounts)
console.log('🏦 External Accounts (Bank Accounts):\n');
// For standard accounts, bank accounts are accessed via the account object's external_accounts
let externalAccounts;
try {
if (account.type === 'standard') {
// Standard accounts: query external accounts directly
externalAccounts = await stripe.account.listExternalAccounts({
object: 'bank_account',
limit: 10
});
} else {
// Express/Custom accounts: use the Connect API
externalAccounts = await stripe.accounts.listExternalAccounts(
account.id,
{ object: 'bank_account', limit: 10 }
);
}
} catch (err) {
console.log(' ⚠️ Could not retrieve bank accounts via API');
console.log(` Error: ${err.message}\n`);
console.log(' 📍 This is normal - bank account details require dashboard access');
console.log(' 📍 Please check manually in Stripe Dashboard:');
console.log(' https://dashboard.stripe.com/settings/payouts\n');
console.log(' 📋 What to look for:');
console.log(' 1. Find "Bank accounts and debit cards" section');
console.log(' 2. Click on account ending in 6-85');
console.log(' 3. Look for "Account holder name" field');
console.log(' 4. Should say: "John Geoffrey Stroh"\n');
return;
}
if (!externalAccounts || externalAccounts.data.length === 0) {
console.log(' ⚠️ No bank accounts found on this Stripe account');
console.log(' 📍 You may need to add a bank account in the dashboard:');
console.log(' https://dashboard.stripe.com/settings/payouts\n');
return;
}
externalAccounts.data.forEach((bankAccount, index) => {
console.log(`\n Bank Account #${index + 1}:`);
console.log(` ├─ Account Holder Name: ${bankAccount.account_holder_name || 'NOT SET ❌'}`);
console.log(` ├─ Account Holder Type: ${bankAccount.account_holder_type || 'Not specified'}`);
console.log(` ├─ Bank Name: ${bankAccount.bank_name || 'Unknown'}`);
console.log(` ├─ Country: ${bankAccount.country}`);
console.log(` ├─ Currency: ${bankAccount.currency.toUpperCase()}`);
console.log(` ├─ Last 4 Digits: ****${bankAccount.last4}`);
console.log(` ├─ Routing Number: ${bankAccount.routing_number || 'N/A'}`);
console.log(` ├─ Status: ${bankAccount.status}`);
console.log(` ├─ Default for currency: ${bankAccount.default_for_currency ? 'Yes ✅' : 'No'}`);
console.log(` └─ Bank Account ID: ${bankAccount.id}`);
// Check if name matches required format
const requiredName = 'John Geoffrey Stroh';
if (bankAccount.account_holder_name === requiredName) {
console.log(`\n ✅ Account holder name matches TSB requirement!`);
} else if (bankAccount.account_holder_name) {
console.log(`\n ⚠️ Account holder name does NOT match TSB requirement`);
console.log(` Current: "${bankAccount.account_holder_name}"`);
console.log(` Required: "${requiredName}"`);
} else {
console.log(`\n ❌ Account holder name is NOT SET`);
console.log(` Required: "${requiredName}"`);
}
});
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
// Check if this is the correct TSB account
const tsbAccount = externalAccounts.data.find(acc =>
acc.last4 === '6-85' || acc.last4 === '0685' || acc.routing_number?.includes('3959')
);
if (tsbAccount) {
console.log('✅ Found your TSB account (ending in 6-85)\n');
} else {
console.log('⚠️ Could not identify TSB account ending in 6-85');
console.log(' Please verify the account details above match your bank.\n');
}
console.log('📍 Next Steps:\n');
const hasCorrectName = externalAccounts.data.some(acc =>
acc.account_holder_name === 'John Geoffrey Stroh'
);
if (hasCorrectName) {
console.log(' ✅ Bank account holder name is correct!');
console.log(' ✅ You should be all set for payouts.\n');
} else {
console.log(' ⚠️ Bank account holder name needs to be updated\n');
console.log(' Option 1: Update via Stripe Dashboard');
console.log(' https://dashboard.stripe.com/settings/payouts\n');
console.log(' Option 2: Remove and re-add bank account with correct name');
console.log(' (This script cannot update the name automatically)\n');
console.log(' Option 3: Contact Stripe Support');
console.log(' https://dashboard.stripe.com/support\n');
console.log(' Option 4: Try the update script');
console.log(' node scripts/update-stripe-bank-name.js\n');
}
} catch (error) {
console.error('❌ Error checking Stripe account:', error.message);
if (error.type === 'StripeAuthenticationError') {
console.error('\n⚠ Authentication failed. Please check:');
console.error(' 1. STRIPE_SECRET_KEY is set in .env');
console.error(' 2. The key starts with sk_test_ or sk_live_');
console.error(' 3. The key is valid and not expired\n');
}
}
}
// Run the check
checkBankAccount().catch(console.error);

222
scripts/verify-stripe-portal.js Executable file
View file

@ -0,0 +1,222 @@
#!/usr/bin/env node
/**
* Verify Stripe Customer Portal Configuration
*
* This script checks if the Customer Portal is configured correctly
* and provides guidance on what needs to be set up.
*
* Usage:
* node scripts/verify-stripe-portal.js
*
* Environment:
* STRIPE_SECRET_KEY - Your Stripe secret key (test or live)
*/
require('dotenv').config();
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
async function verifyPortalConfiguration() {
console.log('\n🔍 Verifying Stripe Customer Portal Configuration\n');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
try {
// Determine if we're in test or live mode
const mode = process.env.STRIPE_SECRET_KEY.startsWith('sk_test_') ? 'TEST' : 'LIVE';
console.log(`📋 Mode: ${mode}\n`);
// Check for portal configurations
const configurations = await stripe.billingPortal.configurations.list({ limit: 10 });
if (configurations.data.length === 0) {
console.log('❌ No Customer Portal configurations found\n');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
console.log('⚠️ YOU NEED TO CONFIGURE THE CUSTOMER PORTAL\n');
printConfigurationSteps(mode);
return;
}
// Show existing configurations
console.log(`✅ Found ${configurations.data.length} portal configuration(s)\n`);
configurations.data.forEach((config, index) => {
console.log(`\n📦 Configuration #${index + 1}:`);
console.log(` ID: ${config.id}`);
console.log(` Active: ${config.active ? 'Yes ✅' : 'No ❌'}`);
console.log(` Default: ${config.is_default ? 'Yes ✅' : 'No ❌'}`);
console.log(` Created: ${new Date(config.created * 1000).toLocaleString()}`);
// Features
console.log('\n 📋 Enabled Features:');
// Customer update
if (config.features.customer_update) {
const emailEnabled = config.features.customer_update.allowed_updates.includes('email');
console.log(` • Email editing: ${emailEnabled ? '✅ Enabled' : '❌ Disabled'}`);
}
// Payment method update
if (config.features.payment_method_update) {
console.log(` • Payment method update: ✅ Enabled`);
} else {
console.log(` • Payment method update: ❌ Disabled`);
}
// Subscription cancellation
if (config.features.subscription_cancel) {
console.log(` • Subscription cancellation: ✅ Enabled`);
console.log(` Mode: ${config.features.subscription_cancel.mode || 'Not set'}`);
// Check for cancellation survey
if (config.features.subscription_cancel.cancellation_reason) {
console.log(` Cancellation survey: ✅ Enabled`);
console.log(` Survey enabled: ${config.features.subscription_cancel.cancellation_reason.enabled ? 'Yes ✅' : 'No ❌'}`);
if (config.features.subscription_cancel.cancellation_reason.options) {
console.log(` Survey options: ${config.features.subscription_cancel.cancellation_reason.options.length} options`);
}
} else {
console.log(` Cancellation survey: ❌ Not configured`);
}
} else {
console.log(` • Subscription cancellation: ❌ Disabled`);
}
// Invoice history
if (config.features.invoice_history) {
console.log(` • Invoice history: ✅ Enabled`);
} else {
console.log(` • Invoice history: ❌ Disabled`);
}
// Business profile
console.log('\n 🏢 Business Information:');
if (config.business_profile) {
console.log(` • Headline: ${config.business_profile.headline || 'Not set'}`);
console.log(` • Privacy policy URL: ${config.business_profile.privacy_policy_url || 'Not set'}`);
console.log(` • Terms of service URL: ${config.business_profile.terms_of_service_url || 'Not set'}`);
} else {
console.log(` • Not configured`);
}
});
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
// Verify against requirements
const defaultConfig = configurations.data.find(c => c.is_default) || configurations.data[0];
console.log('✅ VERIFICATION CHECKLIST:\n');
const checks = [];
// Check 1: Email editing
const emailEnabled = defaultConfig.features.customer_update?.allowed_updates?.includes('email');
checks.push({
name: 'Email editing enabled',
status: emailEnabled,
required: true
});
// Check 2: Payment method update
const paymentMethodEnabled = defaultConfig.features.payment_method_update?.enabled !== false;
checks.push({
name: 'Payment method update enabled',
status: paymentMethodEnabled,
required: true
});
// Check 3: Subscription cancellation
const cancelEnabled = defaultConfig.features.subscription_cancel?.enabled !== false;
checks.push({
name: 'Subscription cancellation enabled',
status: cancelEnabled,
required: true
});
// Check 4: Cancellation survey
const surveyEnabled = defaultConfig.features.subscription_cancel?.cancellation_reason?.enabled === true;
checks.push({
name: 'Exit survey configured',
status: surveyEnabled,
required: true
});
// Check 5: Invoice history
const invoiceEnabled = defaultConfig.features.invoice_history?.enabled !== false;
checks.push({
name: 'Invoice history enabled',
status: invoiceEnabled,
required: true
});
// Print checklist
checks.forEach(check => {
const icon = check.status ? '✅' : '❌';
const required = check.required ? '(REQUIRED)' : '(optional)';
console.log(` ${icon} ${check.name} ${required}`);
});
const allPassed = checks.filter(c => c.required).every(c => c.status);
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
if (allPassed) {
console.log('🎉 PORTAL CONFIGURATION COMPLETE!\n');
console.log(' All required features are enabled.');
console.log(' You can now use the Customer Portal.\n');
} else {
console.log('⚠️ PORTAL CONFIGURATION INCOMPLETE\n');
console.log(' Some required features are not enabled.');
printConfigurationSteps(mode);
}
} catch (error) {
console.error('❌ Error verifying portal configuration:', error.message);
if (error.type === 'StripeAuthenticationError') {
console.error('\n⚠ Authentication failed. Please check:');
console.error(' 1. STRIPE_SECRET_KEY is set in .env');
console.error(' 2. The key starts with sk_test_ or sk_live_');
console.error(' 3. The key is valid and not expired\n');
}
}
}
function printConfigurationSteps(mode) {
const dashboardUrl = mode === 'TEST'
? 'https://dashboard.stripe.com/test/settings/billing/portal'
: 'https://dashboard.stripe.com/settings/billing/portal';
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
console.log(`📍 CONFIGURE CUSTOMER PORTAL (${mode} MODE)\n`);
console.log(`Step 1: Open Dashboard`);
console.log(` ${dashboardUrl}\n`);
console.log(`Step 2: Enable Features (click each to enable)`);
console.log(` ☐ Customer can edit email`);
console.log(` ☐ Customer can update payment methods`);
console.log(` ☐ Customer can cancel subscriptions`);
console.log(` ☐ Customer can view invoice history\n`);
console.log(`Step 3: Configure Cancellation Survey`);
console.log(` ☐ Enable "Ask why they're cancelling"`);
console.log(` ☐ Add question: "Why are you cancelling?"`);
console.log(` ☐ Options:`);
console.log(` • Too expensive`);
console.log(` • No longer need it`);
console.log(` • Found alternative`);
console.log(` • Other`);
console.log(` ☐ Add optional question: "How can we improve?"`);
console.log(` Type: Text input (optional)\n`);
console.log(`Step 4: Business Information`);
console.log(` ☐ Business name: Tractatus AI Safety Framework`);
console.log(` ☐ Support email: support@agenticgovernance.digital\n`);
console.log(`Step 5: Save Configuration`);
console.log(` ☐ Click "Save" or "Activate"\n`);
console.log(`Step 6: Verify`);
console.log(` ☐ Run this script again to verify:\n`);
console.log(` node scripts/verify-stripe-portal.js\n`);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
console.log('📖 Full guide: docs/STRIPE_PORTAL_CONFIGURATION_STEPS.md\n');
}
// Run verification
verifyPortalConfiguration().catch(console.error);