Candidate paywalls
Build candidate offer selection, embedded checkout, entitlement checks, and billing management.
A
JBuild the candidate job-access purchase flow. Offers are public, but checkout, checkout state, grants, and billing-portal access all belong to the signed-in candidate.
Prerequisites
board.context().features.candidatePaywallistrue.- Candidate authentication and a completed candidate profile.
@stripe/stripe-jsin the browser that mounts embedded checkout.- A server-owned session for every
board.me.accessrequest.
Load offers and the current grant
grant always resolves. No access is { hasAccess: false, … }, not an error. Use it for the rendering decision instead of inferring access from gatedCount or a previous checkout redirect.
Start embedded checkout
The response is a mount kit containing sessionId, clientSecret, stripeAccountId, and publishableKey. In the browser, initialize Stripe.js with both the publishable key and connected account, then mount embedded checkout with the returned client secret.
Call the returned cleanup function when the checkout view unmounts.
Confirm access after checkout
An open checkout can be remounted with its returned clientSecret. An expired checkout needs a new session. Even after complete, unlock content only when a fresh grant returns hasAccess: true.
Open subscription management
Only recurring grants have a billing portal. Lifetime access does not.
Expected result
The page shows current public offers and the viewer’s grant. Checkout returns an embedded-checkout mount kit; a completed checkout followed by grant() returns the authoritative access decision.
Verify the result
- Disabled paywalls do not render purchase routes or controls.
- Offer labels and prices come from
board.paywall.offers. - Checkout is unavailable without a signed-in candidate profile.
- A browser return does not unlock content until
grant.hasAccessis true. - Lifetime users never see a recurring-subscription management action.
Errors and edge cases
offers()returns an empty list when the paywall is disabled.- Past-due, unpaid, incomplete, canceled, and pending grants can all return
hasAccess: false; render from both fields without inventing a local status. returnPathmust remain a safe relative path.- The SDK does not mount Stripe UI or process card details.
Production cautions
- Never cache grants, checkout state, or portal URLs publicly.
- Keep card handling inside Stripe’s embedded checkout.
- Re-read the grant on every protected server render, not only in the browser.
- Log API request IDs, not checkout secrets or session credentials.
Continue with Frontend infrastructure to make these routes canonical, indexable, and visually consistent.