Automating Invoice Generation and Sending with Stripe in 2025

Published on
Written byAbhishek Anand
Automating Invoice Generation and Sending with Stripe in 2025

Efficient invoicing is crucial for business cash flow, but it can be time-consuming and error-prone when done manually. By automating invoice generation and delivery through Stripe, you can create a seamless billing workflow that saves time, reduces errors, and gets you paid faster.

This guide will walk you through setting up a comprehensive automated invoicing system using Stripe's powerful API and tools, from initial setup to advanced customization and integration with your existing systems.


Why Automate Your Invoicing with Stripe?

Before diving into implementation, let's explore the key benefits of automating your invoice process with Stripe:

  1. Time savings - Eliminate manual invoice creation and sending
  2. Faster payments - Automatic delivery and payment reminders improve cash flow
  3. Reduced errors - Automated calculations and consistent formatting
  4. Better tracking - Centralized system for monitoring payment status
  5. Professional appearance - Branded, consistent invoices enhance your company image
  6. Scalability - Handle growing invoice volume without increasing administrative overhead

Setting Up Your Stripe Environment for Automated Invoicing

Prerequisites

Before implementing automated invoicing, you'll need:

  1. A Stripe account with API access
  2. Your business information configured in the Stripe Dashboard
  3. A basic understanding of API concepts
  4. Development resources (either in-house or contracted)

Initial Configuration in Stripe Dashboard

Start by configuring your invoice settings in the Stripe Dashboard:

  1. Log into your Stripe Dashboard
  2. Navigate to Settings > Billing > Customer Portal & Invoicing
  3. Configure your default invoice settings:
    • Payment methods (credit card, ACH, wire transfer, etc.)
    • Due date timeframes
    • Late payment reminders
    • Default footer text and terms
  4. Set up your brand appearance under Settings > Branding:
    • Upload your logo
    • Choose brand colors
    • Select font preferences

Setting Up Invoice Templates

Stripe allows you to customize invoice templates:

  1. Go to Settings > Documents > Invoice Templates
  2. Click Create Template
  3. Configure your template settings:
    • Header and footer content
    • Custom fields
    • Tax ID display
    • Line item details

Creating Customers in Stripe

The foundation of automated invoicing is having customer records in Stripe:

const stripe = require('stripe')('sk_test_your_secret_key');

async function createCustomer(customerData) {
  try {
    const customer = await stripe.customers.create({
      name: customerData.name,
      email: customerData.email,
      phone: customerData.phone,
      address: {
        line1: customerData.address.line1,
        line2: customerData.address.line2,
        city: customerData.address.city,
        state: customerData.address.state,
        postal_code: customerData.address.postalCode,
        country: customerData.address.country,
      },
      metadata: {
        customer_id: customerData.internalId,
        account_manager: customerData.accountManager,
      },
      tax_exempt: customerData.taxExempt || 'none',
      tax_id_data: customerData.taxIds || [],
    });
    
    return customer;
  } catch (error) {
    console.error('Error creating customer:', error);
    throw error;
  }
}

You can create customers when they sign up for your service or import existing customers via the Stripe API.

Implementing Automated Invoice Generation

There are several approaches to automated invoice generation with Stripe:

1. Automatic Subscription Invoicing

The simplest form of automated invoicing is through Stripe Subscriptions:

async function createSubscription(customerId, priceId) {
  try {
    const subscription = await stripe.subscriptions.create({
      customer: customerId,
      items: [{ price: priceId }],
      billing_cycle_anchor: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60), // Start in 30 days
      collection_method: 'send_invoice',
      days_until_due: 30, // Invoice due 30 days after generation
    });
    
    return subscription;
  } catch (error) {
    console.error('Error creating subscription:', error);
    throw error;
  }
}

With this approach, Stripe automatically generates and sends invoices based on the subscription billing cycle.

2. Creating One-Off Invoices

For non-subscription billing, you can create individual invoices:

