Fetch Invoice
The Fetch Invoice endpoint retrieves detailed information about a specific invoice by its unique identifier. This endpoint provides comprehensive invoice data including customer details, line items, payment status, transaction history, and associated metadata. It's essential for invoice management, customer service, and payment processing workflows.
Endpoint DetailsCopied!
-
Method:
GET
-
URL:
/api/v0/invoices/{id}
-
Authentication: Required (API Key & Secret)
-
Rate Limiting: 1000 requests per minute
Path ParametersCopied!
Parameter |
Type |
Required |
Description |
Example |
---|---|---|---|---|
|
string (UUID) |
Yes |
Unique invoice identifier |
|
Request ExamplesCopied!
Basic Invoice Fetch
curl -X GET "https://api.devdraft.com/api/v0/invoices/inv_550e8400-e29b-41d4-a716-446655440000" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET"
JavaScript/Node.js Example
const fetchInvoice = async (invoiceId) => {
try {
const response = await fetch(`https://api.devdraft.com/api/v0/invoices/${invoiceId}`, {
method: 'GET',
headers: {
'x-client-key': 'YOUR_CLIENT_KEY',
'x-client-secret': 'YOUR_CLIENT_SECRET'
}
});
if (!response.ok) {
if (response.status === 404) {
throw new Error('Invoice not found');
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const invoice = await response.json();
return invoice;
} catch (error) {
console.error('Error fetching invoice:', error);
throw error;
}
};
// Enhanced invoice fetcher with retry logic
const fetchInvoiceWithRetry = async (invoiceId, maxRetries = 3) => {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fetchInvoice(invoiceId);
} catch (error) {
lastError = error;
// Don't retry for 404 or authentication errors
if (error.message.includes('not found') || error.message.includes('401')) {
throw error;
}
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
console.warn(`Fetch attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`Failed to fetch invoice after ${maxRetries} attempts: ${lastError.message}`);
};
// Usage examples
try {
// Simple fetch
const invoice = await fetchInvoice('inv_550e8400-e29b-41d4-a716-446655440000');
console.log(`Invoice: ${invoice.invoice_number} - Status: ${invoice.status}`);
// With retry logic
const invoiceWithRetry = await fetchInvoiceWithRetry('inv_550e8400-e29b-41d4-a716-446655440000');
console.log(`Total amount: $${calculateInvoiceTotal(invoiceWithRetry)}`);
} catch (error) {
console.error('Failed to fetch invoice:', error.message);
}
React Component Example
import React, { useState, useEffect } from 'react';
const InvoiceDetailComponent = ({ invoiceId }) => {
const [invoice, setInvoice] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const loadInvoice = async () => {
if (!invoiceId) return;
setLoading(true);
setError(null);
try {
const invoiceData = await fetchInvoice(invoiceId);
setInvoice(invoiceData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
loadInvoice();
}, [invoiceId]);
const calculateTotal = () => {
if (!invoice?.items) return 0;
const subtotal = invoice.items.reduce(
(sum, item) => sum + (item.product.price * item.quantity), 0
);
const taxAmount = invoice.tax ? subtotal * (invoice.tax.rate / 100) : 0;
return subtotal + taxAmount;
};
const getStatusColor = (status) => {
const colors = {
'DRAFT': '#6b7280',
'OPEN': '#3b82f6',
'PAID': '#10b981',
'PASTDUE': '#ef4444',
'PARTIALLYPAID': '#f59e0b'
};
return colors[status] || '#6b7280';
};
if (loading) {
return (
<div className="invoice-loading">
<div className="spinner"></div>
<p>Loading invoice...</p>
</div>
);
}
if (error) {
return (
<div className="invoice-error">
<h3>Error Loading Invoice</h3>
<p>{error}</p>
<button onClick={() => window.location.reload()}>
Try Again
</button>
</div>
);
}
if (!invoice) {
return (
<div className="invoice-not-found">
<h3>Invoice Not Found</h3>
<p>The requested invoice could not be found.</p>
</div>
);
}
return (
<div className="invoice-detail">
<div className="invoice-header">
<div>
<h1>{invoice.invoice_number}</h1>
<h2>{invoice.name}</h2>
</div>
<div
className="status-badge"
style={{ backgroundColor: getStatusColor(invoice.status) }}
>
{invoice.status}
</div>
</div>
<div className="invoice-info-grid">
<div className="customer-section">
<h3>Customer Information</h3>
<p><strong>Name:</strong> {invoice.customer?.name || 'N/A'}</p>
<p><strong>Email:</strong> {invoice.email}</p>
{invoice.address && <p><strong>Address:</strong> {invoice.address}</p>}
{invoice.phone_number && <p><strong>Phone:</strong> {invoice.phone_number}</p>}
</div>
<div className="dates-section">
<h3>Important Dates</h3>
<p><strong>Created:</strong> {new Date(invoice.date_created).toLocaleDateString()}</p>
<p><strong>Due:</strong> {new Date(invoice.due_date).toLocaleDateString()}</p>
{invoice.send_date && (
<p><strong>Sent:</strong> {new Date(invoice.send_date).toLocaleDateString()}</p>
)}
{invoice.paidAt && (
<p><strong>Paid:</strong> {new Date(invoice.paidAt).toLocaleDateString()}</p>
)}
</div>
<div className="payment-section">
<h3>Payment Information</h3>
<p><strong>Methods:</strong> {invoice.payment_methods.join(', ')}</p>
<p><strong>Partial Payments:</strong> {invoice.partial_payment ? 'Allowed' : 'Not Allowed'}</p>
<p><strong>Payment Link:</strong> {invoice.payment_link ? 'Enabled' : 'Disabled'}</p>
{invoice.currency && <p><strong>Currency:</strong> {invoice.currency}</p>}
</div>
</div>
<div className="invoice-items">
<h3>Line Items</h3>
<table>
<thead>
<tr>
<th>Product</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{invoice.items.map((item, index) => (
<tr key={index}>
<td>{item.product.name}</td>
<td>{item.quantity}</td>
<td>${item.product.price.toFixed(2)}</td>
<td>${(item.product.price * item.quantity).toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="invoice-total">
<div className="total-line">
<span>Total Amount:</span>
<span className="amount">${calculateTotal().toFixed(2)}</span>
</div>
</div>
{invoice.transactions && invoice.transactions.length > 0 && (
<div className="transaction-history">
<h3>Payment History</h3>
{invoice.transactions.map((transaction, index) => (
<div key={index} className="transaction-item">
<span>{new Date(transaction.dateCreated).toLocaleDateString()}</span>
<span>{transaction.type}</span>
<span>${transaction.amount}</span>
<span className={`status ${transaction.status.toLowerCase()}`}>
{transaction.status}
</span>
</div>
))}
</div>
)}
</div>
);
};
export default InvoiceDetailComponent;
Python Example
import requests
from typing import Optional, Dict, Any
from datetime import datetime
class InvoiceClient:
def __init__(self, client_key: str, client_secret: str, base_url: str = "https://api.devdraft.com"):
self.client_key = client_key
self.client_secret = client_secret
self.base_url = base_url
def _get_headers(self) -> Dict[str, str]:
return {
'x-client-key': self.client_key,
'x-client-secret': self.client_secret
}
def fetch_invoice(self, invoice_id: str) -> Dict[str, Any]:
"""Fetch a single invoice by ID"""
url = f"{self.base_url}/api/v0/invoices/{invoice_id}"
try:
response = requests.get(url, headers=self._get_headers())
if response.status_code == 404:
raise ValueError(f"Invoice {invoice_id} not found")
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"Failed to fetch invoice: {e}")
def fetch_invoice_with_retry(self, invoice_id: str, max_retries: int = 3) -> Dict[str, Any]:
"""Fetch invoice with retry logic"""
last_error = None
for attempt in range(1, max_retries + 1):
try:
return self.fetch_invoice(invoice_id)
except Exception as e:
last_error = e
# Don't retry for certain errors
if "not found" in str(e).lower() or "401" in str(e):
raise e
if attempt < max_retries:
import time
delay = 2 ** attempt
print(f"Attempt {attempt} failed, retrying in {delay}s...")
time.sleep(delay)
raise Exception(f"Failed after {max_retries} attempts: {last_error}")
class InvoiceAnalyzer:
"""Utility class for analyzing invoice data"""
@staticmethod
def calculate_total(invoice: Dict[str, Any]) -> float:
"""Calculate total invoice amount including tax"""
if not invoice.get('items'):
return 0.0
subtotal = sum(
item['product']['price'] * item['quantity']
for item in invoice['items']
)
tax_amount = 0.0
if invoice.get('tax'):
tax_rate = invoice['tax']['rate']
tax_amount = subtotal * (tax_rate / 100)
return subtotal + tax_amount
@staticmethod
def is_overdue(invoice: Dict[str, Any]) -> bool:
"""Check if invoice is overdue"""
if invoice['status'] not in ['OPEN', 'PARTIALLYPAID']:
return False
due_date = datetime.fromisoformat(invoice['due_date'].replace('Z', '+00:00'))
return due_date.date() < datetime.now().date()
@staticmethod
def get_payment_status(invoice: Dict[str, Any]) -> str:
"""Get human-readable payment status"""
status_map = {
'DRAFT': 'Not yet sent',
'OPEN': 'Awaiting payment',
'PAID': 'Paid in full',
'PASTDUE': 'Overdue',
'PARTIALLYPAID': 'Partially paid'
}
return status_map.get(invoice['status'], 'Unknown')
@staticmethod
def get_days_until_due(invoice: Dict[str, Any]) -> int:
"""Get number of days until due date (negative if overdue)"""
due_date = datetime.fromisoformat(invoice['due_date'].replace('Z', '+00:00'))
today = datetime.now().replace(tzinfo=due_date.tzinfo)
delta = due_date - today
return delta.days
# Usage examples
def main():
client = InvoiceClient("YOUR_CLIENT_KEY", "YOUR_CLIENT_SECRET")
analyzer = InvoiceAnalyzer()
try:
# Fetch invoice
invoice = client.fetch_invoice("inv_550e8400-e29b-41d4-a716-446655440000")
# Analyze invoice
total = analyzer.calculate_total(invoice)
is_overdue = analyzer.is_overdue(invoice)
payment_status = analyzer.get_payment_status(invoice)
days_until_due = analyzer.get_days_until_due(invoice)
print(f"Invoice: {invoice['invoice_number']}")
print(f"Customer: {invoice.get('customer', {}).get('name', invoice['email'])}")
print(f"Total Amount: ${total:.2f}")
print(f"Payment Status: {payment_status}")
print(f"Days until due: {days_until_due}")
print(f"Is overdue: {is_overdue}")
# Print line items
print("\nLine Items:")
for item in invoice.get('items', []):
product = item['product']
line_total = product['price'] * item['quantity']
print(f" {product['name']}: {item['quantity']} x ${product['price']} = ${line_total:.2f}")
# Print transaction history
if invoice.get('transactions'):
print("\nTransaction History:")
for txn in invoice['transactions']:
date = datetime.fromisoformat(txn['dateCreated'].replace('Z', '+00:00'))
print(f" {date.strftime('%Y-%m-%d')}: {txn['type']} - ${txn['amount']} ({txn['status']})")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
Response FormatCopied!
Success Response (200 OK)
Complete Invoice Example
{
"id": "inv_550e8400-e29b-41d4-a716-446655440000",
"invoice_number": "INV-000001",
"name": "Q1 2024 Consulting Services",
"app_id": "app_123456789",
"email": "finance@clientcompany.com",
"address": "123 Corporate Blvd, Suite 500, Business City, BC 12345",
"phone_number": "+1-555-987-6543",
"logo": "https://mycompany.com/assets/logo.png",
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"walletId": "abcd1234-5678-90ef-ghij-klmnopqrstuv",
"due_date": "2024-02-28T00:00:00.000Z",
"send_date": "2024-01-15T00:00:00.000Z",
"date_created": "2024-01-15T10:30:00.000Z",
"status": "PARTIALLYPAID",
"payment_methods": ["CRYPTO", "BANK_TRANSFER", "CREDIT_CARD"],
"delivery": "EMAIL",
"payment_link": true,
"partial_payment": true,
"reminder_sent": true,
"paidAt": null,
"paymentMetadata": {
"partialPayments": [
{
"amount": 2500.00,
"transactionHash": "0x1234567890abcdef1234567890abcdef12345678",
"blockNumber": 19123456,
"timestamp": "2024-01-20T14:30:00.000Z"
}
]
},
"paymentStatus": "PARTIALLYPAID",
"taxId": "tax_550e8400-e29b-41d4-a716-446655440123",
"currency": "USDC",
"app": {
"id": "app_123456789",
"name": "Consulting Services LLC",
"email": "billing@consultingservices.com",
"address": "456 Business Park Dr, Corporate City, CC 67890"
},
"customer": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "finance@clientcompany.com",
"name": "ABC Corporation",
"status": "ACTIVE",
"phone": "+1-555-987-6543",
"address": "123 Corporate Blvd, Suite 500, Business City, BC 12345"
},
"wallet": {
"id": "abcd1234-5678-90ef-ghij-klmnopqrstuv",
"address": "0x742d35Cc6635C0532925a3b8d",
"blockchain": "ETHEREUM",
"type": "APP",
"balance": {
"USDC": 15000.50,
"ETH": 2.5
}
},
"tax": {
"id": "tax_550e8400-e29b-41d4-a716-446655440123",
"name": "State Sales Tax",
"rate": 8.5,
"type": "PERCENTAGE",
"description": "Standard state sales tax"
},
"items": [
{
"invoice_id": "inv_550e8400-e29b-41d4-a716-446655440000",
"productId": "prod_consulting_001",
"quantity": 40,
"product": {
"id": "prod_consulting_001",
"name": "Senior Consultant Hours",
"description": "Senior consultant hourly rate for strategic planning and implementation",
"price": 150.00,
"currency": "USD",
"unit": "hour"
}
},
{
"invoice_id": "inv_550e8400-e29b-41d4-a716-446655440000",
"productId": "prod_analysis_001",
"quantity": 1,
"product": {
"id": "prod_analysis_001",
"name": "Market Analysis Report",
"description": "Comprehensive market analysis and competitive positioning report",
"price": 2500.00,
"currency": "USD",
"unit": "report"
}
}
],
"transactions": [
{
"id": "txn_987654321",
"amount": 2500.00,
"currency": "USDC",
"status": "PAYMENT_PROCESSED",
"type": "PAYMENT",
"form": "STABLE_COIN_TRANSACTION",
"dateCreated": "2024-01-20T14:30:00.000Z",
"transaction_id": "0x1234567890abcdef1234567890abcdef12345678",
"customer_name": "ABC Corporation",
"customer_email": "finance@clientcompany.com"
}
]
}
Paid Invoice Example
{
"id": "inv_550e8400-e29b-41d4-a716-446655440001",
"invoice_number": "INV-000002",
"name": "Monthly Subscription - February 2024",
"app_id": "app_123456789",
"email": "subscriber@example.com",
"address": null,
"phone_number": null,
"logo": "https://mycompany.com/assets/logo.png",
"customer_id": "550e8400-e29b-41d4-a716-446655440001",
"walletId": "abcd1234-5678-90ef-ghij-klmnopqrstuv",
"due_date": "2024-02-01T00:00:00.000Z",
"send_date": "2024-01-25T00:00:00.000Z",
"date_created": "2024-01-20T09:15:00.000Z",
"status": "PAID",
"payment_methods": ["CRYPTO"],
"delivery": "EMAIL",
"payment_link": true,
"partial_payment": false,
"reminder_sent": false,
"paidAt": "2024-01-30T16:45:00.000Z",
"paymentMetadata": {
"transactionHash": "0xabcdef1234567890abcdef1234567890abcdef12",
"blockNumber": 19123457,
"gasUsed": 21000,
"gasPrice": "20000000000"
},
"paymentStatus": "PAID",
"taxId": null,
"currency": "USDC",
"customer": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"email": "subscriber@example.com",
"name": "Jane Smith",
"status": "ACTIVE"
},
"items": [
{
"invoice_id": "inv_550e8400-e29b-41d4-a716-446655440001",
"productId": "prod_subscription_001",
"quantity": 1,
"product": {
"id": "prod_subscription_001",
"name": "Premium Monthly Subscription",
"description": "Monthly access to all premium features and priority support",
"price": 99.99,
"currency": "USD"
}
}
],
"transactions": [
{
"id": "txn_987654322",
"amount": 99.99,
"currency": "USDC",
"status": "PAYMENT_PROCESSED",
"type": "PAYMENT",
"form": "STABLE_COIN_TRANSACTION",
"dateCreated": "2024-01-30T16:45:00.000Z",
"transaction_id": "0xabcdef1234567890abcdef1234567890abcdef12",
"customer_name": "Jane Smith",
"customer_email": "subscriber@example.com"
}
]
}
Response Fields ReferenceCopied!
Core Invoice Fields
Field |
Type |
Description |
Example |
---|---|---|---|
|
string |
Unique invoice identifier (UUID) |
|
|
string |
Sequential invoice number |
|
|
string |
Invoice title/description |
|
|
string |
Application identifier |
|
|
enum |
Current invoice status |
|
Customer Information
Field |
Type |
Description |
Example |
---|---|---|---|
|
string |
Customer email address |
|
|
string |
Customer identifier |
|
|
string|null |
Customer billing address |
|
|
string|null |
Customer phone number |
|
|
object |
Detailed customer information |
See customer object |
Dates & Timeline
Field |
Type |
Description |
Example |
---|---|---|---|
|
string |
Payment due date (ISO 8601) |
|
|
string|null |
Invoice send date |
|
|
string |
Creation timestamp |
|
|
string|null |
Payment completion timestamp |
|
Payment Configuration
Field |
Type |
Description |
Example |
---|---|---|---|
|
array |
Accepted payment methods |
|
|
boolean |
Payment link enabled |
|
|
boolean |
Partial payments allowed |
|
|
string|null |
Current payment status |
|
|
object|null |
Payment transaction details |
See payment metadata |
Financial Information
Field |
Type |
Description |
Example |
---|---|---|---|
|
string |
Invoice currency |
|
|
string|null |
Tax configuration ID |
|
|
string|null |
Associated wallet ID |
|
Related Objects
Field |
Type |
Description |
---|---|---|
|
object |
Application/business information |
|
object |
Complete customer details |
|
object|null |
Wallet information and balances |
|
object|null |
Tax configuration details |
|
array |
Invoice line items with products |
|
array |
Payment transaction history |
Error ResponsesCopied!
Invoice Not Found (404 Not Found)
{
"statusCode": 404,
"message": "Invoice not found",
"error": "Not Found"
}
Invalid Invoice ID (400 Bad Request)
{
"statusCode": 400,
"message": "Invalid invoice ID format. Must be a valid UUID.",
"error": "Bad Request"
}
Authentication Error (401 Unauthorized)
{
"statusCode": 401,
"message": "Invalid or missing API credentials",
"error": "Unauthorized"
}
Access Denied (403 Forbidden)
{
"statusCode": 403,
"message": "Access denied. Invoice belongs to different application.",
"error": "Forbidden"
}
Rate Limit Error (429 Too Many Requests)
{
"statusCode": 429,
"message": "Rate limit exceeded. Maximum 1000 requests per minute.",
"error": "Too Many Requests",
"retryAfter": 60
}
Server Error (500 Internal Server Error)
{
"statusCode": 500,
"message": "Internal server error occurred while fetching invoice",
"error": "Internal Server Error"
}
Advanced Use CasesCopied!
Invoice Calculations
// Comprehensive invoice calculator
class InvoiceCalculator {
static calculateSubtotal(invoice) {
if (!invoice.items || invoice.items.length === 0) {
return 0;
}
return invoice.items.reduce((sum, item) => {
return sum + (item.product.price * item.quantity);
}, 0);
}
static calculateTax(invoice) {
if (!invoice.tax) {
return 0;
}
const subtotal = this.calculateSubtotal(invoice);
switch (invoice.tax.type) {
case 'PERCENTAGE':
return subtotal * (invoice.tax.rate / 100);
case 'FIXED':
return invoice.tax.rate;
case 'COMPOUND':
// Complex tax calculation logic
return this.calculateCompoundTax(subtotal, invoice.tax);
default:
return 0;
}
}
static calculateTotal(invoice) {
const subtotal = this.calculateSubtotal(invoice);
const tax = this.calculateTax(invoice);
return subtotal + tax;
}
static calculateAmountDue(invoice) {
const total = this.calculateTotal(invoice);
const paidAmount = this.calculatePaidAmount(invoice);
return Math.max(0, total - paidAmount);
}
static calculatePaidAmount(invoice) {
if (!invoice.transactions) {
return 0;
}
return invoice.transactions
.filter(txn => txn.status === 'PAYMENT_PROCESSED' && txn.type === 'PAYMENT')
.reduce((sum, txn) => sum + txn.amount, 0);
}
static getPaymentProgress(invoice) {
const total = this.calculateTotal(invoice);
const paid = this.calculatePaidAmount(invoice);
return {
total,
paid,
remaining: total - paid,
percentage: total > 0 ? (paid / total) * 100 : 0,
isFullyPaid: paid >= total,
isPartiallyPaid: paid > 0 && paid < total
};
}
}
// Usage
const invoice = await fetchInvoice('inv_550e8400-e29b-41d4-a716-446655440000');
const calculations = {
subtotal: InvoiceCalculator.calculateSubtotal(invoice),
tax: InvoiceCalculator.calculateTax(invoice),
total: InvoiceCalculator.calculateTotal(invoice),
amountDue: InvoiceCalculator.calculateAmountDue(invoice),
progress: InvoiceCalculator.getPaymentProgress(invoice)
};
console.log('Invoice Calculations:', calculations);
Invoice Status Analysis
// Invoice status analyzer with business logic
class InvoiceStatusAnalyzer {
static analyzeStatus(invoice) {
const now = new Date();
const dueDate = new Date(invoice.due_date);
const sendDate = invoice.send_date ? new Date(invoice.send_date) : null;
const paidDate = invoice.paidAt ? new Date(invoice.paidAt) : null;
const analysis = {
currentStatus: invoice.status,
isOverdue: false,
daysPastDue: 0,
daysUntilDue: 0,
canBePaid: false,
canBeEdited: false,
canBeCancelled: false,
requiresReminder: false,
paymentProgress: InvoiceCalculator.getPaymentProgress(invoice)
};
// Calculate days relative to due date
const daysDiff = Math.ceil((dueDate - now) / (1000 * 60 * 60 * 24));
if (daysDiff < 0) {
analysis.isOverdue = true;
analysis.daysPastDue = Math.abs(daysDiff);
} else {
analysis.daysUntilDue = daysDiff;
}
// Determine available actions based on status
switch (invoice.status) {
case 'DRAFT':
analysis.canBeEdited = true;
analysis.canBeCancelled = true;
break;
case 'OPEN':
analysis.canBePaid = true;
analysis.canBeCancelled = true;
analysis.requiresReminder = analysis.isOverdue || analysis.daysUntilDue <= 3;
break;
case 'PARTIALLYPAID':
analysis.canBePaid = true;
analysis.requiresReminder = analysis.isOverdue;
break;
case 'PAID':
// Paid invoices have limited actions
break;
case 'PASTDUE':
analysis.canBePaid = true;
analysis.requiresReminder = true;
break;
}
return analysis;
}
static getActionableItems(invoice) {
const analysis = this.analyzeStatus(invoice);
const actions = [];
if (analysis.canBePaid) {
actions.push({
type: 'PAYMENT',
priority: analysis.isOverdue ? 'HIGH' : 'MEDIUM',
description: `Payment of $${analysis.paymentProgress.remaining.toFixed(2)} is due`,
dueDate: invoice.due_date
});
}
if (analysis.requiresReminder && !invoice.reminder_sent) {
actions.push({
type: 'REMINDER',
priority: analysis.daysPastDue > 7 ? 'HIGH' : 'MEDIUM',
description: `Send payment reminder to ${invoice.email}`,
reason: analysis.isOverdue ? 'OVERDUE' : 'DUE_SOON'
});
}
if (analysis.isOverdue && analysis.daysPastDue > 30) {
actions.push({
type: 'COLLECTION',
priority: 'HIGH',
description: 'Consider collection action for long overdue invoice',
daysPastDue: analysis.daysPastDue
});
}
return actions;
}
}
Payment Link Integration
// Payment link handler for invoice
class InvoicePaymentHandler {
constructor(apiClient) {
this.apiClient = apiClient;
}
async generatePaymentLink(invoiceId) {
try {
const invoice = await this.apiClient.fetchInvoice(invoiceId);
if (!invoice.payment_link) {
throw new Error('Payment link not enabled for this invoice');
}
if (invoice.status === 'PAID') {
throw new Error('Invoice is already paid');
}
const amountDue = InvoiceCalculator.calculateAmountDue(invoice);
if (amountDue <= 0) {
throw new Error('No amount due on this invoice');
}
// Generate secure payment link
const paymentLinkData = {
invoiceId: invoice.id,
amount: amountDue,
currency: invoice.currency,
dueDate: invoice.due_date,
paymentMethods: invoice.payment_methods,
partialPayment: invoice.partial_payment,
customer: {
email: invoice.email,
name: invoice.customer?.name
}
};
const paymentLink = await this.apiClient.createPaymentLink(paymentLinkData);
return {
url: paymentLink.url,
expiresAt: paymentLink.expiresAt,
amount: amountDue,
currency: invoice.currency,
invoiceNumber: invoice.invoice_number
};
} catch (error) {
console.error('Failed to generate payment link:', error);
throw error;
}
}
async sendPaymentReminder(invoiceId, reminderType = 'STANDARD') {
const invoice = await this.apiClient.fetchInvoice(invoiceId);
const analysis = InvoiceStatusAnalyzer.analyzeStatus(invoice);
if (!analysis.requiresReminder) {
return { sent: false, reason: 'Reminder not needed' };
}
const paymentLink = await this.generatePaymentLink(invoiceId);
const reminderData = {
invoiceId: invoice.id,
recipientEmail: invoice.email,
reminderType,
paymentLink: paymentLink.url,
amountDue: analysis.paymentProgress.remaining,
daysOverdue: analysis.daysPastDue,
daysUntilDue: analysis.daysUntilDue
};
const result = await this.apiClient.sendInvoiceReminder(reminderData);
return {
sent: true,
reminderType,
sentAt: new Date().toISOString(),
paymentLink: paymentLink.url
};
}
}
Audit and Compliance
// Invoice audit and compliance checker
class InvoiceAuditor {
static auditInvoice(invoice) {
const audit = {
invoiceId: invoice.id,
auditDate: new Date().toISOString(),
compliance: {
hasRequiredFields: true,
hasValidCustomer: true,
hasValidItems: true,
hasCorrectCalculations: true,
hasTaxCompliance: true
},
warnings: [],
errors: []
};
// Check required fields
const requiredFields = ['invoice_number', 'email', 'customer_id', 'due_date', 'items'];
for (const field of requiredFields) {
if (!invoice[field] || (Array.isArray(invoice[field]) && invoice[field].length === 0)) {
audit.errors.push(`Missing required field: ${field}`);
audit.compliance.hasRequiredFields = false;
}
}
// Validate customer information
if (!invoice.customer || !invoice.customer.id) {
audit.errors.push('Invalid customer information');
audit.compliance.hasValidCustomer = false;
}
// Validate line items
if (!invoice.items || invoice.items.length === 0) {
audit.errors.push('Invoice must have at least one line item');
audit.compliance.hasValidItems = false;
} else {
invoice.items.forEach((item, index) => {
if (!item.product || !item.product.id) {
audit.errors.push(`Invalid product in item ${index + 1}`);
audit.compliance.hasValidItems = false;
}
if (!item.quantity || item.quantity <= 0) {
audit.errors.push(`Invalid quantity in item ${index + 1}`);
audit.compliance.hasValidItems = false;
}
});
}
// Validate calculations
const calculatedTotal = InvoiceCalculator.calculateTotal(invoice);
const reportedTotal = invoice.total_amount;
if (reportedTotal && Math.abs(calculatedTotal - reportedTotal) > 0.01) {
audit.warnings.push(`Calculation mismatch: calculated $${calculatedTotal}, reported $${reportedTotal}`);
}
// Check tax compliance
if (invoice.tax && invoice.tax.rate > 0) {
const calculatedTax = InvoiceCalculator.calculateTax(invoice);
if (calculatedTax <= 0) {
audit.warnings.push('Tax rate specified but no tax calculated');
}
}
// Check due date
const dueDate = new Date(invoice.due_date);
const createdDate = new Date(invoice.date_created);
if (dueDate <= createdDate) {
audit.warnings.push('Due date is not after creation date');
}
// Overall compliance status
audit.isCompliant = audit.errors.length === 0;
audit.hasWarnings = audit.warnings.length > 0;
return audit;
}
static generateComplianceReport(invoices) {
const report = {
totalInvoices: invoices.length,
compliantInvoices: 0,
nonCompliantInvoices: 0,
invoicesWithWarnings: 0,
commonIssues: {},
auditResults: []
};
invoices.forEach(invoice => {
const audit = this.auditInvoice(invoice);
report.auditResults.push(audit);
if (audit.isCompliant) {
report.compliantInvoices++;
} else {
report.nonCompliantInvoices++;
}
if (audit.hasWarnings) {
report.invoicesWithWarnings++;
}
// Track common issues
[...audit.errors, ...audit.warnings].forEach(issue => {
report.commonIssues[issue] = (report.commonIssues[issue] || 0) + 1;
});
});
report.complianceRate = (report.compliantInvoices / report.totalInvoices) * 100;
return report;
}
}
Integration PatternsCopied!
Caching Strategy
// Invoice caching with smart invalidation
class InvoiceCacheManager {
constructor(cache, ttl = 300000) { // 5 minutes default
this.cache = cache;
this.ttl = ttl;
}
async fetchInvoice(invoiceId, useCache = true) {
const cacheKey = `invoice:${invoiceId}`;
if (useCache) {
const cached = await this.cache.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
}
const invoice = await this.apiClient.fetchInvoice(invoiceId);
await this.cache.setex(cacheKey, this.ttl / 1000, JSON.stringify(invoice));
return invoice;
}
async invalidateInvoice(invoiceId) {
await this.cache.del(`invoice:${invoiceId}`);
// Also invalidate related cache keys
await this.cache.del(`invoices:list:*`);
}
async handleInvoiceUpdate(invoiceId) {
// Invalidate and refresh cache
await this.invalidateInvoice(invoiceId);
return await this.fetchInvoice(invoiceId, false);
}
}
Webhook Integration
// Handle invoice-related webhooks
const handleInvoiceWebhook = async (webhookData) => {
const { event, invoice } = webhookData;
switch (event) {
case 'invoice.payment.received':
console.log(`Payment received for invoice ${invoice.invoice_number}`);
// Refresh invoice data
const updatedInvoice = await fetchInvoice(invoice.id);
// Update local systems
await updateAccountingSystem(updatedInvoice);
await sendPaymentConfirmation(updatedInvoice);
// Check if fully paid
const progress = InvoiceCalculator.getPaymentProgress(updatedInvoice);
if (progress.isFullyPaid) {
await fulfillServices(updatedInvoice);
}
break;
case 'invoice.overdue':
console.log(`Invoice ${invoice.invoice_number} is overdue`);
// Send automated reminder
const paymentHandler = new InvoicePaymentHandler(apiClient);
await paymentHandler.sendPaymentReminder(invoice.id, 'OVERDUE');
break;
case 'invoice.cancelled':
console.log(`Invoice ${invoice.invoice_number} was cancelled`);
// Update internal systems
await cancelRelatedOrders(invoice);
break;
}
};
Best PracticesCopied!
Error Handling
-
Validate invoice IDs before making API calls
-
Handle 404 errors gracefully with user-friendly messages
-
Implement retry logic for transient network failures
-
Cache successful responses to reduce API calls
Performance Optimization
-
Use caching for frequently accessed invoices
-
Implement pagination when displaying invoice lists
-
Lazy load detailed invoice data when needed
-
Batch operations when processing multiple invoices
Security Considerations
-
Validate permissions before displaying invoice data
-
Sanitize displayed data to prevent XSS attacks
-
Use HTTPS for all API communications
-
Log access attempts for audit trails
User Experience
-
Show loading states during data fetching
-
Provide clear error messages when invoices aren't found
-
Display payment progress visually for partial payments
-
Enable deep linking to specific invoices
Next StepsCopied!
After fetching an invoice, you can:
-
Process Payments: Handle payment transactions and updates
-
Send Communications: Email reminders, payment confirmations, receipts
-
Generate Documents: Create PDF invoices, statements, reports