# Coupons for items

Codes a buyer types at checkout for money off what your sellers list - made by you for every seller, or by a seller for their own items.

A coupon is a code a buyer types at [checkout](/billing/orders) for money
off one of your sellers' [items](/billing/items). You make coupons that work
on every seller's items; each seller can make their own, which work on their
items only.

These coupons are for items. Codes for your own plans and credit packs are
Stripe [promotion codes](/billing/promotion-codes).

> **Note:**

**Arrives with selling.** Coupons arrive with [selling](/billing/selling)
and run on the same Stripe keys: test keys in Staging for test sales, live keys
in Production once [live selling](/billing/selling#test-in-staging-then-go-live) is open for your app.

**Requires `@buildbase/sdk` 1.2.0 or later.** `<SellerCoupons>`, `useSellerCoupons`, `<CouponField>`, the `coupons` prop of `<CartSummary>`, the `couponCode` prop of `<BuyButton>` and `useCart().applyCoupon` arrive with it.

## Walkthrough

New to selling? [Zero to first sale: marketplace](/billing/first-sale-marketplace) and [Zero to first sale: storefront](/billing/first-sale-storefront) walk the whole flow once, from Stripe keys to a refund and going live.

### When to use it

A launch discount, a code for a newsletter, money off a seller's own
items, or a code that also credits an affiliate.

### Before you start

Selling on. A seller makes coupons once they have started selling.

### Set it up in the console

1. Open **Billing**, **Selling**, **Coupons** and press **New coupon**.
2. Type the code; choose percent or amount; how long it lasts on a
   subscription; uses in all, once per buyer, the start and end, and the
   least order.
3. Save. It works on every seller's items unless you name some.

![The New coupon dialog: code SPRING, 10% off, the first payment only on a subscription, 100 uses, once per buyer, no end date](/images/billing/selling/console-coupons-new.webp)

### Marketplace and storefront

The discount comes off before your fee in both shapes, so you and the
seller share it in proportion. On a subscription, the Stripe coupon is made
on the account the subscription lives on: yours in the marketplace shape,
the seller's in the storefront shape.

### What the seller sees

`<SellerCoupons>`: their own coupons, on their own items only.

### What the buyer sees

A code field in the cart (`<CartSummary coupons />`), the discount and what
is left to pay, or a reason the code cannot be used, in their language.

![CartSummary at 360 pixels wide: lines from two sellers, the code SPRING applied, the discount and the new total, and Check out](/images/billing/selling/sdk-cart-summary.webp)

### Emails and webhooks

No coupon emails or events. The order carries `coupon` and each line its
`discount`, in `order.*` events and the sales export.

### Limits

A code is 3 to 32 letters, digits, `-` or `_`. Up to 50 items per coupon. A
repeating coupon lasts 1 to 36 periods. A cart with a subscription or a
price in parts takes no coupon (`cart_coupon_mixed`).

### Staging and Production

A subscription's Stripe coupon is made on the account the subscription
lives on, with your saved keys, the first time a subscription there uses
the code.

## What a coupon is

| Field                | Says                                                                                                                     |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `code`               | What the buyer types: 3 to 32 letters, digits, `-` or `_`, kept in upper case                                            |
| `kind`               | `percent` or `amount`                                                                                                    |
| `percentBps`         | For `percent`: basis points off each line, 1 to 10000 (10000 is 100%)                                                    |
| `amount`, `currency` | For `amount`: minor units off, in that currency                                                                          |
| `duration`           | On a subscription: `once` (the default), `repeating` or `forever`                                                        |
| `durationPeriods`    | For `repeating`: how many billing periods, 1 to 36                                                                       |
| `maxRedemptions`     | Uses in all; unset is no limit                                                                                           |
| `perBuyerOnce`       | One use per buyer: a signed-in person, or a guest's email                                                                |
| `startsAt`, `endsAt` | When it works, as ISO 8601 date and time; unset is any time. A value that is not a date is refused with `COUPON_INVALID` |
| `minSubtotal`        | The least the lines it applies to must add up to, minor units                                                            |
| `itemIds`            | Only these items (up to 50); empty is every item it can apply to                                                         |
| `active`, `note`     | Switched on or off; a note only the coupon's owner reads                                                                 |
| `affiliateId`        | An affiliate of one of the owner's own programs: the code refers buyers to them                                          |

A percent is rounded down on each line. An amount is spread over the lines
it applies to by their size, and never takes a line below 0. A `currency`
on a percent coupon limits it to that currency.

The code a buyer types is trimmed and read in upper case, so `spring` finds
`SPRING`. A code is unique among the live coupons of your app. Deleting a
coupon frees its code; checkouts already holding it keep their discount.

A coupon with an `affiliateId` also refers the buyer to that affiliate, as
their link would, and the program decides between it and a link; see
[Affiliates: coupon codes](/billing/affiliates#coupon-codes).

## Who gives the discount

| Made by  | Works on                             |
| -------- | ------------------------------------ |
| You      | Every seller's lines in the checkout |
| A seller | That seller's own lines only         |

The discount comes off the line's price before your fee. Your fee, the
seller's share and any tax are all of the price after the discount. So
whoever made the coupon, you and the seller share the money off in the
same proportion as the sale; neither pays the other for it.

A 10% coupon on a $50.00 line with a 10% fee: the buyer pays $45.00, your
fee is $4.50 and the seller gets $40.50.

## One-time prices, subscriptions and prices in parts

A coupon works on a one-time price, a subscription and a price paid in
parts (a deposit, milestones or installments).

A 100% coupon, or an amount that covers the whole price, makes a single
item's checkout a free claim: the order completes at once, with no payment
page. In a [cart](/billing/orders#carts-across-sellers), a line the coupon
makes free is refused (`cart_free`), as a free item is, and a cart that
carries a subscription or a price paid in parts takes no coupon
(`cart_coupon_mixed`): buy that item on its own to use one. A subscription is
never a free claim: its first payment can be 0, and Stripe's page still
saves the card for the payments after it.

### On a subscription

`duration` says how long the discount lasts:

| `duration`  | The discount applies to                                    |
| ----------- | ---------------------------------------------------------- |
| `once`      | The first payment only. A coupon made without one is this. |
| `repeating` | The first `durationPeriods` payments, 1 to 36              |
| `forever`   | Every payment, for as long as the subscription runs        |

A period is one billing period of the price, whatever its interval: a
weekly price's 3 periods are 3 weeks, a yearly price's are 3 years. Stripe
counts its own repeating coupons in months, so we count the periods
ourselves: each paid invoice the discount came off is counted once, and
after the last one we take the discount off the subscription, so the next
invoice is the full price. A `once` coupon on a price with a free trial is
counted the same way, so the trial's invoice of 0 does not spend it.

The discount is a Stripe coupon on the account the subscription lives on:
yours in the marketplace shape, the seller's in the storefront shape. We
make it the first time a subscription in that currency uses the coupon,
and use it again after that. An amount off is in the subscription's
currency.

- **Your fee** stays a percent of what is paid. Stripe takes it from each
  discounted invoice, so you and the seller share the money off in the
  same proportion, as on a one-time sale. With [Stripe Tax](/billing/tax),
  the tax is worked out on the discounted amount and the split is put
  right as before.
- **An introductory price** and a coupon together: the coupon applies on
  top of the intro price. A `forever` coupon carries on to the regular
  price; a `once` coupon is spent on the first payment.
- **A move to another tier** keeps the discount: Stripe keeps it on the
  subscription, so a percent comes off the new price and an amount off
  stays the same amount, for the periods it has left.
- **Each payment** records what the coupon took off it (`discount` on the
  order's `payments`). Refunding a renewal returns up to what was paid for
  it.

### On a price in parts

The discount comes off the whole price once and is spread over the parts
in proportion to each part's amount, rounded down, with what rounding
leaves on the first part. Every part is charged its discounted amount;
each part shows its share (`discount` on `parts`). Your fee is taken on
each part's discounted amount, and with Stripe Tax each part's tax is
worked out on its discounted amount. `duration` does not apply to parts.

A 15% coupon on a $100.00 price with a 30% deposit: the deposit is $25.50
($30.00 less $4.50) and the balance $59.50 ($70.00 less $10.50).

### Not yet

A subscription with an introductory price or a set number of payments runs
on a Stripe schedule, whose phases are written when it starts. A coupon we
end ourselves cannot be taken off them, so on such a subscription a
`repeating` coupon, and a `once` coupon on a price with a free trial, are
refused (`coupon_not_open`). A `forever` coupon, and a `once` coupon
without a trial, work there.

## Uses

Starting checkout with a code holds one use of it, so the last use cannot
go to two buyers at once. If the checkout ends unpaid, the use is given
back, for a subscription's checkout too. If it is paid (for a price in
parts, its first part), the use is kept. The least order (`minSubtotal`)
is measured against a subscription's first payment and against the whole
of a price in parts. `redeemed` counts the checkouts
holding a use and those paid with it.

## In the console

**Billing > Selling > Coupons** lists your coupons and every seller's, each
with what it takes off, how long it lasts on a subscription, its uses, when
it works and whether it is on. Make a coupon with **New coupon**: the code,
percent or amount, how long it lasts on a subscription (the first payment, a
number of payments from 1 to 36, or every payment), uses in all, once per
buyer, the start and end, and the least order. You can switch off or delete
a seller's coupon too; changing it is the seller's, or your server's.

A coupon's code and kind stay as made; every other term can change. A
change applies to checkouts started after it; a subscription keeps the
terms it was bought with. On an order, each payment and each part shows
what the coupon took off it. The console's **Coupons** resource governs the
tab.

## Seller coupons in your app

A seller makes their own coupons inside your app:

```tsx
import { SellerCoupons } from '@buildbase/sdk/react';

<SellerCoupons subject={{ user: true }} currency="usd" />;
```

`subject` is the signed-in person (`{ user: true }`) or a workspace (its
id, or `{ workspaceId }`). In a workspace, reading needs
`workspace:selling:view` and making, changing or deleting needs
`workspace:selling:manage`; pass `canManage={false}` to show a read-only
list. The seller must have started selling first.

To build the screen yourself, `useSellerCoupons(subject)` returns
`coupons`, `create`, `update` and `remove`, with `pending` and
`actionError`:

```tsx
import { useSellerCoupons } from '@buildbase/sdk/react';

const { coupons, create } = useSellerCoupons({ user: true });
await create({ code: 'SPRING', kind: 'percent', percentBps: 1000 });
```

A seller's coupon can only name the seller's own items in `itemIds`.

## A code at checkout

Turn on the code field in the cart:

```tsx
import { CartSummary } from '@buildbase/sdk/react';

<CartSummary coupons />;
```

The buyer types a code and applies it. The discount and what is left to pay
show under the total, and Check out sends the code. A code that cannot be
used says why under the field, in the reader's language.

With your own cart page, `useCart()` does the same:

```tsx
import { useCart } from '@buildbase/sdk/react';

const cart = useCart();
const applied = await cart.applyCoupon(code);
// applied: false when it cannot be used; the reason is cart.couponProblem
// cart.coupon: the code and what it takes off each line
// cart.discount: minor units off
cart.removeCoupon();
```

A guest passes `{ guest: true }` to `applyCoupon`. The cart asks again
whenever its lines change, and `checkout` sends the applied code. If the
code stops working between the check and checkout, the cart drops it and
sets `couponProblem`.

For one item, pass the code to the button:

```tsx
import { BuyButton } from '@buildbase/sdk/react';

<BuyButton
  itemId={item._id}
  variantId={variant.id}
  priceId={price.id}
  couponCode={code}
/>;
```

A code that cannot be used is refused with the reason under the button.
The order then carries `coupon`: `{ id, code, discount }`, and each of its
lines its own `discount`.

## The API

Make and change coupons:

| Who                     | Route                                            |
| ----------------------- | ------------------------------------------------ |
| A signed-in seller      | `/api/v1/public/me/coupons`                      |
| A workspace as a seller | `/api/v1/public/workspaces/:workspaceId/coupons` |

Each takes `GET /` (a page of coupons, newest first), `POST /` (make one),
`GET /:id`, `PATCH /:id` (every term but `code` and `kind`) and
`DELETE /:id`. Another seller's coupon is "not found".

Check a code before checkout. Both write nothing:

| Who     | Route                                   |
| ------- | --------------------------------------- |
| A buyer | `POST /api/v1/public/cart/coupon`       |
| A guest | `POST /api/v1/guest/:orgId/cart/coupon` |

The body is `{ code, lines }`, the lines as for `POST /cart/checkout` (one
line for a single item). The answer is `{ code, problem, message, discount,
currency, lines, duration, durationPeriods }`: `problem` is null when the code can be used, and
`lines` lists the discount on each line it applies to, by index. A code
that cannot be used is an answer, not an error. The guest check is rate
limited like a guest checkout (`GUEST_CHECKOUT_LIMITED`), and checks a
once-per-buyer coupon against the guest's email only at checkout.

Pay with the code: send `couponCode` in the body of
`POST /api/v1/public/items/:id/checkout`, `POST /api/v1/public/cart/checkout`
or their guest twins under `/api/v1/guest/:orgId`.

## Why a code cannot be used

| Problem                  | The buyer reads                            |
| ------------------------ | ------------------------------------------ |
| `coupon_unknown`         | That code is not valid.                    |
| `coupon_inactive`        | That code is not valid.                    |
| `coupon_not_started`     | That code cannot be used yet.              |
| `coupon_expired`         | That code has expired.                     |
| `coupon_used_up`         | That code has been used up.                |
| `coupon_used_by_you`     | You have already used that code.           |
| `coupon_min_subtotal`    | Add more to your order to use that code.   |
| `coupon_not_these_items` | That code does not apply to these items.   |
| `coupon_currency`        | That code does not apply to this currency. |
| `coupon_not_open`        | That code does not apply to this price.    |

## Error codes

| Code                | Status | When                                                                                  |
| ------------------- | ------ | ------------------------------------------------------------------------------------- |
| `COUPON_NOT_FOUND`  | 404    | No coupon with that id for this owner                                                 |
| `COUPON_CODE_TAKEN` | 409    | Another live coupon in your app has that code                                         |
| `COUPON_INVALID`    | 422    | The coupon cannot be saved as given; `problems` says which field                      |
| `CHECKOUT_REFUSED`  | 422    | The code cannot be used at checkout; `problems` has path `couponCode` and the problem |

## Sales exports

The sales export gains two columns at the end, after `subscription_id`:
`discount` (the money off the order) and `coupon_code`. Earlier columns
keep their places.

The export has one row per order, not per payment. For a subscription,
`discount` is what the coupon took off the first payment; each renewal's
discount is on the order's `payments` (read one order). For a price in
parts it is the discount on the whole price, which the parts share.
