'use strict'; /** * Import Stripe Customer Subscriptions Script * * This script imports Stripe subscriptions for a specific customer into the local database * by reusing the updateCustSubStatus() function from subscription.js. * * Usage: * # Import subscriptions for a customer * node scripts/importCustStripeSubs.js * * # Dry run (preview without making changes) * node scripts/importCustStripeSubs.js --dry-run * * # With custom environment file * node scripts/importCustStripeSubs.js --env ../environment_prod.env * * # With debug output * DEBUG=agm:stripe-import node scripts/importCustStripeSubs.js */ // Load environment variables from environment.env file (or custom path) const path = require('path'); const args = process.argv.slice(2); const envArgIndex = args.indexOf('--env'); const customEnvPath = envArgIndex !== -1 && args[envArgIndex + 1] ? args[envArgIndex + 1] : null; const envPath = customEnvPath ? (path.isAbsolute(customEnvPath) ? customEnvPath : path.join(process.cwd(), customEnvPath)) : path.join(__dirname, '../environment.env'); console.log(`Loading environment from: ${envPath}`); require('dotenv').config({ path: envPath }); const debug = require('debug')('agm:stripe-import'), mongoose = require('mongoose'), env = require('../helpers/env'), mongoUtil = require('../helpers/mongo'), { DBConnection } = require('../helpers/db/connect.js'), { stripe } = require('../helpers/subscription_util'), { findApplicatorByCustId, updateCustSubStatus, updateCustSubscriptions, updateSubBillPeriod } = require('../controllers/subscription'); // Configuration - filter out --env and its value from args const filteredArgs = args.filter((arg, idx) => { if (arg === '--env') return false; if (idx > 0 && args[idx - 1] === '--env') return false; return true; }); const CUSTOMER_ID = filteredArgs[0]; // Pass Stripe customer ID as an argument const DRY_RUN = args.includes('--dry-run'); // Use --dry-run to test without making changes /** * This script is to import Stripe customer subscriptions into your local database by reusing the updateCustSubStatus() function from subscription.js. This script fetches subscriptions for a specific Stripe customer and updates the local database accordingly. */ /** * Import Stripe subscriptions for a specific customer */ async function importSubscriptions(stripeCustId) { debug(`Starting import for Stripe customer: ${stripeCustId}`); try { // Find the customer in the local database const dbCustomer = await findApplicatorByCustId(stripeCustId); if (!dbCustomer) { debug(`No customer found in the local database for Stripe ID: ${stripeCustId}`); return; } debug(`Found local customer: ${dbCustomer.username}`); // Fetch subscriptions from Stripe const subscriptions = await stripe.subscriptions.list({ customer: stripeCustId, expand: ['data.latest_invoice.payment_intent'] }); if (!subscriptions.data.length) { debug(`No subscriptions found for Stripe customer: ${stripeCustId}`); return; } debug(`Found ${subscriptions.data.length} subscription(s) for customer: ${dbCustomer.username}`); // Show what would be updated in dry-run mode if (DRY_RUN) { console.log(`[DRY RUN] Would update the following subscriptions for ${dbCustomer.username}:`); for (const subscription of subscriptions.data) { let price = subscription.items.data[0]?.price; const subscriptionType = subscription.metadata?.type || 'N/A'; // Try to determine a human-friendly plan name. Prefer price.nickname, then price.product.name. // If product.name is not available in the expanded response, fetch the price with product expanded. let priceName = 'N/A'; try { if (price) { priceName = price.nickname || (price.product && price.product.name) || 'N/A'; if ((priceName === 'N/A' || !price.product || !price.product.name) && price.id) { // Retrieve price with expanded product const priceFull = await stripe.prices.retrieve(price.id, { expand: ['product'] }); priceName = priceFull.nickname || (priceFull.product && priceFull.product.name) || 'N/A'; } } } catch (err) { debug(`Error fetching price/product for subscription ${subscription.id}: ${err.message}`); } console.log(` - Subscription ID: ${subscription.id}`); console.log(` Status: ${subscription.status}`); console.log(` Type: ${subscriptionType}`); console.log(` Plan: ${price?.id || 'N/A'}`); console.log(` Plan Name: ${priceName}`); console.log(` Current period: ${new Date(subscription.current_period_start * 1000).toISOString()} to ${new Date(subscription.current_period_end * 1000).toISOString()}`); } console.log(`[DRY RUN] No changes were made to the database.`); return; } // Update the local database using updateCustSubStatus() // Do these updates in a single mongo transaction try { await mongoUtil.runInTransaction(async (session) => { // Update the subscription status in the local database const updatedCustomer = await updateCustSubscriptions(dbCustomer._id, subscriptions.data, true); if (!updatedCustomer) { debug(`Failed to update subscription: ${subscriptions.data.length} for customer: ${dbCustomer.username}. NOT FOUND!`); return; } for (const subscription of subscriptions.data) { await updateSubBillPeriod(dbCustomer, subscription, session); } debug(`Successfully updated ${subscriptions.data.length} subscription(s) for customer: ${dbCustomer.username}`); }); } catch (error) { debug(`Error updating subscription: ${subscriptions.data.length} for customer: ${dbCustomer.username}: ${error.message}`); return; // Skip to the next subscription } // } debug(`Import completed for Stripe customer: ${stripeCustId}`); } catch (error) { debug(`Error importing subscriptions for Stripe customer ${stripeCustId}: ${error.message}`); throw error; } } /** * Main function to handle the import process */ async function main() { if (!CUSTOMER_ID) { console.log(''); console.log('Usage: node scripts/importCustStripeSubs.js [options]'); console.log(''); console.log('Options:'); console.log(' --dry-run Preview without making changes'); console.log(' --env PATH Custom environment file path (default: ../environment.env)'); console.log(''); console.log('Examples:'); console.log(' node scripts/importCustStripeSubs.js cus_ABC123xyz'); console.log(' node scripts/importCustStripeSubs.js cus_ABC123xyz --dry-run'); console.log(' DEBUG=agm:stripe-import node scripts/importCustStripeSubs.js cus_ABC123xyz'); console.log(''); process.exit(1); } if (DRY_RUN) { debug('Running in DRY RUN mode - no changes will be made'); } try { await importSubscriptions(CUSTOMER_ID); !DRY_RUN && console.log('Import process completed successfully'); process.exit(0); } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); } } // Connect to the database and run the script async function runScript() { const dbConn = new DBConnection('Import Customer Stripe Subscriptions Script'); try { await dbConn.initialize({ setupExitHandlers: false }); debug('Connected to database'); await main(); } catch (error) { console.error('Database connection or script execution failed:', error.message); process.exit(1); } finally { await dbConn.close(); } } // Execute the script runScript(); // Error handling process .on('uncaughtException', function (err) { debug('Uncaught exception:', err); process.exit(1); }) .on('unhandledRejection', (reason, p) => { debug('Unhandled rejection at Promise', p, 'reason:', reason); });