async function createInvoice(customerId, description, amount, metadata = {}) {
  try {
    // First create an invoice item
    await stripe.invoiceItems.create({
      customer: customerId,
      amount: Math.round(amount * 100), // Convert to cents
      currency: 'usd',
      description,
      metadata,
    });
    
    // Then create and finalize the invoice
    const invoice = await stripe.invoices.create({
      customer: customerId,
      auto_advance: true, // Automatically finalize and send the invoice
      collection_method: 'send_invoice',
      days_until_due: 30,
    });
    
    return invoice;
  } catch (error) {
    console.error('Error creating invoice:', error);
    throw error;
  }
}

3. Bulk Invoice Generation

For services that bill multiple customers at once (e.g., monthly service billing):

async function generateMonthlyInvoices(customers, serviceDescription, baseFee) {
  const results = {
    success: [],
    failed: [],
  };
  
  for (const customer of customers) {
    try {
      // Create invoice item
      await stripe.invoiceItems.create({
        customer: customer.id,
        amount: Math.round(baseFee * 100),
        currency: 'usd',
        description: `${serviceDescription} - ${new Date().toLocaleString('en-US', { month: 'long', year: 'numeric' })}`,
        metadata: {
          billing_period: new Date().toISOString().substring(0, 7), // YYYY-MM format
          service_type: 'monthly',
        },
      });
      
      // Add any customer-specific line items
      if (customer.additionalServices) {
        for (const service of customer.additionalServices) {
          await stripe.invoiceItems.create({
            customer: customer.id,
            amount: Math.round(service.price * 100),
            currency: 'usd',
            description: service.description,
          });
        }
      }
      
      // Create and finalize the invoice
      const invoice = await stripe.invoices.create({
        customer: customer.id,
        auto_advance: true,
        collection_method: 'send_invoice',
        days_until_due: 30,
        metadata: {
          batch_id: `monthly-${new Date().toISOString().substring(0, 7)}`,
        },
      });
      
      results.success.push({
        customerId: customer.id,
        invoiceId: invoice.id,
        amount: invoice.total / 100, // Convert from cents
      });
    } catch (error) {
      results.failed.push({
        customerId: customer.id,
        error: error.message,
      });
    }
  }
  
  return results;
}

Customizing Invoice Content and Appearance

Adding Line Items with Detail

For more detailed invoices with multiple line items:

async function createDetailedInvoice(customerId, items, notes = '') {
  try {
    // Add each line item
    for (const item of items) {
      await stripe.invoiceItems.create({
        customer: customerId,
        price_data: {
          currency: 'usd',
          product_data: {
            name: item.description,
          },
          unit_amount: Math.round(item.unitPrice * 100),
        },
        quantity: item.quantity,
        metadata: item.metadata || {},
      });
    }
    
    // Create the invoice
    const invoice = await stripe.invoices.create({
      customer: customerId,
      collection_method: 'send_invoice',
      days_until_due: 30,
      footer: notes,
    });
    
    // Finalize the invoice
    const finalizedInvoice = await stripe.invoices.finalizeInvoice(invoice.id);
    
    // Send the invoice
    await stripe.invoices.sendInvoice(finalizedInvoice.id);
    
    return finalizedInvoice;
  } catch (error) {
    console.error('Error creating detailed invoice:', error);
    throw error;
  }
}

Including Custom Fields

For B2B invoicing, you often need to include customer-specific information:

async function createB2BInvoice(customerId, items, customFields = {}) {
  try {
    // Add each line item
    for (const item of items) {
      await stripe.invoiceItems.create({
        customer: customerId,
        price_data: {
          currency: 'usd',
          product_data: {
            name: item.description,
          },
          unit_amount: Math.round(item.unitPrice * 100),
        },
        quantity: item.quantity,
      });
    }
    
    // Create the invoice with custom fields in metadata
    const invoice = await stripe.invoices.create({
      customer: customerId,
      collection_method: 'send_invoice',
      days_until_due: 30,
      metadata: {
        ...customFields,
        po_number: customFields.poNumber,
        project_code: customFields.projectCode,
        department: customFields.department,
      },
    });
    
    // Update the invoice with custom footer containing PO number if provided
    if (customFields.poNumber) {
      await stripe.invoices.update(invoice.id, {
        footer: `PO Number: ${customFields.poNumber}\nThank you for your business!`,
      });
    }
    
    // Finalize and send
    const finalizedInvoice = await stripe.invoices.finalizeInvoice(invoice.id);
    await stripe.invoices.sendInvoice(finalizedInvoice.id);
    
    return finalizedInvoice;
  } catch (error) {
    console.error('Error creating B2B invoice:', error);
    throw error;
  }
}

Automating Invoice Delivery

Email Delivery Settings

Configure how invoices are delivered by email:

async function updateInvoiceEmailSettings(customerId, settings) {
  try {
    const customer = await stripe.customers.update(customerId, {
      invoice_settings: {
        custom_fields: settings.customFields || [],
        footer: settings.footerText,
        default_payment_method: settings.defaultPaymentMethod,
        rendering_options: {
          amount_tax_display: settings.showTaxSeparately ? 'separate' : 'include',
        },
      },
      preferred_locales: settings.preferredLocales || ['en'],
      email: settings.updatedEmail || undefined,
    });
    
    return customer;
  } catch (error) {
    console.error('Error updating invoice email settings:', error);
    throw error;
  }
}

Scheduling Invoice Generation and Delivery

For businesses with regular billing cycles, scheduling invoice generation is essential:

const cron = require('node-cron');

// Schedule monthly invoice generation on the 1st of each month at 2:00 AM
cron.schedule('0 2 1 * *', async () => {
  console.log('Running monthly invoice generation...');
  try {
    // Get all customers that need monthly invoicing
    const customers = await getCustomersForMonthlyBilling();
    
    // Generate and send invoices
    const results = await generateMonthlyInvoices(
      customers,
      'Monthly Service Fee',
      99.00
    );
    
    console.log(`Successfully generated ${results.success.length} invoices`);
    if (results.failed.length > 0) {
      console.error(`Failed to generate ${results.failed.length} invoices`);
      // Implement notification for failed invoices
      notifyAccountingTeam(results.failed);
    }
  } catch (error) {
    console.error('Error in scheduled invoice generation:', error);
    // Implement error notification
    notifyTechnicalTeam(error);
  }
});

Handling Invoice Payments and Tracking

Tracking Payment Status

Monitor the status of your invoices:

async function getInvoiceStatus(invoiceId) {
  try {
    const invoice = await stripe.invoices.retrieve(invoiceId);
    
    let status = '';
    if (invoice.status === 'paid') {
      status = 'Paid';
    } else if (invoice.status === 'open') {
      status = 'Awaiting Payment';
    } else if (invoice.status === 'draft') {
      status = 'Draft';
    } else if (invoice.status === 'uncollectible') {
      status = 'Bad Debt';
    } else if (invoice.status === 'void') {
      status = 'Voided';
    }
    
    const daysOverdue = invoice.due_date 
      ? Math.floor((Date.now() / 1000 - invoice.due_date) / (24 * 60 * 60)) 
      : 0;
    
    return {
      invoiceId: invoice.id,
      customerName: invoice.customer_name,
      customerEmail: invoice.customer_email,
      amount: invoice.total / 100,
      currency: invoice.currency,
      status,
      daysOverdue: daysOverdue > 0 ? daysOverdue : 0,
      pdfUrl: invoice.invoice_pdf,
      hostedInvoiceUrl: invoice.hosted_invoice_url,
    };
  } catch (error) {
    console.error('Error retrieving invoice status:', error);
    throw error;
  }
}

