> ## Documentation Index
> Fetch the complete documentation index at: https://docs-test.rye.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Payment flows

> Charge your users in points, fiat, or a mix — while paying Rye via drawdown. Rye is agnostic to how you collect funds from the user.

In a rewards program you pay Rye for every order out of your pre-funded [drawdown balance](/api-v2/payment-providers/drawdown), 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:

| Ledger                 | Who controls it | What moves                                                                                              |
| ---------------------- | --------------- | ------------------------------------------------------------------------------------------------------- |
| **User charge**        | You             | Points and/or fiat you collect from the user, in whatever amount you choose.                            |
| **Drawdown deduction** | Rye             | The 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:

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Settle on the intent's terminal state">
    Watch the checkout intent reach a terminal state — via the [`checkout_intent.completed` / `checkout_intent.order_failed`](/api-v2/webhooks/types) webhooks, or by polling the [intent lifecycle](/api-v2/checkout-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.
  </Step>
</Steps>

<Warning>
  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.
</Warning>

## 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).

```ts theme={null}
// 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](https://docs.stripe.com/payments/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.

```ts theme={null}
// 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.

```ts theme={null}
// 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:

```jsonc theme={null}
{
  "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:

```ts theme={null}
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.
```

<Note>
  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.
</Note>
