Skip to content

Credit Card Invoicing

A credit card programme needs more than authorization. Purchases accumulate, a statement closes on a fixed day each month, the cardholder pays some or all of it by a due date, and what they do not pay accrues interest. QPay's internal ledger implements that whole lifecycle — see QPay Ledger Core for the accounts it runs on.

Everything on this page applies only to card applications whose account type is CREDIT, and only when you are using QPay's internal ledger. If you run your own ledger, invoicing is your responsibility.

The billing cycle

A billing cycle is configured per card product. Every card issued against that product inherits it.

Field Meaning Range
closing_day Day of the month the statement closes 1–28
due_day_offset Days after closing that payment is due 1–60
grace_days Days after the due date before interest starts 0–60
minimum_payment_bps Minimum payment as basis points of the total 0–10000
interest_monthly_bps Monthly interest rate in basis points ≥ 0
late_fee_minor_units Flat late fee, in minor units —
timezone IANA timezone the cycle runs in e.g. America/Sao_Paulo

Why closing_day stops at 28

Every month has a 28th. Allowing 29–31 would mean clamping in February, which makes cycle length vary from month to month and interest arithmetic inconsistent. Capping at 28 keeps every cycle the same shape.

The cutoff is not a date — it is a precise instant: local midnight on closing_day in the product's own timezone, stored as a UTC timestamp. That is what makes a statement auditable: the exact window can be reproduced later even if the configuration has since changed. Each statement is identified by a cycle_ref of YYYY-MM, taken from the cutoff in the product's timezone.

flowchart LR
    A([Purchases accumulate<br/>on the open cycle]) --> B([Cutoff<br/>local midnight, closing_day])
    B --> C([Statement issued<br/>total + minimum fixed])
    C --> D([Due date<br/>cutoff + due_day_offset])
    D --> E([Grace ends<br/>due date + grace_days])
    E --> F([Interest accrues<br/>once per overdue day])

Statement closing

Closing a cycle turns an open, accumulating balance into a fixed document.

The statement total is the signed movement within the window — everything posted after the previous cutoff, up to and including this one. It is never read from a live account balance, because a balance would include purchases that settled after the cutoff, which is wrong every time the job runs even slightly late. Consecutive windows abut exactly: each one starts from the previous statement's stored cutoff, not from a recomputed value.

Closing changes no totals. It only partitions existing debt by time — the amount simply moves from the open cycle onto a receivable for that statement.

Statement lines are on a posted-date basis. A purchase made before the cutoff but cleared by the network afterwards appears on the following statement, because the ledger only knows about it once it settles. This is normal, and it is the most common cardholder question about any credit statement.

A cycle whose total is zero produces no statement. A negative total — the cardholder is in credit — also produces no statement and carries into the next cycle. Both cases are reported as skips with the amount, so the decision is auditable rather than silent.

A cutoff that has not arrived cannot be closed

Statements are compared as instants, per product. You cannot close a cycle early — an invoice a cardholder has already seen must never reopen at a different figure. Re-running a cycle that already closed is reported as skipped, not as an error.

What is fixed and what stays live

Value Where it lives Changes after closing?
total_amount Stored Never — it is the document that was sent
minimum_amount Stored Never
cutoff_at, due_at Stored Never
outstanding_amount Derived live from the ledger Yes — falls as the cardholder pays

The minimum payment rounds up, so a cardholder who pays the stated figure is never a centavo short of the minimum.

Payments

A payment is recorded once and allocated in the same atomic operation.

Money arrives into your Corporate_Account_Asset — never into the regulated Pool_Account_Asset, which holds cardholder funds and may not be used to settle your own obligations. From there it is allocated to open statements.

Allocation order is oldest statement first, and it is not caller-controllable. That minimises interest for the cardholder and matches Brazilian market practice. Each allocation is paired with a restoration of the same amount of Limit_Available, so paying an invoice is what gives the cardholder their purchasing power back.

