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

# Claim rewards on specific products only

> Enforce product restrictions when honoring reward codes

Rewards can be restricted to specific products. The division of labor:

* **Rigaly enforces**: code validity, expiry, one-time use, cooldowns, time-of-day windows (`time_active`), points balance.
* **Your code enforces**: which products the reward applies to — using the reward's `conditions` text (or your own mapping keyed by reward ID).

## 1. Create the reward with machine-readable conditions

Put your product rule in `conditions`. Customers see this text in the app, and your checkout gets it back on validation — a simple convention like `SKU:` prefixes makes it parseable:

```bash theme={null}
curl -X POST https://api.rigaly.com/api/v1/rewards \
  -H "Authorization: Bearer rgly_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Free Medium Coffee",
    "description": "Redeem for any medium hot coffee",
    "points_cost": 500,
    "conditions": "Valid for medium hot coffees only. SKU:COF-M-*"
  }'
```

<Tip>
  For more structure, keep a mapping in your own system keyed by the reward `id` (returned at creation) — e.g. `{ "9970514e-...": { "skuPattern": "^COF-M-" } }` — and use `conditions` purely as customer-facing text.
</Tip>

## 2. At checkout: validate → check cart → complete

<CodeGroup>
  ```javascript Node.js theme={null}
  async function redeemRewardCode(code, cart, checkoutId) {
    const headers = { Authorization: `Bearer ${process.env.RIGALY_API_KEY}` };

    // 1. Validate (read-only)
    const validation = await fetch(
      `https://api.rigaly.com/api/v1/redemptions/${code}`,
      { headers }
    );
    if (!validation.ok) throw new Error("Invalid or unknown code");
    const { redemption } = (await validation.json()).data;
    if (redemption.status !== "pending") {
      throw new Error(`Code already ${redemption.status}`);
    }

    // 2. YOUR check: does the cart contain a qualifying product?
    //    Convention: "SKU:<pattern>" inside the reward's conditions text
    const skuRule = redemption.reward.conditions?.match(/SKU:([\w*-]+)/)?.[1];
    if (skuRule) {
      const pattern = new RegExp("^" + skuRule.replace(/\*/g, ".*") + "$");
      const qualifying = cart.items.find((item) => pattern.test(item.sku));
      if (!qualifying) {
        throw new Error(`"${redemption.reward.name}" requires a qualifying product in the cart`);
      }
      applyDiscount(cart, qualifying); // your business logic
    }

    // 3. Complete — deducts points, marks the code used (atomic)
    const completion = await fetch(
      `https://api.rigaly.com/api/v1/redemptions/${code}/complete`,
      {
        method: "POST",
        headers: { ...headers, "Idempotency-Key": `checkout-${checkoutId}-redeem` },
      }
    );
    if (!completion.ok) {
      revertDiscount(cart); // completion failed (cooldown, balance…) — undo
      const { error } = await completion.json();
      throw new Error(error.message);
    }
    return (await completion.json()).data;
  }
  ```

  ```python Python theme={null}
  import re

  def redeem_reward_code(code, cart, checkout_id):
      headers = {"Authorization": f"Bearer {os.environ['RIGALY_API_KEY']}"}

      # 1. Validate (read-only)
      validation = requests.get(
          f"https://api.rigaly.com/api/v1/redemptions/{code}", headers=headers
      )
      validation.raise_for_status()
      redemption = validation.json()["data"]["redemption"]
      if redemption["status"] != "pending":
          raise ValueError(f"Code already {redemption['status']}")

      # 2. YOUR check: does the cart contain a qualifying product?
      conditions = redemption["reward"].get("conditions") or ""
      sku_rule = re.search(r"SKU:([\w*-]+)", conditions)
      if sku_rule:
          pattern = re.compile("^" + sku_rule.group(1).replace("*", ".*") + "$")
          qualifying = next((i for i in cart["items"] if pattern.match(i["sku"])), None)
          if not qualifying:
              raise ValueError(f"'{redemption['reward']['name']}' requires a qualifying product")
          apply_discount(cart, qualifying)  # your business logic

      # 3. Complete — deducts points, marks the code used (atomic)
      completion = requests.post(
          f"https://api.rigaly.com/api/v1/redemptions/{code}/complete",
          headers={**headers, "Idempotency-Key": f"checkout-{checkout_id}-redeem"},
      )
      if not completion.ok:
          revert_discount(cart)
          raise ValueError(completion.json()["error"]["message"])
      return completion.json()["data"]
  ```

  ```php PHP theme={null}
  <?php
  function redeemRewardCode(string $code, array &$cart, string $checkoutId): array {
      $auth = "Authorization: Bearer " . getenv("RIGALY_API_KEY");

      // 1. Validate (read-only)
      $ch = curl_init("https://api.rigaly.com/api/v1/redemptions/{$code}");
      curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [$auth]]);
      $redemption = json_decode(curl_exec($ch), true)["data"]["redemption"] ?? null;
      if (!$redemption || $redemption["status"] !== "pending") {
          throw new Exception("Invalid or already-used code");
      }

      // 2. YOUR check: does the cart contain a qualifying product?
      if (preg_match('/SKU:([\w*-]+)/', $redemption["reward"]["conditions"] ?? "", $m)) {
          $pattern = '/^' . str_replace('*', '.*', $m[1]) . '$/';
          $qualifying = null;
          foreach ($cart["items"] as $item) {
              if (preg_match($pattern, $item["sku"])) { $qualifying = $item; break; }
          }
          if (!$qualifying) {
              throw new Exception($redemption["reward"]["name"] . " requires a qualifying product");
          }
          applyDiscount($cart, $qualifying); // your business logic
      }

      // 3. Complete — deducts points, marks the code used (atomic)
      $ch = curl_init("https://api.rigaly.com/api/v1/redemptions/{$code}/complete");
      curl_setopt_array($ch, [
          CURLOPT_POST => true,
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_HTTPHEADER => [$auth, "Idempotency-Key: checkout-{$checkoutId}-redeem"],
      ]);
      $body = json_decode(curl_exec($ch), true);
      if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) {
          revertDiscount($cart);
          throw new Exception($body["error"]["message"]);
      }
      return $body["data"];
  }
  ```

  ```bash curl theme={null}
  # 1. Validate — returns the reward with its conditions text
  curl https://api.rigaly.com/api/v1/redemptions/AB12CD34 \
    -H "Authorization: Bearer rgly_sk_YOUR_KEY"

  # 2. (your system checks the cart against reward.conditions)

  # 3. Complete
  curl -X POST https://api.rigaly.com/api/v1/redemptions/AB12CD34/complete \
    -H "Authorization: Bearer rgly_sk_YOUR_KEY" \
    -H "Idempotency-Key: checkout-981-redeem"
  ```
</CodeGroup>

## Why validate and complete separately?

Completing is irreversible — it deducts the customer's points. Validating first lets you refuse the code (wrong products in the cart, store policy) **before** anything is consumed, so the customer keeps their points and can use the code elsewhere.
