Skip to main content

Modelling Example: B2B Invoices

This document shows how to map a business-to-business receivables client — a wholesaler, logistics provider, or factoring company collecting commercial invoices — onto the platform. The client described here ("FacturaPro") is fictional. Companion examples: BNPL, Personal Loan, Recurring Invoices, Debt Buyer, Revolving Credit.

B2B collections differ from consumer collections less in entities than in dynamics:

  1. The debtor is a company. The schema supports this directly: Debtor.type: "legal" with companyName required — contacts are roles (accounts payable), not individuals.
  2. Non-payment is usually a dispute or a cash-flow negotiation, not a default. Short payment, damaged goods, missing PO numbers. The core workflow is dispute → adjustment → promise → payment, and the model has a first-class tool for the promise step.
  3. Few accounts, large amounts, partial everything. Partial payments, partial credit notes, partial disputes — the fee/credit mechanics get used constantly.

Entity mapping

B2B conceptPlatform entity / mechanism
Business customerAccount, debtor type: "legal" + companyName
Contract / customer relationshipProduct (optional; useful when one customer has several)
Overdue invoiceClaim (one per invoice, ref = invoice number)
Credit notecredit_claims (partial or full)
Disputepause_journeys (+ resolution via credit or resume)
Payment commitmentcreate_promises_to_pay (amount, currency, dueDate)
Withdrawn/cancelled invoiceresolve_claims reason CLAIM_INVALIDATED / CLAIM_DISCARDED
Late-payment interest (B2B statutory)named fees entries via debit_claims

Promises to pay are the mechanism the consumer examples never needed: a lightweight, trackable commitment — "we will pay €X by date D" — attached to a claim. The platform tracks the outcome and emits event.promiseToPay.resolved / event.promiseToPay.expired, which the strategy consumes: kept promise → de-escalate; expired promise → escalate. This is exactly the cadence of B2B collections calls, where every conversation ends in a promise, and it is far lighter than an Instalment Plan (no schedule, no mandate — just amount and date).

Fictional client: "FacturaPro"

Assumed policies:

  • Wholesale distributor; invoices €2,000–€250,000, payment terms 30–60 days.
  • Invoices are placed at due date + 14 days. Statutory B2B late-payment interest and a fixed recovery fee apply.
  • Disputes are frequent (~15% of overdue invoices) and must silence dunning immediately — dunning a disputed invoice damages the commercial relationship.
  • Escalation ladder: reminder → account-manager call (promise expected) → formal notice → legal handover.

Worked example: Almacenes Rivera

Almacenes Rivera (a retail chain) has three overdue invoices totalling €78,000.

  • Account = Almacenes Rivera, primary debtor { "type": "legal", "companyName": "Almacenes Rivera S.A.", "contactInformation": { /* AP dept */ } }.
  • Claims = one per invoice: FP-INV-10231 (€30k), FP-INV-10298 (€28k), FP-INV-10344 (€20k), each with statutory interest and the recovery fee as named fees. Account-level strategy contacts Rivera once about the full position — but each invoice keeps its own lifecycle, which is essential because they diverge immediately:

Invoice 10298 is disputed (short delivery claimed). FacturaPro pauses dunning on it (pause_journeys) while the other two continue. The dispute resolves with an agreed €4,000 credit note:

// POST /v1/{clientId}/credit_claims
{
"FP-INV-10298": { "amount": 400000, "fees": 0 },
}

then resume_journeys puts the corrected invoice back into the flow. Had the invoice been withdrawn entirely, resolve_claims with CLAIM_INVALIDATED would close it without pretending it was collected.

Alternative for long-running disputes: split the invoice instead of pausing all of it — credit the disputed €4,000 out of FP-INV-10298 and place it as its own claim carrying relatedClaims: [{ "type": "SPLIT_FROM", "claimReference": "FP-INV-10298" }]. The undisputed €24,000 keeps its dunning journey while only the split claim sits paused, and the lineage stays queryable.

Invoices 10231 and 10344 — the collections call. Rivera's finance director commits to paying the older invoice this month and the rest next month. That is two promises:

// POST /v1/{clientId}/create_promises_to_pay
{
"FP-INV-10231": {
"amount": 3000000,
"currency": "EUR",
"dueDate": "2026-08-15",
},
"FP-INV-10344": {
"amount": 2000000,
"currency": "EUR",
"dueDate": "2026-09-15",
},
}

The strategy holds escalation while a promise is open. If event.promiseToPay.resolved fires (payment arrived), the next step de-escalates; on event.promiseToPay.expired, the formal notice goes out — no human has to remember to follow up.

Partial payment without a promise: if Rivera simply wires €10,000 against invoice 10344, credit_claims reduces it; the claim stays open for the remainder.

Discovery questions for a real B2B client

  1. What does their dispute process look like, and who decides? Map dispute-open → pause_journeys, dispute-resolved → credit note + resume_journeys. Volume decides whether this is API-automated or backoffice-manual.
  2. Are promises to pay part of their collections culture? If their team already works call-and-promise cycles, wire create_promises_to_pay and the promise events into the escalation ladder from day one — it is the highest-leverage feature for this profile.
  3. How do credit notes flow? From their ERP automatically (credit_claims integration) or manually? Partial credits against fees vs principal need the named-fee semantics agreed.
  4. Statutory interest and recovery fees — jurisdiction-specific (e.g. EU Late Payment Directive): fixed at placement or accruing (→ debit_claims, see the revolving example)?
  5. When does legal handover happen, and what does it need? Typically an export of claim + promise + dispute history; confirm get_events covers the audit trail their lawyers expect.

This example focuses on what to model; the step-by-step mechanics of each API call are covered by the generic use cases: