# Discount codes

A cart carries at most one discount code. The code is stored verbatim and evaluated lazily on every cart read: attaching an unusable code does not fail the request. Instead, every cart response reports the outcome through two fields:

- `discount_code_error`: null when the code applies (or no code is set), otherwise one of the values below.
- `discounts[]`: the list of discounts currently applied to the cart, each as `{id, name, code, kind, amount}` where `kind` is one of `order`, `products`, `shipping`, `bogo` and `amount` is an integer in minor units. Automatic (codeless) discounts also appear here.

| `discount_code_error` | Meaning |
| --- | --- |
| `invalid` | No discount with this code exists (or it is inactive). |
| `expired` | The discount's validity window has passed or not started. |
| `min_order` | The cart total is below the discount's minimum order value. |
| `not_eligible` | The cart or customer does not match the discount's conditions. |
| `usage_limit` | The discount's usage limit is exhausted. |

Because evaluation is lazy, a code that is valid today can become `expired` or `usage_limit` later; re-read the cart to get the current state. Usage limits are additionally re-checked under a lock at checkout: a code exhausted between attach and checkout causes a 409 cart conflict with a `discount_unavailable` entry (see the Checkout page).

Both endpoints require `commerce:write`, an `active` cart, and pass the cart ownership check. Both return the full cart envelope, the same shape as `GET /v1/api/commerce/carts/{id}`.

## PUT /v1/api/commerce/carts/{id}/discount-code

Attaches a discount code to the cart, replacing any previously attached code. The request succeeds for any well-formed code; check `discount_code_error` and `discounts[]` in the response to see whether it applied.

### Parameters

**Path**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (UUID) | yes | Cart id. Must be an `active` cart. |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | string | yes | Max 100 characters, must be non-blank after trimming. |

```bash title="Request"
curl -X PUT https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4/discount-code \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{"code": "WELCOME10"}'
```

```javascript title="Request (JS)"
const res = await fetch(
  `https://api.borkol.com/v1/api/commerce/carts/${cartId}/discount-code`,
  {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ code: "WELCOME10" }),
  }
);
const { data: cart } = await res.json();
if (cart.discount_code_error) {
  // show why the code did not apply: invalid | expired | min_order | not_eligible | usage_limit
}
```

```json title="Response"
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "discount_code": "WELCOME10",
    "discount_code_error": null,
    "discounts": [
      {
        "id": "71c9e0aa-2f4b-4d31-9a77-0b1c2d3e4f5a",
        "name": "Welcome 10%",
        "code": "WELCOME10",
        "kind": "order",
        "amount": 450
      }
    ],
    "...": "same cart shape as GET /v1/api/commerce/carts/{id}"
  }
}
```

When the code cannot apply, the response is still 200 with, for example, `"discount_code": "SUMMER24", "discount_code_error": "expired", "discounts": []`.

### Errors

| Status | Body | When |
| --- | --- | --- |
| 422 | `{"message": "...", "errors": {"code": ["..."]}}` | Code missing, blank after trim, or longer than 100 characters. |
| 404 | `{"message": "..."}` | Cart unknown, expired, or not `active`. |
| 403 | ownership, scope, module, or suspension body | See the Carts page. |
| 429 | `{"message": "Too Many Attempts."}` | Rate limit exceeded. |

## DELETE /v1/api/commerce/carts/{id}/discount-code

Removes the discount code from the cart. Automatic (codeless) discounts are unaffected and may still appear in `discounts[]`.

### Parameters

**Path**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (UUID) | yes | Cart id. Must be an `active` cart. |

No body.

```bash title="Request"
curl -X DELETE https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4/discount-code \
  -H "Authorization: Bearer {api_key}"
```

```json title="Response"
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "discount_code": null,
    "discount_code_error": null,
    "...": "same cart shape as GET /v1/api/commerce/carts/{id}"
  }
}
```

### Errors

| Status | Body | When |
| --- | --- | --- |
| 404 | `{"message": "..."}` | Cart unknown, expired, or not `active`. |
| 403 | ownership, scope, module, or suspension body | See the Carts page. |
| 429 | `{"message": "Too Many Attempts."}` | Rate limit exceeded. |
