PlanyDocs
Encore

Offers and purchases

What players can buy, what it credits, every state a purchase passes through, and every reason one is refused.

An offer is something a player can buy to fund the servers. A purchase is one player buying it.

The Encore key

Every Encore has an encore_key — ek-01j9… — and it is the only thing your game needs to ship.

It is not a credential. It is in every copy of the game, and treat it that way: what it unlocks is reading the servers' public funding and starting a purchase, and anybody who owns the game may do both. It names no account, no project and no studio. The player is proven separately, by Steam, on every purchase.

Encore → your Encore → Integration → Encore key

Offers

Encore → your Encore → Offers → New offer → name, price and credit → Create offer
FieldRule
display_nameRequired. What the player sees in the Steam overlay. At most 128 characters.
priceUS dollars, in whole cents, and positive. Steam shows each player the price in their own currency.
creditUS dollars, positive, and at most the price less Steam's share (30%; the refusal names the exact ceiling).
activeNew offers are active. Set false to take one off sale.
item_idOutput only. The item id sent to Steam, stable for the offer's life.

Why the credit is capped

Steam pays you the price less its share, and you owe Plany the credit. A credit above the net would leave you owing more than you received on every sale, so it is refused:

PriceMost it can credit, to the cent
$0.99$0.69
$4.99$3.49
$9.99$6.99
$19.99$13.99

Crediting less than the most is allowed, and is how a studio covers its own costs or support. The gap is yours.

Changing and removing an offer

  • UpdateOffer (plany encore offers update) takes display_name, description, price, credit and active. Price and credit are checked together.
  • A price change applies to purchases begun afterwards. An order already open with Steam settles at the price and credit it was opened at.
  • DeleteOffer (plany encore offers delete) removes an offer nobody has bought. One with purchases is deactivated instead, because a purchase has to keep naming what was bought. Its id cannot be reused afterwards.

A game lists only active offers, and lists them even before the Encore is verified — with purchasable: false — so you can build and test the store before anybody can buy.

A purchase, step by step

  1. The game asks Steam for a ticket

    ISteamUser::GetAuthTicketForWebApi("plany-encore"). The identity must be exactly plany-encore; a ticket minted for anything else is refused.

  2. The game calls BeginPurchase

    With the Encore key, the offer id, the ticket and a fresh request id. Plany has Steam confirm the player with your key — so a ticket for another game proves nothing here — records the purchase, reserves its credit against your outstanding limit, and only then opens the order with Steam.

  3. The Steam overlay appears

    Opened by Steam's servers, not by the game. The player approves or declines, and Steam delivers MicroTxnAuthorizationResponse_t to the game.

  4. The game calls CompletePurchase

    With what the overlay said. Plany asks Steam where the order stands, settles an approved one, and credits the project only once Steam reports it settled.

The price, the credit and the project are never taken from any request. A game cannot say what the player paid; it can only say which offer they chose.

A crashed game still credits

CompletePurchase is safe to call more than once, and safe not to call at all. Plany asks Steam about any order that has not moved for two minutes and settles it on its own, so a game that crashes between the player approving and calling CompletePurchase still funds the servers — usually within a couple of minutes.

A decline reported by the game is never believed on its own: Plany confirms it with Steam before cancelling. An order nobody ever answers is cancelled after an hour and its reservation released.

States

stateFinalMeaning
STATE_INITIATEDOpened with Steam; the player has not answered, or declined and Steam has not confirmed it yet.
STATE_AUTHORIZEDThe player approved. The order is settling with Steam, or the credit is being retried. The player has paid; thank them.
STATE_CREDITED✓Settled, and the project credited. ledger_entry names the entry in your ledger.
STATE_CANCELLED✓The player declined, or never answered within an hour. Nothing was charged.
STATE_FAILED✓Steam refused the order, had no record of it after an hour, or reversed it before it was credited. Nothing was credited.
STATE_REVERSED✓Credited, then refunded or charged back, and the credit taken back.

A sandbox purchase passes through the same states and credits nothing. It carries sandbox: true.

Making a retry safe: request_id

