Your first order
This walkthrough places one order from start to finish. Paths are shown
relative to the base URL — prepend
https://api.pekangame.space/api/reseller. Every request needs the
Authorization: Bearer header from Authentication; it is
omitted below for brevity.
1. Check your balance
Section titled “1. Check your balance”GET /v1/balance{ "balance_sen": 125000 }All money is in sen (RM 1.00 = 100). 125000 is RM 1,250.00.
2. Read the catalogue
Section titled “2. Read the catalogue”GET /v1/catalog{ "games": [ { "code": "MLMY", "name": "Mobile Legends (Malaysia)", "checkout_input": { "field": "server_id", "options": null, "player_id_format": "numeric" }, "packages": [ { "code": "MLMY-14", "name": "14 Diamonds", "price_sen": 1200 }, { "code": "MLMY-86", "name": "86 Diamonds", "price_sen": 6300 } ] } ]}price_sen is your price for that package, in sen. Order against
packages[].code — see Product codes.
checkout_input is each game’s input contract. Check it programmatically
rather than hardcoding per game:
| Key | Value | What to send |
|---|---|---|
player_id_format |
numeric |
player_id is digits only |
text |
player_id is letters, digits and # . _ - (e.g. a Riot ID, JettMain#1234) |
|
field |
null |
no server_id — sending one is rejected |
server_id |
server_id is required, digits only |
|
zone_id |
server_id is required and must be one of options |
The request field is always named server_id, even for a zone. options is
the exact picklist when present; a zone game with options: null accepts any
value. No value may contain spaces, and each is at most 64 characters. A value
that breaks the contract gets a 422 VALIDATION_FAILED naming the field —
nothing is charged.
3. Place the order
Section titled “3. Place the order”POST /v1/ordersContent-Type: application/json
{ "product_code": "MLMY-86", "player_id": "123456789", "server_id": "2201", "idempotency_key": "8f2b8c4e-1d3a-4a9c-9b1e-2f6a7c0d5e11", "max_price_sen": 6300}server_idonly when the game’scheckout_input.fieldis notnull(Mobile Legends needs one; many games don’t).idempotency_keyis a fresh UUID you generate per order. See Idempotency & retries.max_price_sen(optional, recommended) is the most you agree to pay, in sen — usually theprice_senyou last read from the catalogue.
Prices change — send max_price_sen
Section titled “Prices change — send max_price_sen”Prices can change whenever PekanGame updates its catalogue, so a price_sen
you read earlier may no longer be current. You are always charged the
current price at the moment of the order:
- current price ≤
max_price_sen→ the order goes through at the current price (lower, if it has dropped); - current price >
max_price_sen→422 PRICE_CHANGED, nothing is charged, anddetails.current_price_sentells you the new price; - no
max_price_sen→ the order goes through at whatever the current price is.
Don’t retry a PRICE_CHANGED automatically at current_price_sen — that is
the same as not sending a ceiling. Check the new price against your own margin
first.
On success you get HTTP 201 and the order:
{ "order_number": "PG-7QK2M9X4RJ", "product_code": "MLMY-86", "player_id": "123456789", "server_id": "2201", "price_sen": 6300, "payment_status": "paid", "delivery_status": "not_started", "wallet_refunded": false, "wallet_refund": null, "created_at": "2026-09-10T09:14:52+00:00", "delivered_at": null}Your Purchase Balance is charged price_sen at this moment. payment_status is paid
straight away (it is a Purchase Balance debit). delivery_status then progresses on its
own — see the table below.
Order status values
Section titled “Order status values”payment_status is always paid for a Purchase Balance order.
delivery_status |
Meaning |
|---|---|
not_started |
Accepted, not yet submitted for fulfilment. |
processing |
Being fulfilled. |
pending |
Submitted; awaiting the final result. |
delivered |
Done. delivered_at is set. |
failed |
Delivery failed. PekanGame may retry; a Purchase Balance refund is a separate later action. |
needs_review |
The outcome is ambiguous and being confirmed manually. Rare. |
partially_delivered |
Part of the order was delivered and the rest failed. The undelivered part is refunded to your Purchase Balance as a separate later action. Rare. |
delivered is final. failed and partially_delivered are usually final
too, but PekanGame may re-attempt the part that failed, so either can still
reach delivered later (you get an order.delivered webhook if so). Poll or
use the webhook until the order is delivered, failed or
partially_delivered.
For a failed or partially delivered order, check wallet_refunded separately
to learn whether the Purchase Balance has actually been credited, and
wallet_refund.amount_sen for how much — for a partial delivery it is only
the undelivered share. Do not infer a refund from the status alone.
4. Get the result
Section titled “4. Get the result”Delivery is asynchronous. Two ways to learn the outcome:
- Poll
GET /v1/orders/{order_number}untildelivery_statusisdelivered,failedorpartially_delivered. - Webhook — register an endpoint once and receive a signed
order.delivered/order.failed/order.partially_deliveredcallback. See Delivery notifications.
GET /v1/orders/PG-7QK2M9X4RJ{ "order_number": "PG-7QK2M9X4RJ", "product_code": "MLMY-86", "player_id": "123456789", "server_id": "2201", "price_sen": 6300, "payment_status": "paid", "delivery_status": "delivered", "wallet_refunded": false, "wallet_refund": null, "created_at": "2026-09-10T09:14:52+00:00", "delivered_at": "2026-09-10T09:15:07+00:00"}A failed order can later be refunded to your Purchase Balance after PekanGame decides
not to retry it. In that case the order still reports payment_status: paid
and delivery_status: failed, but wallet_refunded becomes true and
wallet_refund supplies the actual credited amount and time. See
Purchase Balance.