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

id

string (UUID)

Yes

Unique invoice identifier

inv_550e8400-e29b-41d4-a716-446655440000

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

id

string

Unique invoice identifier (UUID)

"inv_550e8400-e29b-41d4"

invoice_number

string

Sequential invoice number

"INV-000001"

name

string

Invoice title/description

"Q1 2024 Consulting Services"

app_id

string

Application identifier

"app_123456789"

status

enum

Current invoice status

"PARTIALLYPAID"

Customer Information

Field

Type

Description

Example

email

string

Customer email address

"client@example.com"

customer_id

string

Customer identifier

"550e8400-e29b-41d4"

address

string|null

Customer billing address

"123 Business St"

phone_number

string|null

Customer phone number

"+1-555-123-4567"

customer

object

Detailed customer information

See customer object

Dates & Timeline

Field

Type

Description

Example

due_date

string

Payment due date (ISO 8601)

"2024-02-28T00:00:00.000Z"

send_date

string|null

Invoice send date

"2024-01-15T00:00:00.000Z"

date_created

string

Creation timestamp

"2024-01-15T10:30:00.000Z"

paidAt

string|null

Payment completion timestamp

"2024-01-30T16:45:00.000Z"

Payment Configuration

Field

Type

Description

Example

payment_methods

array

Accepted payment methods

["CRYPTO", "BANK_TRANSFER"]

payment_link

boolean

Payment link enabled

true

partial_payment

boolean

Partial payments allowed

true

paymentStatus

string|null

Current payment status

"PARTIALLYPAID"

paymentMetadata

object|null

Payment transaction details

See payment metadata

Financial Information

Field

Type

Description

Example

currency

string

Invoice currency

"USDC"

taxId

string|null

Tax configuration ID

"tax_550e8400-e29b-41d4"

walletId

string|null

Associated wallet ID

"abcd1234-5678-90ef"

Related Objects

Field

Type

Description

app

object

Application/business information

customer

object

Complete customer details

wallet

object|null

Wallet information and balances

tax

object|null

Tax configuration details

items

array

Invoice line items with products

transactions

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:

  1. Process Payments: Handle payment transactions and updates

  2. Send Communications: Email reminders, payment confirmations, receipts

  3. Generate Documents: Create PDF invoices, statements, reports