async function getOverdueInvoices(daysThreshold = 7) {
  try {
    const now = Math.floor(Date.now() / 1000);
    const thresholdDate = now - (daysThreshold * 24 * 60 * 60);
    
    const invoices = await stripe.invoices.list({
      status: 'open',
      due_date: {
        lt: thresholdDate,
      },
      limit: 100,
    });
    
    return invoices.data.map(invoice => ({
      invoiceId: invoice.id,
      customerName: invoice.customer_name,
      customerEmail: invoice.customer_email,
      amount: invoice.total / 100,
      currency: invoice.currency,
      daysOverdue: Math.floor((now - invoice.due_date) / (24 * 60 * 60)),
      pdfUrl: invoice.invoice_pdf,
      hostedInvoiceUrl: invoice.hosted_invoice_url,
    }));
  } catch (error) {
    console.error('Error retrieving overdue invoices:', error);
    throw error;
  }
}

Implementing Automatic Payment Reminders

Set up automatic reminders for overdue invoices:

async function sendPaymentReminder(invoice) {
  try {
    // First, check if the invoice is still unpaid
    const currentInvoice = await stripe.invoices.retrieve(invoice.invoiceId);
    if (currentInvoice.status !== 'open') {
      return { success: true, alreadyPaid: true };
    }
    
    // Send a reminder email through your email service
    await sendReminderEmail({
      to: invoice.customerEmail,
      subject: `Payment Reminder: Invoice #${invoice.invoiceNumber}`,
      body: `
Dear ${invoice.customerName},

This is a friendly reminder that invoice #${invoice.invoiceNumber} for ${invoice.amount} ${invoice.currency.toUpperCase()} is now ${invoice.daysOverdue} days overdue.

You can view and pay your invoice online: ${invoice.hostedInvoiceUrl}

If you have any questions or concerns, please don't hesitate to contact our billing department.

Thank you for your business!
      `,
    });
    
    // Log the reminder in Stripe metadata
    await stripe.invoices.update(invoice.invoiceId, {
      metadata: {
        ...currentInvoice.metadata,
        last_reminder_sent: new Date().toISOString(),
        reminder_count: String(parseInt(currentInvoice.metadata?.reminder_count || '0') + 1),
      },
    });
    
    return { success: true, alreadyPaid: false };
  } catch (error) {
    console.error('Error sending payment reminder:', error);
    return { success: false, error: error.message };
  }
}

Best Practices for Invoice Automation

As you implement your automated invoicing system, keep these best practices in mind:

  1. Start with a pilot group - Begin with a small subset of customers to test your automation before rolling out to everyone.
  2. Implement detailed logging - Track all invoice-related operations for debugging and auditing purposes.
  3. Set up alerts for failures - Create notification systems for failed invoice generation or delivery.
  4. Regularly reconcile with accounting - Ensure Stripe invoices match your accounting system records.
  5. Create customer-friendly invoices - Use clear descriptions and organize line items logically.
  6. Test across different scenarios - Verify your system works with different currencies, tax situations, and customer types.
  7. Document your invoice process - Create clear documentation for your team on how the automated system works.
  8. Implement retry logic - Add automated retries for failed invoice operations with exponential backoff.

Troubleshooting Common Issues

Failed Invoice Creation

If invoices fail to create:

  1. Check API credentials - Ensure your Stripe API key is valid and has the necessary permissions.
  2. Verify customer existence - Confirm the customer ID exists in Stripe.
  3. Check for required fields - Make sure all required fields are populated.
  4. Look for currency mismatches - Ensure the currency is consistent across all line items.

Delivery Problems

If customers aren't receiving invoices:

  1. Check customer email - Verify the email address is correct.
  2. Check spam filters - Ask customers to check spam folders and whitelist Stripe emails.
  3. Verify Stripe email services - Ensure Stripe's email services aren't experiencing issues.

Payment Application Issues

If payments aren't being properly applied to invoices:

  1. Check webhook configuration - Ensure your webhooks are properly set up to catch payment events.
  2. Verify payment method - Confirm the customer has a valid payment method on file.
  3. Check for payment errors - Look for failed payment attempts in the Stripe Dashboard.

