# Repejo embedded checkout

> Put Repejo's checkout on your own page with one script tag and one custom
> element. Repejo renders the amount screen, the donor form, the payment
> (Swish, card, Apple Pay / Google Pay, Autogiro, …) and the thank-you
> screen; your page gets DOM events when the donor is done. No API token
> and no backend are needed. For your own amount buttons or donor form use
> Payment Sessions instead (see the next section).

- TypeScript / React types for every element: https://test.repejo.se/assets/repejo-checkout.d.ts
- Payment Sessions (server-side; own amount / donor UI): https://test.repejo.se/docs/api-payment-sessions.md
- Webhooks: https://test.repejo.se/docs/api-webhooks.md
- Human docs: https://test.repejo.se/docs/website-implementation and https://test.repejo.se/docs/web-component

## Embedded checkout or Payment Sessions?

Decide before writing front-end code; if the integrating organisation hasn't
said which they want, **ask** — the two need different code.

| You want | Build |
|----------|-------|
| Repejo's own amount screen and donor form on your page; no backend | **Embedded checkout** — this document |
| Fixed-amount one-time Swish buttons ("Swisha 100 kr") with no backend | `<repejo-donate-button amount="…">` — this document |
| Your own amount buttons or donor form, or the payment created from your server with per-donor `metadata` | **Payment Sessions** — `POST /api/v1/payment_session`, then redirect or `<repejo-checkout session-token="…">`; needs an organisation API token and an API-type checkout |
| Fundraising team competitions (`team`, `teams-picker`) | Embedded checkout only — not available on payment sessions |

The embedded element **cannot take an amount**: there is no amount attribute
and no amount query parameter. A custom amount picker always means Payment
Sessions. Both integrations can live on the same site.

## Prerequisites

1. A **checkout** created in the Repejo back office (*Settings → Checkouts*).
   Everything the donor sees is configured there, not in markup: the
   periodicities (one-time, monthly, yearly — a checkout can offer two and
   the donor switches between them), the payment methods, predefined and
   minimum amounts, required donor fields, texts, colours, fonts, languages,
   the fundraising goal, and the campaign section (image + story + progress
   bar rendered next to the form when *Branding → Campaign → Show campaign
   section* is on).
2. The checkout's **short code** — the embed handle, e.g. `aunt123`. Copy it
   from the checkout's *Användning* (Usage) tab, which also shows the finished
   snippet. It is **not** the `chk_…` checkout id; that id is only used by
   the Payment Sessions API. If it is unclear which value you were given, ask.
3. The **host**: `https://test.repejo.se` (the test environment — mock Swish,
   Autogiro and BankID, so a donation completes end to end without real
   money) or `https://app.repejo.se` (production). A short code exists only
   on the host where its checkout was created, so the script's `data-host`
   and every element's `host` must name that environment. The examples below
   use `https://test.repejo.se`, the environment that served this document.

## Quickstart

```html
<script defer src="https://test.repejo.se/assets/checkout.js" data-host="https://test.repejo.se"></script>

<repejo-checkout short_code="aunt123" host="https://test.repejo.se"></repejo-checkout>

<script>
  window.addEventListener("repejo:completed", (e) => {
    // e.detail.type: "recurring" | "onetime" | "gift_card" | "lead" — show your
    // own thank-you, or:
    window.location.href = "/thank-you";
  });
</script>
```

Load the script once per page (in `<head>` or before `</body>`); it defines
every `repejo-*` element below. The snippet on the Usage tab also carries
`data-organisation-id="org_…"` — on `checkout.js` it only feeds the Meta
Pixel page view on script load, so keep it if you copied it and leave it
out otherwise. (Repejo's own hosted page loads a separate consent script
that reads it too; that script is not part of an embed.)

A checkout has no thank-you redirect setting: after a completed donation the
element shows its own confirmation screen (text from branding). To send the
donor somewhere else, act on `repejo:completed`.

## Elements

Every element takes `short_code` and `host`. `short_code` is the canonical
attribute name (it mirrors the Lit property); `<repejo-checkout>` accepts the
kebab-case `short-code` as an alias. The live widgets at the end of this
document use `short-code`.

### `<repejo-checkout>` — the checkout inline on your page

Renders whichever checkout the short code points to: recurring donation,
one-time donation, membership, gift card, petition / lead form, … The form
sits in a card whose background, border and shadow come from branding (see
Styling).

