TANITARDevelopers
GuidesShop

Reversal

Undo a shop purchase, refund the player from the debit the order actually posted, and account for what was delivered.

A reversal undoes a purchase: the player gets their currency back, the awards the order granted are revoked, and the quantity counts again toward the player's per-player limit. It needs its own scope, shop:reverse, which neither catalog authoring nor fulfilment confers, and an Idempotency-Key.

Reverse an order

curl -X POST "$REWARD_API_URL/v1/shop/orders/ord_123/reversal" \
  -H "Authorization: Bearer $TANITAR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "reasonCode": "ITEM_OUT_OF_PRINT",
    "reason": "The supplier discontinued this item; refunding every open order."
  }'
FieldRequiredRules
reasonCodeYesYour own stable code: uppercase letters, digits and _, at most 64 characters. The player sees it.
reasonYes1 to 1000 characters for your team. The player does not see it.
acknowledgeCodeLossFor code ordersMust be true to reverse an order that delivered a code. See Delivered codes.

The body has no amount and no player. What is refunded is read from the ledger entries of the debit the order posted, never from the request and never from the order's advertised total, and it is credited to the player who placed the order. The refund is posted under a key derived from that debit, so a repeated reversal can never credit twice.

The response is the order in status reversed, with a reversal record:

FieldMeaning
initiatoroperator for a reversal through this route, player for a player's own cancellation
reasonCode, reasonWhat you sent (reason is only in your view)
refundWhat came back, per currency
lines[]Per line: listingKey, recovery, codesLost
reversedAtWhen
actorIdThe API key or administrator that reversed it; null for a player cancellation
refundOperationKeyThe ledger operation that credited the refund
codeLossAcknowledgedWhether a code loss was acknowledged

shop.order.reversed is published in the same transaction.

Which orders can be reversed

Order statusReversible
settled, awaiting-fulfillment, in-progress, fulfilledYes
rejectedNo. Nothing was charged
cancelled, reversedNo. The value already came back

Reversing an order that is not reversible is refused with 409 SHOP_ORDER_NOT_REVERSIBLE, before anything is written.

What happens to the goods

What each line's goods come to is fixed by its fulfilment kind; a reversal does not choose it:

Fulfilment kindrecoveryEffect
awardaward-revokedThe award granted by the order is revoked
manualnever-deliveredNothing in Tanitar to take back; handle any physical return yourself
codecode-loss-acceptedThe code cannot be taken back; codesLost counts them

Stock is not restored. A reversal does not put units back into counted stock or the code pool. If you want an item back on sale, top up its stock.

A free or fully discounted order has no debit. It can still be reversed; the refund reads as a leg of "0" in the listing's currency.

Delivered codes

A code that has been delivered cannot be un-delivered: the player may already have redeemed it. Reversing such an order is a decision to accept that loss, so Tanitar asks you to make it explicitly. Without "acknowledgeCodeLoss": true, the reversal is refused with 422 SHOP_CODE_LOSS_UNACKNOWLEDGED before anything is written. If you need to revoke the code itself, do it with the code's issuer.

Player cancellation

A player can cancel their own manual order with POST /player/v1/shop/orders/:orderId/cancel until your team claims it. It performs a full refund, restores limit headroom and publishes shop.order.reversed with initiator: "player", the reason code SHOP_CANCELLED_BY_PLAYER, and status cancelled instead of reversed. Once you claim the order, the player's cancellation is refused with 409 SHOP_ORDER_TRANSITION_CONFLICT; if you want to refund it after that, reverse it.

What your players see

The player's view of a reversed or cancelled order shows initiator, reasonCode, refund, the per-line recovery and reversedAt. It does not show your reason or who on your team reversed it, so choose reason codes your storefront can translate into a message.

Refusals

StatuserrorCodeCause
404SHOP_ORDER_NOT_FOUNDNo order with that id
409SHOP_ORDER_NOT_REVERSIBLEThe order is rejected, cancelled or already reversed
409SHOP_ORDER_TRANSITION_CONFLICTThe order changed state concurrently
422SHOP_CODE_LOSS_UNACKNOWLEDGEDThe order delivered a code and acknowledgeCodeLoss is not true
422SHOP_ORDER_NOT_REFUNDABLEThe order names no currency to refund in
422SHOP_REASON_REQUIREDreasonCode or reason is missing, or reason is only whitespace. Nothing is read or written
400NonereasonCode or reason is malformed: empty, too long, or reasonCode is not an uppercase code

On this page