Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/pages/charter.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ review window, and a versioned re-ratification.
`*.ochk.io` reference site. Commitments **03** (open spec), **04** (free
forever), and **05** (offline-verifiable) bind these specifically.

> **A note, not charter text.** On ochk.app the 20% fee is rounded to a whole
> sat and is at least 1 sat, so on the smallest prices it is not exactly 20%: a
> 2-sat price pays a 1-sat fee, and so does a 7-sat price.

## If we ever break one of these

The protocol layer continues to work without us. Every receipt we ever signed
Expand Down
30 changes: 16 additions & 14 deletions src/pages/me/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,15 @@ instead.
| `POST` | `/api/session/refresh` | refresh inside the policy window — free |
| `POST` | `/api/session/invalidate` | tear down — free, needs no session |

## Authenticated · payment authorization (Per action)
## Authenticated · payment intents

| Method | Path | Purpose |
| ------ | ------------------------ | ---------------------------------------------------------------------------------- |
| `POST` | `/api/payment/authorize` | authorize a payment, at most 20 per account per site a UTC day · moves no sats yet |
| Method | Path | Purpose |
| ------ | ------------------------ | ------------------------------------------------------------------------------------------------- |
| `POST` | `/api/payment/authorize` | record a payment intent, at most 20 per account per site a UTC day · moves no sats, earns nothing |

Call it from your server with the person's sign-in token as
It records a payment intent and settles nothing: the answer carries
`mode: 'stub'`, `settles: false` and `is_stub: true`, no sats move, and nobody
earns from it. Call it from your server with the person's sign-in token as
`Authorization: Bearer`: it sends no CORS headers, so a browser on another site
can't call it, and without a session it answers `401 payment_not_authenticated`.
The body takes `amount_sats` (or `usd_cents`) and a `description` of up to 280
Expand Down Expand Up @@ -594,15 +596,15 @@ to another change answers `400 unexpected_field`.

`POST /api/me/projects` takes:

| Field | |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `display_name` | Required. 1–80 characters, under the naming rules above. |
| `domain` | Required. A DNS hostname. |
| `mode` | `'test'` (the default) or `'live'`. |
| `federation_slug` | Optional. One listed at `/api/federations`; without it, the first live one. |
| `enabled_subtypes` | Optional. Built-in events to start with, at their default prices; any other name answers `422 invalid event seed`. Without it, the three defaults. |
| `user_share_pct` | Optional, with `enabled_subtypes`: what your users earn of each default price, `0` to `0.8`. Below `0.8`, each event is saved at `0.8` with the lower price that earns them the same; a lower price outside the platform's price bounds answers `422 invalid event seed`. Missing or out of range, `0.8`. |
| `pay` | Optional. An `add` change, as for pay periods below, to pay from the start; anything else answers `400 invalid_pay`. |
| Field | |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `display_name` | Required. 1–80 characters, under the naming rules above. |
| `domain` | Required. A DNS hostname. |
| `mode` | `'test'` (the default) or `'live'`. |
| `federation_slug` | Optional. One listed at `/api/federations`; without it, the first live one. |
| `enabled_subtypes` | Optional. Built-in events to start with, at their default prices; any other name answers `422 invalid event seed`. Without it, the three defaults. |
| `user_share_pct` | Optional, with `enabled_subtypes`: what your users earn of each default price, above `0` and at most `0.8`. Below `0.8`, each event is saved at `0.8` with the lower price that earns them the same; a share of `0`, which earns nothing, or a lower price outside the platform's price bounds answers `422 invalid event seed`. Missing or out of range, `0.8`. |
| `pay` | Optional. An `add` change, as for pay periods below, to pay from the start; anything else answers `400 invalid_pay`. |