| Attribute | Type | Notes |
|-----------|------|-------|
| `short_code` | string | Required. Alias: `short-code`. |
| `host` | URL | The Repejo environment. Defaults to `https://app.repejo.se` — set it explicitly. |
| `team` | `ftm_…` | Attribute the donation to a fundraising team in a team competition — the embedded form of the hosted page's `?rp_team=`. An unknown or unlinked id is ignored and the plain checkout renders. |
| `teams-picker` | boolean | Show a "choose your team" screen first, then the checkout. Skipped when `team` (or `?rp_team=`) is already known. Hosted-page equivalent: `?teams=picker`. |
| `metadata-<key>` | string | One attribute per public custom attribute, e.g. `metadata-member_id="42"`. Only keys defined as **public** under *Settings → Custom attributes* are accepted (`a-z`, `0-9`, `_`); other keys are ignored. Stored on the donor's session and echoed as `metadata` on the payer / subscription / transaction and on their webhooks. |
| `payer-name`, `payer-email`, `payer-phone-number` | string | Prefill the donor form. Validated per field server-side; a malformed value is dropped silently instead of blocking the donation. |
| `user-has-consented-to-marketing-cookies` | boolean | Set it when your consent tool reports marketing consent, so Meta Pixel / dataLayer events may fire. With Cookiebot on the page the consent is read automatically. |
| `live-product-id` | `prod_…` | Preselect a live product (the donor arrived via `<repejo-live-products>`); pins a summary card of it above the form. |
| `session-token`, `bare` | — | **Payment Sessions only**: a server-created session, and the chrome-less rendering it allows. Not for a plain embed — `bare` is ignored on every other checkout type. |

Create it from JavaScript by setting attributes or properties, before or
after `appendChild` — the element fetches the checkout once `short_code` is
set:

```js
const el = document.createElement("repejo-checkout");
el.host = "https://test.repejo.se";
el.short_code = "aunt123";
el.setAttribute("metadata-member_id", "42");
document.querySelector("#donate").appendChild(el);
```

Older snippets use `<repejo-onetime-checkout>` for a one-time checkout. It
takes only `short_code` and `host` — `metadata-*`, `team`, `teams-picker`
and the `payer-*` prefill are silently ignored on it — so use
`<repejo-checkout>`, which renders the same form and supports every
attribute above.

### `<repejo-checkout-button>` — a button that opens the checkout in a modal

```html
<repejo-checkout-button short_code="aunt123" host="https://test.repejo.se"></repejo-checkout-button>
```

The label comes from the checkout's branding (*checkout button text*).
Attributes: `short_code`, `host`, `size` (`default` or `large`), `team`,
`teams-picker`, `metadata-<key>`, `payer-name` / `payer-email` /
`payer-phone-number`, `user-has-consented-to-marketing-cookies` — same
meaning as above; prefill and metadata are forwarded to the modal's
checkout. While the modal is open the page URL carries
`?rp_modal=<short_code>`, which is what reopens it when the donor returns
from the Swish app — leave that parameter alone in your router.

### `<repejo-sticky-checkout>` — an always-visible side tab that opens the modal

```html
<repejo-sticky-checkout short_code="aunt123" host="https://test.repejo.se"></repejo-sticky-checkout>
```

Place it once in the site layout; it renders a tab on the left edge of the
viewport and opens the same modal as the button. Same attributes as the
button except `size`.

### `<repejo-donate-button>` — one-click Swish with a preset amount

```html
<repejo-donate-button short_code="swish1" amount="100" host="https://test.repejo.se"></repejo-donate-button>
```

For a **one-time** checkout. `amount` is in the checkout's currency (`100` =
100 SEK — not öre). The direct Swish flow needs Swish to be the checkout's
**only one-time payment method**: the button then reads "Swisha 100 kr" and
a click starts a Swish donation of that amount — on a phone the Swish app
opens with the amount filled in, on desktop the donor enters a phone number
first. With more than one one-time method enabled the button reads
"Donera 100 kr" and the modal opens on the payment-method picker instead.
Every donor field the checkout marks as required is still asked for in the
modal, so for a true one-click experience create a dedicated one-time
checkout with Swish alone and only the phone number required. Several
buttons with different amounts give you an amount picker without a
backend. Attributes: `short_code`, `host`, `amount` (required), `team` (no
team picker here), `payer-name` / `payer-email` / `payer-phone-number`,
`user-has-consented-to-marketing-cookies`.

### The hosted page — no embed at all

Every checkout also has a landing page on Repejo's host, `https://test.repejo.se/s/aunt123`
(share the link or the QR code from the Usage tab). `?rp_team=ftm_…`
attributes donations to a team; `?teams=picker` opens the team picker first.

## Events

The elements dispatch DOM `CustomEvent`s that **bubble up to `window`**.
`<repejo-checkout>` dispatches them itself, so listening on the element
works too; the modal embeds (`<repejo-checkout-button>`,
`<repejo-sticky-checkout>`, `<repejo-donate-button>`) render the checkout
in a portal at the end of `<body>`, so listen on `window` or `document`,
not on the button.

| Event | When | `event.detail` |
|-------|------|----------------|
| `repejo:first-interaction` | the donor first interacts with the form | — |
| `repejo:registered` | the donor left the amount / contact step; on a petition or lead checkout this is the sign-up itself | — |
| `repejo:completed` | recurring mandate signed, one-time payment captured, gift card bought, or lead registered | see below |
| `repejo:cancelled` | **Payment Sessions only** — the donor pressed the cancel button of an embedded session | `{ session_token }` |