Anything left over after every open statement is settled goes to the current open cycle, reducing the next statement or leaving a credit balance. An overpaid card has a negative Limit_Used and an Limit_Available above the granted ceiling — the cardholder can spend beyond their line, and the ledger allows it.

Money cannot be allocated before it has arrived: the intake account structurally cannot go into debit, so an allocation attempt beyond what was actually received is rejected rather than creating phantom funds.

Payments have no separate record

There is no payments table. The ledger movements are the record, which is what makes the reconciliation controls below possible. Replaying the same payment (same idempotency key) reconstructs the original allocations; reusing that key for a different amount is a conflict.

Finance charges

Once a statement passes its due date plus its grace period, it starts accruing.

  • Interest is charged once per overdue day, computed on the receivable's live outstanding balance — not on the original total_amount. A cardholder who partially pays accrues less from that point on.
  • The monthly rate is spread over a fixed 30-day basis, so an overdue day costs the same in February as in March.
  • Daily interest truncates down. Rounding can only ever favour the cardholder — deliberately the opposite of the minimum payment, which rounds up.
  • The late fee is charged once, ever, per statement — never once per overdue day.
  • Both interest and the late fee consume credit limit, because every centavo of debt must be matched by consumed limit.

Charges land on the open cycle, not the unpaid statement

Interest on March's unpaid statement appears on April's statement. A closed document is never edited. This surprises people reading the ledger for the first time and is worth stating plainly to your support team.

If the job has not run for a while it catches up, oldest overdue day first, up to a bounded number of days per run so that repeated runs converge and nothing is silently forgiven. Anything still outstanding is reported.

A future business day is clamped to today and the clamp is reported — the opposite of statement closing, which refuses. Charging a day before it has happened would be an overcharge that can never be reversed, since a given day can only ever be charged once.

Statements are skipped with an explicit reason: already settled, in credit, still within grace (and when grace ends), or already accrued through a given date.

Corrections after a statement closes

A refund, chargeback, or reversal for a charge that already appeared on a closed statement does not touch that statement. It credits the current open cycle, reducing the next statement or leaving a credit balance.

Two different statements, only one of which is retroactive

"This charge was refunded" and "your invoice went down" are not the same thing. The first can be true retroactively; the second never is.

Reconciliation controls

The ledger ships a read-only report that re-derives its own invariants and reports any disagreement. A break is only ever reported — it is never posted or auto-corrected. Breaks always state both disagreeing figures, never just the difference, so the cause is diagnosable.

Control Checks A break means
C1 Limit_Total_Granted = Limit_Available + Limit_Used A defect in limit origination or a limit adjustment
C2 Limit_Used equals the open cycle plus every statement receivable Settlement and invoicing have diverged
C3 Pending holds agree between the two shadow accounts A stale hold, or a clearing file that never arrived
C4 Each stored total_amount matches a recomputed window sum A closing that read a live balance, a clock or timezone error, or tampering
C5 Only a closing may debit and only an allocation may credit a receivable A posting made outside the invoicing flow
C6 The invoice payment float nets to zero per currency An orphan payment that was received but never allocated

C3 and C6 report complete: false: C3 cannot see holds the processor has not yet passed on, and C6 has no bank-statement feed to match against. They are still worth running — they just cannot claim completeness.

The report also returns the suspense-account policy verbatim, explaining why unresolved discrepancies are surfaced for investigation rather than quietly absorbed.

When these jobs run

QPay runs statement closing, finance-charge accrual, and reconciliation automatically on an hourly ticker. Hourly rather than nightly because cycles close at local midnight in their own timezone, so "today" begins at a different instant for every product.

Each job also has an HTTP endpoint so you can trigger it out of band — to close a cycle immediately after changing configuration, or to pull a reconciliation report on demand. That is what Run a Billing Cycle walks through.

Next steps

  • Run a Billing Cycle


    Configure a cycle, close statements, record a payment, accrue charges, and reconcile.

    Read guide

  • Use the QPay Ledger


    Join the ledger and onboard a cardholder before you can invoice them.

    Read guide

  • Ledger Accounts API


    Interactive reference for every invoicing endpoint.

    API docs