# Managing the cart

A Groupon cart is created together with its first item: creating a cart needs at least one item,
and an empty `items` array is rejected. After that, a cart can become empty: removing its last
line keeps the cart and returns it with `itemCount: 0`.

## Operations

| Operation | Endpoint | When |
|---|---|---|
| Create cart | `POST /partner_storefront/carts` | Shopper adds the first item |
| Read cart | `GET /partner_storefront/carts/{cartId}` | Refresh prices and availability, for example on cart page load |
| Add items | `POST /partner_storefront/carts/{cartId}/items` | Shopper adds another item |
| Change quantity | `PATCH /partner_storefront/carts/{cartId}/items/{itemId}` | Shopper changes a quantity |
| Remove item | `DELETE /partner_storefront/carts/{cartId}/items/{itemId}` | Shopper removes a line |
| Abandon cart | `DELETE /partner_storefront/carts/{cartId}` | Shopper empties the cart, or the session ends |

Every operation except Abandon returns the full cart, including a fresh `buyLink`. The cart's
full field list is in the [API reference](/reference).

Limits: at most 20 different options per cart; quantity 1 to 100 per line.

Store the cart id against the shopper's session on your server. Only your API key can read or
change a cart it created; any other cart id returns `INVALID_CART_ID`.

## Creating a cart

```bash
curl -s -X POST "https://api-staging-core.livingsocial.com/partner_storefront/carts" \
  -H "Authorization: Bearer $GROUPON_API_KEY" \
  -H "User-Agent: MyStore/1.0 (+https://mystore.example)" \
  -H "Content-Type: application/json" \
  -d '{
    "country": "US",
    "items": [{
      "productId": "0b6f7a52-3c1d-4e8a-9f20-5d7c1a2b3c4d",
      "optionId":  "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "quantity": 2,
      "expectedPrice": 4500
    }]
  }'
```

All items are validated before anything is added. If any item fails, nothing is added and no
cart is created.

**About `expectedPrice`**: send the per-unit retail price your site is showing. If Groupon's
live price differs, the call fails with `PRICE_MISMATCH` (HTTP 409) and tells you the current
price. Update your database and your UI, ask the shopper to confirm, then retry with the new
price. Without `expectedPrice`, the shopper may see a different price at checkout than on your
site.

## Changing the cart

- **Read** re-prices and re-validates each line live. Call it when your cart page loads, and
  before showing the Buy now button after a long idle period.
- **Add items**: if the cart already has a line for the same option, its quantity is increased
  by `quantity`. This call is therefore not idempotent: see [Rules for your
  integration](/docs/rules.md).
- **Change quantity**: `itemId` is the id of the line in the cart (it equals the `optionId`).
  `quantity` is the new absolute quantity, not a delta; `0` is rejected, use Remove instead.
  Because it sets an absolute value, it is safe to retry.
- **Remove item** returns the updated cart. Removing the last line returns a cart with
  `itemCount: 0`.
- **Abandon** returns nothing. After a successful abandon, discard the `cartId` and use a new
  cart for anything the shopper adds next. Do not send further calls on an abandoned `cartId`.
  Adding items to an abandoned or purchased cart does not fail: it silently starts a new, empty
  cart under the same id, and the shopper's earlier lines are lost. Also discard the `cartId`
  once an order has been placed from it (see [Checkout and order
  confirmation](/docs/checkout-and-order-confirmation.md)).

## Using the cart response

After every cart call, overwrite your local cart with the response. Groupon's copy is
authoritative for quantity, price and availability.

- **buyLink**: set your Buy now button's href to this exact string from the latest response,
  with or without CJ. Open it in the same tab or a new tab; do not fetch it server-side, and do
  not rewrite it.
- **available: false**: the line cannot be bought. Show `unavailableReason` to the shopper, and
  remove the line before sending them to checkout. Also mark that product or option not listable
  in your database.
- **messages** are non-fatal warnings about the response:
  - `price_unavailable`: a line's live price could not be resolved (`price` is `null`; the line
    is excluded from totals).
  - `item_declined`: Groupon declined to add an item even though the call succeeded. The item is
    not in `items`. Tell the shopper it could not be added.
- Always compare `items` in the response with what you asked for. A success does not guarantee
  every requested line was added.
- `totals.grandTotal` excludes tax and any promo code. The final amount is shown on Groupon's
  checkout page.

## buyLink with and without CJ

The rest of the cart is the same with and without CJ. Only `buyLink` differs. The examples below
are trimmed to `id`, `buyLink` and `itemCount`; every other field is as described in the [API
reference](/reference).

With CJ, an affiliate tracking link: the shopper passes through CJ, which records your publisher
ID, and is redirected to Groupon's checkout page for the cart.

```json
{
  "id": "3f2b8c1e-5a4d-4b7c-9e10-2a6f8d9c0b11",
  "buyLink": "https://www.jdoqocy.com/click-7654321-15230314?sid=3f2b8c1e-5a4d-4b7c-9e10-2a6f8d9c0b11&url=https%3A%2F%2Fpartner.groupon.com%2Fcheckout%2Fcart%2F3f2b8c1e-5a4d-4b7c-9e10-2a6f8d9c0b11",
  "itemCount": 1
}
```

Without CJ (the default), the direct checkout URL for the cart. The shopper lands on Groupon's
checkout page straight away, and no commission is tracked.

```json
{
  "id": "3f2b8c1e-5a4d-4b7c-9e10-2a6f8d9c0b11",
  "buyLink": "https://partner.groupon.com/checkout/cart/3f2b8c1e-5a4d-4b7c-9e10-2a6f8d9c0b11",
  "itemCount": 1
}
```

Do not branch your code on the shape of `buyLink`. Treat it as an opaque URL on both paths.

## Recovery rules

A cart that no longer exists (for example after you abandon it, or after an order is placed from
it):

- Reading it returns an empty cart (`itemCount: 0`).
- Changing or removing one of its lines returns `INVALID_PRODUCT_ID` (HTTP 404).

**Recovery**: when a cart you expect to hold items comes back with `itemCount: 0`, or a change to
a line returns `INVALID_PRODUCT_ID`, or any cart call returns `INVALID_CART_ID`, treat the cart
as gone. Create a new cart from your local copy of the shopper's cart, store the new `cartId` in
place of the old one, and use the new cart's `buyLink`.
