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

# Discount Reconciliation Examples

> Practical examples showing how Gameball reconciles order-level discounts, gift cards, and redemptions across line items so cashback is calculated on the correct amount.

# Discount Reconciliation Examples

This guide explains how the **Discount Reconciliation Layer** distributes order-level discounts (coupons, gift cards, points redemptions) across line items when the POS sends them only at the order level. It walks through the algorithm with the same payload shape used by the [Order API](/api-reference/order/order).

**Endpoint:** `POST https://api.gameball.co/api/v4.0/integrations/orders`

**Headers:**

```
apikey: {your-api-key}
secretkey: {your-secret-key}
Content-Type: application/json
```

***

## Why Reconciliation Is Needed

Many POS and e-commerce systems apply a discount, gift card, or loyalty redemption at the **order level** (via `totalDiscount` or a reduced `totalPaid`) and never push the discount down to the line items. The cashback engine calculates rewards **per line item**, so without reconciliation the customer is over-rewarded.

```
Order:  totalPaid = 80, totalDiscount = 20
Items:  Item A ($30) + Item B ($70) = $100
Gap:    Line items sum to $100, but customer paid $80 → 20 extra points awarded
```

The reconciliation layer detects this mismatch and distributes the gap proportionally across eligible line items so that `Σ line items = totalPaid`.

***

## Field Definitions

### Line Item Fields

| Field      | Definition                                                           | Example                                          |
| ---------- | -------------------------------------------------------------------- | ------------------------------------------------ |
| `price`    | Original unit price of a single item (does NOT change with quantity) | If 1 unit = \$50, then `price: 50` even if qty 2 |
| `quantity` | Number of units                                                      | `quantity: 2`                                    |
| `discount` | Total discount applied to this line item (NOT per unit)              | If \$10 off this line, then `discount: 10`       |
| `taxes`    | Total tax for this line item after discount (NOT per unit)           | `taxes: 7.5`                                     |

### Order Level Fields

| Field           | Definition                                                       | Example                                     |
| --------------- | ---------------------------------------------------------------- | ------------------------------------------- |
| `totalPrice`    | Sum of all line items + taxes (before any discounts)             | Items = 100 → `totalPrice: 100`             |
| `totalDiscount` | Sum of ALL discounts (line item + order level + points + coupon) | Coupon 20 + Points 20 → `totalDiscount: 40` |
| `totalPaid`     | What the customer actually paid (`totalPrice - totalDiscount`)   | 100 - 40 = `totalPaid: 60`                  |

<Info>
  Cashback is earned per line item based on `(price × quantity) + taxes - discount`. The reconciliation layer ensures the sum of line item totals equals `totalPaid` before cashback runs.
</Info>

***

## Reconciliation Formulas

### Step 0 — Filter Negative-Price Items

```
FOR each line item:
    IF item.price < 0:
        remove the item   // gift card redemptions, store credit, etc.
```

### Step 1 — Detect Mismatch

```
itemNet       = (item.price × item.quantity) + item.taxes - item.discount
lineItemTotal = Σ itemNet

mismatch = (lineItemTotal + order.totalShipping) - order.totalPaid
```

<Info>
  `totalPaid` includes shipping but line items don't, so shipping is added on the line-item side of the equation.
</Info>

### Step 2 — Distribute Proportionally Across ALL Items

```
totalNet = Σ itemNet         // denominator uses each item's NET value (after any existing discount)

FOR each item (in order):
    IF remainingAmount ≤ 0: break

    share = ROUND( (itemNet / totalNet) × mismatch, 2 )

    IF share ≥ itemNet:
        item.discount += itemNet           // cap: drives this item's cashback base to 0, never negative
        remainingAmount -= itemNet
    ELSE:
        item.discount += share
        remainingAmount -= share
```

<Note>
  Items that already have a POS-applied discount aren't skipped — they're weighted by their **net** value, so a more-discounted item naturally receives a smaller share.
</Note>

### Step 3 — Final Validation

