From 5c7f0ab1e74d41288e9f7ede975d6a0b9b13e009 Mon Sep 17 00:00:00 2001 From: TheFlow Date: Sat, 18 Oct 2025 22:19:30 +1300 Subject: [PATCH] feat(stripe): add diagnostic tools for Customer Portal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- scripts/check-stripe-bank-account.js | 159 +++++++++++++++++++ scripts/verify-stripe-portal.js | 222 +++++++++++++++++++++++++++ 2 files changed, 381 insertions(+) create mode 100644 scripts/check-stripe-bank-account.js create mode 100755 scripts/verify-stripe-portal.js diff --git a/scripts/check-stripe-bank-account.js b/scripts/check-stripe-bank-account.js new file mode 100644 index 00000000..17a3a7d8 --- /dev/null +++ b/scripts/check-stripe-bank-account.js @@ -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); diff --git a/scripts/verify-stripe-portal.js b/scripts/verify-stripe-portal.js new file mode 100755 index 00000000..f65b593f --- /dev/null +++ b/scripts/verify-stripe-portal.js @@ -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);