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.
Before diving into implementation, let's explore the key benefits of automating your invoice process with Stripe:
Before implementing automated invoicing, you'll need:
Start by configuring your invoice settings in the Stripe Dashboard:
Stripe allows you to customize invoice templates:
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.
There are several approaches to automated invoice generation with Stripe:
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.
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;
}
}
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;
}
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;
}
}
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;
}
}
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;
}
}
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);
}
});
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;
}
}
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 };
}
}
As you implement your automated invoicing system, keep these best practices in mind:
If invoices fail to create:
If customers aren't receiving invoices:
If payments aren't being properly applied to invoices:
As your business grows, consider these strategies for scaling your invoice automation:
// 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;
}
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));
}
}
}
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:
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 };
}
}
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);
}
}
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;
}
}
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;
}
}
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;
}
}
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 };
}
}
Start your journey with us today and get access to our resources and tools.