It answers `200 { ok: true, project }`. The signing secret is not in it: reveal
it on the project's **Keys & webhooks** tab.
Expand Down
2 changes: 1 addition & 1 deletion src/pages/me/glossary.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ names do not change.
| **project** | Your app's record on orangecheck: its key, its domain, who can sign in, and whether it pays. | `project_key`, `IntegratorPriceConfig` |
| **who can sign in** | Everyone, or only accounts backed by bitcoin above your bar. [Details](/me/require-bond). | `require_bond`, `projectKey` in `withOcAuth` |
| **Pay your users: Off · Always · Promos** | Whether your events earn your users sats right now. [Details](/me/paying-users). | pay periods, `pay_periods`, `not_paying` |
| **Your user earns N sats** | What one event pays your user. You pay that ÷ 0.8, the 20% fee included: earns 100, you pay 125. | `site_pays`, `user_share_pct` |
| **Your user earns N sats** | What one event pays your user. You pay that plus a fee of 20%, at least 1 sat, so they get about 80%: earns 100, you pay 125. | `site_pays`, `user_share_pct` |
| **One-time · Per action · Per session** | The three event groups: things done once (signing up), each time (a payment), and every session. | class A · B · C |
| **balance** · **Top up** | The sats your project holds to pay its users. "About N days left" is how long it lasts at your current rate. | escrow, runway |
| **Release schedule** | When your users' shares are theirs: right away (the default), or in batches, weekly, monthly or when you say. [Details](/me/drops). | drops, drop windows, `drop.closed` |
Expand Down
5 changes: 3 additions & 2 deletions src/pages/me/integrations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ this is in the sign-in path, and none of it shows unless the person visits.
## Example prices

Prices are yours to set, and only matter if you pay your users. For each event,
set what your user earns. You pay that ÷ 0.8, the 20% fee included: earns 100,
you pay 125. Some starting points, for made-up apps:
set what your user earns. You pay that plus the fee: 20% of the price, rounded
to a whole sat and at least 1 sat, so your user gets about 80%. Earns 100, you
pay 125. Some starting points, for made-up apps:

| Example app | Event | Your user earns | You pay |
| -------------- | ------------------------------------ | ------------------- | ------------------- |
Expand Down
6 changes: 4 additions & 2 deletions src/pages/me/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,10 @@ signed in as yourself, or catch that error, until you go live. Details:
### 2 · what each event earns

For each event, set what your user earns: a fixed number of sats, or a share of
a payment. You pay that ÷ 0.8, the 20% fee included: earns 100, you pay 125. The
smallest earning is 1 sat, and you pay 2.
a payment. You pay that plus the fee: 20% of the price, rounded to a whole sat
and at least 1 sat, so your user gets about 80%. Earns 100, you pay 125. The
smallest earning is 1 sat, and you pay 2. The **Paying** tab shows the exact
price for each earning you set.

