medusa-invoice-sbl
v0.0.6
Published
A starter for Medusa plugins.
Maintainers
Readme
Medusa Invoice Download Plugin
A Medusa plugin that enables customers and admins to download invoices as PDF files for orders. Supports custom PDF templates and automatic email attachments.
Features
- Storefront API: Authenticated customers can download invoices for their own orders
- Admin API: Admins can download invoices for any order
- Template-Based PDF Generation: Support for custom PDF templates via file paths or functions
- Email Integration: Automatically attach invoices to order creation emails (configurable)
- Admin UI: Download button widget in order details sidebar
- Authentication: Proper authentication and authorization checks
Compatibility
This plugin is compatible with Medusa v2.4.0 and above.
Installation
- Install the plugin:
npm install medusa-invoice
# or
yarn add medusa-invoice- Add the plugin to your
medusa-config.js:
module.exports = {
// ... other config
plugins: [
{
resolve: "medusa-invoice",
options: {
// Optional: Custom template (file path or function)
template: "./templates/invoice-template.js",
// Optional: Attach invoice to order creation emails
attachInvoiceToEmail: true,
// Optional: Default template path if no template provided
defaultTemplatePath: "./templates/default-invoice.js",
},
},
],
}- Run migrations (if any):
npx medusa db:migrateConfiguration Options
template (optional)
The PDF template to use for invoice generation. Can be:
- File path (string): Path to a template file that exports a function
- Function: A function that accepts
OrderDTOand returnsPromise<Buffer>
Example template files are available in the templates/ directory:
templates/invoice-template.example.js- Full-featured template with styling, tables, and formattingtemplates/invoice-template-simple.example.js- Minimal template for quick setup
Basic template structure:
const PDFDocument = require("pdfkit")
// Helper to convert BigNumber values
function toNumber(value) {
if (typeof value === "number") return value
if (typeof value === "string") return Number(value) || 0
if (value && typeof value === "object" && "numeric" in value) {
return value.numeric || 0
}
return 0
}
module.exports = async function generateInvoice(order) {
const doc = new PDFDocument({ margin: 50 })
const buffers = []
doc.on("data", buffers.push.bind(buffers))
// Custom PDF generation logic
doc.fontSize(20).text("Invoice", { align: "center" })
doc.moveDown()
doc.text(`Order ID: ${order.id}`)
doc.text(`Total: ${toNumber(order.total).toFixed(2)}`)
// ... more content
doc.end()
return new Promise((resolve) => {
doc.on("end", () => {
resolve(Buffer.concat(buffers))
})
})
}Important: Always use the toNumber() helper when working with order totals (subtotal, tax_total, shipping_total, total) as they may be BigNumber objects, strings, or numbers.
attachInvoiceToEmail (optional)
Boolean flag to enable/disable automatic invoice attachment to order creation emails. Default: false
defaultTemplatePath (optional)
Path to a default template file to use when no template is provided. If not specified, a basic default template is used.
API Endpoints
Storefront API
Download Invoice
Endpoint: GET /store/invoice/download/:order_id
Authentication: Required (customer)
Description: Download invoice PDF for a specific order. Only works for orders belonging to the authenticated customer.
Response: PDF file with Content-Type: application/pdf
Example:
curl -X GET \
'http://localhost:9000/store/invoice/download/order_123' \
-H 'Authorization: Bearer <customer_token>' \
--output invoice.pdfAdmin API
Download Invoice
Endpoint: GET /admin/invoice/download/:order_id
Authentication: Required (admin)
Description: Download invoice PDF for any order.
Response: PDF file with Content-Type: application/pdf
Example:
curl -X GET \
'http://localhost:9000/admin/invoice/download/order_123' \
-H 'Authorization: Bearer <admin_token>' \
--output invoice.pdfGet Settings
Endpoint: GET /admin/invoice/settings
Authentication: Required (admin)
Description: Get plugin configuration settings.
Response:
{
"settings": {
"attachInvoiceToEmail": true,
"hasTemplate": true,
"hasDefaultTemplatePath": false
}
}Creating Custom Templates
Template Function Signature
Your template function must accept an OrderDTO and return a Promise<Buffer>:
import { OrderDTO } from "@medusajs/framework/types"
async function myTemplate(order: OrderDTO): Promise<Buffer> {
// Generate PDF using PDFKit or any PDF library
// Return PDF buffer
}Using PDFKit
The plugin uses PDFKit internally. You can use it in your templates:
const PDFDocument = require("pdfkit")
module.exports = async function generateInvoice(order) {
const doc = new PDFDocument({ margin: 50 })
const buffers = []
doc.on("data", buffers.push.bind(buffers))
// Your custom PDF generation
doc.fontSize(20).text("Invoice", { align: "center" })
doc.moveDown()
// Access order data
doc.text(`Order ID: ${order.id}`)
doc.text(`Email: ${order.email}`)
doc.text(`Total: ${order.total}`)
// Access order items
if (order.items) {
order.items.forEach((item) => {
doc.text(`${item.title} - ${item.quantity}x ${item.unit_price}`)
})
}
// Access addresses
if (order.shipping_address) {
doc.text(`Shipping: ${order.shipping_address.address_1}`)
}
doc.end()
return new Promise((resolve) => {
doc.on("end", () => {
resolve(Buffer.concat(buffers))
})
})
}OrderDTO Structure
The OrderDTO object contains:
id: Order IDemail: Customer emailtotal: Order totalsubtotal: Order subtotaltax_total: Tax totalshipping_total: Shipping totalitems: Array of order itemsshipping_address: Shipping address objectbilling_address: Billing address objectcreated_at: Order creation date- And more...
Email Integration
When attachInvoiceToEmail is enabled, the plugin automatically generates an invoice PDF when an order is created and attaches it to the order creation email notification.
The subscriber listens to the order.placed event (checkout completion in some Medusa setups). The flow is:
- Checks if invoice attachment is enabled
- Generates the invoice PDF
- Attaches it to the email notification
Notes:
- Ensure
attachInvoiceToEmailistruein your plugin configuration. - The plugin sends its own email (with attachment) using the configured notification provider; this operates independently of other plugins' email templates.
Troubleshooting attachments:
Verify your notification provider supports attachments (some providers or custom adapters ignore
attachmentsby default).Enable debug logging and check server logs for these messages from the invoice subscriber:
[Invoice Subscriber] Payload summary - keys: ...; attachments=...(shows payload shape)[Invoice Subscriber] ✅ Email sent successfully - Order: ...; attachment_sizes=[...](shows attachment byte size)
If attachments do not appear, confirm your provider reads
attachments,files, orattachmentsRawfrom the payload. We now include several shapes for compatibility:attachments(base64 content,encoding: "base64") - common provider shapefiles(base64 content, minimal fields)attachmentsRaw(Buffer) - for Nodemailer-like providers that expect BuffersgAttachments(SendGrid-style: base64 +disposition: "attachment")
If your provider requires a different shape, either adapt your provider to accept one of these fields or tell me which provider you're using and I can add a small adapter to map our payload into the provider's expected format.
Admin UI
The plugin adds a download button widget to the order details page in the Medusa Admin. The widget appears in the sidebar and allows admins to download invoices with a single click.
Workflows
The plugin exports a workflow for programmatic invoice generation:
import { generateInvoiceWorkflow } from "medusa-invoice"
const { result } = await generateInvoiceWorkflow(container).run({
input: {
orderId: "order_123",
customerId: "customer_456", // Optional, required for storefront
template: customTemplate, // Optional
},
})
const pdfBuffer = result.bufferTypeScript Support
The plugin is written in TypeScript and exports types:
import type {
InvoicePluginOptions,
InvoiceTemplate,
InvoiceGenerationResult,
} from "medusa-invoice"Development
Building the Plugin
npm run buildDevelopment Mode
npm run devTroubleshooting
Invoice Generation Fails
- Check that the order exists and has required data
- Verify template function returns a valid PDF buffer
- Check server logs for detailed error messages
Email Attachment Not Working
- Ensure
attachInvoiceToEmailis set totruein config - Verify notification service is properly configured
- Check that the
order.placedevent is being emitted
Template Not Loading
- Verify template file path is correct (relative to project root or absolute)
- Ensure template exports a function with correct signature
- Check file permissions
License
MIT
Support
For issues and questions, please open an issue on GitHub.
