Bill
Invoice Statuses

Invoice Statuses

Track invoice lifecycle from creation through payment with automatic status management


Invoice statuses represent the current state of an invoice in its lifecycle, from initial creation through final payment or closure. Payload automatically manages status transitions as invoices are created, published, paid, or closed, ensuring accurate tracking of accounts receivable and payment status. Understanding invoice statuses is essential for managing billing workflows, monitoring collections, and maintaining accurate financial records.

Prerequisites

Before working with invoice statuses, ensure you understand:


Invoice Status Reference


Quick reference for all invoice statuses and their characteristics.

StatusDescriptionAccepts PaymentsCan ModifyAutopay
draftUnpublished invoice being preparedNoYesNo
openPublish trigger - automatically becomes unpaid/partially_paid/paid based on balance---
unpaidPublished invoice with no payments appliedYesYesYes (on due date)
partially_paidInvoice with partial payment appliedYesYesNo
paidFully paid invoice with zero balanceNoNoNo
closedManually cancelled or voided invoiceNoNoNo

Understanding Invoice Statuses


Invoices move through different statuses as they progress from creation to payment.

Draft Status

Invoices in draft status are unpublished and not yet sent to customers.

Characteristics:

  • Invoice is being prepared but not finalized
  • Not visible to customers or included in receivables
  • No autopay processing occurs

Transitioning from draft:

# Publish draft invoice to make it active
invoice = pl.Invoice.get("inv_draft123")
invoice.update(
    status="open"  # Moves to open status
)

Open Status (Publishing Trigger)

Setting an invoice to open status publishes the invoice and automatically transitions it based on balance.

How it works:

  • When you set status: 'open', the system evaluates the invoice balance
  • Automatically becomes unpaid if balance_due equals the full amount
  • Automatically becomes partially_paid if some payments already exist (0 < balance < total)
  • Automatically becomes paid if the balance is zero (rare but possible)

Example:

# Publish a draft invoice
invoice = pl.Invoice.get("inv_draft123")
invoice.update(
    status="open"  # Automatically becomes unpaid/partially_paid/paid
)
 
# After update, the invoice will have the appropriate status
updated_invoice = pl.Invoice.get("inv_draft123")
print(updated_invoice.status)  # "unpaid" (if no payments exist)

Unpaid Status

Invoices in unpaid status are published and awaiting payment with no payments applied.

Characteristics:

  • Invoice is active and visible to customers
  • Autopay will process on due date (if enabled)
  • Can send payment requests and reminders
  • Can still be modified (add line items, adjust amounts)

Partially Paid Status

Invoices in partially_paid status have received some payment but still have a remaining balance.

Characteristics:

  • One or more payments applied to invoice
  • totals.paid is greater than zero
  • totals.balance_due is greater than zero
  • Still eligible for additional payments
  • Can send payment reminders for outstanding amount

Payment tracking:

invoice = pl.Invoice.get("inv_abc123")
 
print(f"Status: {invoice.status}")  # "partially_paid"
print(f"Total: ${invoice.totals['total']}")
print(f"Paid: ${invoice.totals['paid']}")
print(f"Balance due: ${invoice.totals['balance_due']}")
 
# See all payment allocations
for payment in invoice.payments:
    print(f"Payment: ${payment.amount} on {payment.created_at}")

Paid Status

Invoices in paid status have been fully paid with zero balance remaining.

Characteristics:

  • totals.balance_due equals zero
  • totals.paid equals totals.total
  • paid_timestamp records when invoice was fully paid

Automatic transition:

# When final payment is applied, status automatically becomes "paid"
invoice = pl.Invoice.get("inv_abc123")
 
payment = pl.Transaction.create(
    type="payment",
    amount=invoice.totals["balance_due"],  # Pay remaining balance
    sender={
        "account_id": invoice.payer["account_id"],
        "method_id": "pm_card_456"
    },
    invoice_allocations=[
        {
            "invoice_id": invoice.id,
            "amount": invoice.totals["balance_due"]
        }
    ]
)
 
# Invoice automatically transitions to paid status
paid_invoice = pl.Invoice.get(invoice.id)
print(paid_invoice.status)  # "paid"
print(paid_invoice.paid_timestamp)  # Timestamp of full payment

Closed Status

Invoices in closed status have been manually cancelled or voided.

Characteristics:

  • Manually set by updating status: 'closed'
  • Indicates invoice was cancelled, voided, or written off
  • Cannot accept payments or be modified

Closing an invoice:

invoice = pl.Invoice.get("inv_abc123")
invoice.update(
    status="closed",
    attrs={
        "closure_reason": "Customer cancelled service",
        "closed_by": "[email protected]",
        "closed_date": "2024-01-20"
    }
)

