Creating invoices

Build one-off invoices with line items.

View as MarkdownInstall skills

Not every charge is recurring. Use standalone invoices to bill a customer for one-off work, professional services, or any ad-hoc amount — adding line items, discounts, and tax, then finalizing and collecting payment, all independent of subscriptions.

OverviewAsk

A standalone invoice is the same invoice object a subscription generates, but you assemble it yourself instead of letting a billing cycle do it. The lifecycle is identical:

StateMeaning
draftEditable. You can add, edit, and remove line items. Nothing is owed yet.
openFinalized. The amount is locked and VINR is attempting (or awaiting) collection.
paidA payment succeeded for the full amount.
uncollectibleWritten off — collection failed and dunning gave up.
voidCancelled before payment. Cannot be reopened.

The key idea: draft invoices are mutable, finalized invoices are not. Build everything you need while the invoice is a draft, then finalize once.

How it worksAsk

You create an empty draft, attach one or more invoice items (each becomes a line on the statement), and finalize. On finalization VINR applies discounts and tax, computes the amount due, and — depending on collection_method — either charges the customer's default payment method automatically or emails them a hosted invoice to pay manually.

Create the draft

Create an invoice for a cust_ customer. It starts empty and in draft.

Add invoice items

Each mbu_-free line is an invoice item with an amount (minor units) and a description. Items added to a draft attach to it immediately.

Finalize

VINR locks the totals, applies coupons and tax, assigns an invoice number, and moves the invoice to open.

Collect

With collection_method: 'charge_automatically', VINR immediately charges the default method. With send_invoice, the customer receives a payment link and a due date.

Example requestAsk

The snippet below bills a customer EUR 145.00 across two line items, applies a fixed discount, finalizes, and charges automatically.

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

// 1. Create a draft invoice.
const invoice = await vinr.invoices.create({
  customer: 'cust_8Fk2pQ',
  currency: 'EUR',
  collection_method: 'charge_automatically', // or 'send_invoice'
  description: 'Onboarding & setup, May 2026',
});                                            // "inv_..." in state "draft"

// 2. Attach line items to the draft.
await vinr.invoiceItems.create({
  invoice: invoice.id,
  amount: 12000,                               // €120.00
  description: 'Implementation (8h)',
});
await vinr.invoiceItems.create({
  invoice: invoice.id,
  amount: 2500,                                // €25.00
  description: 'Data migration',
});

// 3. (Optional) apply a discount and finalize.
const finalized = await vinr.invoices.finalize(invoice.id, {
  discounts: [{ coupon: 'WELCOME10' }],
});

console.log(finalized.status);                 // "open" -> "paid" once collected
console.log(finalized.amount_due);             // 13050  (€130.50 after 10% off)

Add every line item before you call finalize. Once an invoice is open its totals are immutable — to change a finalized invoice you must void it and create a new one.

Prefer raw REST? The same finalize step over HTTP:

curl -X POST https://sandbox.api.vinr.com/v1/invoices/inv_3Rd9Lm/finalize \
  -H "X-Api-Key: $VINR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "discounts": [{ "coupon": "WELCOME10" }] }'

Key parametersAsk

Prop

Type

Resulting eventsAsk

Finalizing and collecting an invoice emits a predictable sequence you can subscribe to on a webhook endpoint:

  • invoice.finalized — the draft became open; totals are locked.
  • invoice.paid — collection succeeded. Fulfil access or deliver goods here.
  • invoice.payment_failed — automatic collection failed; the invoice enters dunning.
const event = vinr.webhooks.verify(payload, signature); // x-vinr-signature header
if (event.type === 'invoice.paid') {
  const inv = event.data.object;               // { id: "inv_...", amount_paid, ... }
  await grantAccess(inv.customer);
}

Edge casesAsk

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page