Skip to main content

Payments

note

We will soon be deprecating credits for product purchases. Prices will be specified by price and currency properties.

The Jest platform allows developers to sell in-game products using the Payment SDK methods. We enable these transactions primarily through popular digital wallets, such as Apple Pay and Google Wallet. Jest handles the entire checkout process with the player, while your game is responsible for granting the purchased items and confirming the purchase.

Payments on Jest
Payments on Jest via digital wallets

Pricing

Players purchase in-game products in USD on the Jest platform.

As a developer, you define the products and USD prices available in your game. When your game calls begin_purchase, Jest handles checkout end-to-end with the player.

note

When using a sandbox user, players see real product prices in the game UI, but the platform checkout modal makes clear that no charge will be made. The resulting JestPurchase records 0 in both price and credits (deprecated).

Purchase lifecycle

A purchase represents a single successful checkout of a product by a specific player.

  1. Display available products to the player (via get_products()).
  2. Start checkout by calling begin_purchase(sku).
  3. If checkout succeeds, the SDK returns an incomplete purchase containing a purchase_token.
  4. Grant the purchased item in your game, then confirm the purchase by calling complete_purchase(purchase_token).
  5. Until completed, the purchase remains incomplete and will continue to be returned by get_incomplete_purchases() (for example, after a crash).

Checkout cancellation and error handling are managed by your game; see Start a purchase (begin_purchase) for details.

Happy path (checkout succeeds)

Recovery (startup reconciliation)

If checkout succeeds but the game crashes, loses connectivity, or is closed before complete_purchase is called, the purchase remains incomplete. The platform will keep returning it via get_incomplete_purchases() until you confirm it.

If has_more is true, continue fetching until all incomplete purchases are processed.

How to use the SDK

Payload shapes

Where the SDK references JestPurchase, it contains:

PropertyTypeDescription
purchase_tokenStringRequired to call complete_purchase.
product_skuStringThe product purchased.
creditsfloat(Deprecated, use price and currency instead) Total USD value
created_atintUnix timestamp (ms since epoch) when the purchase was created.
completed_atintUnix timestamp (ms since epoch) when confirmed; 0 until confirmed.
estimated_revenuefloatThe (estimated) USD share of revenue from this purchase that the publisher will receive.
pricefloatThe purchase price in the currency specified in currency
currencyStringThe currency code (ISO 4217) the price is in

Set up products

Set up and price products using the Jest Developer Console.

For more information, see Manage products.

List products (get_products)

To retrieve the products available for purchase in your game, call get_products.

var payment = JestSDK.payment
var products = await payment.get_products()

for product in products:
print("Product: %s (%s) - %s %s" % [product.name, product.sku, product.price, product.currency])

Products include the following information:

PropertyNote
skuAn identifier for the product, configured when set up in the Developer Console.
nameThe display name of the product that may be shown to the player during checkout.
descriptionShort description of the product that may be shown to the player during checkout.
priceProduct price in the currency specified in currency
currencyThe currency (ISO 4217 code) the price is in

Displaying prices

The way product prices are displayed should be based on the price and the currency. currency is an ISO 4217 which can be used to select the correct currency symbol or formatting.

Start a purchase (begin_purchase)

Call begin_purchase with the sku of a product returned by get_products().

The method returns a JestPurchaseResult with one of the following outcomes:

Success

  • status == JestPurchaseResult.Status.SUCCESS with purchase and purchase_signed populated. Checkout completed successfully. The returned purchase is incomplete and must be granted and confirmed by your game.

Cancellation

  • status == JestPurchaseResult.Status.CANCELED The player canceled the checkout flow.

Error

  • status == JestPurchaseResult.Status.ERROR with error containing one of:
    • internal_error - A transient error occurred. Your game may retry the purchase.
    • invalid_product - The requested sku is not available for purchase. Do not retry with the same sku.
var payment = JestSDK.payment
var result = await payment.begin_purchase("gems_100")

if result.status == JestPurchaseResult.Status.CANCELED:
# Handle cancellation with UI feedback
return

if result.status == JestPurchaseResult.Status.ERROR:
print("Purchase failed: %s" % result.error)
# Handle the error with different product or a retry
return

# status == SUCCESS
var purchase = result.purchase
var purchase_signed = result.purchase_signed
print("Purchase successful: %s" % purchase.product_sku)

Grant and confirm a purchase (complete_purchase)

When begin_purchase returns SUCCESS, your game must:

  1. Grant the purchased product to the player.
  2. Confirm the purchase by calling complete_purchase(purchase_token).

Until complete_purchase is called, the purchase remains incomplete and will continue to be returned by get_incomplete_purchases().

warning

Only call complete_purchase after you have durably granted the purchase (for example, your backend has verified the signed data and recorded the grant). If you confirm first and then crash before granting, get_incomplete_purchases() cannot recover the purchase because it's already confirmed.

# Recommended: send purchase_signed to your backend to verify and grant
await verify_and_grant_purchase_server_side(purchase_signed)

# Only confirm after the grant succeeded
var complete_result = await JestSDK.payment.complete_purchase(purchase.purchase_token)

if not complete_result.ok:
if complete_result.error == "internal_error":
# Retry later. Leaving the purchase incomplete is safe.
pass
else:
# invalid_token: don't retry with the same token.
pass

complete_purchase returns a JestResult:

  • Success (ok == true).
  • Error (ok == false) with error being:
    • internal_error: a transient error occurred; retry.
    • invalid_token: the token is not valid (already confirmed, wrong player, etc.); don't retry with the same token.
tip

If you have a backend, treat purchase.purchase_token as an idempotency key and store it so you never grant the same purchase twice, including during retries or recovery flows.

Recover incomplete purchases (get_incomplete_purchases)

Your game must check for incomplete purchases every time it starts. This is what makes purchases resilient to crashes, power loss, and network failures between a successful checkout and complete_purchase.

var payment = JestSDK.payment
var has_more = true

while has_more:
var result = await payment.get_incomplete_purchases()

if not result.ok:
break

# Recommended: send purchases_signed to your backend to verify and grant
await verify_and_grant_purchases_server_side(result.purchases_signed)

for purchase in result.purchases:
var complete_result = await payment.complete_purchase(purchase.purchase_token)

if not complete_result.ok:
if complete_result.error == "internal_error":
# Retry later. Leaving it incomplete is safe.
return
# invalid_token: don't retry with the same token.

has_more = result.has_more

The response is capped (currently 50 purchases per call). If has_more is true, confirm the returned purchases and call again until it is false.

Signed purchase data (JWT)

The begin_purchase and get_incomplete_purchases methods return purchase data in two forms:

  1. As objects (purchase / purchases) for convenience.
  2. As signed tokens (purchase_signed / purchases_signed) in the form of a signed JSON Web Token (JWT).

The data inside the signed token is equivalent to the plain object. For critical actions such as granting items or crediting accounts, you must only trust the signed token after verifying its signature.

For server-side verification examples and details, see the HTML5 SDK Payments documentation.

warning

Failure to verify the signed token can leave your game vulnerable to exploitation.

Revenue estimate

Jest's economics model splits revenue between the publisher, the platform and publishers who bring users in to the platform. JestPurchase includes an estimate, in USD, of the share you (as the publisher) are entitled to after the purchase is completed.

At the time of purchase, this is an estimate only, as the final revenue may depend on player subscription or other factors. See our economics model for more information.

Final revenue will be reported via the developer console (details coming soon).