| Event | Group | Your user earns | You pay |
| ----------------------- | ----------- | ------------------- | ------------------- |
Expand Down
29 changes: 19 additions & 10 deletions src/pages/me/sdk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1143,13 +1143,17 @@ const paid = await oc.event.fire({

## `oc.config.validate` · check a price config locally

Check your config against the same rules the server enforces, with no network
call. You edit a project's prices on its **Paying** tab at
[ochk.app](https://ochk.app/me/projects); the SDK has no `oc.config.update()`,
because configs belong to projects, not accounts.

For each event you set what your user earns. You pay that ÷ 0.8, the 20% fee
included: earns 100, you pay 125. The smallest earning is 1 sat, and you pay 2.
Check your config's shape and each event's own price limits, with no network
call. me.ochk.io checks more when you save, among them the domain, the
operator's price bounds and a price that earns your user nothing, so a config
that passes here can still be refused. You edit a project's prices on its
**Paying** tab at [ochk.app](https://ochk.app/me/projects); the SDK has no
`oc.config.update()`, because configs belong to projects, not accounts.

For each event you set what your user earns. You pay that plus the fee: 20% of
the price, rounded to a whole sat and at least 1 sat, so your user gets about
80%. Earns 100, you pay 125. The smallest earning is 1 sat, and you pay 2. The
**Paying** tab shows the exact price for each earning you set.

```ts
import type { IntegratorPriceConfig } from '@orangecheck/me-client';
Expand Down Expand Up @@ -1344,7 +1348,7 @@ takes them only with an ochk.app session, so from your site they answer `401`. A
| `oc.session.refresh(id)` | `(string) => Promise<Session>` | ochk.app only. Free. |
| `oc.session.invalidate(id)` | `(string) => Promise<void>` | ochk.app only. Free. |

## `oc.payment.authorize` · a payment (Per action)
## `oc.payment.authorize` · a payment intent

Call it from your server, with the person's sign-in token. me.ochk.io answers no
browser on another site, so your page posts the token from `signInWithOc()` to
Expand All @@ -1370,8 +1374,13 @@ share one. The token decides whose payment it is: me.ochk.io uses its account's
`401 payment_not_authenticated`. The token must come from a sign-in on your live
project's verified domain; one from any other site answers
`403 payment_site_not_registered`. `user_envelope_id` is the receipt's id, the
same as `id`, and `verify_url` opens that receipt. Today the call records the
authorization and moves no sats, because paying is in testing.
same as `id`, and `verify_url` opens that receipt.

The call records a payment intent and settles nothing: the answer carries
`mode: 'stub'`, `settles: false` and `is_stub: true`. No sats move, your balance
is not charged, and the person earns nothing from it. To bill for a payment,
fire the `payment_authorization` event with `oc.event.fire` and its
`payment_amount_sats`.

## Other signals

Expand Down
59 changes: 38 additions & 21 deletions src/pages/reference/multi-account.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -155,41 +155,58 @@ import { useState } from 'react';
function Header() {
const { account, roster, switchAccount, addAccountUrl, trySignOut } =
useOcSession();
const [failed, setFailed] = useState(false);
const [problem, setProblem] = useState<string | null>(null);

async function leave(scope?: 'current') {
const r = await trySignOut(scope ? { scope } : undefined);
if (r.ok) setProblem(null);
else if (r.reason === 'several_sessions')
setProblem(
"Your browser sent more than one ochk.io sign-in, so you weren't signed out. Check which account is signed in now, then try again."
);
else setProblem("Couldn't sign you out. Try again.");
}

async function switchTo(didOc: string) {
try {
await switchAccount(didOc);
setProblem(null);
} catch {
setProblem(
"Couldn't switch accounts. Check which account is signed in now, then try again."
);
}
}

return (
<nav>
<span>signed in as {account?.didOc}</span>
{roster.map((peer) => (
<button
key={peer.didOc}
onClick={() => switchAccount(peer.didOc)}
>
<button key={peer.didOc} onClick={() => switchTo(peer.didOc)}>
switch to {peer.displayName ?? peer.didOc}
</button>
))}
<a href={addAccountUrl()}>add another account</a>
<button
onClick={async () =>
setFailed(!(await trySignOut({ scope: 'current' })).ok)
}
>
leave this account
</button>
<button onClick={async () => setFailed(!(await trySignOut()).ok)}>
sign out of everything
</button>
{failed ? (
<span role="alert">Couldn't sign you out. Try again.</span>
) : null}
<button onClick={() => leave('current')}>leave this account</button>
<button onClick={() => leave()}>sign out of everything</button>
{problem ? <span role="alert">{problem}</span> : null}
</nav>
);
}
```

Use `trySignOut` wherever a person signs out. It resolves to `{ ok: false }`
when the auth host refused or didn't answer, and the session may then still be
live, so say so. `signOut` resolves the same way either way and can't tell you.
Use `trySignOut` wherever a person signs out. It resolves to
`{ ok: false, reason }` when the auth host refused or didn't answer, and the
session may then still be live, so say so. `'several_sessions'` means the
browser sent more than one ochk.io sign-in: ochk.io cleared the extra cookies
and ended nothing, and the provider has read the session again, so the page
shows the account still signed in. `signOut` resolves the same way either way
and can't tell you.

`switchAccount` throws when it did not switch. When the refusal was
`several_sessions`, it reads the session again first and its message is a
sentence for the person. Any other refusal's message names the reason for
developers, so show your own words, as above.

No family package renders the roster for you. `AccountMenu` from
`@orangecheck/app-design/chrome` draws the account button with the items you
Expand Down
Loading