Payments
Multi-gateway payments and Cashier-style billing for AdonisJS — 18 gateways behind one driver contract, across Brazil, Europe, North America, Latin America and India, with invoice emission, method routing and webhook-driven business logic.
@adonis-agora/payments brings multi-gateway payments and Cashier-style billing to
AdonisJS. One contract — charge, refund, createSubscription, parseWebhook —
normalized across every supported gateway, so your services talk to one interface instead
of one SDK per provider.
Nothing in the contract is region-specific: money is an integer in the currency's smallest unit, a payment method is a name the driver either supports or refuses, and a webhook is a signature to verify and an event to normalize. What differs between a card charge in the euro area and a Pix charge in Brazil is which driver you route to — not the code that calls it. A charge can go out through one gateway and emit its invoice through a completely different provider, in a single call.
The one rule
A payment is confirmed only by the webhook. A charge() returns a PENDING record —
the money hasn't moved until the gateway calls your endpoint. The processor's idempotency
ledger is what makes "trust only the webhook" safe: forged callbacks are rejected and
redeliveries are no-ops. Point billing.dispatcher at durable and a
half-processed confirmation survives a restart as well. See
Webhooks.
The problem it solves
Payment integrations are usually a mess of gateway SDKs, hand-rolled status mirrors, and
webhook controllers that duplicate each other. @adonis-agora/payments collapses that into
four guarantees:
- One driver contract, every gateway. Every gateway normalizes onto the same
PaymentsDriver. Adding a gateway is a config entry, not a code change — and a driver declares which methods it supports, so routing a card to a Pix-only gateway fails at the manager, not at the gateway. - Money moves only on confirmation. The idempotent webhook processor (a gateway-event ledger written before any work runs) makes redeliveries no-ops. Your business logic — grant credits, activate a subscription — runs at the right moment, exactly once. And the endpoint that receives it will not boot unverified: a driver that can authenticate a delivery and was given no credential refuses to start.
- Billing mirrors the gateway. Subscriptions, payments, disputes and trials sync into Lucid tables automatically, Cashier-style, with billable mixins on your models — and the library creates those tables itself, so adopting it is a config file, not a migration.
- Invoices are a separate concern. Pay through one provider, emit the invoice
through another — the
invoiceoption on the charge call joins them.
Quickstart
The minimal loop — install, configure a gateway, charge — with one provider and no invoice provider:
Install and configure:
node ace add @adonis-agora/paymentsThis registers the providers in adonisrc.ts, publishes config/payments.ts (plus the dashboard
and client config files), and registers the ace commands. There is no migration to run: the
billing layer creates its own tables on first use.
Set a gateway secret and make your first charge:
ASAAS_API_KEY=your-asaas-api-keyimport env from '#start/env'
import { defineConfig, payments } from '@adonis-agora/payments'
export default defineConfig({
default: 'asaas',
providers: {
asaas: payments.asaas({ apiKey: env.get('ASAAS_API_KEY') }),
},
methods: {
pix: 'asaas',
credit_card: 'asaas',
},
})import { getPayments } from '@adonis-agora/payments/services/main'
const customer = await getPayments().driver('pix').createCustomer({
name: 'Jane Doe',
email: 'jane@example.com',
taxId: '123.456.789-00',
})
// charge via Pix — the customer scans the QR, then the webhook confirms
const payment = await getPayments().driver('pix').charge({
customerId: customer.id,
amount: 1990, // R$ 19,90 — integer cents, never floats
})
// `driver('pix')` binds the method: the charge goes out as Pix without repeating it.Register the webhook and react to confirmations:
node ace payments:webhook # prints the URL + event list per provider
node ace make:webhook-handler payment.succeeded@inject()
export default class PaymentSucceededHandler {
static readonly eventType = 'payment.succeeded'
async handle(event: WebhookEvent): Promise<void> {
// dispatch a durable workflow — durable owns retry + exactly-once
}
}The happy path, end to end
you open a charge
payment.status = 'pending' — the record exists, but nothing has been paid. Never grant anything here.
the customer acts
The customer scans the Pix QR, or approves the card — outside your process, on their own clock. Your app is not in this step and cannot observe it.
the webhook confirms it
One request, five ordered steps — each one is a guarantee:
- 1validate the signatureforged callbacks rejected
- 2ledger the eventredeliveries stop here
- 3sync the billing tablesbilling_payments / billing_subscriptions
- 4run your handlergrant credits, activate the subscription
- 5publish diagnosticsagora:payments:payment.succeeded
Getting Started
Install, configure a gateway, register webhooks and make your first charge — with no migration to run.
Concepts
Money as an integer, one contract over every gateway, the pending-until-webhook rule, and the idempotency ledger.
Configuration
Providers, method routing, the invoice section, and the billing layer — who owns the schema, which dispatcher runs a webhook, where handlers live.
Providers
18 gateways side by side — methods, subscriptions, disputes, externalReference.
Webhooks
Signature validation, a delivery that carries several events, the idempotency ledger, and the four homes for your business logic.
Billing
Billable mixins, the billing tables, webhook sync, and durable-backed dispatch.
Invoices
Emit an invoice attached to a charge, through a provider independent of the gateway.
Client
The browser status endpoint and the usePaymentStatus polling hook — ownership first, off until you enable it.
Dashboard
The console at /payments — health first, then revenue, payments, customers, subscriptions, disputes, the webhook ledger and the audit trail, with refund, retry and dispute resolution.
Diagnostics
Every milestone on the observability bus — gateway-action vs business events, Telescope.
Custom providers
Write a gateway or invoice provider as a config factory, or extend a bundled driver.
Testing
The fake driver, a real manager over it, the in-memory store, awaiting dispatched webhooks — exercise billing without a gateway or DB.
Patterns
A cookbook with the gateway differences side by side — Pix, subscriptions, where business logic lives, metered billing, splits, recovery.
Production
The operational checklist — webhook secrets, choosing a dispatcher, reconciliation, what to watch.
Troubleshooting
The symptoms that actually happen, and how to tell the causes apart.
CLI
payments:webhook, payments:sync, payments:health, make:billable and make:webhook-handler.
API reference
Every config option, driver method, exported helper, type, event and command — from source.