Skip to content

Invoices

Invoices collect an outstanding balance for an order — most commonly the remaining balance on a catering pre-order after any deposit. Each invoice has an unguessable public token that acts as its capability: anyone holding the token can view the invoice and pay the balance, so it can be embedded directly in a "pay your balance" link.

Fetching an invoice is public. Creating and confirming a balance payment require the kitchenclick:payments.create scope. Payment is processed through Stripe — see Payments for the client-side confirmation flow.

The token is the secret

The publicToken is a long random string (not a hashkey). Treat it like a bearer credential — never log it or expose it beyond the intended recipient.

Get Invoice

GET /v1/ecommerce/invoices/{publicToken}

Authentication: None (Public — the token is the capability)

Rate Limit: 100/min per IP

Path Parameters

ParameterTypeDescription
publicTokenstringThe invoice's public token (≥ 20 chars)

Response

json
{
  "status": "success",
  "data": {
    "invoice_id": "inv_00000a1B2c3D4e5",
    "invoice_number": "INV-2026-0001",
    "status": "partially_paid",
    "issued_at": "2026-06-10T14:30:00Z",
    "due_at": "2026-06-15T14:30:00Z",
    "order_number": "1247",
    "order_type": "catering",
    "fulfillment_method": "delivery",
    "scheduled_at": "2026-06-15T18:00:00Z",
    "customer": { "name": "Jane Doe", "email": "jane@example.com", "phone": "(555) 123-4567" },
    "location": {
      "location_id": "loc_00000k1L2m3N4o5",
      "name": "Downtown Kitchen",
      "address": "123 Main St, Austin, TX 78701",
      "phone": "(555) 555-5555",
      "timezone": "America/Chicago"
    },
    "line_items": [
      { "item_id": "itm_00000a1B2c3D4e5", "name": "Catering Box - Chicken", "quantity": 2, "unit_price": "50.00", "subtotal": "100.00" }
    ],
    "subtotal": "150.00",
    "tax_total": "11.03",
    "fees_total": "0.00",
    "delivery_fee": "0.00",
    "tip_amount": "0.00",
    "total": "161.03",
    "amount_paid": "80.52",
    "balance_due": "80.51",
    "payments": [
      { "type": "deposit", "method": "online", "amount": "80.52", "paid_at": "2026-06-09T10:15:00Z" }
    ],
    "replaced_by_token": null
  }
}
FieldTypeDescription
invoice_idstringInvoice ID (prefix inv_)
invoice_numberstringHuman-readable invoice number
statusstringissued, partially_paid, paid, or void
balance_duestringOutstanding balance (decimal string; 0.00 when paid, negative if overpaid)
amount_paidstringTotal paid across all payment legs
totalstringGrand total = subtotal + tax + fees + delivery + tip
line_itemsarrayCaptured order lines (item_id, name, quantity, unit_price, subtotal)
paymentsarraySucceeded payment legs (type, method, amount, paid_at)
replaced_by_tokenstring | nullIf void, the public token of the replacement invoice

Money values are strings

All monetary amounts are returned as decimal strings (e.g. "80.51") to preserve precision. Parse them with a decimal-safe type, not a float.

Errors

StatusCondition
404Token not found, malformed (< 20 chars), or belongs to another organization

Create Balance Payment Intent

Create a Stripe PaymentIntent for the invoice's current outstanding balance. Returns a client_secret for confirming the payment client-side (see Payments).

POST /v1/ecommerce/invoices/{publicToken}/balance-intent

Authentication: OAuth Token

Scope: kitchenclick:payments.create

Rate Limit: 30/min per client

Request Body

None.

Response

json
{
  "status": "success",
  "data": {
    "client_secret": "pi_3Xy..._secret_abc",
    "payment_intent_id": "pi_3Xy...",
    "publishable_key": "pk_live_...",
    "amount": 8051
  }
}
FieldTypeDescription
client_secretstringStripe client secret — confirm the payment with Stripe.js
payment_intent_idstringStripe PaymentIntent ID — pass to Confirm Balance
publishable_keystring | nullStripe publishable key for this account
amountintegerAmount in cents (e.g. 8051 = $80.51), from the current balance_due

Errors

StatusMessageCondition
404Invoice not foundUnknown/invalid token
422This invoice has no outstanding balance.Invoice is paid, void, or balance ≤ 0
500Failed to initialize payment. Please try again.Payment processor error

Confirm Balance Payment

Record the balance payment after the customer confirms it client-side. Idempotent — calling again with the same payment_intent_id returns the current invoice without double-charging.

POST /v1/ecommerce/invoices/{publicToken}/confirm-balance

Authentication: OAuth Token

Scope: kitchenclick:payments.create

Rate Limit: 30/min per client

Request Body

json
{
  "payment_intent_id": "pi_3Xy..."
}
FieldTypeRequiredDescription
payment_intent_idstringYesThe PaymentIntent ID from Create Balance Payment Intent

Response

Returns the full updated invoice (same shape as Get Invoice) plus a top-level overpayment flag:

json
{
  "status": "success",
  "overpayment": false,
  "data": {
    "invoice_id": "inv_00000a1B2c3D4e5",
    "status": "paid",
    "amount_paid": "161.03",
    "balance_due": "0.00",
    "payments": [
      { "type": "deposit", "method": "online", "amount": "80.52", "paid_at": "2026-06-09T10:15:00Z" },
      { "type": "balance", "method": "payment_link", "amount": "80.51", "paid_at": "2026-06-10T15:45:00Z" }
    ]
  }
}
FieldTypeDescription
overpaymentbooleantrue when the captured amount differs from the expected balance — e.g. the balance was settled at the POS between intent creation and confirmation. The payment is still recorded for reconciliation.

Errors

StatusMessageCondition
404Invoice not foundUnknown/invalid token
422This invoice has been voided.Invoice is void
422Payment not completedPaymentIntent has not succeeded — payment_status reflects the Stripe status
500Payment verification failedCould not verify the payment with the processor

Changelog
DateChange
2026-06-17Initial publication.

ShopHero CommerceCore Platform