Invoice lifecycle

Draft, open, paid, void, and uncollectible states.

View as MarkdownInstall skills

Every invoice on VINR moves through a small, well-defined set of states. Knowing which transitions are allowed — and which events fire on each — lets you reconcile revenue, automate fulfilment, and reason about money that is owed, collected, or written off.

The five statesAsk

StatusMeaningMutable?Terminal?
draftBeing assembled; line items can still change.YesNo
openFinalized and awaiting payment. Amount due is locked.NoNo
paidFully collected.NoYes
voidCancelled before payment; no money owed.NoYes
uncollectibleOwed, but you've given up collecting it (bad debt).NoYes*

*uncollectible is terminal for collection purposes, but you can still mark a later out-of-band payment, which moves it to paid.

The transition graphAsk

A draft is the only mutable state. Finalizing locks the invoice and computes amount_due; from there money decides the rest.

draft → open

Finalization applies discounts and tax, freezes line items, and assigns the invoice number. Drafts that belong to a subscription finalize automatically about an hour after creation; standalone drafts finalize when you call finalize.

open → paid

A successful payment against the invoice — automatic collection on the customer's default method, or a recorded out-of-band payment — settles it.

open → void

You cancel an invoice that should never be collected (duplicate, error, customer churned before paying). Voiding leaves an audit trail; deleting a draft does not. Only open and draft invoices can be voided.

open → uncollectible

You accept the invoice won't be paid and write it off as bad debt. This stops dunning but keeps the receivable visible for accounting.

void and uncollectible look similar but mean opposite things to finance. Void = "this was never a real debt." Uncollectible = "this was a real debt we failed to collect." Pick deliberately — they reconcile differently.

Driving the lifecycle in codeAsk

This walks a standalone invoice from draft to a terminal state. Amounts are integers in minor units (1000 = EUR 10.00).

import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

// 1. Create a draft and attach a line item.
const draft = await vinr.invoices.create({
  customer: 'cust_8Qx2',
  collectionMethod: 'charge_automatically',
});                                          // "inv_..." status: draft

await vinr.invoiceItems.create({
  invoice: draft.id,
  amount: 2500,                              // EUR 25.00
  currency: 'EUR',
  description: 'Onboarding setup fee',
});

// 2. Finalize -> open. Locks line items and computes amount_due.
const open = await vinr.invoices.finalize(draft.id);
console.log(open.status, open.amountDue);    // "open" 2500

// 3a. Collect now -> paid.
const paid = await vinr.invoices.pay(open.id);
console.log(paid.status);                    // "paid"

To retire an invoice instead of collecting it, choose the transition that matches your accounting intent:

// Cancel a debt that should never have existed.
await vinr.invoices.void('inv_3kPq');         // open -> void

// Write off a real debt you can't collect.
await vinr.invoices.markUncollectible('inv_7Lm9'); // open -> uncollectible

Once an invoice is open you cannot edit its line items — amount_due is locked for audit integrity. If the amount is wrong, void it and issue a corrected invoice rather than mutating the original.

Events on each transitionAsk

Wire fulfilment and reconciliation to webhooks, not to API responses, so retries and out-of-band payments are handled the same way. Verify every payload before trusting it.

const event = vinr.webhooks.verify(payload, signature); // x-vinr-signature

switch (event.type) {
  case 'invoice.finalized':     /* draft -> open */      break;
  case 'invoice.paid':          /* -> paid: grant access */ break;
  case 'invoice.payment_failed':/* stays open: dunning */ break;
  case 'invoice.voided':        /* -> void */             break;
  case 'invoice.marked_uncollectible': /* -> uncollectible */ break;
}
TransitionEvent
draftopeninvoice.finalized
openpaidinvoice.paid
collection fails (stays open)invoice.payment_failed
openvoidinvoice.voided
openuncollectibleinvoice.marked_uncollectible

Edge casesAsk

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page