Storefront checkout
Solo and Pro Mayar payment links on the marketing app.
The marketing site sells Solo and Pro as one-time purchases through hosted Mayar payment links. This path is separate from authenticated STARTER membership checkout.
Do not reuse STARTER checkout
Do not call the Hono /api/billing membership checkout for Solo/Pro. Use
POST /api/checkout and @repo/payments/storefront only.
Environment variables
Set these in the root .env (see Environment):
| Key | Purpose |
|---|---|
MAYAR_PAYMENT_LINK_SOLO | Hosted checkout URL for Solo |
MAYAR_PAYMENT_LINK_PRO | Hosted checkout URL for Pro |
MAYAR_PRODUCT_ID_SOLO | Product ID used to filter storefront webhooks |
MAYAR_PRODUCT_ID_PRO | Product ID used to filter storefront webhooks |
MAYAR_WEBHOOK_SECRET | Shared secret for /api/webhooks/mayar |
Create the payment links in the Mayar dashboard (sandbox: web.mayar.club).
Checkout flow
sequenceDiagram
participant User
participant Marketing
participant Checkout as POST_/api/checkout
participant Mayar
User->>Marketing: Click Solo or Pro
Marketing->>Checkout: plan solo or pro
Checkout->>Checkout: getStorefrontCheckoutUrl
Checkout-->>Marketing: checkoutUrl
Marketing->>Mayar: Redirect to payment link- Pricing UI calls
useCreateCheckout()(apps/marketing/src/mutations/use-create-checkout.ts). POST /api/checkoutwith{ plan: "solo" | "pro" }(apps/marketing/src/app/api/checkout/route.ts).- Server resolves the URL via
getStorefrontCheckoutUrlinpackages/payments/src/storefront.ts. - Client redirects with
window.location.assign(checkoutUrl). - After payment, users land on
/[locale]/checkout/success(thank-you page).
export function getStorefrontCheckoutUrl(plan: StorefrontPlan): string | null {
const url =
plan === "solo"
? env.MAYAR_PAYMENT_LINK_SOLO
: env.MAYAR_PAYMENT_LINK_PRO;
return url || null;
}Webhook
Point Mayar webhooks at:
{MARKETING_URL}/api/webhooks/mayarAuthorize with x-mayar-webhook-secret or Authorization: Bearer <MAYAR_WEBHOOK_SECRET>.
The handler validates the payload and treats known Solo/Pro product IDs as storefront events. Non-storefront product IDs are acknowledged with { received: true } and ignored. This route currently acks storefront events; it does not write Subscription rows.