Build

Rewards

Earn or redeem once the order is paid, and refund to claw back what it earned.

The rule that decides everything here: you call earn or redeem for an order, never both. A customer paying with a reward does not also earn on the full order: on a recurring program the redeem itself earns on what they actually paid, and says so in newReward. It is the same rule the counter follows, and it is why the two calls are separate.

What counts as the order amount

Trovy rewards the amount you send; it does not see your items. Decide once what counts, for example whether tips, tax, delivery fees or service charges earn rewards, and whether a promo code comes off first, and send that same basis to earn, redeem and refund. A refund claws back in proportion to the amount the order recorded, so a refund on a different basis claws back the wrong share, or is refused as more than is left. When you refund a payment that includes something outside the basis (a tip, say), send only the part of the refund that falls inside it.

Earn, after the payment succeeds

ORDER_ID=ORD-10233   # your own order id: the key and the body name the same order

curl -X POST https://api.sandbox.trovy.ca/v1/earn \
  -H "Authorization: Bearer $TROVY_SECRET_KEY" \
  -H "Idempotency-Key: $ORDER_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "cus_01HX8Z9K2M4N6P8Q",
    "orderId": "ORD-10233",
    "amountCents": 5000
  }'

amountCents is the order's amount on your basis, in the business's currency. Call it once the payment is settled: an earn on an order that never completes is a reward the business gave away.

What comes back:

{
  "replayed": false,
  "earnedCents": 300,
  "reward": { "id": "rwd_…", "valueCents": 300, "minimumOrderCents": 1000 },
  "transactionId": "txn_…",
  "sms": { "status": "sent" }
}
  • earnedCents is this order's contribution.
  • reward.valueCents is the reward's total. It can be more than earnedCents when your program holds a new reward back (usable: false, for program.redemptionDelayDays days, until a local midnight): orders placed in the meantime add to it. Once a reward is usable it stops growing, and the next order redeems it instead.
  • The cap. A reward never grows past program.capCents. An order that would take it further earns only up to the cap, possibly earnedCents: 0, and the response still shows the waiting reward.
  • sms is whether the customer was told. Trovy sends that text.

An order that earns less than 50¢ still records, with earnedCents: 0 and no reward or text. That is a success, not an error: the order belongs in the business's history either way.

Pass storeId (from GET /v1/stores) to put an order on one of your locations, for the business's reports. Without it the order goes to the default location. A reward earned at one location can be spent at any.

At checkout

Read the customer's rewards before you take payment, and let the first one decide:

curl -X GET https://api.sandbox.trovy.ca/v1/customers/cus_01HX8Z9K2M4N6P8Q/rewards \
  -H "Authorization: Bearer $TROVY_SECRET_KEY"

Rewards list usable ones first, then the soonest to expire, so rewards[0] is the one to look at. Decide from usable, not from the list being non-empty. Then:

  1. rewards[0] is usable and the order reaches its minimumOrderCents: offer it. If the customer takes it, redeem on this order (below).
  2. rewards[0] is usable but the order is below its minimum, or the customer declines it: record nothing for this order. An earn would be refused with 409 ACTIVE_REWARD_EXISTS (the waiting reward is in details.reward), because a customer cannot build a new reward while one is waiting. A later refund of this order answers ORDER_NOT_FOUND, which is expected.
  3. No reward, or only one with usable: false: earn on this order. It adds to the held-back reward.

On a first-order-only program (program.recurring is false in GET /v1/business), a customer earns one reward, ever: once they have one, an earn answers 409 FIRST_ORDER_ONLY, even after they have spent it. Treat that like branch 2 and record nothing. A first order that was refunded in full does not count, so it can be earned again.

A reward is used whole. On an order worth less than the reward, redeeming spends all of it and the customer loses the difference. Either offer a reward only on an order at least as large as its valueCents, or tell the customer before they accept.

Redeem, after the payment succeeds

ORDER_ID=ORD-10456   # a new order, so a new key: one key names one event

curl -X POST https://api.sandbox.trovy.ca/v1/redeem \
  -H "Authorization: Bearer $TROVY_SECRET_KEY" \
  -H "Idempotency-Key: $ORDER_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "cus_01HX8Z9K2M4N6P8Q",
    "rewardId": "rwd_01HX8Z9K2M4N6P8Q",
    "orderId": "ORD-10456",
    "amountCents": 12000
  }'

Order matters, and it is the same order as an earn. Take the reward's valueCents off the order yourself, capture the remainder, and then tell Trovy. amountCents is the order's full amount on your basis, before the reward.

Do not redeem ahead of the capture. A redeemed reward is never handed back, not even by refunding the order, so a redeem followed by a failed capture costs your customer their reward.

If the redeem is refused after you have captured, you have given one discount, and what happens next depends on the code:

  • MINIMUM_ORDER_NOT_MET, REWARD_NOT_YET_USABLE or REWARD_CHANGED: the customer still has the reward. Do not reach for a different one.
  • REWARD_EXPIRED or REWARD_ALREADY_REDEEMED: the reward is gone, spent elsewhere or expired in the last minute.

Either way the order is not recorded yet, and a refused call does not use its key. Try recording what the customer paid as an earn, under the same idempotency key. If that answers 409 ACTIVE_REWARD_EXISTS, the reward (or a newer one, on a recurring program) is still waiting: record nothing for this order, as at checkout. After MINIMUM_ORDER_NOT_MET that is always the answer, so skip the earn.

Redeeming may create a new reward in the same call (newReward), when the program is a recurring one and the amount still paid is enough to earn on. That is the counter rule again: the part of the order the customer actually paid for can earn.

Orders that change

  • Cancelled after payment (sold out, kitchen closed, no driver). A redeemed reward never comes back, not even with a full refund. If your orders can be cancelled after payment, record loyalty when an order can no longer be cancelled, at handoff or pickup, and keep the offered reward against the order in your own system until then so it cannot be spent twice. An order cancelled before you recorded anything needs no call.
  • Reduced after payment, an item removed or comped: a partial refund of that order, with its own refund id.
  • Increased after payment, an item added: a new order with its own id (for example ORD-10233-2), because an order is recorded once. It earns or redeems like any other order. The amount already recorded for an order cannot be raised.

Refund, to claw it back

REFUND_ID=ORD-10233-refund-1   # your refund's own id, never the order's key

curl -X POST https://api.sandbox.trovy.ca/v1/refunds \
  -H "Authorization: Bearer $TROVY_SECRET_KEY" \
  -H "Idempotency-Key: $REFUND_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "ORD-10233",
    "amountCents": 5000
  }'

Name the original order, not the earn call, and give each refund its own idempotency key, your refund's id rather than the order's: keyed by the order, a second partial refund of the same amount would replay the first and change nothing. Partial refunds are allowed and claw back proportionally.

What a refund does depends on what the order did:

The orderA refund
Earned a reward that is still unspentReduces it in proportion, and cancels it at zero
Earned a reward that has since been redeemed, or expiredIs recorded, and changes nothing. It is never taken out of a different reward
Redeemed a rewardNever brings that reward back. Only the new reward the order earned, on a recurring program, is reduced
Earned nothingIs recorded, and changes no balance

rewardAdjustment in the response says which of these happened. The most you can refund is what the customer actually paid: the order amount less any reward applied.

Refunding more than an order's remaining refundable amount is refused, and the error says how much is left. Refunding twice with the same idempotency key replays; with a different one it is a second, separate refund.

Currency

Every …Cents field is in the business's own currency, which GET /v1/business reports. One business has one currency and it cannot change once it has transacted, so read it at setup. Trovy does no conversion and will never return an amount in a unit other than the business's own.