Invoice Lifecycle


Invoices follow a standard lifecycle with automatic status transitions.

Standard Invoice Flow

The typical invoice lifecycle progresses through these stages:

draft → open (trigger) → unpaid → partially_paid → paid
  ↓                        ↓            ↓
closed                  closed       closed

Manual Payment Lifecycle

Invoices with manual payment collection follow this flow:

draft → open (trigger) → unpaid → (manual payment) → paid
  ↓                        ↓              ↓
closed                  closed    partially_paid → (additional payment) → paid
                                          ↓
                                        closed

Manual payment process:

  1. Invoice created and set to open which transitions to unpaid
  2. Payment request automatically sent to payer (if global setting enabled)
  3. Customer makes payment via payment link, API, or external method
  4. First partial payment transitions from unpaid to partially_paid
  5. Additional payments can be applied until invoice reaches paid status
  6. Invoice can be closed at any time if cancelled or written off

Autopay Lifecycle

Invoices with autopay enabled follow a slightly different flow:

draft → open (trigger) → unpaid → (autopay on due_date) → paid
  ↓                        ↓
closed                  closed (if autopay fails, stays unpaid)

Autopay process:

  1. Invoice created with autopay_settings.allowed: true
  2. Set to open which transitions to unpaid (assuming no prior payments)
  3. Automatically charges payment method on due_date
  4. Successful payment transitions from unpaid to paid
  5. Failed payment keeps invoice in unpaid status for retry or manual payment

Querying Invoices by Status


Filter and search invoices based on their current status.

import payload
pl = payload.Session('secret_key_3bW9...', api_version='v2')
from datetime import date
 
# Query invoices by status
 
# Get all unpaid invoices
unpaid_invoices = pl.Invoice.filter_by(
    q='status == "unpaid"',
    order_by='asc(due_date)'
).all()
 
print(f'Found {len(unpaid_invoices)} unpaid invoices')
 
for invoice in unpaid_invoices:
    print(f'  {invoice.number}: ${invoice.totals['balance_due']} due {invoice.due_date}')
 
# Get overdue invoices
today = date.today().isoformat()
overdue_invoices = pl.Invoice.filter_by(
    q=f'status == "unpaid" && due_date < "{today}"',
    order_by='asc(due_date)'
).all()
 
print(f'\nFound {len(overdue_invoices)} overdue invoices')
 
# Get partially paid invoices
partial_invoices = pl.Invoice.filter_by(
    q='status == "partially_paid"',
    order_by='desc(modified_at)'
).all()
 
print(f'\nFound {len(partial_invoices)} partially paid invoices')
 
for invoice in partial_invoices:
    paid_amount = invoice.totals['paid']
    remaining_amount = invoice.totals['balance_due']
    print(f'  {invoice.number}: ${paid_amount} paid, ${remaining_amount} remaining')
 
# Get recently paid invoices
paid_invoices = pl.Invoice.filter_by(
    q='status == "paid"',
    order_by='desc(paid_timestamp)',
    limit=10
).all()
 
print(f'\nFound {len(paid_invoices)} recently paid invoices')

Common Status Queries

Unpaid invoices:

# Get all invoices awaiting payment
unpaid = pl.Invoice.filter_by(
    q='status == "unpaid"',
    order_by="asc(due_date)"
).all()

Overdue invoices:

# Find invoices past their due date
from datetime import date
 
today = date.today().isoformat()
overdue = pl.Invoice.filter_by(
    q=f'status == "unpaid" && due_date < "{today}"',
    order_by="asc(due_date)"
).all()

Partially paid invoices:

# Track invoices with partial payments
partial = pl.Invoice.filter_by(
    q='status == "partially_paid"',
    order_by="desc(modified_at)"
).all()

Recently paid invoices:

# Review successful payments
paid = pl.Invoice.filter_by(
    q='status == "paid"',
    order_by="desc(paid_timestamp)",
    limit=50
).all()

Draft invoices:

# Find unpublished invoices
drafts = pl.Invoice.filter_by(
    q='status == "draft"',
    order_by="desc(created_at)"
).all()

Closed invoices:

# Review cancelled or voided invoices
closed = pl.Invoice.filter_by(
    q='status == "closed"',
    order_by="desc(modified_at)"
).all()

Status-Based Reporting

Group invoices by status for reporting:

# Get invoice counts by status
status_breakdown = pl.Invoice.filter_by(
    group_by="status"
).all()
 
print("Invoice Status Summary:")
for group in status_breakdown:
    print(f"  {group.status}: {group.count} invoices")

Calculate accounts receivable:

