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

# Sell paid listings

> Charge visitors to add an entry to one of your CMS collections.

The CMS becomes a marketplace primitive when you wire it to a payment plan
with `purpose: 'cms_item'`. The fulfillment pipeline runs automatically:
once the buyer pays, the item is created (or queued for review) without
any custom webhook code.

## 1. Create the plan

From the dashboard, create a `payment_plan` and set:

| Field         | Value                                                                     |
| ------------- | ------------------------------------------------------------------------- |
| `purpose`     | `cms_item`                                                                |
| `fulfillment` | `{ "collection": "sponsors", "duration_days": 30, "moderation": "auto" }` |
| `provider`    | `stripe` (embedded checkout) **or** `invoice` (custom redirect link)      |

## 2. Show a checkout button on your site

```ts theme={null}
import { platform } from '@/lib/platform';

async function buy() {
  const result = await platform.cms.submit('sponsors', {
    data: form.values,
    payment_plan_id: 42,
    return_url: window.location.href,
  });

  if (result.status === 'checkout' && result.mode === 'embedded') {
    // mount Stripe embedded checkout with result.clientSecret
  } else if (result.status === 'checkout' && result.mode === 'redirect') {
    window.location.href = result.redirectUrl;
  }
}
```

## 3. What happens behind the scenes

1. The buyer's form data is stashed in `cms_pending_submissions`.
2. They are sent to checkout (Stripe embedded or custom redirect).
3. On payment success, the Stripe webhook calls the unified fulfillment
   dispatcher, which:
   * reads the pending submission,
   * validates it against the collection schema,
   * inserts a `cms_item` with `source = 'payment'` and `expires_at` based
     on `duration_days`,
   * emits a `purchase.fulfilled` event.

If `moderation` is set to `manual`, the new item is created with
`status = 'pending_review'` and a dashboard notification fires. Otherwise it
goes live immediately.