```
newLineItemTotal = Σ item.GetTotalPaid()
finalMismatch    = ABS( (newLineItemTotal + totalShipping) - order.totalPaid )

IF finalMismatch > 0.01:
    log warning, proceed anyway   // never block the order
```

***

## Decision Table

| Mismatch  | Meaning                                            | Action                            |
| --------- | -------------------------------------------------- | --------------------------------- |
| `≤ 0.01`  | Line items already match `totalPaid`               | Skip — proceed to cashback engine |
| `> 0.01`  | Order-level discount/redemption not on line items  | Distribute across eligible items  |
| `< -0.01` | Customer paid more than line items sum (fees/tips) | Log warning, no distribution      |

***

## Example 1: Order-Level Coupon (Most Common)

**Scenario:** Customer applied a coupon at checkout. The POS reduced `totalPaid` and set `totalDiscount` but left line items at full price.

| Component            | Calculation     | Value    |
| -------------------- | --------------- | -------- |
| Item A (1 × \$30)    |                 | \$30     |
| Item B (1 × \$70)    |                 | \$70     |
| **Line Item Total**  |                 | \$100    |
| `totalPaid`          |                 | \$80     |
| **Mismatch**         | 100 - 80        | **\$20** |
| Item A share         | (30 / 100) × 20 | \$6      |
| Item B share         | (70 / 100) × 20 | \$14     |
| Item A cashback base | 30 - 6          | \$24     |
| Item B cashback base | 70 - 14         | \$56     |

```json theme={null} theme={null}
{
  "orderId": "INV-2026-002001",
  "totalPrice": 100,
  "totalDiscount": 20,
  "totalPaid": 80,
  "lineItems": [
    { "productId": "A", "price": 30, "quantity": 1, "discount": 6 },
    { "productId": "B", "price": 70, "quantity": 1, "discount": 14 }
  ]
}
```

<Note>
  The `discount` values shown (6 and 14) are what reconciliation writes onto the line items. The integration only needs to send `discount: 0` — Gameball fills these in.
</Note>

***

## Example 2: Mixed — Some Items Already Discounted

**Scenario:** Item A has a \$5 line discount from the POS. The mismatch is distributed across **both** items, weighted by net value, so the item with the bigger existing discount automatically gets a smaller share.

| Component                   | Calculation    | Value    |
| --------------------------- | -------------- | -------- |
| Item A (1 × \$30, disc \$5) | net            | \$25     |
| Item B (1 × \$70)           | net            | \$70     |
| **Line Item Total**         |                | \$95     |
| `totalPaid`                 |                | \$75     |
| **Mismatch**                | 95 - 75        | **\$20** |
| Item A share                | (25 / 95) × 20 | \$5.26   |
| Item B share                | (70 / 95) × 20 | \$14.74  |
| Item A final discount       | 5 + 5.26       | \$10.26  |
| Item B final discount       | 0 + 14.74      | \$14.74  |
| Item A cashback base        | 30 - 10.26     | \$19.74  |
| Item B cashback base        | 70 - 14.74     | \$55.26  |

```json theme={null} theme={null}
{
  "orderId": "INV-2026-002002",
  "totalPrice": 100,
  "totalDiscount": 25,
  "totalPaid": 75,
  "lineItems": [
    { "productId": "A", "price": 30, "quantity": 1, "discount": 10.26 },
    { "productId": "B", "price": 70, "quantity": 1, "discount": 14.74 }
  ]
}
```

***

## Example 3: All Items Already Discounted

**Scenario:** Every line item already has a POS-applied discount, but their sum still doesn't match `totalPaid`. The remaining gap is distributed across all items by net value.

| Component                    | Calculation    | Value    |
| ---------------------------- | -------------- | -------- |
| Item A (1 × \$30, disc \$5)  | net            | \$25     |
| Item B (1 × \$70, disc \$15) | net            | \$55     |
| **Line Item Total**          |                | \$80     |
| `totalPaid`                  |                | \$60     |
| **Mismatch**                 | 80 - 60        | **\$20** |
| Item A added share           | (25 / 80) × 20 | \$6.25   |
| Item B added share           | (55 / 80) × 20 | \$13.75  |
| Item A final discount        | 5 + 6.25       | \$11.25  |
| Item B final discount        | 15 + 13.75     | \$28.75  |
| Item A cashback base         | 30 - 11.25     | \$18.75  |
| Item B cashback base         | 70 - 28.75     | \$41.25  |

