1. Database Schema Blueprint
A robust invoicing system requires distinct database objects. You must separate the Invoice header metadata (payment method, statuses) from individual Invoice Line Items (products, quantities, unit prices) to support historical compliance.
// SQL structure for standard Invoices
CREATE TABLE invoices (
id VARCHAR(36) PRIMARY KEY,
customer_id VARCHAR(36) NOT NULL,
invoice_number VARCHAR(50) UNIQUE,
status VARCHAR(20) DEFAULT 'unpaid',
subtotal DECIMAL(10,2),
tax DECIMAL(10,2),
total DECIMAL(10,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
2. High-Performance PDF Generation
While spawning a headless browser like Puppeteer is ideal for complicated layouts, drawing raw shapes and texts with pdfkit uses significantly less RAM and compiles the output buffer in milliseconds.
const PDFDocument = require('pdfkit');
const fs = require('fs');
function generateInvoicePDF(invoiceData, path) {
const doc = new PDFDocument({ size: 'A4', margin: 50 });
doc.pipe(fs.createWriteStream(path));
// Add invoice headers
doc.fontSize(20).text('KODYFIER INVOICE', { align: 'right' });
doc.fontSize(10).text(`Number: ${invoiceData.invoice_number}`);
doc.text(`Date: ${new Date().toLocaleDateString()}`);
doc.moveDown();
doc.text(`To: ${invoiceData.customer_name}`);
// Draw line table
doc.lineGap(10);
invoiceData.items.forEach(item => {
doc.text(`${item.name} x${item.qty} - ₹${item.price}`);
});
doc.end();
}
3. Stripe Webhook Listener
Never mark an invoice paid based solely on frontend redirection. Webhooks provide transactional integrity. You must validate the Stripe webhook signature headers using the raw buffer input before processing.
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
// Note: stripe requires the RAW buffer body for signature checks
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle success checkout event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
markInvoicePaid(session.metadata.invoice_id);
}
res.json({ received: true });
});
Discussion