212 lines
8.0 KiB
JavaScript
Executable File
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);
|
|
}); |