```json theme={null} theme={null}
{
  "orderId": "INV-2026-002003",
  "totalPrice": 100,
  "totalDiscount": 40,
  "totalPaid": 60,
  "lineItems": [
    { "productId": "A", "price": 30, "quantity": 1, "discount": 11.25 },
    { "productId": "B", "price": 70, "quantity": 1, "discount": 28.75 }
  ]
}
```

***

## Example 4: External Loyalty Redemption as Payment Method

**Scenario:** A POS like Square treats a points redemption as a payment method (not a discount). `totalPaid` is already reduced; line items remain at full price; `totalDiscount` is zero.

| Component                      | Calculation       | Value     |
| ------------------------------ | ----------------- | --------- |
| Item A (1 × \$150)             |                   | \$150     |
| Item B (1 × \$150)             |                   | \$150     |
| **Line Item Total**            |                   | \$300     |
| `totalPaid` (after redemption) |                   | \$200     |
| **Mismatch**                   | 300 - 200         | **\$100** |
| Item A share                   | (150 / 300) × 100 | \$50      |
| Item B share                   | (150 / 300) × 100 | \$50      |

```json theme={null} theme={null}
{
  "orderId": "INV-2026-002004",
  "totalPrice": 300,
  "totalDiscount": 0,
  "totalPaid": 200,
  "lineItems": [
    { "productId": "A", "price": 150, "quantity": 1, "discount": 50 },
    { "productId": "B", "price": 150, "quantity": 1, "discount": 50 }
  ]
}
```

<Info>
  The algorithm doesn't need to know **why** there's a mismatch (coupon, gift card, loyalty as discount, loyalty as payment). It reconciles purely on `lineItemTotal vs totalPaid` — making it universal across POS patterns.
</Info>

***

## Example 5: POS Coupon + External Redemption Combined

**Scenario:** Customer used a $20 coupon AND redeemed $20 of points through the POS. Neither was distributed to line items. Reconciliation handles both in a single pass.

| Component           | Calculation     | Value    |
| ------------------- | --------------- | -------- |
| Item A (1 × \$50)   |                 | \$50     |
| Item B (1 × \$50)   |                 | \$50     |
| **Line Item Total** |                 | \$100    |
| Coupon discount     |                 | \$20     |
| Points redeemed     |                 | \$20     |
| `totalPaid`         | 100 - 20 - 20   | **\$60** |
| **Mismatch**        | 100 - 60        | **\$40** |
| Item A share        | (50 / 100) × 40 | \$20     |
| Item B share        | (50 / 100) × 40 | \$20     |

```json theme={null} theme={null}
{
  "orderId": "INV-2026-002005",
  "totalPrice": 100,
  "totalDiscount": 40,
  "totalPaid": 60,
  "redeemedAmount": 20,
  "lineItems": [
    { "productId": "A", "price": 50, "quantity": 1, "discount": 20 },
    { "productId": "B", "price": 50, "quantity": 1, "discount": 20 }
  ]
}
```

<Note>
  This was the previous double-distribution bug: reconciliation distributed \$40, then `DistributeRedeemedAmount` distributed another \$20 on top. The single-pass reconciliation now handles both deductions together, so cashback is correctly calculated on \$60.
</Note>

***

## Example 6: Cap Protection — Discount Larger Than Cheap Item

**Scenario:** A large redemption could mathematically push a cheap item's cashback base below zero. The cap ensures the per-item discount never exceeds the item's value.