# Sum all unpaid and partially paid invoices
sum_balance_due = pl.attr.totals.balance_due.sum()
receivables = (
    pl.Invoice.select(sum_balance_due)
    .filter_by(q='status == "unpaid" || status == "partially_paid"')
    .all()
)
 
total_receivables = getattr(receivables[0], str(sum_balance_due))
 
print(f"Total accounts receivable: ${total_receivables}")

Managing Status Transitions


Control invoice status changes through API operations.

Publishing Draft Invoices

Move invoices from draft to open status:

# Single invoice
invoice = pl.Invoice.get("inv_draft123")
invoice.update(
    status="open"
)
 
# Batch publish multiple drafts
drafts = pl.Invoice.filter_by(
    q='status == "draft"'
).all()
 
for draft in drafts:
    draft_invoice = pl.Invoice.get(draft.id)
    draft_invoice.update(status="open")
    print(f"Published {draft.number}")

Closing Invoices

from datetime import date
 
import payload
 
pl = payload.Session('secret_key_3bW9...', api_version='v2')
 
# Close or cancel an invoice
 
invoice = pl.Invoice.get('inv_abc123')
 
print(f'Invoice {invoice.number}:')
print(f'  Current status: {invoice.status}')
print(f'  Balance due: ${invoice.totals['balance_due']}')
 
invoice.update(
    status='closed',
    attrs={
        'closure_reason': 'Customer cancelled service',
        'closed_by': '[email protected]',
        'closed_date': date.today().isoformat(),
    },
)
 
print(f'\nInvoice closed: {invoice.id}')
print(f'  Status: {invoice.status}')  # "closed"
print(f'  Reason: {invoice.attrs["closure_reason"]}')
 
# Closed invoices cannot be paid or modified
print('\nClosed invoices:')
print('  - Cannot accept payments')
print('  - Cannot be modified')
print('  - Are excluded from accounts receivable reports')
print('  - Maintain full audit trail via attrs')

Mark invoices as closed or voided:

from datetime import date
 
invoice = pl.Invoice.get("inv_abc123")
invoice.update(
    status="closed",
    attrs={
        "closure_reason": "Customer cancelled",
        "closed_by": "[email protected]",
        "closed_date": date.today().isoformat()
    }
)

Schema Reference


Fields relevant to invoice status management:

Invoice Status Fields

status
enum[string]
The current status of this invoice. Possible values: "draft" (not yet published/sent), "open" (published and awaiting payment, same as unpaid), "unpaid" (published with no payments), "partially_paid" (some payment received but balance remains), "paid" (fully paid), or "closed" (manually closed/cancelled). Status transitions are restricted - for example, paid invoices cannot be modified.
Values: draft, open, unpaid, partially_paid, paid, closed, sync
paid_timestamp
string (date)Read-only
The date and time when this invoice was fully paid (when balance_due reached zero). Read-only field that is automatically set when the invoice transitions to "paid" status. Useful for tracking payment completion, calculating payment cycles, and reporting on collection times. Null until the invoice is fully paid.
totals
object
Financial summary of this invoice, including subtotal, tax, total amounts, and payment status. All fields are read-only and automatically calculated based on the line items and payments associated with this invoice. Provides a quick overview of the invoice's financial state.
balance_due
number (double)Read-only
The remaining unpaid balance on this invoice (total - paid). Read-only field that represents the outstanding amount the payer still owes. When this reaches zero, the invoice is fully paid.
paid
number (double)Read-only
The total amount that has been successfully paid toward this invoice. Read-only field that updates as payments are received and allocated to this invoice. Use this to track how much has been collected.
subtotal
number (double)Read-only
The subtotal of all line items on this invoice before tax is applied. This is the sum of all line item amounts (value * qty for each item). Read-only field that is automatically calculated based on the invoice items. Represents the pre-tax total.
tax
number (double)Read-only
The total tax amount for this invoice. Calculated by applying tax rates to taxable line items. Read-only field that updates when line items or tax rates change. This amount is added to the subtotal to get the total amount due.
total
number (double)Read-only
The total amount due for this invoice (subtotal + tax). This is the full amount the payer owes. Read-only field that is automatically calculated from line items and tax. Represents the complete invoice amount before any payments are applied.

Next Steps

Enhance your invoice management with additional features


Create and Manage Invoices

Build invoices with line items using Creating Invoices, automate recurring invoice generation with Billing Schedules, and attach supporting documents with Invoice Attachments.

Process Invoice Payments

Process invoice payments programmatically with Payment API, enable autopay for recurring billing with Automatic Payments, and send payment links to customers with Payment Requests.

Track and Report Status Changes

Monitor status transitions in real-time with Webhook Events, generate comprehensive financial reports with Reporting Overview, and analyze payment data with Transaction Reports.


Related articles