agmission/Development/server/scripts/importCustStripeSubs.js

212 lines
8.0 KiB
JavaScript
Executable File

'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 <stripe_customer_id>
*
* # Dry run (preview without making changes)
* node scripts/importCustStripeSubs.js <stripe_customer_id> --dry-run
*
* # With custom environment file
* node scripts/importCustStripeSubs.js <stripe_customer_id> --env ../environment_prod.env
*
* # With debug output
* DEBUG=agm:stripe-import node scripts/importCustStripeSubs.js <stripe_customer_id>
*/
// 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 <stripe_customer_id> [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);
});