# Bundles

A seller sells several of their own items together, at one price - the buyer gets everything each included item gives.

A bundle is an [item](/billing/items) that includes other items of the same
seller, sold together at one price: "Course + ebook + 1:1 call", or "3 mugs".
Buying it gives the buyer everything each included item gives, as if each
were bought on its own.

> **Note:**

**Arrives with selling.** Bundles 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.** The "Included items" section of `<ItemEditor>`, `ItemBenefit.quantity`, `ItemView.includes`, `OrderLineView.components` and `HoldingView.bundle` 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

"Course + ebook + call" or "3 mugs": several of one seller's items, sold
together at one price.

### Before you start

An item type that allows the `item` benefit kind (the **Bundle** starter
does), and the included items live, sold by checkout at a one-time price.

### Set it up in the console

Under **Billing**, **Item types**, start a type from the **Bundle**
starter, or allow the item benefit on your own type, and publish it. A
bundle's included items then show on its page under **Billing**,
**Selling**, **Items**, and an order lists what each line included.

### Marketplace and storefront

The same in both: your fee is taken on the bundle's price, and the money
goes as for any line of that shape.

### What the seller sees

The "Included items" section of `<ItemEditor>`, and **Needs update** on a
bundle when an included item stops selling.

### What the buyer sees

One price; after paying, everything each included item gives, each grant
marked with the bundle it came from (`bundleContents` lists them).

### Emails and webhooks

As any order: **Order Receipt**, `order.paid`, and one `grant.created` per
benefit of each included item.

### Limits

Up to 10 included items, each 1 to 100 times, one-time prices only, no
bundle inside a bundle.

### Staging and Production

Nothing of its own: it follows its items.

## How a bundle is made

In the console, a bundle's included items show on its page under **Billing >
Selling > Items**, each linked to its own page, with a mark on any that cannot
be sold with the bundle now; an order of a bundle lists what each line
included under **Billing > Selling > Orders**.

A bundle is an ordinary item whose benefits include `item` benefits: each
names one of the seller's items, an option of it (`variantId`, the first
when unset) and how many (`quantity`, 1 to 100). The item type must allow
the `item` benefit kind (the "Bundle" starter does).

```json
{
  "title": "Course + ebook + call",
  "benefits": [
    {
      "id": "course",
      "kind": "item",
      "title": "The course",
      "itemId": "<course id>"
    },
    {
      "id": "ebook",
      "kind": "item",
      "title": "The ebook",
      "itemId": "<ebook id>"
    },
    {
      "id": "mugs",
      "kind": "item",
      "title": "Mugs",
      "itemId": "<mug id>",
      "quantity": 3
    }
  ],
  "variants": [
    {
      "id": "std",
      "prices": [
        { "id": "usd", "currency": "usd", "mode": "fixed", "amount": 5900 }
      ]
    }
  ]
}
```

Sellers make bundles in `<ItemEditor>` ("Included items"), through the
same item routes as any item.

## Rules

A bundle includes at most 10 items, each once, and sells one-time prices
only. An included item must be:

- the same seller's, and not the bundle itself or another bundle;
- live (published or unlisted);
- sold by checkout, at a one-time price, to anyone (no named buyers, no
  co-sellers);
- delivering only what selling delivers today.

An included item may be booked by time: the buyer picks its slot at the
bundle's checkout (`slotStarts`), and a bundle may include at most one
slot's seats of it. See [Booking calendars](/billing/bookings#in-a-bundle).

Publishing refuses a bundle that breaks a rule, with the problem at
`benefits.<id>`:

| Code                        | Means                                                         |
| --------------------------- | ------------------------------------------------------------- |
| `too_many_items`            | More than 10 included items                                   |
| `duplicate_item`            | The same item included twice                                  |
| `bundle_one_time_only`      | The bundle has a subscription or a price paid in parts        |
| `bundle_self`               | The bundle includes itself                                    |
| `bundle_item_not_found`     | The included item no longer exists                            |
| `bundle_other_seller`       | The included item is another seller's                         |
| `bundle_item_not_live`      | The included item is not on sale                              |
| `bundle_unknown_variant`    | The included item has no such option                          |
| `bundle_nested`             | The included item is a bundle                                 |
| `bundle_item_listing`       | The included item is a listing, with no checkout              |
| `bundle_item_recurring`     | The included item sells by subscription or in parts           |
| `bundle_item_restricted`    | The included item is for named buyers only                    |
| `bundle_item_co_sold`       | The included item is shared with co-sellers                   |
| `bundle_item_undeliverable` | The included item delivers something selling does not yet     |
| `bundle_item_booking_seats` | The bundle includes more of a booked item than a slot's seats |

When an included item stops selling (archived, back to draft, rejected),
the bundle stays live and is marked "Needs update"; checkout refuses it
with `CHECKOUT_REFUSED`, problem `bundle_item_unavailable`, until the
seller fixes it. Buyers who already bought it keep what they got.

## Buying a bundle

- **Price.** One price, the bundle's; the app's fee is taken on it, and a
  [coupon](/billing/coupons) applies to it like any line.
- **Stock.** Checkout holds the stock of every included item that counts
  stock (quantity times bundle quantity) and gives it back if the checkout
  ends unpaid. Short of any, it refuses with `OUT_OF_STOCK` and holds
  nothing. A one-of-a-kind item cannot be included more than once
  (`bundle_single_quantity`).
- **What the buyer gets.** Each included item's benefits, granted from that
  item (`grant.itemId`), each marked with the bundle it came from
  (`bundle`). Access checks, files and licence keys work as for a direct
  purchase.
- **The order.** The line keeps a copy of what was included
  (`components`), and the [sales export](/billing/orders) has a
  `bundle_items` column.
- **Refunds.** A full refund of a bundle line revokes each included item's
  grants by that item's own after-refund rule; a partial refund revokes
  nothing.

## Reading what a bundle includes

Item reads (the seller's, the console's, and the public catalog) carry
`includes`: each included item's `itemId`, `variantId`, `quantity`,
`title`, `variantLabel`, `cover`, whether it can be sold with the bundle
now (`available`), and whether it is booked by time (`bookable`).

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

const contents = bundleContents(item); // [{ title: 'Wheel course', quantity: 1, available: true }, ...]
```

## Errors

A refused request answers `{ error, message, code }`, with `problems` naming
the included item. The console shows the `message`; the SDK shows each
`code` in the reader's language.

| `code`                | What the person sees                                                                         | What to do                                                        |
| --------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `ITEM_INVALID`        | This item cannot go live yet, with the problem at `benefits.<id>` (codes in [Rules](#rules)) | Fix the bundle so it follows the rules, then publish again        |
| `CHECKOUT_REFUSED`    | An included item cannot be sold with the bundle now (`bundle_item_unavailable`)              | The seller fixes the included item or the bundle ("Needs update") |
| `OUT_OF_STOCK`        | Not enough left for this order                                                               | Wait for the seller to restock the included item, or buy fewer    |
| `ITEM_STATE_CONFLICT` | This item changed meanwhile. Reload it and try again                                         | Reload the bundle and try again                                   |
