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

# Earn on specific products or hours

> Award points only for qualifying line items or time windows

Rigaly doesn't need to know your product catalog. **Your code decides which line items qualify** and sends Rigaly the qualifying amount. This keeps the integration simple and puts you in full control of the rules.

## Pattern

1. At order completion, filter the cart down to qualifying items (by SKU, category, tag — whatever your system uses).
2. Optionally check the time-of-day / day-of-week window.
3. Call `POST /points/issue` with points for the **qualifying subtotal only**.

## Example: points only on coffee SKUs, 2–5 pm happy hour

<CodeGroup>
  ```javascript Node.js theme={null}
  const QUALIFYING_SKUS = /^COF-/;             // your rule: coffee products only
  const HAPPY_HOUR = { start: 14, end: 17 };   // your rule: 2 pm – 5 pm

  async function awardHappyHourPoints(order) {
    // 1. Your code filters qualifying line items
    const qualifyingUsd = order.items
      .filter((item) => QUALIFYING_SKUS.test(item.sku))
      .reduce((sum, item) => sum + item.priceUsd * item.quantity, 0);

    // 2. Your code checks the time window
    const hour = new Date(order.completedAt).getHours();
    const inWindow = hour >= HAPPY_HOUR.start && hour < HAPPY_HOUR.end;

    if (qualifyingUsd === 0 || !inWindow) return null; // nothing to award

    // 3. Rigaly issues points for the qualifying amount only
    const response = await fetch("https://api.rigaly.com/api/v1/points/issue", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.RIGALY_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": `order-${order.id}-happyhour`,
      },
      body: JSON.stringify({
        identifier: order.customerEmail,
        points: Math.round(qualifyingUsd * 100),
        description: `Happy-hour coffee points — order #${order.id}`,
      }),
    });
    return (await response.json()).data;
  }
  ```

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

  QUALIFYING_SKUS = re.compile(r"^COF-")      # your rule: coffee products only
  HAPPY_HOUR = range(14, 17)                  # your rule: 2 pm – 5 pm

  def award_happy_hour_points(order):
      # 1. Your code filters qualifying line items
      qualifying_usd = sum(
          item["price_usd"] * item["quantity"]
          for item in order["items"]
          if QUALIFYING_SKUS.match(item["sku"])
      )

      # 2. Your code checks the time window
      hour = datetime.fromisoformat(order["completed_at"]).hour
      if qualifying_usd == 0 or hour not in HAPPY_HOUR:
          return None

      # 3. Rigaly issues points for the qualifying amount only
      response = requests.post(
          "https://api.rigaly.com/api/v1/points/issue",
          headers={
              "Authorization": f"Bearer {os.environ['RIGALY_API_KEY']}",
              "Idempotency-Key": f"order-{order['id']}-happyhour",
          },
          json={
              "identifier": order["customer_email"],
              "points": round(qualifying_usd * 100),
              "description": f"Happy-hour coffee points — order #{order['id']}",
          },
      )
      return response.json()["data"]
  ```

  ```php PHP theme={null}
  <?php
  const QUALIFYING_SKU_PATTERN = '/^COF-/';   // your rule: coffee products only
  const HAPPY_HOUR_START = 14;                // your rule: 2 pm – 5 pm
  const HAPPY_HOUR_END = 17;

  function awardHappyHourPoints(array $order): ?array {
      // 1. Your code filters qualifying line items
      $qualifyingUsd = 0;
      foreach ($order["items"] as $item) {
          if (preg_match(QUALIFYING_SKU_PATTERN, $item["sku"])) {
              $qualifyingUsd += $item["price_usd"] * $item["quantity"];
          }
      }

      // 2. Your code checks the time window
      $hour = (int) date("G", strtotime($order["completed_at"]));
      if ($qualifyingUsd === 0 || $hour < HAPPY_HOUR_START || $hour >= HAPPY_HOUR_END) {
          return null;
      }

      // 3. Rigaly issues points for the qualifying amount only
      $ch = curl_init("https://api.rigaly.com/api/v1/points/issue");
      curl_setopt_array($ch, [
          CURLOPT_POST => true,
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_HTTPHEADER => [
              "Authorization: Bearer " . getenv("RIGALY_API_KEY"),
              "Content-Type: application/json",
              "Idempotency-Key: order-{$order['id']}-happyhour",
          ],
          CURLOPT_POSTFIELDS => json_encode([
              "identifier" => $order["customer_email"],
              "points" => (int) round($qualifyingUsd * 100),
              "description" => "Happy-hour coffee points — order #{$order['id']}",
          ]),
      ]);
      return json_decode(curl_exec($ch), true)["data"];
  }
  ```

  ```bash curl theme={null}
  # The API call your code makes once it has computed the qualifying amount:
  curl -X POST https://api.rigaly.com/api/v1/points/issue \
    -H "Authorization: Bearer rgly_sk_YOUR_KEY" \
    -H "Idempotency-Key: order-1042-happyhour" \
    -H "Content-Type: application/json" \
    -d '{
      "identifier": "customer@example.com",
      "points": 850,
      "description": "Happy-hour coffee points — order #1042"
    }'
  ```
</CodeGroup>

## Variations

* **Bonus multipliers**: send `points: qualifyingPoints * 2` for double-points promotions on selected products.
* **Category exclusions**: award full points but skip gift cards / alcohol by filtering them out of the subtotal.
* **Split descriptions**: issue two calls (with distinct `Idempotency-Key`s) — one for base points, one for the bonus — so the customer sees both lines in their history.

<Note>
  Time-of-day rules on the **redemption** side don't need any code: rewards have a built-in `time_active` schedule that Rigaly enforces automatically. Product-specific *earning*, as shown here, is your code's job by design.
</Note>
