Guide

Build a billing page

Show a customer what they owe, what they've paid, and let them download and pay invoices, from your own product.

Scopes: customers:read and invoices:read. Add payments:write only if you start checkouts yourself.

Totals#

GET /customers/{id}/statement gives open, overdue and paid totals and counts, per currency. Show one block per currency; never add NGN and USD together.

The invoice list#

GET /invoices?customerId= lists the customer's invoices, newest first. Each has status, overdue, total, dueDate and display-ready lines.

statusShow the customerButtons
awaiting_approval awaiting_final_approval returnedNothing: they haven't been sent it yet.None
approved“Due”, or “Overdue” when overdue is true.Download, Pay
paid“Paid” on paidAt.Download invoice, Download receipt
voided“Cancelled”, or hide it.None

Download and Pay buttons#

  • Call POST /invoices/{id}/links for a fresh pdfUrl and payUrl when the customer needs them. Links expire, so don't store them.
  • With a few invoices on the page, fetch links as it loads. With many, link each button to your server and fetch the link on click, as below. That also keeps you well under the rate limit.
  • payUrl is null for paid and voided invoices: hide Pay.
  • For a receipt, use POST /receipts/{invoiceId}/links.
// Express. Your page calls these routes on your server; the API key never reaches the browser.
const flux = (path, init = {}) =>
  fetch(`${process.env.FLUX_BASE_URL}/api/v1${path}`, {
    ...init,
    headers: { Authorization: `ApiKey ${process.env.FLUX_API_KEY}`, ...init.headers },
  });

app.get("/billing", requireLogin, async (req, res) => {
  const customerId = req.user.fluxCustomerId;
  const [statement, invoices] = await Promise.all([
    flux(`/customers/${customerId}/statement`).then((r) => r.json()),
    flux(`/invoices?customerId=${customerId}&limit=50`).then((r) => r.json()),
  ]);
  // Show only what the customer has been sent: approved, paid (and voided, if you like).
  const visible = invoices.data.filter((i) => ["approved", "paid"].includes(i.status));
  res.render("billing", { totals: statement.currencies, invoices: visible });
});

// "Download" and "Pay" buttons link here. Fresh links on every click; nothing stored.
app.get("/billing/invoices/:id/:action(pdf|pay)", requireLogin, async (req, res) => {
  const invoice = await flux(`/invoices/${req.params.id}`).then((r) => r.json());
  if (invoice.customerId !== req.user.fluxCustomerId) return res.sendStatus(404);
  const links = await flux(`/invoices/${invoice.id}/links`, { method: "POST" }).then((r) => r.json());
  const target = req.params.action === "pay" ? links.payUrl : links.pdfUrl;
  return target ? res.redirect(target) : res.redirect("/billing");
});
Keep the key on the server

Call the API from your server. Check the invoice belongs to the signed-in customer before you return links for it.

Keeping it fresh#

Read from Flux when the page loads, or cache briefly and refresh on invoice.issued, invoice.paid and invoice.voided webhooks.