Skip to content

Billing

Eight tools that configure and inspect automated monthly PIX billing. They share every validation rule with the staff UI’s “Cobrança” panel — the same zod schemas from @marques-boxing/billing-contracts, not a second copy of the same rules — so an input the panel refuses is refused here with the same message.

Due day5, 10, 15 or 20 — nothing else. These four days exist in every month, February included, so a fixed due day never has to be clamped.
AmountA positive decimal with at most two places. 0 is refused, and a third decimal place is refused rather than rounded.
PhoneA Brazilian mobile or landline, normalized to E.164 (+5511999998888).
First cycleThe first due day on or after today. No partial first month and no retroactive cycle.

Answers “what would automated billing do for this contract” with zero side effects: nothing is written, no charge is created and no message is sent.

Input: contract_id, due_day, agreed_amount, optional as_of and timezone.

Returns the first cycle due date, the cycle month, the computed billing key (<contract_id>:<YYYY-MM>), the amount in reais and centavos, the E.164 recipient, and eligibility_errors — stable codes such as student_not_active, invalid_phone, contract_ended_before_due_date.

Run it before billing_enable_contract.

Turns billing on for one contract. Requires an active student, an active in-date contract and a valid phone; refuses with the eligibility codes otherwise. confirm: true is mandatory — a missing field then fails validation instead of quietly meaning “not confirmed”.

Sets billing_enabled, due_day, agreed_amount and billing_started_on (the first due day on or after today), and clears any previous billing_disabled_* note.

The agreed_amount is a snapshot of this contract, not a pointer to the plan’s price. A later price change on the plan does not move it.

Stops future cycles and pauses the reminders of the contract’s still-open invoices. Issued debt is retained: no invoice is cancelled and no status changes. The reason is mandatory — it is what staff read months later when they ask why a student stopped being billed.

Returns the ids of the invoices whose reminders were paused.

Clears reminders_paused on one invoice. An explicit action by design: nothing resumes reminders automatically.

Records a payment made outside the provider — cash, a direct PIX, a card machine — through public.record_manual_payment. One call, one transaction: it inserts the payment, marks the invoice paid once the recorded total covers it, and stamps who recorded it. The staff UI’s payment form calls the same function.

Input: invoice_id, amount, method (pix / credit_card / boleto), paid_at (ISO 8601 with an offset).

Reads this organization’s billing settings: the two independent kill switches (charges_enabled, messages_enabled), the pilot recipient allowlist, the provider environment, the charge expiry and the timezone.

The first read creates the row. A missing row and a row with both kill switches off mean the same thing to the engine, but “not configured” invites the reading “unknown”, and the one reading nobody may ever make about a system that moves money is “unknown, therefore probably on”. After the first read there is a row that says false out loud. It is created with org_id and the two kill switches explicitly false; every other column takes the migration’s own default, so a created row is indistinguishable from one Postgres would have made. The create is idempotent — two callers racing on the first read end with one row.

Only owner and staff create it. An automation caller reads settings on every scheduler tick and must never be the account that brings an organization’s billing configuration into existence, so it gets the defaults reported — both kill switches off — and no write.

Owner only. Changes any of the settings above. Each kill switch stops its own side effect on its own: charges off still allows messages, and messages off still allows charges. While pilot_recipients_e164 is non-empty, only those numbers receive a message.

Lists this organization’s invoices, newest due date first. Filters: contract_id, an exact status, or unpaid: true for pending or overdue — use unpaid for “who owes us money”, since that question is two statuses.

Paginated like every other list tool (limit / offset, returns { items, total, has_more }).