Platform Commerce

Sell items on your platform through Invo, with two ways to pay: a user's existing Invo-network balance, or a direct card payment on Invo's hosted checkout. Funding is resolved on your server, so network value always moves through Invo — never around it.

Who this is for: platform tenants (non-game apps — content platforms, creator stores, marketplaces). Every endpoint below is available only to platform-tagged accounts; a standard game account receives 403. For in-game item spending on a game account, use the Item Purchase API instead.

Two funding legs

Balance

The user spends Invo-network currency they already hold. A ledger debit — no card, settles synchronously (the response is the completed sale). Amount is in your currency units. Invo fee: 3.5% flat.

funding_source: "balance"

Card

A direct card payment (USD) on Invo's hosted checkout — card, Apple Pay, Google Pay, 3-D Secure, and international billing address all handled by us. Settles asynchronously on a verified webhook. Invo fee: 3.5% + $0.30.

funding_source: "card"

Authentication

All Platform Commerce endpoints are called from your server with your secret key:

X-Game-Secret-Key: your_secret_key_here

Never call these from a client, and never ship the secret key in a browser or app bundle. Funding is resolved server-side; for the card leg the browser only ever receives a single-use checkout_url.

1. Create a purchase

POST

/api/platform-commerce/purchase

Request body

FieldTypeReq.Notes
client_request_idstringyesYour idempotency key. A repeat with the same id is de-duplicated.
funding_sourcestringyes"balance" or "card". Validated + enforced server-side.
player_emailstringyesThe buyer's email.
player_namestringyes≤ 255 chars.
item_idstringyesYour item identifier. ≤ 255 chars.
item_namestringyesShown to the buyer on the card checkout. ≤ 255 chars.
item_quantityintyes1–1000.
unit_pricedecimalyesRounded to 2 dp.
total_pricedecimalyesMust equal unit_price × quantity. Card: USD, $0.50–$999.99. Balance: your currency units.
item_description, item_categorystringnoitem_category ≤ 100 chars.
player_phonestringnoOptional.
success_url, cancel_urlstringnoCard only — where to send the buyer after checkout.
metadataobjectnoCard only — your correlation data, echoed back to you.

Note: the card leg does not take a billing address — the buyer enters it on Invo's hosted checkout and we capture it from the confirmed payment.

Balance response — settled (200)

{
  "status": "success",
  "funding_source": "balance",
  "order_id": 123,
  "transaction_id": "TXN_...",
  "new_balance": "45.00",
  "previous_balance": "50.00",
  "currency_name": "Gems",
  "financial_breakdown": {
    "total_paid": "5.00",
    "developer_revenue": "4.82",
    "platform_fee": "0.18"
  }
}

Card response — send the buyer to checkout (200)

{
  "status": "requires_payment",
  "funding_source": "card",
  "session_id": "<id>",
  "checkout_url": "https://invo.network/checkout?session=<token>",
  "expires_at": 1690000000,
  "amount_usd": "5.00"
}

Card leg: send the buyer to the hosted checkout

A card purchase is not paid when this returns — the order does not exist yet. Send the buyer to checkout_url; they pay on Invo's hosted page. Treat the sale as final only when you receive the platform_commerce.purchased webhook. The link expires at expires_at (15 minutes).

2. The card checkout flow

  1. Your server calls POST /purchase with funding_source: "card" and gets a checkout_url.
  2. Send the buyer to it — either redirect (window.location = checkout_url) or embed it in an iframe.
  3. The buyer enters card + billing address on Invo's hosted checkout (card / Apple Pay / Google Pay / 3-D Secure) and pays.
  4. Invo verifies the payment, records the sale + fee, captures the billing address, and fires platform_commerce.purchased.
  5. The buyer is returned to your success_url (or cancel_url). Fulfil the item on the webhook, not on the browser return.

Embedding (optional)

If you iframe the checkout, listen for the completion message from Invo's page:

window.addEventListener('message', (e) => {
  if (e.data && e.data.type === 'INVO_CHECKOUT_COMPLETE') {
    // e.data.data.status === 'completed'
    // Show a confirmation UI — but still fulfil on the webhook.
  }
});

3. Check status

GET

/api/platform-commerce/purchase-status/{order_id}

Poll an order (useful as a fallback to the webhook for card purchases). Returns:

{
  "order_id": 123,
  "status": "completed",         // completed | pending_payment | refunded
  "game_currency_amount": "5.00", // balance leg
  "usd_amount": "5.00",           // card leg
  "payment_method": "card",       // card | network_currency
  "created_at": "2026-07-20T..."
}

4. Refund

POST

/api/platform-commerce/refund

Refund a completed purchase. Body: { "order_id": 123 } or { "client_request_id": "..." }, plus optional reason.

{
  "status": "refunded",
  "funding_source": "card",             // or "balance"
  "order_id": 123,
  "transaction_id": "TXN_...",
  "refunded_amount_usd": "5.00",        // card;  balance -> "refunded_amount" + "new_balance"
  "stripe_refund_id": "...",            // card only
  "fee_retained": true
}

The customer is made whole — a balance purchase re-credits the wallet, a card purchase refunds the card.

Invo retains its fee on refunds (fee_retained: true).

Only a completed order is refundable; a second refund on the same order returns 409.

Error codes

HTTPCodeMeaning
400Validation (missing/invalid field, price mismatch, bad email).
400AMOUNT_BELOW_MINIMUMFee rounds to zero — amount too small.
400BELOW_STRIPE_MINIMUMCard total below $0.50.
400ABOVE_CARD_MAXIMUMCard total above $999.99.
401Missing or invalid secret key.
403NOT_A_PLATFORM_TENANTThis API is for platform tenants only.
409Duplicate client_request_id / order already refunded.
429Rate limited.
503service_unavailableTemporarily unavailable — retry shortly.

Webhooks

Subscribe (see Webhook Management). Card purchases must be fulfilled here — this is the source of truth, not the browser return.

platform_commerce.purchased

{
  "event": "platform_commerce.purchased",
  "data": {
    "transaction_id": "TXN_...",
    "order_id": 123,
    "funding_source": "card",            // or "balance"
    "player_email": "buyer@example.com",
    "identity_id": "...",
    "item_id": "sticker_pack_01",
    "item_name": "Sticker Pack",
    "item_quantity": 1,
    "total_price_usd": "5.00",           // card;  balance -> "total_price" + "currency_name" + "new_balance"
    "client_request_id": "...",          // card
    "fee_breakdown": {
      "total_paid": "5.00", "platform_fee": "0.30", "developer_revenue": "4.70"
    }
  }
}

platform_commerce.refunded

{
  "event": "platform_commerce.refunded",
  "data": {
    "order_id": 123,
    "funding_source": "card",
    "player_email": "buyer@example.com",
    "identity_id": "...",
    "refunded_amount": "5.00",
    "amount_unit": "usd",                // "usd" (card) or "currency" (balance)
    "fee_retained": true
  }
}

Idempotency. Always send a stable client_request_id.

Balance: a repeat returns 409 (already processed).

Card: a repeat returns the same checkout session (idempotent_replay: true) so a retry can't create a second charge.

Fees. Balance leg: 3.5% flat. Card leg: 3.5% + $0.30.

Your revenue on each sale is the total minus the Invo fee, returned in financial_breakdown (balance response) and on the purchase webhook (fee_breakdown).