# Donations

URL: https://developers.uidu.org/docs/domains/donations

> Fetch donation campaigns and accept donations against uidu's fundraising API.

* **Use it for:** [Public website](https://developers.uidu.org/docs/paths/public-website.md) · [Server integration](https://developers.uidu.org/docs/paths/server-integration.md)
* **Reads:** public token — works in the browser
* **Writes:** Bearer (`apiKey`) — server-side only
* **CLI:** `uidu donations`
* **Auth:** what each token can do and where it may run — [Authentication](https://developers.uidu.org/docs/foundations/authentication.md)

Donation campaigns are your workspace's fundraisers — a charity drive, a sponsorship pool, a
crowdfunding goal. You show them and their progress with the public token, and record each gift
with `createDonation` from your server.

## Mental model

* **`DonationCampaign`** — the fundraiser: `name`, a rich-text `body` (Tiptap JSON — render it with
  [`<RichText>`](https://developers.uidu.org/docs/tools/react/components.md)), `cover`, `kind`, `startsAt`, `publishedAt`,
  `publicPath` / `publicUrl`, a `primaryAddress`, and live totals: `goal`, `donationsAmount`,
  `donationsCount`, `donationsAverage&#x60;. It may carry a &#x2A;*`form`** — a donor questionnaire with the same
  shape as [Forms](https://developers.uidu.org/docs/domains/forms.md) — for anything beyond name and email.
* **`Donation`** — one gift to one campaign (`donationCampaignId`): `amount`, `currency`, the donor
  as a contact, and optionally a message (`body`), a `dedication` ("in memory of…") and the answers to
  the campaign's form (`formResponseAttributes`). It comes back with `id`, `amount`, `currency`,
  `createdAt` and `contact.id`.

Shapes and gotchas:

* **Money is in cents.** `goal`, `donationsAmount`, `donationsAverage` and a donation's `amount` are
  integers in the currency's minor unit: `5000` is 50.00. Divide by 100 to display, multiply by 100
  (and round) before you write. `currency` is an ISO code: `'EUR'`, `'USD'`…
* **Recording is not charging.** `createDonation` records the gift and the donor; taking the money is a
  separate step with your payment processor (Stripe, …), unless payment runs through uidu's checkout
  (`orderAttributes` / `subscriptionAttributes`, below).
* **A gift is not a campaign.** `createDonation` records a gift *to* a campaign;
  `createDonationCampaign` creates the campaign itself.
* Both mutations return the payload as-is — `{ errors, donation }` / `{ errors, donationCampaign }`,
  or null — with validation failures in `errors`, not thrown. Both need the `apiKey` client: a
  donation is submitted from a server action, never with the public token in the browser. See
  [Writes](https://developers.uidu.org/docs/foundations/writes.md) and [Authentication](https://developers.uidu.org/docs/foundations/authentication.md).

## Use cases

### Campaigns page with progress bars

Goal: list the active campaigns, each with how much it has raised against its goal.

```tsx
// app/donate/page.tsx — a Server Component
import Link from 'next/link';
import { listDonationCampaigns } from '@uidu/client';
import { uidu } from '@/lib/uidu';

const euros = (cents: number | null | undefined) =>
  ((cents ?? 0) / 100).toLocaleString('en', {
    style: 'currency',
    currency: 'EUR',
  });

export default async function CampaignsPage() {
  const campaigns = await listDonationCampaigns(uidu, { first: 20 });

  return (
    <ul>
      {campaigns.map((c) => {
        const pct = c.goal
          ? Math.min(100, ((c.donationsAmount ?? 0) / c.goal) * 100)
          : 0;
        return (
          <li key={c.id}>
            <Link href={`/donate/${encodeURIComponent(c.id)}`}>{c.name}</Link>
            <div className="h-2 rounded-full bg-slate-200">
              <div
                className="h-full rounded-full bg-emerald-500"
                style={{ width: `${pct}%` }}
              />
            </div>
            <p>
              {euros(c.donationsAmount)} raised of {euros(c.goal)} ·{' '}
              {c.donationsCount ?? 0} donors
            </p>
          </li>
        );
      })}
    </ul>
  );
}
```

* Summaries are cheap: `id`, `name`, `cover`, `goal`, `donationsAmount`, `donationsCount`,
  `startsAt`, `publishedAt`, `publicPath`. The `body`, `donationsAverage` and the donor `form` come
  only from `getDonationCampaign`.
* A campaign without a `goal` has no bar to fill — guard the division, as above.

### Campaign page with a donation form

Goal: show one campaign and let a visitor give an amount, with an optional message.

```tsx
// app/donate/[id]/page.tsx
import { createDonation, getDonationCampaign } from '@uidu/client';
import { RichText } from '@uidu/react';
import { notFound } from 'next/navigation';
import { uidu } from '@/lib/uidu'; // createClient({ workspace, publicToken, apiKey })

export default async function CampaignPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const campaign = await getDonationCampaign(uidu, {
    id: decodeURIComponent(id),
  });
  if (!campaign) notFound();
  const campaignId = campaign.id;

  async function donate(formData: FormData) {
    'use server';
    const message = String(formData.get('message') ?? '');
    const result = await createDonation(uidu, {
      input: {
        attributes: {
          donationCampaignId: campaignId,
          amount: Math.round(Number(formData.get('amount')) * 100), // euros in, cents out
          currency: 'EUR',
          contactAttributes: {
            email: String(formData.get('email')),
            contactableAttributes: {
              kind: 'person',
              firstName: String(formData.get('firstName')),
            },
          },
          body: message
            ? {
                type: 'doc',
                content: [
                  {
                    type: 'paragraph',
                    content: [{ type: 'text', text: message }],
                  },
                ],
              }
            : undefined,
        },
      },
    });
    if (result?.errors?.length) return; // return result.errors to the form
    // result?.donation?.id, result?.donation?.amount (cents) — now charge with your processor
  }

  return (
    <article>
      <h1>{campaign.name}</h1>
      {campaign.body && <RichText doc={campaign.body} />}
      <form action={donate}>
        <input name="amount" type="number" min="1" step="0.01" required />
        <input name="firstName" required />
        <input name="email" type="email" required />
        <textarea name="message" />
        <button>Donate</button>
      </form>
    </article>
  );
}
```

* **The donor.** `contactAttributes` creates the donor's contact in the same mutation — a new contact
  on every gift unless you pass the `id` of an existing one inside it (`contactAttributes: { id }`).
  `DonationAttributes` also has `contactId`.
* **Message and dedication** are JSON — typically a Tiptap document, as above.
* **The campaign's form.** When `campaign.form` is set, render it like any form (its questions are at
  `campaign.form.inputs`) and send the answers as
  `formResponseAttributes: { formId: campaign.form.id, fieldValuesAttributes: [...] }` — each
  `content` is `{ value }`, see [Forms](https://developers.uidu.org/docs/domains/forms.md).
* **Paying through uidu's checkout.** `createDonation`'s input also takes `orderAttributes` (a
  one-time payment) or `subscriptionAttributes` (recurring billing), next to `attributes`. With either,
  the donation's `amount` is computed from the order's or the plan's price, not from the `amount` you
  send. The attributes they expect depend on how checkout is configured in your workspace — see
  [Schema](https://developers.uidu.org/docs/reference/schema.md) for `OrderAttributes` and `SubscriptionAttributes`.
* The demo's version, with error handling, is `submitDonation` in `apps/demo/src/lib/actions.ts`.

### Launch a campaign from your server

Goal: create a fundraiser with a goal from a script or an admin tool.

```ts
import { createDonationCampaign } from '@uidu/client';
import { uidu } from '@/lib/uidu'; // must carry apiKey

const result = await createDonationCampaign(uidu, {
  input: { attributes: { name: 'Winter appeal', goal: 500000 } }, // €5,000.00 in cents
});

if (result?.errors?.length)
  throw new Error(result.errors.map((e) => e.name).join(', '));
result?.donationCampaign?.id;
```

* `DonationCampaignAttributes` also takes `body`, `abstract`, `cover`, `kind`, `category`, start and
  end dates and `publishedAt` — see [Schema](https://developers.uidu.org/docs/reference/schema.md). Change it later with
  `updateDonationCampaign`, remove it with `deleteDonationCampaign`.
* The same from a terminal: `uidu donations create | update | delete` — [CLI](https://developers.uidu.org/docs/tools/cli.md).

## API at a glance

Every `@uidu/client` function in **Donations**, generated from the source — the [full reference](https://developers.uidu.org/docs/reference/client.md#donations) has the rest of the SDK.

| Function                                                                                                  | Kind  | Auth         | CLI                | Description                                                                                                                   |
| --------------------------------------------------------------------------------------------------------- | ----- | ------------ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| [`createDonation`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/donations.ts)         | write | Bearer       | —                  | Record a donation (amount in cents) to a campaign, creating the donor's contact inline; returns `{ errors, donation }` as-is. |
| [`createDonationCampaign`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/donations.ts) | write | Bearer       | `donations create` | Create a donation campaign (`goal` in cents); returns the payload as-is, `{ errors, donationCampaign }`.                      |
| [`deleteDonationCampaign`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/mutations.ts) | write | Bearer       | `donations delete` | Delete a donation campaign by `{ input: { id } }`. Returns the payload (with `errors`) or null.                               |
| [`getDonationCampaign`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/donations.ts)    | read  | public token | `donations get`    | One donation campaign with its body, its totals (`goal`, `donationsAmount`, in cents) and its donor `form`, or null.          |
| [`listDonationCampaigns`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/donations.ts)  | read  | public token | `donations list`   | The workspace's donation campaigns as a flat array of summaries with their totals in cents; page with `first`/`after`.        |
| [`updateDonationCampaign`](https://github.com/uidu-org/api.js/blob/main/packages/client/src/mutations.ts) | write | Bearer       | `donations update` | Update a donation campaign: `{ input: { id, attributes } }`. Returns the payload (with `errors`) or null.                     |

**Types:** `DonationCampaign`

**CLI:**

| Entity      | list | get | create | update | delete |
| ----------- | :--: | :-: | :----: | :----: | :----: |
| `donations` |   ✓  |  ✓  |    ✓   |    ✓   |    ✓   |