Scaling Your Invoice Automation

As your business grows, consider these strategies for scaling your invoice automation:

Improving Performance

// Implement caching for frequently accessed data
const NodeCache = require('node-cache');
const customerCache = new NodeCache({ stdTTL: 3600 }); // Cache for 1 hour

async function getCustomerWithCaching(customerId) {
  // Check cache first
  const cachedCustomer = customerCache.get(customerId);
  if (cachedCustomer) {
    return cachedCustomer;
  }
  
  // If not in cache, fetch from Stripe
  const customer = await stripe.customers.retrieve(customerId);
  
  // Store in cache
  customerCache.set(customerId, customer);
  
  return customer;
}

Handling Failures Gracefully

async function createInvoiceWithRetry(customerId, description, amount, metadata = {}, maxRetries = 3) {
  let retries = 0;
  
  while (retries < maxRetries) {
    try {
      // Attempt to create invoice
      const invoice = await createInvoice(customerId, description, amount, metadata);
      return invoice;
    } catch (error) {
      retries++;
      console.error(`Invoice creation failed (attempt ${retries}/${maxRetries}):`, error.message);
      
      if (retries >= maxRetries) {
        throw new Error(`Failed to create invoice after ${maxRetries} attempts: ${error.message}`);
      }
      
      // Exponential backoff before retry
      const delay = Math.pow(2, retries) * 1000 + Math.random() * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Conclusion

Automating your invoice generation and delivery process with Stripe's API offers significant benefits for businesses of all sizes. By following the implementation steps and best practices outlined in this guide, you can create a robust, scalable invoicing system that saves time, reduces errors, and accelerates cash flow.

The key components of a successful invoice automation system include:

  1. Customer management - Maintaining accurate customer records in Stripe
  2. Invoice generation - Creating detailed, professional invoices automatically
  3. Customization and branding - Ensuring invoices reflect your brand identity
  4. Delivery automation - Sending invoices at the right time with the right information
  5. Payment tracking - Monitoring invoice status and facilitating timely payments
  6. System integration - Connecting with your accounting and business systems
  7. Error handling - Gracefully managing failures and edge cases

As your business grows, your automated invoicing system can scale with you, handling increasing volume and complexity while maintaining accuracy and efficiency. By leveraging Stripe's powerful API and following the patterns described in this guide, you'll transform invoicing from a tedious manual process into a streamlined, automated workflow that benefits both your team and your customers. }; } }

// Schedule daily check for overdue invoices cron.schedule('0 9 * * *', async () => { try { // Get invoices overdue by more than 3 days const overdueInvoices = await getOverdueInvoices(3);

console.log(`Found ${overdueInvoices.length} overdue invoices`);

// Send reminders for each overdue invoice
for (const invoice of overdueInvoices) {
  await sendPaymentReminder(invoice);
  
  // Avoid rate limiting
  await new Promise(resolve => setTimeout(resolve, 1000));
}

} catch (error) { console.error('Error in overdue invoice processing:', error); } });


## Integrating with Your Business Systems

### Connecting with Accounting Software

To keep your financial systems in sync, integrate Stripe with your accounting software:

