Checkout: buying a plan from a link
An app owner can sell any of its plans from a link, to someone who has no account yet. The link opens the suite portal's Buy page; the buyer proves their email, gives billing details and pays; the paid plan is then claimed into an organization, by signing in (or creating an account) at the link in the receipt email.
The link
<portal>/buy?app=<product code>&plan=<plan code>&org=<app owner slug>
Each row of a product's Plans tab has a Copy payment link button. It needs the portal URL
(SUITE_PORTAL_URL of the Licentra app) and the app owner's slug (shown on the organization page).
The Buy page checks all three with GET /v1/public/checkout/quote; anything that does not match answers
404, so a link cannot be used to find out which vendors exist. Only the owner's active plans are sold
(never a reseller's).
The flow
| Step | Call (no sign-in unless noted) | What it proves or does |
|---|---|---|
| 1 | POST /v1/public/checkout/email-codes | Emails a 6-digit code (10 minutes). 5 per address and 20 per client IP per hour |
| 2 | POST /v1/public/checkout/email-codes/verify | A right code returns a verification token (30 minutes, single use). 5 wrong tries lock the code |
| 3 | POST /v1/public/checkout/orders | Spends the token (the order's email is the verified one) and creates the order with billing name and address. Returns an order token and the product's public payment settings. A free plan is granted here and returns its claim token |
| 4 | POST /v1/public/checkout/orders/{id}/pay | Needs the order token and a payment token. Marks the order paid, mints the claim token and emails the claim link |
| 5 | POST /v1/admin/me/claims (signed in) | Issues the license to the organization the account acts for (found or created as a client organization) |
Only SHA-256 hashes of codes and tokens are stored. The claim token is single use per organization: claiming
again from the same organization returns the same license; another organization gets 409. It is valid for 90
days. The plan starts at the claim, not at the payment, and the claiming organization must not already have an
active license for the product.
Free plans and repeat sign-ups
A plan with no price (or a price of 0) skips payment but not the email check. It is granted once per person and product: the address is normalized (plus-tags dropped, Gmail dots ignored), and a unique index backs the rule up under races. A business email domain also gets one free plan per product; shared providers (gmail.com, outlook.com, …) are exempt from the domain rule only.
Payment
Each product has its own payment settings (Plans tab, GET/PUT /v1/admin/products/{id}/payment-settings,
app_owner_admin to edit): the Google Pay gateway, gateway merchant id, merchant id and name, and an
environment. Public values only; no secret is stored. The buyer's browser opens Google Pay with these values, so
the money goes to the app owner's own account.
TEST: Google's test environment and the dev payment provider. No money moves; the Buy page also shows a Simulate payment button so the flow works on a machine without Google Pay.PRODUCTION:Payanswers400until a payment processor that verifies the Google Pay token is connected behindCheckoutService.Pay. Not built yet.
Email
The code and the claim link are sent through the suite's mail service, like every other suite email. Licentra authenticates to it with the shared app's certificate and names its own send-from and Reply-To on each message: License__MailFrom and License__MailReplyTo (per-app configuration). The send is triggered by the buyer's own request on the public Buy page, since the buyer has no account. In dev mode without the mail service (Mail__ServiceUrl empty) the message is written to the log instead.
Configuration
| Variable | Where | Meaning |
|---|---|---|
Mail__ServiceUrl | Licentra API | The mail service's URL. Empty = checkout and invitation email off (dev mode logs them) |
License__MailFrom | Licentra API | The send-from for checkout and invitation email: empty = the mail service's default sender; otherwise the default or an address on Mail__AllowedSenders |
License__MailReplyTo | Licentra API | Reply-To on checkout and invitation email |
License__PortalUrl | Licentra API | Portal base URL used in the claim link |
LICENTRA_BASE_URL | Portal (runtime) / VITE_LICENTRA_BASE_URL | Where the Buy page calls |
SUITE_PORTAL_URL | Licentra app | Builds the payment link |
Known gaps
- No payment processor behind
PRODUCTION; no refunds, invoices, tax or currency conversion. - A work-only app (T-pilot) can be claimed by a personal account; the catalog's per-app
audiencesare not checked at claim time yet.