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:
parent
8b5a515a29
commit
5c7f0ab1e7
2 changed files with 381 additions and 0 deletions
159
scripts/check-stripe-bank-account.js
Normal file
159
scripts/check-stripe-bank-account.js
Normal 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
222
scripts/verify-stripe-portal.js
Executable 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);
|
||||
Loading…
Add table
Reference in a new issue