```javascript
async function syncInvoiceToAccounting(invoiceId) {
  try {
    // Retrieve detailed invoice information
    const invoice = await stripe.invoices.retrieve(invoiceId, {
      expand: ['customer', 'lines.data'],
    });
    
    // Format the data for your accounting system
    const accountingData = {
      invoiceNumber: invoice.number,
      customerName: invoice.customer.name,
      customerEmail: invoice.customer.email,
      customerReference: invoice.customer.metadata.customer_id,
      invoiceDate: new Date(invoice.created * 1000).toISOString().split('T')[0],
      dueDate: new Date(invoice.due_date * 1000).toISOString().split('T')[0],
      amount: invoice.total / 100,
      currency: invoice.currency,
      status: invoice.status,
      lineItems: invoice.lines.data.map(line => ({
        description: line.description,
        quantity: line.quantity,
        unitPrice: line.unit_amount / 100,
        amount: line.amount / 100,
        taxRate: line.tax_rates.length > 0 ? line.tax_rates[0].percentage : 0,
      })),
      metadata: invoice.metadata,
    };
    
    // Send to accounting system API (implementation depends on your accounting software)
    const response = await sendToAccountingSystem(accountingData);
    
    // Update Stripe invoice with accounting reference
    await stripe.invoices.update(invoiceId, {
      metadata: {
        ...invoice.metadata,
        accounting_ref: response.accountingReference,
        last_synced: new Date().toISOString(),
      },
    });
    
    return { success: true, accountingRef: response.accountingReference };
  } catch (error) {
    console.error('Error syncing invoice to accounting:', error);
    return { success: false, error: error.message };
  }
}

Webhook Integration for Real-time Updates

Set up webhooks to keep your systems updated when invoice status changes:

// Express server example
const express = require('express');
const app = express();

// Parse Stripe webhook events
app.post('/webhook/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;
  
  try {
    // Verify the event came from Stripe
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
  
  // Handle specific event types
  switch (event.type) {
    case 'invoice.created':
      // A new invoice has been created
      await handleInvoiceCreated(event.data.object);
      break;
    case 'invoice.finalized':
      // An invoice has been finalized
      await handleInvoiceFinalized(event.data.object);
      break;
    case 'invoice.paid':
      // An invoice has been paid
      await handleInvoicePaid(event.data.object);
      break;
    case 'invoice.payment_failed':
      // Invoice payment failed
      await handleInvoicePaymentFailed(event.data.object);
      break;
    case 'invoice.payment_action_required':
      // Extra authentication required
      await handlePaymentActionRequired(event.data.object);
      break;
    default:
      // Unexpected event type
      console.log(`Unhandled event type ${event.type}`);
  }
  
  // Return a response to acknowledge receipt of the event
  res.json({ received: true });
});

// Example of one of the webhook handlers
async function handleInvoicePaid(invoice) {
  try {
    console.log(`Invoice ${invoice.id} has been paid!`);
    
    // Sync with accounting system
    await syncInvoiceToAccounting(invoice.id);
    
    // Send thank you email to customer
    await sendThankYouEmail(invoice);
    
    // Update internal systems
    await updateOrderFulfillmentSystem(invoice);
    
    // Log successful payment
    await logPaymentSuccess(invoice);
  } catch (error) {
    console.error('Error handling paid invoice:', error);
    // Implement notification for your team
    await notifyTeamOfError(error, invoice.id);
  }
}

Advanced Features for Enterprise Invoicing

Multi-Currency Support

For businesses operating globally:

async function createMultiCurrencyInvoice(customerId, items, currency = 'usd') {
  try {
    // Add each line item with the specified currency
    for (const item of items) {
      await stripe.invoiceItems.create({
        customer: customerId,
        price_data: {
          currency,
          product_data: {
            name: item.description,
          },
          unit_amount: Math.round(item.unitPrice * 100),
        },
        quantity: item.quantity,
      });
    }
    
    // Create the invoice in the specified currency
    const invoice = await stripe.invoices.create({
      customer: customerId,
      collection_method: 'send_invoice',
      days_until_due: 30,
      currency,
    });
    
    // Finalize and send
    const finalizedInvoice = await stripe.invoices.finalizeInvoice(invoice.id);
    await stripe.invoices.sendInvoice(finalizedInvoice.id);
    
    return finalizedInvoice;
  } catch (error) {
    console.error('Error creating multi-currency invoice:', error);
    throw error;
  }
}

Tax Rate Management

Handle tax rates for different jurisdictions:

async function createTaxRateForRegion(displayName, percentage, jurisdiction, type = 'vat') {
  try {
    const taxRate = await stripe.taxRates.create({
      display_name: displayName,
      percentage,
      inclusive: false, // Exclusive tax
      country: jurisdiction.country,
      state: jurisdiction.state,
      description: `${type.toUpperCase()} tax for ${jurisdiction.country} - ${jurisdiction.state || 'All states'}`,
      tax_type: type, // 'vat', 'sales_tax', or 'gst'
    });
    
    return taxRate;
  } catch (error) {
    console.error('Error creating tax rate:', error);
    throw error;
  }
}

async function createInvoiceWithTax(customerId, items, taxRates = []) {
  try {
    // Add each line item with applicable tax rates
    for (const item of items) {
      await stripe.invoiceItems.create({
        customer: customerId,
        price_data: {
          currency: 'usd',
          product_data: {
            name: item.description,
          },
          unit_amount: Math.round(item.unitPrice * 100),
        },
        quantity: item.quantity,
        tax_rates: item.taxExempt ? [] : taxRates,
      });
    }
    
    // Create the invoice
    const invoice = await stripe.invoices.create({
      customer: customerId,
      collection_method: 'send_invoice',
      days_until_due: 30,
    });
    
    // Finalize and send
    const finalizedInvoice = await stripe.invoices.finalizeInvoice(invoice.id);
    await stripe.invoices.sendInvoice(finalizedInvoice.id);
    
    return finalizedInvoice;
  } catch (error) {
    console.error('Error creating invoice with tax:', error);
    throw error;
  }
}

Bulk Operations for Large Organizations

For organizations dealing with a high volume of invoices:

async function bulkCreateDraftInvoices(customers, defaultItems, customItemsMap = {}) {
  const results = {
    success: [],
    failed: [],
  };
  
  // Process in batches to avoid rate limiting
  const batchSize = 20;
  for (let i = 0; i < customers.length; i += batchSize) {
    const batch = customers.slice(i, i + batchSize);
    const batchPromises = batch.map(async (customer) => {
      try {
        // Add default items for all customers
        for (const item of defaultItems) {
          await stripe.invoiceItems.create({
            customer: customer.id,
            price_data: {
              currency: 'usd',
              product_data: {
                name: item.description,
              },
              unit_amount: Math.round(item.unitPrice * 100),
            },
            quantity: item.quantity,
          });
        }
        
        // Add custom items for this specific customer if they exist
        const customItems = customItemsMap[customer.id] || [];
        for (const item of customItems) {
          await stripe.invoiceItems.create({
            customer: customer.id,
            price_data: {
              currency: 'usd',
              product_data: {
                name: item.description,
              },
              unit_amount: Math.round(item.unitPrice * 100),
            },
            quantity: item.quantity,
          });
        }
        
        // Create draft invoice (not finalized yet)
        const invoice = await stripe.invoices.create({
          customer: customer.id,
          auto_advance: false, // Keep as draft
          collection_method: 'send_invoice',
          days_until_due: 30,
          metadata: {
            batch_id: `bulk-${new Date().toISOString().split('T')[0]}`,
          },
        });
        
        results.success.push({
          customerId: customer.id,
          invoiceId: invoice.id,
        });
      } catch (error) {
        results.failed.push({
          customerId: customer.id,
          error: error.message,
        });
      }
    });
    
    // Wait for all promises in the current batch to resolve
    await Promise.all(batchPromises);
    
    // Sleep between batches to avoid rate limiting
    if (i + batchSize < customers.length) {
      await new Promise(resolve => setTimeout(resolve, 2000));
    }
  }
  
  return results;
}

async function reviewAndFinalizeBulkInvoices(batchId) {
  try {
    // Get all draft invoices from this batch
    const invoices = await stripe.invoices.list({
      status: 'draft',
      limit: 100,
      metadata: {
        batch_id: batchId,
      },
    });
    
    const results = {
      finalized: [],
      failed: [],
    };
    
    // Process invoices in smaller batches
    const batchSize = 10;
    for (let i = 0; i < invoices.data.length; i += batchSize) {
      const batch = invoices.data.slice(i, i + batchSize);
      const batchPromises = batch.map(async (invoice) => {
        try {
          // Finalize the invoice
          const finalizedInvoice = await stripe.invoices.finalizeInvoice(invoice.id);
          
          // Send the invoice
          await stripe.invoices.sendInvoice(finalizedInvoice.id);
          
          results.finalized.push({
            invoiceId: finalizedInvoice.id,
            customerId: finalizedInvoice.customer,
            amount: finalizedInvoice.total / 100,
          });
        } catch (error) {
          results.failed.push({
            invoiceId: invoice.id,
            customerId: invoice.customer,
            error: error.message,
          });
        }
      });
      
      // Wait for all promises in the current batch to resolve
      await Promise.all(batchPromises);
      
      // Sleep between batches
      if (i + batchSize < invoices.data.length) {
        await new Promise(resolve => setTimeout(resolve, 2000));
      }
    }
    
    return results;
  } catch (error) {
    console.error('Error in bulk finalization:', error);
    throw error;
  }
}

Testing Your Invoice Automation System

Before deploying to production, test your system thoroughly:

// Test helper for invoice generation
async function testInvoiceGeneration() {
  try {
    // Create a test customer
    const customer = await stripe.customers.create({
      name: 'Test Customer',
      email: 'test@example.com',
      metadata: {
        is_test: 'true',
      },
    });
    
    console.log('Created test customer:', customer.id);
    
    // Create a simple invoice
    const invoice = await createInvoice(
      customer.id,
      'Test Service',
      99.99,
      { test: 'true' }
    );
    
    console.log('Created test invoice:', invoice.id);
    
    // Verify invoice details
    const retrievedInvoice = await stripe.invoices.retrieve(invoice.id);
    console.log('Invoice amount:', retrievedInvoice.total / 100);
    console.log('Invoice status:', retrievedInvoice.status);
    
    // Clean up - void the invoice
    await stripe.invoices.voidInvoice(invoice.id);
    console.log('Voided test invoice');
    
    // Clean up - delete the customer
    await stripe.customers.del(customer.id);
    console.log('Deleted test customer');
    
    return { success: true };
  } catch (error) {
    console.error('Test failed:', error);
    return { success: false, error: error.message };
  }
}

// Test the full invoice workflow
async function testFullInvoiceWorkflow() {
  try {
    // Create test customer
    const customer = await stripe.customers.create({
      name: 'Workflow Test',
      email: 'workflow-test@example.com',
    });
    
    // Create invoice items
    const items = [
      { description: 'Service Plan - Basic', unitPrice: 49.99, quantity: 1 },
      { description: 'Additional Users (5)', unitPrice: 10.00, quantity: 5 },
    ];
    
    // Create and send invoice
    const invoice = await createDetailedInvoice(
      customer.id, 
      items, 
      'Thank you for your business!'
    );
    
    console.log('Created and sent test invoice:', invoice.id);
    
    // Simulate payment (in test mode)
    const paymentIntent = await stripe.paymentIntents.create({
      amount: invoice.amount_due,
      currency: invoice.currency,
      customer: customer.id,
      payment_method: 'pm_card_visa', // Test payment method
      confirm: true,
      off_session: true,
    });
    
    // Apply payment to invoice
    const paidInvoice = await stripe.invoices.pay(invoice.id, {
      paid_out_of_band: true,
    });
    
    console.log('Invoice payment status:', paidInvoice.status);
    
    // Verify the invoice is marked as paid
    const finalInvoice = await stripe.invoices.retrieve(invoice.id);
    console.assert(finalInvoice.status === 'paid', 'Invoice should be paid');
    
    // Clean up
    await stripe.invoices.voidInvoice(invoice.id);
    await stripe.customers.del(customer.id);
    
    return { success: true };
  } catch (error) {
    console.error('Workflow test failed:', error);
    return { success: false, error: error.message };
  }
}

Ready to get started?

Start your journey with us today and get access to our resources and tools.