| Component                    | Calculation             | Value    |
| ---------------------------- | ----------------------- | -------- |
| Item A (1 × \$5)             |                         | \$5      |
| Item B (1 × \$95)            |                         | \$95     |
| **Line Item Total**          |                         | \$100    |
| `totalPaid`                  |                         | \$10     |
| **Mismatch**                 | 100 - 10                | **\$90** |
| Item A share (capped at \$5) | (5 / 100) × 90 = 4.50   | \$4.50   |
| Item B share                 | (95 / 100) × 90 = 85.50 | \$85.50  |
| Item A cashback base         | 5 - 4.50                | \$0.50   |
| Item B cashback base         | 95 - 85.50              | \$9.50   |

```json theme={null} theme={null}
{
  "orderId": "INV-2026-002006",
  "totalPrice": 100,
  "totalDiscount": 90,
  "totalPaid": 10,
  "lineItems": [
    { "productId": "A", "price": 5,  "quantity": 1, "discount": 4.5  },
    { "productId": "B", "price": 95, "quantity": 1, "discount": 85.5 }
  ]
}
```

***

## Example 7: No Mismatch (Pass-Through)

**Scenario:** The POS already distributed every discount onto line items. `Σ line items` already equals `totalPaid`. Reconciliation runs but does nothing.

| Component                    | Calculation | Value   |
| ---------------------------- | ----------- | ------- |
| Item A (1 × \$30, disc \$6)  |             | \$24    |
| Item B (1 × \$70, disc \$14) |             | \$56    |
| **Line Item Total**          |             | \$80    |
| `totalPaid`                  |             | \$80    |
| **Mismatch**                 | 80 - 80     | **\$0** |

```json theme={null} theme={null}
{
  "orderId": "INV-2026-002007",
  "totalPrice": 100,
  "totalDiscount": 20,
  "totalPaid": 80,
  "lineItems": [
    { "productId": "A", "price": 30, "quantity": 1, "discount": 6  },
    { "productId": "B", "price": 70, "quantity": 1, "discount": 14 }
  ]
}
```

***

## Edge Cases Reference

| Edge Case                                 | Handling                                                           |
| ----------------------------------------- | ------------------------------------------------------------------ |
| No mismatch                               | Skip — proceed directly to cashback                                |
| Items with existing discount              | Included in distribution, weighted by net value (smaller share)    |
| Single line item                          | Full mismatch applied to that item (capped at its net value)       |
| Share ≥ item net value                    | Capped at net value — item's cashback base goes to 0, not negative |
| Rounding remainder                        | Silently absorbed (typically `< $0.01`)                            |
| No line items                             | Skip — cashback uses transaction-level `totalPaid`                 |
| `totalPaid` > `lineItemTotal + shipping`  | Log warning, no distribution (fees / tips case)                    |
| Negative-price items (gift card)          | Filtered out in Step 0                                             |
| Shipping                                  | Added to line item side of the mismatch equation                   |
| Gameball's own redemption (HoldReference) | Same flow — `DistributeAmountAcrossLineItems` is shared            |

***

## Summary Table

| Example | Scenario                            | Line Item Total | totalPaid | Mismatch | Cashback Base |
| ------- | ----------------------------------- | --------------- | --------- | -------- | ------------- |
| 1       | Order-level coupon                  | \$100           | \$80      | \$20     | \$80          |
| 2       | Mixed — some items pre-discounted   | \$95            | \$75      | \$20     | \$75          |
| 3       | All items pre-discounted (fallback) | \$80            | \$60      | \$20     | \$60          |
| 4       | Loyalty as payment method           | \$300           | \$200     | \$100    | \$200         |
| 5       | POS coupon + external redemption    | \$100           | \$60      | \$40     | \$60          |
| 6       | Cap protection — cheap item         | \$100           | \$10      | \$90     | \$10          |
| 7       | No mismatch (pass-through)          | \$80            | \$80      | \$0      | \$80          |

***

## Key Takeaway

<Check>
  **Reconciliation guarantees `Σ line item cashback base = totalPaid`.**

  Integrations don't need to figure out *which* deduction caused the gap (coupon, gift card, loyalty as discount, loyalty as payment). Just send:

  * Line items at their natural prices
  * `totalPaid` reflecting what the customer actually paid

  Gameball detects the mismatch, distributes it proportionally across eligible items, and never blocks the order if reconciliation can't fully close the gap.
</Check>
