Skip to main content
In a rewards program you pay Rye for every order out of your pre-funded drawdown balance, which makes you the merchant of record. How you then collect value back from the user — points, a card charge, or a blend of the two — is entirely up to you. Rye is agnostic to it. That decoupling is the key idea on this page:
LedgerWho controls itWhat moves
User chargeYouPoints and/or fiat you collect from the user, in whatever amount you choose.
Drawdown deductionRyeThe order’s purchase price minus your commission, deducted from your balance when the order places.
These two ledgers are independent. The sections below assume every order is placed with the { "type": "drawdown" } payment method.

The hold → submit → settle pattern

All three charge models share the same shape. You hold value on the user’s side, submit the order to Rye, and then settle the hold based on how the checkout intent resolves:
1

Place a hold on the user's funds

Reserve points or authorize the card before you submit the order, so you know the funds are good. Don’t capture them yet.
2

Submit the checkout intent with drawdown

Create (or confirm) the checkout intent with paymentMethod: { type: "drawdown" }. Rye deducts purchase price − commission from your balance when the order places.
3

Settle on the intent's terminal state

Watch the checkout intent reach a terminal state — via the checkout_intent.completed / checkout_intent.order_failed webhooks, or by polling the intent lifecycle.
  • completed → finalize the hold (deduct the points / capture the card authorization).
  • failed → roll the hold back (refund the points / release the authorization). Rye automatically credits the drawdown deduction back to your balance on failure, so neither ledger is left charged.
Never capture the user’s funds before the intent reaches completed. If you capture early and the order fails, you’ve charged the user for an order that never placed.

1. Pay by points

The user redeems points for the order.
  1. Hold: place a hold on the user’s points balance for the redemption amount (decrement an available balance, move the points into a pending bucket — however your ledger models it).
  2. Submit: create the checkout intent with drawdown.
  3. Settle:
    • completed → finalize the deduction (the held points are now spent).
    • failed → roll the deduction back (return the held points to the user’s available balance).
// 1. Hold points
const hold = await points.hold(userId, pointsForOrder);

// 2. Submit with drawdown
const intent = await client.checkoutIntents.purchase({
  productUrl,
  quantity: 1,
  buyer,
  paymentMethod: { type: "drawdown" },
});

// 3. Settle when the intent reaches a terminal state (webhook or poll)
function onIntentSettled(intent) {
  if (intent.state === "completed") {
    points.capture(hold.id); // points are now spent
  } else if (intent.state === "failed") {
    points.release(hold.id); // return points to the user
  }
}

2. Pay by fiat

The user pays with a card. Use whatever processor you prefer — the mechanics are the same. With Stripe, place a hold on a payment method by creating a manual-capture PaymentIntent.
  1. Hold: authorize the card for the charge amount (don’t capture).
  2. Submit: create the checkout intent with drawdown.
  3. Settle:
    • completed → capture the authorization.
    • failed → cancel/release the authorization.
// 1. Authorize the card (Stripe manual capture = a hold)
const paymentIntent = await stripe.paymentIntents.create({
  amount: chargeAmountSubunits,
  currency: "usd",
  customer: stripeCustomerId,
  payment_method: paymentMethodId,
  capture_method: "manual", // hold, don't capture
  confirm: true,
});

// 2. Submit with drawdown
const intent = await client.checkoutIntents.purchase({
  productUrl,
  quantity: 1,
  buyer,
  paymentMethod: { type: "drawdown" },
});

// 3. Settle when the intent reaches a terminal state (webhook or poll)
function onIntentSettled(intent) {
  if (intent.state === "completed") {
    stripe.paymentIntents.capture(paymentIntent.id);
  } else if (intent.state === "failed") {
    stripe.paymentIntents.cancel(paymentIntent.id);
  }
}

3. Pay with a mixture

Split the charge across points and fiat — e.g. the user covers part of the order with points and puts the rest on a card. This is just (1) and (2) run together: hold both, submit one drawdown order, then settle both holds on the same terminal state. How you split the charge between points and fiat is entirely up to you — Rye doesn’t see it. The only thing Rye does is deduct purchase price − commission from your drawdown balance.
// 1. Hold both tenders
const pointsHold = await points.hold(userId, pointsPortion);
const cardHold = await stripe.paymentIntents.create({
  amount: fiatPortionSubunits,
  currency: "usd",
  customer: stripeCustomerId,
  payment_method: paymentMethodId,
  capture_method: "manual",
  confirm: true,
});

// 2. Submit a single drawdown order
const intent = await client.checkoutIntents.purchase({
  productUrl,
  quantity: 1,
  buyer,
  paymentMethod: { type: "drawdown" },
});

// 3. Settle both holds together
function onIntentSettled(intent) {
  if (intent.state === "completed") {
    points.capture(pointsHold.id);
    stripe.paymentIntents.capture(cardHold.id);
  } else if (intent.state === "failed") {
    points.release(pointsHold.id);
    stripe.paymentIntents.cancel(cardHold.id);
  }
}

Discounting to incentivize purchases

Because you control the user-facing charge, you can discount an order to drive redemptions — and still come out ahead, as long as the discount stays within your commission. Recall that drawdown deducts purchase price − commission:
A $100 product with $20 commission results in an $80 drawdown deduction.
So your true cost for that order is $80. If you charge the user the full $100, you net the $20 commission. If you discount the user’s price by X, you net 20 − X (in dollars). As long as the discount X is less than the commission, the checkout is still profitable. To size the discount safely, read the commission before you charge the user. When the checkout intent reaches awaiting_confirmation, the offer carries a commission field with the amount you’d earn if the order places:
{
  "state": "awaiting_confirmation",
  "offer": {
    "cost": {
      "total":    { "currencyCode": "USD", "amountSubunits": 10000 }, // $100.00
      "subtotal": { "currencyCode": "USD", "amountSubunits": 10000 }
    },
    "commission": {
      "estimate": false,
      "amount":   { "currencyCode": "USD", "amountSubunits": 2000 }   // $20.00
    }
  }
}
Cap any incentive discount at offer.commission.amount and the order stays in the black:
const offer = intent.offer;
const commission = offer.commission.amount.amountSubunits; // 2000 ($20)

// Keep the discount within the commission to stay profitable
const discount = Math.min(desiredDiscountSubunits, commission);
const chargeAmountSubunits = offer.cost.total.amountSubunits - discount;
// Hold `chargeAmountSubunits` from the user, then submit & settle as above.
If offer.commission.estimate is true, the amount is a pre-checkout estimate and may differ slightly from the finalized commission. Leave headroom against the estimate if you discount aggressively.