Selling keys and game cards
Steam keys, PlayStation or Xbox cards, software licences, prepaid cards.
Steam keys, PlayStation or Xbox cards, software licences, prepaid cards. Anything delivered as a different code for each buyer, which a distributor sends you as a list.
It is done on the product page, on each variant: Keys and codes.
The pool: one per variant
Each variant has its own pool of codes. Every sale takes one out of it, and the same one is never handed to two people.
It goes per variant because that is how this trade works: the €20 card and the €50 card are the same product with different codes, and so are a key for the European region and one for the global one.
As soon as you add the first code, the variant starts selling this way:
- It is not shipped. It asks for no shipping rate and no address.
- It does not sell without stock. Selling out of stock is switched off: a code you do not have cannot be promised.
Adding codes
Paste the list your distributor sent and press Add to the pool. They are accepted the way they usually arrive:
- one per line,
- separated by commas, semicolons or tabs,
- in quotes,
- or the whole CSV, header included: a line that only says
key,code,serial,license,clave… is skipped.
A code has to be between 4 and 200 characters. Up to 20,000 fit at once; if you have more, split them into batches.
Each time you add is a batch, with a name (if you give it none, the date and time) and, if you like, the supplier. It matters for what comes later: when a distributor cancels a batch, you need to know which one it was.
Duplicates are caught
If a code is already in the shop, it does not go in again. It makes no difference whether it is in this variant or another, or whether you type it in upper or lower case.
When it finishes it tells you how many went in, how many you already had, and the last four characters of the duplicates, so you can take it up with the supplier.
This happens more than you would think: the same file uploaded twice, or a supplier who sends a new batch that includes part of the last one. Without this, two customers would get the same key, and the second would find it already redeemed.
How stock is counted
The variant's stock is the codes left unsold. You do not touch it by hand: it is recalculated on its own when codes are added, set aside for an order, when a batch is withdrawn and when codes go back into the pool.
The storefront shows exactly that. So a unit with no code behind it is never sold.
The panel shows you the pool by status: unsold, set aside, delivered and withdrawn. And each batch, with how many it has left.
Get warned before you run out
In Warn me when this many are left you enter a number. When a sale is paid
and leaves the pool at that number or below, you get a Telegram alert: "3 codes
left for…" (in the language set with TELEGRAM_IDIOMA, English by default).
People who sell keys usually find out they have run out when a customer can no longer buy. For this you need Telegram alerts set up.
On purchase: set aside, and delivered once paid
The code is set aside when the order is placed, not when it is paid. If a few minutes pass between the two — a card payment that asks the bank for confirmation, say — another customer could take the last one and the first would have paid for nothing.
Two buyers at the same time never get the same one: the database decides when setting it aside, not an "the first free one" query that two people can run at once. If they ran out while paying, the order does not go through and the customer is told how many were left.
It is delivered once paid. A bank-transfer order arrives before the money does, and a delivered code cannot be taken back: once the customer redeems it, the value is gone. When the payment is marked as captured, the code becomes delivered and the email with the notice goes out (its subject, for now, in Spanish: «Tus códigos del pedido»).
If you cancel an order that was never paid, its codes go back into the pool at that moment. Codes already delivered are not touched: that is a matter for the refund.
The buyer sees it covered, and uncovers it
On their order page the code appears covered: only the last four
characters, ····7K2Q. Next to it, whatever you set on the variant: where it
is redeemed, the region, the languages and how to redeem it ("Open Steam →
Games → Activate a product…").
They uncover it themselves, with a button. And when is recorded: the first time, the date is stored, and every time they look at it a record is kept with the IP.
Why this way rather than the code in plain sight:
- It is the proof of delivery. The day the bank asks you about a chargeback, "the customer uncovered the code on 3 March at 18:04 from this IP" is what you have to answer with.
- A covered code can come back. If you refund an order in full and the code had not been uncovered, it goes back into the pool and can be sold again. One already uncovered cannot: it is withdrawn, because the buyer may have redeemed it, and the reason is recorded.
The code does not travel in the email. The email only says it is ready and sends them to the order page.
Withdrawal, in the EU. A code counts as digital content, just like a download: checkout asks for the same consent checkbox before charging, and a code already uncovered is left out of the withdrawal, like a download already started. Without that checkbox the exclusion would not be valid, which is why it is asked for.
Withdrawing a batch
If a distributor tells you a batch is no good — it leaked, it was stolen, they are cancelling it — withdraw the whole batch, with the reason.
Every code in that batch becomes withdrawn, including those already delivered, and stock is recalculated. The panel tells you which orders are affected, so you can send them a new one.
A buyer who tries to uncover it is told the code has been withdrawn and to get in touch for a replacement (for now that message is only in Spanish). The reason you wrote is kept to show whoever complains.
Encryption at rest
Codes are stored encrypted (AES-256-GCM). A copy of the database, a dump left on a test server or an ill-timed query show not a single code: only the ciphertext and the last four characters.
The encryption key comes from:
PCC_CLAVE_CIFRADO= # if it is not set, JWT_SECRET is usedIt is also used to compute the fingerprint that catches duplicates, so a dump of the database is no good for checking whether a particular code is in there either.
Three things to know about that key:
- If neither is set, codes are encrypted with a fixed key that is in the
source code. In other words, as if they were not encrypted. Under Docker
this does not happen, because the container generates
JWT_SECRETthe first time; on a manual install, set one before adding the first code. - Do not change it with codes inside. The ones already in the pool can no
longer be read, and duplicate detection stops recognising them. Setting
PCC_CLAVE_CIFRADOlater has the same effect: until then they were encrypted withJWT_SECRET. - Keep it somewhere other than the server. A database backup without its key cannot bring the codes back.
What you need
- Email set up (
SMTP_*). Without it, the code is still delivered on payment, but the notice waits in the queue until there is a way to send it. STOREFRONT_URL, so the email carries the link to the order page.- A theme that shows codes on the order page. It asks for them at
GET /tienda/pedidos/:id/codigosand uncovers them withPOST /tienda/pedidos/:id/codigos/:codigo/revelar.