A plain embed never fires `repejo:cancelled`: there is no cancel step, the
donor just leaves the page or closes the modal.

```ts
type RepejoCompletedEventDetail =
  | {
      checkout_short_code: string;
      type: "recurring" | "onetime" | "gift_card";
      payer: { name: string | null; phone_number: string | null; email: string | null };
      metadata: Record<string, string>; // the metadata-* attributes, echoed back
    }
  | { type: "lead" } // petition / lead sign-up: no payer, no metadata
  | { type: "onetime"; source: "post_signature_donation" }; // a gift added after signing a petition
```

Only `repejo:completed` is part of the contract; the other events are
informational. The events are a UX convenience (thank-you page, analytics)
— for bookkeeping use webhooks (below). The declaration file at
`https://test.repejo.se/assets/repejo-checkout.d.ts` types the events on `window` and on
the elements, the element properties, and JSX for React.

## Styling

Typography is host-overridable with CSS custom properties on the element:

```css
repejo-checkout {
  --font-sans: Roboto, sans-serif;
  --font-weight-xl: 400;
  --font-weight-lg: 500;
  --font-weight-base: 400;
}
```

Available: `--font-sans` (base font) and, per size, `--text-xl` /
`--font-family-xl` / `--font-weight-xl` (large headings, 24px, weight 600),
`--text-lg` / `--font-family-lg` / `--font-weight-lg` (subheadings, 18px,
500), `--text-base` / `--font-family-base` / `--font-weight-base` (body,
16px, 400), `--text-sm` / `--font-family-sm` / `--font-weight-sm` (small,
14px, 600) and `--text-xs` (12px). A web font must be loaded by your page
(`@font-face` or a font link) before the element renders.

The card itself — background, border, shadow, width — every colour, and the
input corner radius and text inset come from the checkout's **branding** in
the back office. They are **not** overridable from host-page CSS variables:
branding writes them onto the component, so a page-level
`--color-checkout-background` has no effect. To blend the checkout into a
coloured page, set the checkout background to the page colour (or
transparent), border width 0 and "hide box shadow" in branding. Pill-shaped
inputs need a larger text inset so the text clears the curve. `bare` removes
the card entirely, but only on Payment Sessions.

## Reconciling donations

Every completed donation lands in the back office (the donor under
*Givare*, the payment under the checkout's transactions). For your own
systems, register a webhook endpoint in the back office and subscribe to:

- `payer.created` / `payer.updated` — the donor record.
- `subscription.created` / `subscription.updated` — a recurring mandate signed on a recurring checkout.
- `receivable.created` / `transaction.created` (and their `.updated`) — a one-time payment.

Your public `metadata-*` values arrive as `data.metadata` on each of them.
`payment_session.*` events fire **only** for Payment Sessions — a plain embed
never emits them. Payload shapes, HMAC-SHA256 signature verification and the
retry / idempotency contract: https://test.repejo.se/docs/api-webhooks.md

## Analytics

With *Settings → Integrations → Google Tag Manager* on, the payment
checkouts (recurring, one-time, membership, gift card) push GA4 funnel
events, a server-built `purchase` and `repejo_click` element interactions to
the page's `dataLayer`, gated on marketing consent (Cookiebot or the consent
attribute above); petition / lead checkouts push none of these:
https://test.repejo.se/docs/gtm-datalayer.md

## Live widgets

Standalone elements that read the same checkout and update live over a
websocket — for a campaign page next to (or instead of) the form. They take
`short-code` (kebab-case), `host` and an optional `locale`, style themselves
through a few attribute shortcuts (`background-color`, `text-color`, …) plus
per-widget CSS custom properties, and need `checkout.js` on the page:

| Element | Shows |
|---------|-------|
| `<repejo-progress-bar>` | raised vs goal (or signatures vs signature goal) in the checkout's branding |
| `<repejo-donation-goal>` | goal progress bar with sub-goal ticks; `team-id="ftm_…"` tracks one team |
| `<repejo-donation-list>` | the three most recent donations, rolling |
| `<repejo-donation-ticker>` | a scrolling news-ticker of recent donations |
| `<repejo-leaderboard>` | top donors or teams (`mode` = `teams`, `onetime` or `recurring`; `limit`) |
| `<repejo-top-donation>` | the single biggest one-time gift |
| `<repejo-donation-card>` | stream overlay (OBS / Twitch) popping a card per donation |
| `<repejo-matching-donation-card>` / `-row` | an active sponsor matching campaign |
| `<repejo-live-products>` | a mini-shop of preset gift products that opens the checkout |

The full attribute and CSS-variable reference for each widget is in
`https://test.repejo.se/llms.txt` (section "Embeddable elements").
