Features
Deliverables: what a paid order produces
The payment provider says "this order was paid". A deliverable decides what
the buyer receives. site/lib/deliverables/config.ts lists them:
| Deliverable | The buyer gets | Typical product |
|---|---|---|
licence-key (default) | The provider's licence key, activated by the Mac app | A paid Mac app |
github-invite | Read access to a private GitHub repository | A template, source code, a course (and the template's own selling site) |
Both at once is allowed: ["licence-key", "github-invite"] sells an app with
its source code.
Every paid order also gets a receipt. Every refund gets a confirmation email
that says plainly what stopped working. All of it runs in the payment webhook
as best-effort effects, so a slow GitHub or a mail outage never turns a
settled payment into a retry (site/lib/deliverables/index.ts).
licence-key
Nothing to set up beyond the payment provider (docs/go-live.md). The provider
issues the key and emails it; the thank-you page shows it; the app activates it
through the site's licence routes. A refund revokes the key at the provider,
and the app notices at its next weekly check.
github-invite
How it works
-
Checkout asks for the buyer's GitHub username in a custom field keyed
github_username(GITHUB_USERNAME_FIELDinconfig.ts). -
The paid webhook invites that account with read access (
pull) and emails the buyer:- "invitation sent" when GitHub accepted it;
- a link to
/claimwhen the field was empty, misspelled, or GitHub refused.
-
/claimlets the buyer name the right account later:- they enter their email and username;
- if that address bought, it receives a signed, 24-hour confirmation link;
- opening the link re-checks the purchase and sends the invitation.
The form answers everyone identically, so it cannot reveal who bought. Typing a buyer's email gets a stranger nothing, because the link goes to the buyer. No database: the link carries its own signature (
site/lib/deliverables/claim.ts). -
A refund removes access. Both Creem and Polar send the original checkout's custom fields with the refund. The username comes back with it, so the collaborator is removed and any unaccepted invitation withdrawn.
-
When the site cannot finish, it tells you. An invitation refused because the token is wrong, or a refund with no username (access claimed through
/claimleaves none on the order), sends an email toSITE.supportEmailsaying exactly what to do by hand.
Setup
-
In
site/lib/deliverables/config.ts, add"github-invite"toDELIVERABLES. -
A private repository holding what you sell →
GITHUB_ACCESS_REPO=owner/name. -
A fine-grained personal access token (GitHub → Settings → Developer settings → Fine-grained tokens):
- Repository access: only that repository.
- Permissions: Administration: Read and write, and nothing else. This is the permission GitHub requires to manage collaborators.
→
GITHUB_ACCESS_TOKEN. Set a reminder for its expiry: an expired token turns every sale into a "claim link" email and a note to you. -
CLAIM_SECRET: at least 32 random characters (openssl rand -base64 48). -
The checkout field, a required text field keyed exactly
github_username:- Creem: the product's custom fields;
- Polar: Settings → Custom Fields, then attach it to the product.
-
Creem only: turn on licence keys for the product, even though the buyer never uses one. Creem's proof of purchase for
/claimis a licence key issued to the address (hasPurchasedinsite/lib/payments/creem/adapter.ts). -
Test once, end to end, in the provider's test mode:
- a purchase with your own GitHub username → the invitation arrives;
- a purchase with the field left empty → the claim link → the confirmation email → the invitation;
- a refund → your access is gone.
Limits worth knowing
- GitHub invitations expire after seven days. A buyer who waited too long
uses
/claim, which sends a fresh one. - GitHub caps the collaborators a repository can have by plan. If you
expect more buyers than your plan's cap, sell through an organization with
a team instead: invite to the team (same token permission on the org). This
is a change to
github.ts, not included yet. - Removing access does not delete code a buyer already cloned. The refund
email says so; the licence (
LICENSE) is what governs what they may do with it.
This page is docs/deliverables.md in the repository, copied 2026-09-25.