Never retry BeginPurchase without a request id. It opens a Steam order, and a timeout does not tell you whether it did. A blind retry can put a second overlay in front of the player, and a player who approves both is charged twice.

Send a fresh random id — a UUID — with every purchase attempt, and reuse it only when retrying that same attempt:

  • A second call with the same id, for the same Encore and the same player, returns the purchase the first call opened. No second order.
  • The same id from a different player is refused with ALREADY_EXISTS (HTTP 409). A game client chooses the id, and handing one player another's open order would disclose it.
  • A retry of an attempt whose order never opened gets the refusal again rather than the purchase, so a game does not wait for an overlay that is never coming.
  • At most 128 characters.

The player pressing the button again is a new attempt and a new id. The SDKs do all of this for you.

Refusals

Every refusal of a purchase happens before the player is charged. A named refusal carries its reason twice — as a google.rpc.ErrorInfo reason (domain encore.plany.cloud) and, for the 400s, as a google.rpc.PreconditionFailure violation type — so a client in any language can switch on it.

ReasonHTTPgRPCWhenWhat a game does
ENCORE_NOT_VERIFIED400FAILED_PRECONDITIONThe Encore is still being verified, is in review, was declined, or was revoked.Hide the buy buttons.
ENCORE_NOT_READY400FAILED_PRECONDITIONVerified, but not yet transferred into its project, so there is nothing to credit.Same. It clears on its own.
PUBLISHER_UNAVAILABLE400FAILED_PRECONDITIONThe studio's Steam key is not working — usually revoked. Only the studio can fix it by rotating it.Say the store is unavailable.
PUBLISHER_LIMIT_REACHED400FAILED_PRECONDITIONThe link's outstanding limit would be exceeded — or has not been set yet. Clears as the studio settles, or as other players' open purchases finish.Try again later.
OFFER_INACTIVE400FAILED_PRECONDITIONThe offer was taken off sale, or does not exist.Refresh the offers.
ORDER_REFUSED400FAILED_PRECONDITIONSteam would not open the order — a wallet or region it will not sell in, say — or a retried attempt was already cancelled.Say nothing was charged.
PLAYER_NOT_AUTHENTICATED401UNAUTHENTICATEDSteam did not vouch for the player: no ticket, an expired one, one for another app or identity, or Steam offline.Ask the player to be signed in to Steam.

And the unnamed ones:

HTTPgRPCWhen
400INVALID_ARGUMENTA required field is missing, or a request id is too long.
404NOT_FOUNDNo Encore has this key; or CompletePurchase named a purchase or order id that is not this Encore's. The two are one answer on purpose.
409ALREADY_EXISTSA request id reused by a different player.
429RESOURCE_EXHAUSTEDRate limited — per address, and on opening purchases per Encore key. Nothing was done. Back off and retry.
503UNAVAILABLESteam is not answering. From BeginPurchase, nothing was charged; from CompletePurchase, the purchase will be settled on its own.

None of the named refusals improves by retrying in the next few seconds.

Refunds and chargebacks

Steam refunds happen on Steam's side, sometimes days later. Plany reads each link's Steam order report every six hours:

  • A refunded or charged-back order that was credited has its credit taken back out of the project — in proportion when Steam itemises a partial refund, and in full otherwise — and becomes STATE_REVERSED. The project's ledger shows a refund, or a reversal for a chargeback.
  • An order reversed before it was credited becomes STATE_FAILED and credits nothing.
  • The project's balance can go negative. The credit may already have paid for hosting that genuinely ran. A negative balance keeps the servers asleep until it is topped up past zero.
  • What you owe Plany on the link falls by the amount taken back.

Orders your game sold some other way, through its own backend, appear in the same Steam report and are ignored.

Reading purchases

Encore → your Encore → Funding → Purchases

Newest first. Each carries the offer, Steam's order_id, the player's SteamID64 in player_id, the price and credit, the state and its message, and the times it was credited or reversed.

roles/account.owner, roles/account.admin, roles/account.billing and roles/account.viewer can all read purchases. Creating and changing offers is owner and administrator.