Payment tokens
What the payer pays to: each method issues its own kind of token.
A payment token is the thing you put in front of the payer: the address the money is sent to. Each payment method defines its own kind of token. Not every token is an address: PSE's is a link that sends the payer to their own bank. On a template, payment_tokens is a per-method map, with one entry per accepted method that issues a template token. An intent has a single method, so its payment_tokens is that method's token directly.
Today tokens come in three forms. Bre-B issues a key (in Colombia, a llave: a short alias like a cellphone number or a code, registered in the Bre-B directory) and an optional QR code that encodes the same destination, so the payer can scan instead of type. PSE issues a payment link: the page where the payer approves the payment at their bank.
Templates and intents both carry tokens, and the difference between them is the difference between a poster on a wall and a ticket with your name on it: a template's token is shared by every payer; an intent's token identifies one expected payment.
Where the token lives
| Owner | payment_tokens is… | Bre-B | PSE |
|---|---|---|---|
| Template | A per-method map | payment_tokens.co_breb | No entry: PSE has no template token |
| Intent | The token of its single method | payment_tokens (key and QR) | payment_tokens.pse_url |
The Bre-B token
The Bre-B token has three parts (on a template, under payment_tokens.co_breb):
| Field | What it is |
|---|---|
key_type | The kind of key: cellphone, email, alphanumeric, identification, or merchant_code. Assigned by the network when the key is registered. It is not known at creation time. |
key_value | The key itself: what the payer enters in their bank's app to address the transfer. |
qr | The QR generated for the key, when one was requested (create_qr: true). |
The QR object carries everything needed to display or embed it: the EMV payload in plain text (emvco), a rendered image in base64 (image, with image_width and error_correction_level), the provider's payment_id, its usage_mode, and, for dynamic QRs, the fixed amount and expires_at.
You can request a custom key alias with method_params.co_breb.custom_key_value, and override the QR's merchant data (merchant_category_code, receiver_city, receiver_postal_code) with qr_code_data. If omitted, Mono generates the key and uses your program's configuration.
Key per template or key per intent
method_params.co_breb.key_per decides at which level the Bre-B key is minted, and it shapes the whole collection model:
key_per | Token lives on | QR kind | Payments are… |
|---|---|---|---|
template (default) | The template | Static: multiple use, no amount | Anonymous until they arrive: any payer, any number of payments. |
intent | Each intent | Dynamic: single use, fixed amount, expires | Referenced: each payment maps back to the intent that expected it. |
A template with key_per: "template" receives payments directly. It cannot host intents, because a shared key cannot tell which intent a payment belongs to. A template with key_per: "intent" has no key of its own (payment_tokens stays null): every key is minted when an intent is created. A templateless intent gets its own dedicated key too.
The PSE token
A PSE intent's payment_tokens has a single field:
| Field | What it is |
|---|---|
pse_url | The page where the payer approves the payment at their bank. Redirect the payer there; their bank sends them back to the payin's redirect_url. |
Each PSE intent gets its own link, for one payment attempt. A template has no PSE token, so a template that offers only PSE keeps payment_tokens as null. See PSE for the whole redirect flow.
Token setup is asynchronous
Registering a token with the method's network takes a callback round-trip, which is why templates and intents are born in created. Neither is payable until the token is confirmed:
- The template moves to
readyand fillspayment_tokenswhen every pending token is confirmed (announced bypayin_template_ready). - The intent moves to
ready_to_payand fillspayment_tokens(announced bypayin_intent_ready_to_pay).
If registration exhausts its retries, the template ends failed (key_registration_failed, key_already_registered) or the intent ends failed (key_registration_failed, payment_token_registration_failed).
PSE generates its link in a single call, with no callback, but only after the intent is created. The intent still starts in created and reports the link at ready_to_pay, like Bre-B. A link that cannot be generated fails the intent and its payin with the same reason: risk_control, provider_unavailable, internal_error, or unknown.
Tokens are torn down
A token only lives while its owner can be paid. When a template is pruned or deleted, or an intent reaches a final state (successful, failed, or expired), Mono deregisters its tokens with the method's provider. The owner's prune_state / prune_reason fields track that teardown. PSE links are not torn down: each one belongs to a single payment attempt.