Building a checkout that collects the right account id

7 min read

We check that the field is present, never that it is right, and a credit that lands in the wrong account cannot be pulled back. So the correctness of that field is your checkout.

For most of this catalogue the order carries the account being credited, and the value's correctness is entirely your checkout's job: we check that the field is present, never that it is right, and credit that reaches the wrong account cannot be pulled back by you or by us. There is no cancellation window, because fulfilment begins as soon as the order is accepted. So the field deserves more care than an ordinary form input, and this is how to build one that earns it.

What is validated, and what is not

The server's guarantee is small and it is worth knowing exactly:

  • player_id is a string of at most 190 characters.
  • It is trimmed, and a value that is empty after trimming counts as absent — so a field of spaces is not an answer.
  • A denomination that needs one and did not get one is refused with 422 player_id_required, before anything is charged and without creating an order.

That is all. There is no format rule, no digits-only check, no length rule per product and no per-service mask. An id of the right shape belonging to the wrong person is indistinguishable from a correct one, here and at the other end, and no system can tell them apart.

The 422 is deliberately not a 409: it says the request is one field short of being fillable, which is the caller's to fix, where a conflict would say the catalogue is in a state you cannot affect.

The catalogue will not tell you which denominations need one

GET /v1/catalog returns the SKU, the pricing kind, the quantity bounds, your price and the list price. It does not carry a flag saying an account id is required.

So record the requirement yourself, per denomination, when you add it to your shop — and let 422 player_id_required be the backstop rather than the mechanism. Discovering it from the refusal means asking the customer for something after they thought they had finished, which is the worst moment to ask.

The distinction between the two kinds of denomination, and what each returns, is in the two kinds of order.

Build the field so the mistake is hard

  1. One field, labelled as what it is — the account the credit goes to — in your own words, consistently, across every product. Not a different label per service: the buyer is always answering the same question.
  2. Accept a paste and leave it alone. Strip leading and trailing whitespace and invisible formatting characters, and stop there. Do not uppercase it, do not insert separators, do not "correct" it.
  3. Ask for it twice when the value cannot be checked against anything. A confirm field costs one input and catches the transposed digit that costs a whole order.
  4. Echo it back before the pay button, on its own line, large, digit-grouped if that helps it be read aloud — with a checkbox the buyer ticks to confirm it is theirs. The last moment at which the mistake is free is before the request, and this is the only thing standing in it.
  5. Never accept a display name as the id. A display name changes whenever its owner feels like it and is not unique; if your customers keep sending one, your field is asking the wrong question in its label.
  6. Collect it before you take payment, not after. A payment taken for an order you then cannot place is a refund you have to make by hand.
  7. Store the exact string you sent on your own order row, character for character.

Normalising: do less than you feel like doing

The temptation is to "clean" the input. Resist most of it.

  • Do trim, and strip zero-width and directional marks — those arrive invisibly from a copy out of a chat app and turn a correct id into an unrecognised one.
  • Do not remove dashes, letters or punctuation. Some services use them, and a normaliser that deletes a meaningful character has produced a wrong id from a right one — which is the same failure as a typo, except that it is yours and it happens to every customer rather than to one.
  • Do not truncate. If a value is longer than you expect, that is a question to ask, not a substring to send.

Server-side, before you send

Validate for *presence and shape of your own choosing*, then send the string unmodified. Two habits worth having:

  • Reject an empty-after-trim value in your own code, with your own message, rather than sending it and reading the 422 back. Your message can be in your customer's language and can point at the field.
  • Keep the requirement flag and the collected value on the same record, so an order that failed to collect one is visible in your own data rather than only in a log line.

After the order is placed

The order keeps the value that was sent. It is shown on the order's page in the dashboard as the account that was credited, beside the denomination, the quantity, the price and the delivery time — and read back through GET /v1/orders/{number}.

That row is the entire evidence base if a customer says they gave you a different number. Store the same string on your side and the two either agree, in which case the credit went where it was asked to go, or they do not, in which case you know immediately which side made the mistake.

Why there is no cancel

Fulfilment is automatic and starts as soon as the order is accepted, so there is no window in which an order can be withdrawn — and what has reached an account cannot be taken back out of it. This is the one irreversible thing in the product, and it is irreversible on both sides: we cannot recall credit any more than you can.

A failure is a different event and is handled for you: an order that could not be filled returns its whole value to your wallet automatically and fires order.refunded. An order that succeeded against the id you sent is a successful order, whoever owns that id.

Questions

Can I validate an id against your API before ordering?

There is no lookup endpoint, and the catalogue carries no format to validate against. The check that exists is the one you build into your checkout, which is why this guide is mostly about that.

What if a customer gives me an id for the wrong service?

It will look perfectly valid, and the order may well succeed against somebody else's account in the service you sent it to. Ask for the service by name in your own form and label the field with it.

Is player_id ever needed for a code product?

No. A code product asks the buyer for nothing, and a value sent for one answers a question that denomination never posed. Send it only where you recorded that it is needed.

A request timed out after I sent an id. Did the top-up happen?

You have no answer, which is not the same as a refusal. Send the identical request with the identical Idempotency-Key — if the first one placed an order you get that order back rather than a second top-up. See retrying an order without buying it twice.

Last updated

Want to put this into practice?

Agreed wholesale prices, instant automatic delivery, and an API for shops.

More guides