Invoice Management¶
Complete guide to managing invoices, viewing invoice details, and downloading PDF documents in the Reserva platform.
Overview¶
The invoice system provides comprehensive invoice management with support for:
- Invoice Creation - Create invoices with automatic number generation and tax calculation
- Invoice Listing - Paginated list of invoices with filtering
- Invoice Details - Complete invoice information with line items
- PDF Generation - Professional print-ready PDF invoices
- Dual Authentication - Support for both staff and customer access
- Multi-type Support - Subscription, appointment, and setup fee invoices
- Payment Tracking - Real-time payment status and history
Key Concepts:
- Staff Access = Full invoice management within tenant
- Customer Access = View and download own invoices only
- Invoice Types = SUBSCRIPTION, APPOINTMENT, SETUP_FEE
- Invoice Status = DRAFT, SENT, PAID, OVERDUE, CANCELLED
Available Invoice Types¶
Subscription Invoices¶
- Type:
SUBSCRIPTION - Purpose: Subscription plan payments (upgrades, renewals)
- Amount: Based on plan pricing and billing cycle
- Related: Links to subscription record
- Metadata: Includes plan details and billing period
Appointment Invoices¶
- Type:
APPOINTMENT - Purpose: Service booking payments
- Amount: Service pricing + taxes
- Related: Links to appointment record
- Metadata: Includes service details and staff info
Setup Fee Invoices¶
- Type:
SETUP_FEE - Purpose: One-time tenant onboarding fees
- Amount: Fixed setup amount
- Related: Links to tenant record
- Metadata: Includes setup package details
Create New Invoice¶
Create a new invoice with automatic number generation and tax calculations.
Endpoint¶
Authentication: Required (JWT token - Staff only)
Access Control¶
Staff Users:
- Requires:
TENANT_ADMINorSUPER_ADMINrole - Can create invoices for their tenant only
- All invoice data validated and calculated automatically
Permissions:
TENANT_ADMIN: Can create invoices within their tenantSUPER_ADMIN: Can create invoices for any tenant
Request Body¶
{
"tenant_id": "507f1f77bcf86cd799439011",
"invoice_type": "appointment",
"appointment_id": "507f1f77bcf86cd799439012",
"customer_id": "507f1f77bcf86cd799439013",
"bill_to_name": "John Doe",
"bill_to_email": "john@example.com",
"bill_to_phone": "+628123456789",
"bill_to_address": "Jl. Sudirman No. 123, Jakarta",
"line_items": [
{
"description": "Hair Cut & Styling",
"quantity": 1,
"unit_price": "150000.00",
"amount": "150000.00",
"tax_rate": 11.0,
"tax_amount": "16500.00"
}
],
"notes": "Thank you for your business!",
"currency": "IDR"
}
Required Fields:
| Field | Type | Description | Example |
|---|---|---|---|
tenant_id |
string | Tenant ID (must match authenticated user's tenant) | 507f1f77bcf86cd799439011 |
invoice_type |
string | Type: subscription, appointment, setup_fee | appointment |
bill_to_name |
string | Customer or business name | John Doe |
bill_to_email |
string | Email address for invoice delivery | john@example.com |
line_items |
array | Array of line items (min 1 item) | See example above |
Optional Fields:
| Field | Type | Description | Example |
|---|---|---|---|
customer_id |
string | Customer ID (for appointment invoices) | 507f1f77bcf86cd799439013 |
appointment_id |
string | Appointment ID (for appointment invoices) | 507f1f77bcf86cd799439012 |
subscription_id |
string | Subscription ID (for subscription invoices) | 507f1f77bcf86cd799439014 |
bill_to_phone |
string | Customer phone number | +628123456789 |
bill_to_address |
string | Billing address | Jl. Sudirman No. 123 |
notes |
string | Additional notes or instructions | Thank you! |
currency |
string | Currency code (default: IDR) | IDR |
invoice_date |
date | Invoice creation date (default: today) | 2025-01-15 |
due_date |
date | Payment deadline (default: +30 days) | 2025-02-14 |
Line Item Fields:
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
description |
string | Yes | Item description | Hair Cut & Styling |
quantity |
number | Yes | Quantity (must be > 0) | 1 |
unit_price |
decimal | Yes | Price per unit | 150000.00 |
amount |
decimal | Yes | Total line amount (quantity × unit_price) | 150000.00 |
tax_rate |
number | No | Tax percentage (default: 11.0 for Indonesia) | 11.0 |
tax_amount |
decimal | No | Tax amount (amount × tax_rate / 100) | 16500.00 |
Request Example¶
Create appointment invoice:
curl -X POST "https://api.myreserva.id/api/v1/invoices" \
-H "Authorization: Bearer YOUR_STAFF_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "507f1f77bcf86cd799439011",
"invoice_type": "appointment",
"appointment_id": "507f1f77bcf86cd799439012",
"customer_id": "507f1f77bcf86cd799439013",
"bill_to_name": "John Doe",
"bill_to_email": "john@example.com",
"line_items": [
{
"description": "Hair Cut & Styling",
"quantity": 1,
"unit_price": "150000.00",
"amount": "150000.00",
"tax_rate": 11.0,
"tax_amount": "16500.00"
}
],
"notes": "Thank you for your business!",
"currency": "IDR"
}'
Create subscription invoice:
curl -X POST "https://api.myreserva.id/api/v1/invoices" \
-H "Authorization: Bearer YOUR_STAFF_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "507f1f77bcf86cd799439011",
"invoice_type": "subscription",
"subscription_id": "507f1f77bcf86cd799439014",
"bill_to_name": "Salon Beauty Co.",
"bill_to_email": "billing@salonbeauty.com",
"line_items": [
{
"description": "PRO Plan - Monthly Subscription",
"quantity": 1,
"unit_price": "599000.00",
"amount": "599000.00",
"tax_rate": 11.0,
"tax_amount": "65890.00"
}
],
"currency": "IDR"
}'
Response¶
Success (201 Created):
{
"id": "68ee2bb70db240b13fe242ab",
"tenant_id": "507f1f77bcf86cd799439011",
"invoice_number": "INV-2025-001",
"invoice_type": "appointment",
"invoice_date": "2025-01-15",
"due_date": "2025-02-14",
"status": "draft",
"customer_id": "507f1f77bcf86cd799439013",
"appointment_id": "507f1f77bcf86cd799439012",
"bill_to_name": "John Doe",
"bill_to_email": "john@example.com",
"line_items": [
{
"description": "Hair Cut & Styling",
"quantity": 1,
"unit_price": "150000.00",
"amount": "150000.00",
"tax_rate": 11.0,
"tax_amount": "16500.00"
}
],
"subtotal": "150000.00",
"tax_total": "16500.00",
"total_amount": "166500.00",
"paid_amount": "0.00",
"balance_due": "166500.00",
"currency": "IDR",
"notes": "Thank you for your business!",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
Automatic Calculations¶
The system automatically calculates the following:
- Invoice Number: Sequential per tenant (e.g., INV-2025-001, INV-2025-002)
- Subtotal: Sum of all line item amounts
- Tax Total: Sum of all line item tax amounts
- Total Amount: Subtotal + Tax Total
- Balance Due: Total Amount - Paid Amount (initially equals total)
- Due Date: Invoice date + 30 days (if not specified)
- Status: Initially set to DRAFT
Business Rules¶
- Tenant Isolation: invoice.tenant_id must match authenticated user's tenant
- Customer Validation: If customer_id provided, customer must exist in tenant
- Appointment Validation: If appointment_id provided, appointment must exist in tenant
- Subscription Validation: If subscription_id provided, subscription must exist in tenant
- Sequential Numbers: Invoice numbers auto-generated per tenant (thread-safe)
- Initial Status: All new invoices start with DRAFT status
- Monetary Validation: All amounts validated and rounded to 2 decimal places
- Line Items Required: Must have at least 1 line item
- Tax Calculation: Tax amounts must match (amount × tax_rate / 100)
Error Responses¶
400 Bad Request - Validation error:
403 Forbidden - Tenant mismatch:
404 Not Found - Customer not found:
422 Unprocessable Entity - Invalid data:
{
"detail": [
{
"loc": ["body", "bill_to_email"],
"msg": "value is not a valid email address",
"type": "value_error.email"
}
]
}
Integration with Other Systems¶
After creating an invoice, you can:
- Send to Customer: Use send invoice email endpoint (when implemented)
- Generate Payment Link: Create Paper.id payment request
- Download PDF: Use
/invoices/{id}/downloadendpoint - Update Status: Mark as sent, paid, or cancelled
- Track Payments: Monitor via webhooks and payment updates
List Invoices¶
Retrieve paginated list of invoices with comprehensive filtering options.
Endpoint¶
Authentication: Required (JWT token - Staff or Customer)
Query Parameters¶
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
page |
integer | No | Page number (1-based, default: 1) | 1 |
size |
integer | No | Items per page (1-100, default: 20) | 20 |
status_filter |
string | No | Filter by status | paid |
invoice_type |
string | No | Filter by type | appointment |
start_date |
date | No | Start date (YYYY-MM-DD) | 2025-01-01 |
end_date |
date | No | End date (YYYY-MM-DD) | 2025-12-31 |
customer_id |
string | No | Filter by customer (staff only) | 507f1f77bcf86cd799439011 |
Status Values: draft, sent, paid, overdue, cancelled
Invoice Types: subscription, appointment, setup_fee
Access Control¶
Staff Users:
- Can view all invoices within their tenant
- Can use
customer_idfilter to find specific customer invoices - Requires:
STAFFrole or higher - Tenant isolation automatically enforced
Customer Users:
- Can only view their own invoices
customer_idparameter ignored (automatically filtered)- Requires: Valid customer JWT token
- Access restricted to invoices where they are bill_to
Super Admin:
- Can view invoices across all tenants
- Requires:
SUPER_ADMINrole
Request Example¶
Staff - List all invoices:
curl -X GET "https://api.myreserva.id/api/v1/invoices?page=1&size=20&status_filter=paid" \
-H "Authorization: Bearer YOUR_STAFF_JWT_TOKEN"
Staff - Filter by customer:
curl -X GET "https://api.myreserva.id/api/v1/invoices?customer_id=507f1f77bcf86cd799439011" \
-H "Authorization: Bearer YOUR_STAFF_JWT_TOKEN"
Customer - List own invoices:
curl -X GET "https://api.myreserva.id/api/v1/invoices?page=1&size=10" \
-H "Authorization: Bearer YOUR_CUSTOMER_JWT_TOKEN"
Response¶
{
"items": [
{
"id": "68ee2bb70db240b13fe242ab",
"invoice_number": "INV-2025-001",
"invoice_type": "appointment",
"bill_to_name": "John Doe",
"total_amount": "166500.00",
"paid_amount": "166500.00",
"currency": "IDR",
"status": "paid",
"invoice_date": "2025-01-15",
"due_date": "2025-02-14",
"created_at": "2025-01-15T10:30:00Z"
},
{
"id": "68ee2bb70db240b13fe242ac",
"invoice_number": "INV-2025-002",
"invoice_type": "subscription",
"bill_to_name": "Salon Beauty Co.",
"total_amount": "599000.00",
"paid_amount": "0.00",
"currency": "IDR",
"status": "sent",
"invoice_date": "2025-01-20",
"due_date": "2025-02-19",
"created_at": "2025-01-20T14:20:00Z"
}
],
"total": 45,
"page": 1,
"size": 20,
"pages": 3
}
Field Descriptions:
items- Array of invoice summary objectstotal- Total count of invoices matching filterpage- Current page numbersize- Items per pagepages- Total number of pagesinvoice_number- Unique sequential invoice number per tenantinvoice_type- Type of invoice (subscription/appointment/setup_fee)bill_to_name- Customer or business nametotal_amount- Total invoice amount including taxespaid_amount- Amount paid so far (for partial payments)status- Current invoice statusinvoice_date- Date invoice was createddue_date- Payment due date
Business Rules¶
- Tenant Isolation: Automatically filtered by authenticated user's tenant
- Customer Filtering: Customers see only their own invoices (customer_id auto-applied)
- Staff Filtering: Staff can filter by any customer in their tenant
- Soft Deletion: Deleted invoices excluded from results
- Sorting: Invoices sorted by creation date (newest first)
- Pagination: Maximum 100 items per page
Subscription Plan Limitations¶
Invoice access is not directly limited by subscription plan, but invoice creation follows these rules:
| Plan | Max Appointments/Month | Impact on Appointment Invoices |
|---|---|---|
| FREE | 100 | Limited appointment invoices |
| PRO | 2,000 | Standard invoice volume |
| ENTERPRISE | Unlimited | Unlimited invoices |
Note: Subscription invoices (for plan payments) are not subject to plan limits.
Get Invoice Details¶
Retrieve detailed information about a specific invoice including all line items and payment history.
Endpoint¶
Authentication: Required (JWT token - Staff or Customer)
Path Parameters¶
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
invoice_id |
string | Yes | Invoice ID (ObjectId) | 68ee2bb70db240b13fe242ab |
Access Control¶
Staff Users:
- Can view any invoice within their tenant
- Full access to all invoice details
- Requires:
STAFFrole or higher
Customer Users:
- Can only view their own invoices
- Returns
403 Forbiddenif accessing another customer's invoice - Requires: Valid customer JWT token
Super Admin:
- Can view invoices across all tenants
- Requires:
SUPER_ADMINrole
Request Example¶
Staff:
curl -X GET "https://api.myreserva.id/api/v1/invoices/68ee2bb70db240b13fe242ab" \
-H "Authorization: Bearer YOUR_STAFF_JWT_TOKEN"
Customer:
curl -X GET "https://api.myreserva.id/api/v1/invoices/68ee2bb70db240b13fe242ab" \
-H "Authorization: Bearer YOUR_CUSTOMER_JWT_TOKEN"
Response¶
{
"id": "68ee2bb70db240b13fe242ab",
"tenant_id": "507f1f77bcf86cd799439010",
"invoice_number": "INV-2025-001",
"invoice_type": "appointment",
"invoice_date": "2025-01-15",
"due_date": "2025-02-14",
"status": "paid",
"customer_id": "507f1f77bcf86cd799439013",
"appointment_id": "507f1f77bcf86cd799439012",
"bill_to_name": "John Doe",
"bill_to_email": "john@example.com",
"bill_to_phone": "+628123456789",
"bill_to_address": "Jl. Sudirman No. 123, Jakarta",
"line_items": [
{
"description": "Hair Cut & Styling",
"quantity": 1,
"unit_price": "150000.00",
"amount": "150000.00",
"tax_rate": 11.0,
"tax_amount": "16500.00"
}
],
"subtotal": "150000.00",
"tax_total": "16500.00",
"total_amount": "166500.00",
"paid_amount": "166500.00",
"balance_due": "0.00",
"currency": "IDR",
"notes": "Thank you for your business!",
"payment_method": "paper_id",
"paper_invoice_id": "PI-20250115-ABC123",
"paper_invoice_url": "https://stg-v2.paper.id/abc123",
"paper_payment_url": "https://payper.id/short123",
"paid_at": "2025-01-15T11:00:00Z",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T11:00:00Z"
}
Field Descriptions:
id- Unique invoice identifiertenant_id- Tenant this invoice belongs toinvoice_number- Human-readable sequential invoice numberinvoice_type- Type: subscription, appointment, or setup_feeinvoice_date- Date invoice was createddue_date- Payment deadlinestatus- Current status (draft/sent/paid/overdue/cancelled)customer_id- Customer this invoice is billed to (optional)appointment_id- Related appointment ID (for appointment invoices)bill_to_*- Billing contact informationline_items[]- Array of invoice line items with pricingsubtotal- Sum of all line item amounts before taxtax_total- Sum of all tax amountstotal_amount- Grand total including all taxespaid_amount- Amount received so farbalance_due- Remaining amount owed (total - paid)currency- Currency code (IDR, USD, etc.)notes- Additional notes or instructionspayment_method- Payment gateway used (paper_id)paper_*- Paper.id integration fields for paymentpaid_at- Timestamp when full payment received
Invoice Status Lifecycle¶
Status Descriptions:
DRAFT- Invoice created but not sent to customerSENT- Invoice delivered to customer, awaiting paymentPAID- Full payment received, invoice closedOVERDUE- Past due date without payment (auto-updated)CANCELLED- Invoice voided or cancelled
Error Responses¶
400 Bad Request - Invalid invoice ID format:
403 Forbidden - Customer accessing wrong invoice:
404 Not Found - Invoice doesn't exist:
Business Rules¶
- Tenant Isolation: Invoice must belong to authenticated user's tenant
- Customer Restriction: Customers can only access invoices where they are the bill_to
- Staff Access: Staff have full read access within their tenant
- Soft Deletion: Deleted invoices return 404
- ObjectId Validation: invoice_id must be valid MongoDB ObjectId (24 hex chars)
Download Invoice PDF¶
Download invoice as professionally formatted PDF document for printing or record-keeping.
Endpoint¶
Authentication: Required (JWT token - Staff or Customer)
Path Parameters¶
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
invoice_id |
string | Yes | Invoice ID (ObjectId) | 68ee2bb70db240b13fe242ab |
Access Control¶
Staff Users:
- Can download any invoice within their tenant
- Full access to PDF generation
- Requires:
STAFFrole or higher
Customer Users:
- Can only download their own invoices
- Returns
403 Forbiddenif accessing another customer's invoice - Requires: Valid customer JWT token
Super Admin:
- Can download invoices across all tenants
- Requires:
SUPER_ADMINrole
PDF Features¶
The generated PDF includes:
- Professional A4 Layout - Print-ready formatting
- Tenant Branding - Company name, email, phone, website
- Invoice Metadata - Invoice number, dates, status
- Line Items Table - Detailed breakdown with quantities and pricing
- Tax Calculations - Per-line tax amounts (PPN 11% for Indonesia)
- Totals Section - Subtotal, tax total, grand total
- Payment Status - Paid amount and balance due
- Custom Notes - Additional information from invoice
- Generation Timestamp - PDF creation date footer
Request Example¶
Staff - Download any invoice:
curl -X GET "https://api.myreserva.id/api/v1/invoices/68ee2bb70db240b13fe242ab/download" \
-H "Authorization: Bearer YOUR_STAFF_JWT_TOKEN" \
--output invoice.pdf
Customer - Download own invoice:
curl -X GET "https://api.myreserva.id/api/v1/invoices/68ee2bb70db240b13fe242ab/download" \
-H "Authorization: Bearer YOUR_CUSTOMER_JWT_TOKEN" \
--output invoice.pdf
JavaScript/Frontend Example:
async function downloadInvoice(invoiceId, authToken) {
const response = await fetch(
`https://api.myreserva.id/api/v1/invoices/${invoiceId}/download`,
{
headers: {
'Authorization': `Bearer ${authToken}`
}
}
);
if (!response.ok) throw new Error('Download failed');
// Get filename from Content-Disposition header
const contentDisposition = response.headers.get('Content-Disposition');
const filename = contentDisposition
? contentDisposition.split('filename=')[1].replace(/"/g, '')
: `invoice_${invoiceId}.pdf`;
// Create blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
Response¶
Success (200 OK):
- Content-Type:
application/pdf - Content-Disposition:
attachment; filename="invoice_INV-2025-001.pdf" - Body: Binary PDF file stream
Typical File Size: 50-200KB per invoice
Frontend Integration¶
For comprehensive frontend integration examples including React, Vue, and error handling, see:
📖 Frontend Integration Guide - Complete implementation examples for all major frameworks
Key Integration Topics Covered:
- Basic download with vanilla JavaScript
- Preview in new tab
- React/TypeScript component example
- Axios implementation
- Vue 3 Composition API
- Common pitfalls and solutions
- UX best practices
- Testing checklist
Error Responses¶
400 Bad Request - Invalid invoice ID:
401 Unauthorized - Missing or invalid token:
403 Forbidden - Customer accessing wrong invoice:
404 Not Found - Invoice doesn't exist:
500 Internal Server Error - PDF generation failed:
Performance Considerations¶
| Metric | Value | Notes |
|---|---|---|
| Generation Time | 100-300ms | Warm server |
| Cold Start | 1-2 seconds | First request (serverless) |
| File Size | 50-200KB | Depends on line items |
| Concurrent Downloads | Unlimited | No performance degradation |
Optimization Tips:
- Show loading spinner during generation
- Cache invoice data (not PDF) on frontend
- Use Content-Disposition filename for better UX
- Clean up blob URLs to prevent memory leaks
Business Rules¶
- On-Demand Generation: PDFs generated fresh on each request (not cached)
- Tenant Isolation: Invoice must belong to authenticated user's tenant
- Customer Restriction: Customers can only download their own invoices
- Staff Access: Staff can download any invoice in their tenant
- Format Validation: invoice_id must be valid ObjectId
- Filename Format:
invoice_{invoice_number}.pdf
UX Best Practices¶
- Loading State: Show "Generating PDF..." during request
- Success Feedback: Display filename after successful download
- Error Handling: Show user-friendly error messages
- Mobile Support: PDFs may preview instead of download on mobile (browser behavior)
- Download History: Track downloads for user convenience
- Dual Actions: Provide both "Download" and "Preview" buttons
Common Use Cases¶
1. Customer Invoice Portal¶
Scenario: Customer wants to view and download their invoices
# Step 1: List customer's invoices
curl -X GET "https://api.myreserva.id/api/v1/invoices?page=1&size=10" \
-H "Authorization: Bearer CUSTOMER_JWT_TOKEN"
# Step 2: Get detailed invoice information
curl -X GET "https://api.myreserva.id/api/v1/invoices/68ee2bb70db240b13fe242ab" \
-H "Authorization: Bearer CUSTOMER_JWT_TOKEN"
# Step 3: Download PDF for records
curl -X GET "https://api.myreserva.id/api/v1/invoices/68ee2bb70db240b13fe242ab/download" \
-H "Authorization: Bearer CUSTOMER_JWT_TOKEN" \
--output invoice.pdf
2. Staff Invoice Management¶
Scenario: Staff needs to review unpaid invoices for follow-up
# Filter by status and date range
curl -X GET "https://api.myreserva.id/api/v1/invoices?status_filter=sent&start_date=2025-01-01&end_date=2025-01-31" \
-H "Authorization: Bearer STAFF_JWT_TOKEN"
# Get details for specific invoice
curl -X GET "https://api.myreserva.id/api/v1/invoices/68ee2bb70db240b13fe242ac" \
-H "Authorization: Bearer STAFF_JWT_TOKEN"
3. Accounting Export¶
Scenario: Accountant needs all paid invoices for reporting
# Get all paid invoices for the month
curl -X GET "https://api.myreserva.id/api/v1/invoices?status_filter=paid&start_date=2025-01-01&end_date=2025-01-31&size=100" \
-H "Authorization: Bearer STAFF_JWT_TOKEN"
API Reference Summary¶
| Endpoint | Method | Purpose | Key Response | Access |
|---|---|---|---|---|
/invoices |
POST | Create new invoice | Complete invoice with generated number | TENANT_ADMIN+ |
/invoices |
GET | List invoices with filtering | Paginated invoice summaries | Staff/Customer |
/invoices/{id} |
GET | Get invoice details | Complete invoice with line items | Staff/Customer |
/invoices/{id}/download |
GET | Download PDF | Binary PDF file stream | Staff/Customer |
Related Documentation¶
- Subscription Management - Subscription invoices and billing
- Appointment Management - Appointment invoices
- Frontend Integration Guide - Complete PDF download implementation examples
- Webhook Integration - Payment confirmation webhooks
Troubleshooting¶
Invoice Not Found (404)¶
Symptoms: Getting 404 when accessing invoice
Checks:
- Verify invoice ID is valid ObjectId (24 hex characters)
- Check invoice hasn't been soft-deleted
- Confirm invoice belongs to your tenant
- Ensure you're using correct authentication token
Fix:
- Use exact invoice ID from list endpoint
- Check invoice status is not deleted
- Verify tenant context
PDF Download Corrupted¶
Symptoms: Downloaded PDF won't open or displays garbled text
Checks:
- Verify using
responseType: 'blob'in Axios/fetch - Check Content-Type header is
application/pdf - Ensure no text processing on response body
- Verify complete file download (check file size)
Fix:
// CORRECT - Use blob response type
const response = await axios.get(url, {
responseType: 'blob'
});
Customer Can't Access Invoice (403)¶
Symptoms: Customer gets 403 Forbidden error
Checks:
- Verify customer is accessing their own invoice
- Check invoice customer_id matches authenticated customer
- Confirm customer token is valid and not expired
Fix:
- Ensure customer_id in invoice matches authenticated customer
- Use correct customer JWT token
PDF Generation Failed (500)¶
Symptoms: Server error during PDF generation
Checks:
- Check invoice data is complete (has line items)
- Verify tenant information exists in database
- Review server logs for specific error
- Ensure all required fields present
Fix:
- Contact backend support with invoice ID
- Provide error message from response
- Check invoice data completeness
Best Practices¶
For Invoice Listing¶
✅ DO:
- Use pagination for large result sets
- Filter by status to find specific invoices
- Sort by date for chronological view
- Cache results briefly on frontend
❌ DON'T:
- Request size > 100 items per page
- Ignore pagination metadata
- Skip error handling
- Assume unlimited results
For Invoice Details¶
✅ DO:
- Validate invoice ID format before request
- Handle 403 errors gracefully for customers
- Display all line items clearly
- Show payment status prominently
❌ DON'T:
- Hardcode invoice IDs
- Ignore access control errors
- Skip validation
- Assume invoice exists
For PDF Downloads¶
✅ DO:
- Show loading state during generation
- Use blob response type for binary data
- Clean up blob URLs after use
- Handle mobile preview behavior
- Provide both download and preview options
❌ DON'T:
- Process PDF as text
- Skip Content-Disposition filename
- Create memory leaks with unreleased blobs
- Ignore error responses
- Assume instant generation
Next Steps:
- Create your first invoice:
POST /invoices - Review invoice listing:
GET /invoices - Check invoice details:
GET /invoices/{id} - Test PDF download:
GET /invoices/{id}/download - Implement frontend integration using invoice-download.md
- Set up error handling for all endpoints