Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.papp.sa/llms.txt

Use this file to discover all available pages before exploring further.

This page explains how Points handles order reversals after creation. There are two distinct operations:
OperationEndpointTypical use
CancelPOST /v1/orders/{uuid}/cancelThe order should no longer continue
RefundPOST /v1/orders/{uuid}/refundMoney/points need to be returned after settlement

Cancel an order

Use cancellation when the order should be stopped rather than financially reversed later. Example:
curl -X POST https://business.papp.sa/api/v1/orders/550e8400-e29b-41d4-a716-446655440000/cancel \
  -H "x-api-key: $POINTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Customer requested cancellation" }'

Current behavior

  • validates merchant ownership of the order
  • rejects already-cancelled or already-refunded orders
  • defaults the reason to "Order cancelled via API" if omitted
  • emits webhook event cancelled

Refund an order

Use refund after an order is already settled and value must be returned. Example full refund:
curl -X POST https://business.papp.sa/api/v1/orders/550e8400-e29b-41d4-a716-446655440000/refund \
  -H "x-api-key: $POINTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Example partial refund:
curl -X POST https://business.papp.sa/api/v1/orders/550e8400-e29b-41d4-a716-446655440000/refund \
  -H "x-api-key: $POINTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 25.00,
    "reason": "One line item returned"
  }'

Current behavior

  • supports both full and partial refunds
  • emits webhook event refunded
  • updates order_status to fully_refunded or partially_refunded

How Points value is reversed

Replacing (redeem) orders

For a redeem checkout, refunding reverses the redeemed value and updates the order status accordingly.

Earning orders

For an earning order, refunding reverses previously awarded points. In partial refunds, the backend calculates the proportional points to reverse and includes remaining amounts in the refund payload used internally.

Auto-refund edge case

There is also an internal system event called auto_refunded. This can occur when the backend automatically reverses a replacing order because an expected transaction record was missing during reconciliation. Integrators should treat it as a refund-class terminal state and handle it in webhook consumers.

Merchant-dashboard refunds (storefront orders)

Merchants who sell their own physical / digital products through the Points storefront (not pure API integrators) can refund a ProductOrder from the dashboard. The button is on the Order details page (Product Orders → show) and is more than a gateway refund — it runs a full-stack reversal in a fixed order:
StepWhat happensReversible?
1Gateway refund — Tabby, Tamara, or Moyasar (VISA / MADA / Apple Pay / MasterCard) is refunded for the captured amountNo — once accepted by the gateway, money is back with the buyer
2Linked EARNING order is fully refunded — points awarded to the buyer are withdrawn back, merchant’s wallet is credited backYes (admin can re-issue)
3Linked REPLACING order is fully refunded — points the buyer redeemed are returned to their wallet, merchant’s settle wallet entry is reversedYes
4metadata.refund_status = 'refunded' is stamped on the ProductOrder, the badge flips to “Refunded”, and the trigger button disappearsYes
If step 1 fails (gateway rejects, captured amount mismatch, missing reference), the whole flow aborts — nothing else is touched. If steps 2-3 fail after step 1 succeeded, the metadata is still stamped (the money is already back with the buyer) and the failures land in the server log for manual reconciliation.

3-day refund window

The dashboard refund button disappears 3 days after the order was created. After that, the weekly settlement run (MakeProductSettlements, Thursday in Riyadh time) has already swept the order into the merchant’s payout, so an automated reversal would race the settlement and leave the books out of sync.
Order ageRefund buttonNotes
< 72 hoursVisibleStandard flow above; runs end-to-end via RefundProductOrderService
≥ 72 hoursHiddenSettlement has captured the order; refund needs to be coordinated with finance manually
Direct calls to the service (e.g. from admin tooling or a stale browser tab) are guarded by the same window — past 72 hours they return:
{ "ok": false, "message": "The amount cannot be refunded after 3 days from the transaction date." }

Settlement skips refunded orders

Once metadata.refund_status = 'refunded' is stamped, the weekly product-settlement job excludes the order from the merchant’s next payout — so the merchant is never paid for an order whose money already went back to the buyer.
This whole section is for the merchant dashboard flow. The public POST /v1/orders/{uuid}/refund endpoint (described above) is the API equivalent for loyalty Order reversals and is not subject to the 3-day window.
If the order should simply stop and has not entered your final fulfilment path, cancel is usually cleaner than refund.
If value was already settled, use the refund endpoint so Points can properly reverse the financial and loyalty effects.
Even when not required, always send a human-readable reason so support teams can reconcile merchant actions against webhook history.

Webhooks you should expect

ActionWebhook
Cancelcancelled
Refundrefunded
Internal automatic reversalauto_refunded

Next

Order Lifecycle

See where cancellation and refund fit in the state machine.

Webhook Events

Payload examples for cancelled, refunded, and related events.