Products & Payments
A Product is anything you sell — a membership tier, a course, a pass, a download. Buying it grants its entitlement, which unlocks whatever you’ve gated with it: pages, data, files, even chat channels. Manage in Config → Monetization → Products.
Pricing reads like a sentence: one-time payment or subscription renewing every N days/weeks/months/years (monthly, quarterly, every 45 days…), with an optional free trial; access forever, for N days (7/14/30-day passes), or while subscribed. Payments run on your own Stripe (or manual/cash with owner approval) — no platform take-rate.
Sell it from a page
// pricing page
const products = await serenities.payments.products();
// checkout — with optional coupon, order bump, and affiliate tag
await serenities.payments.checkout('pro', {
couponCode: form.coupon,
bumpOfferKey: 'workbook',
ref: new URLSearchParams(location.search).get('ref') || undefined,
});
// gate content with the entitlement
// access rule: { type: 'has_entitlement', key: 'pro' }Payment success grants the entitlement automatically (webhook-driven) — no glue code. Manual/cash payments create an approval request the owner confirms.
Manual & cash payments, built in
No processor required for this one. payments.verifyManual(offerKey) pops a ready-made modal — your QR methods and instructions, a note field, an optional receipt upload — and submits a request for your approval. One call, no UI to build.
// built-in modal — resolves { submitted, requestId } or { cancelled }
await serenities.payments.verifyManual('pro');
// or wire your own UI to the same endpoints:
await serenities.payments.requestManual('pro', { note: 'Paid via bank transfer, ref #1234' });
const mine = await serenities.payments.manualStatus(); // this member's requests + statusNothing unlocks until you approve the request (Config → Payments). Approving a while-subscribed product grants exactly one billing period — there’s no card on file to auto-renew a manual payment, so each renewal needs its own approval.
Coupons & order bumps
- Coupons: percent or fixed off, per-product scoping, expiry, max uses. A use only counts when someone actually pays — abandoned checkouts never consume one. Run many codes at once.
- Order bumps: a one-time add-on (“add the workbook for $27”) in the same payment — its entitlement is granted alongside the main one, even next to a subscription.
Custom payment gateways (Razorpay, Paytm, eSewa…)
Any gateway your buyers prefer can grant the same access, because gating only checks entitlements — never how someone paid. A backend function receives the gateway’s payment notification, verifies the charge against the gateway’s API (with your secret key stored as an env var), and grants one billing period per verified charge.
// each verified charge = one period; renewals extend, replays are ignored
const got = await ctx.entitlements.get(appUserId, 'pro');
const existing = got && got.entitlement;
const base = existing?.expiresAt && new Date(existing.expiresAt) > new Date()
? new Date(existing.expiresAt).getTime() : Date.now();
const expiresAt = new Date(base + 30 * 86400000).toISOString();
existing
? await ctx.entitlements.extend(appUserId, 'pro', expiresAt, { source: { provider: 'razorpay', paymentId } })
: await ctx.entitlements.grant(appUserId, 'pro', { expiresAt, source: { provider: 'razorpay', paymentId } });- Recurring works too: each cycle’s charge extends the expiry; payment failure suspends with a grace window; cancellation revokes.
- Fail-closed by design: if a renewal never arrives, access simply lapses at the expiry — nobody keeps access for free.
- The full recipe (webhook URL, verification, replay guard) is in the developer code guide under payments.
Affiliate attribution, built-in
A ?ref= tag on the checkout — and any redeemed coupon code — is stamped into the buyer’s payment and entitlement record. Your affiliate pages read attribution straight from your own ledger: code-based tracking that survives ad blockers, with no third-party affiliate tool.