TurboJet
Payments

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):

KeyPurpose
MAYAR_PAYMENT_LINK_SOLOHosted checkout URL for Solo
MAYAR_PAYMENT_LINK_PROHosted checkout URL for Pro
MAYAR_PRODUCT_ID_SOLOProduct ID used to filter storefront webhooks
MAYAR_PRODUCT_ID_PROProduct ID used to filter storefront webhooks
MAYAR_WEBHOOK_SECRETShared 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
  1. Pricing UI calls useCreateCheckout() (apps/marketing/src/mutations/use-create-checkout.ts).
  2. POST /api/checkout with { plan: "solo" | "pro" } (apps/marketing/src/app/api/checkout/route.ts).
  3. Server resolves the URL via getStorefrontCheckoutUrl in packages/payments/src/storefront.ts.
  4. Client redirects with window.location.assign(checkoutUrl).
  5. After payment, users land on /[locale]/checkout/success (thank-you page).
packages/payments/src/storefront.ts
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/mayar

Authorize 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.

On this page