Verified traps
12.1 Unknown fields are silently ignored
A body with a misspelled field name returns 201 Created with no warning, and the payment is created without that feature.
{ "amount": 100, "service_fee": true } <- wrong name
-> 201 Created, service_fee_amount: 0 <- the fee pass-through was NOT applied
This is the most dangerous failure mode of the API, because the result looks successful. It is
especially dangerous for AI-generated code, which tends to write other gateways' field names
(card_token, payment_method, customer_id, amount_cents) with full confidence.
Mandatory defence: after creating a payment, verify in the response that the computed fields
(sales_tax_amount, service_fee_amount, total, reusable) match what you expected. And validate
your request body against the schema in openapi.yaml, which declares additionalProperties: false
precisely to catch what the API lets through.
12.2 amount has no ceiling and coerces types
The API accepts absurd amounts (11 digits) and numeric strings ("150.00"). Enforce a sane maximum
before sending.
12.3 Two different kinds of 404
| Response | Meaning |
|---|---|
{"message":"Pago no encontrado."} |
The route exists; the payment does not. Wrong id, or an id from the other environment. |
{"message":"The route ... could not be found."} |
The route does not exist. Malformed URL or a nonexistent endpoint. |
Telling them apart saves hours: the second is never fixed by changing the id.
12.4 Timestamps are Honduras local time with no offset
expires_at, created_at and paid_at come back as YYYY-MM-DD HH:MM:SS in Honduras time
(UTC-6), with no timezone marker in the string. Parsing them as UTC shifts every value by six
hours - enough to make a future expiry look expired, or an order look paid before it was created.
Attach the offset explicitly when parsing.
12.5 A customer-selected tip is not in the total you were given
With tip_customer_selectable, the total returned at creation excludes the tip because the
customer has not chosen it yet. The amount actually charged can therefore be higher than the total
you stored. Reconcile against the webhook payload or a fresh lookup, never against the creation
response.
12.6 metadata can come back as an empty array
When no metadata was sent, the API may return "metadata": [] instead of {}. Strict
deserializers that expect an object will throw. Treat both shapes as "no metadata".
12.7 Do not hardcode the checkout domain from the official docs
The checkout_url returned is on
aura.roki.systems/pay/link/{slug}. Never hardcode or allow-list a checkout domain - always
redirect to the exact checkout_url the API returned.
12.8 success_url accepts http://
Although the official documentation requires HTTPS, the API accepts unencrypted URLs. Use HTTPS anyway - it is a return URL in a payment flow.
