Anatomy of a checkout that converts
Few fields, a total that never moves, one clear method choice, declines explained in plain words and a state for every outcome. A walk through what a payment page must get right, with our own checkout as the example.
Written by KrokoPay teamPublished 7 min read
A checkout is the only page on your site where every visitor has already decided to pay. It does not need to persuade anyone. Its job is to stay out of the way: answer the buyer's questions before they are asked, accept the payment, and say clearly what happened.
There are no conversion numbers here: we have not measured any we would trust. What follows is reasoning, plus the decisions we made while building KrokoPay's own hosted checkout. Treat it as a checklist to argue with.
The first three questions a buyer has are how much, for what and to whom. Put the answers at the top: the seller's name, a one-line description of the item and the amount, written in the buyer's notation. A Brazilian reader expects R$ 1.234,50; an English reader is used to R$1,234.50. The currency stays the same; only the way it is written changes.
Then keep that total still. If the button and the header disagree, the buyer stops to work out why. When something legitimately changes the total, say so next to the amount, not in a footnote.
In our checkout the order summary shows the seller, the item and the total, and the pay button repeats the same amount: Pay R$189.00, not just Pay. On narrow screens the order details fold away behind a toggle, but the item and the total stay visible.
Every field is a question, and every question is a chance to hesitate. Before adding one, write down who will read the answer and when. If nobody can name a use, the field goes.
| Field | Why it is there | When to ask |
|---|---|---|
| Receipt and support | Always, as one field | |
| Full name | Receipt and reconciliation | Always |
| Card number, expiry, security code | The card payment itself | Only after the buyer picks card |
| Tax ID (CPF or CNPJ) | Some invoices and business flows in Brazil | Only if your business needs it, clearly marked |
| Phone | Rarely needed to pay | Only if someone will actually call |
The fields that remain should do work for the buyer. Use the right keyboard (inputmode="numeric" for card digits), the right autofill hints (autocomplete="cc-number", "email"), and format as people type. Detect the card brand instead of asking for it, and check the number's checksum before submitting, so a typo is caught on the page instead of coming back as a decline.
Information: Card data is not yours to keep
Pix and card are not two skins of the same thing. With Pix the buyer leaves for their bank app, approves there and comes back; with card they fill a form and get an answer on the spot. Presenting them as identical tabs hides the one thing the buyer needs to know: what happens after the click.
- Pix: "Pay with the QR code or the copy-and-paste code in your bank app." The page then shows the code, a countdown to its expiry and a waiting state that updates on its own.
- Card: "Pay with a credit card." The form appears in place, and the answer is shown on the same page.
One line under each option is enough. If you only accept one method, do not show a choice at all: our checkout renders the single method directly when a payment link is configured that way. A question with one possible answer is noise.
"Payment failed" may be the most expensive sentence on the page. It leaves the buyer guessing whether they were charged. Most declines carry a reason, and most reasons have an obvious next step. Say both, without blaming anyone and without leaking internal error text.
failure_code | What the buyer reads | Next step offered |
|---|---|---|
card_declined | The issuer declined without giving a reason | Another card, or Pix |
insufficient_funds | Not enough available limit for this amount | Another card, or Pix |
expired_card | The issuer says the card has expired | Check the date, or another card |
incorrect_cvc | The security code doesn't match | Check the 3 or 4 digits and retry |
processing_error | It couldn't be processed this time | Try again in a moment |
Every decline screen in our checkout also says, in its own line, that nothing was charged. That sentence answers the question the buyer is too anxious to ask. On your side the attempt arrives as a charge.failed event with the same failure_code, so support can see exactly what the buyer saw.
Success: Try every decline yourself
4000 0000 0000 0002 is declined, 4000 0000 0000 9995 has insufficient funds. Nothing real is charged.A checkout is a state machine the buyer can see. The happy path gets designed because it is in the mockup; the others get a generic error box. Write them all down, and give each its own sentence and its own way forward.
- Waiting for payment: the Pix code, its countdown, and the reassurance that the page updates by itself.
- Processing: the button keeps its width, shows a spinner and cannot be pressed twice.
- Paid: a receipt with the amount, method, date and a receipt id the buyer can quote to support.
- Expired: the Pix code ran out; no payment was made; one button generates a new code for the same order.
- Declined: the reason, "nothing was charged", and the next step.
- Unavailable: the seller turned the link off. Say so, and show how to reach them.
The forgotten ones are expired and unavailable: a link shared months ago in a group chat will be opened long after you switched it off, and deserves a clear sentence, not a 404. And when a buyer reopens a link they already paid, show the receipt instead of a second form. Paying twice by accident is the worst outcome a checkout can produce.
Many buyers will open your checkout on the same phone they will pay with. A QR code is hard to scan with the device showing it, so the copy-and-paste code must be just as prominent, with a thumb-sized copy button and a fallback when copying is blocked.
- Touch targets of at least 44px, with space between them.
- No horizontal scroll at 360px wide, and labels that wrap instead of being cut off. Portuguese copy runs longer than English.
- Inputs at 16px or larger, so mobile browsers do not zoom in on focus.
Buyers trust what they can check, not rows of padlocks: whose name is on the page, what they are paying for and who receives the money. Our checkout shows the seller's brand and legal name, and tells Pix payers to confirm that the recipient in their bank app matches it.
A checkout earns trust by being predictable: the same amount on every screen, no surprises, and a clear answer at the end.
Do not add badges for certifications you do not hold, and do not invent urgency. A countdown belongs on a Pix code because the code really expires; on a product page, it is pressure.
- Can a buyer tell the seller, the item and the total within five seconds?
- Does the amount on the button match the amount in the summary?
- Does each payment method say, in one line, what happens next?
- Does every decline name a reason, a next step and "nothing was charged"?
- Do expired, unavailable and already-paid links each have their own screen?
- Does the page work at 360px, with long Portuguese labels, using only a thumb?
You can walk through all of this in our checkout demo or read how the hosted page fits your integration in the checkout docs. On the server side, pair it with idempotency keys and a webhook consumer that can take duplicates, which is the subject of Webhooks without surprises.
