Skip to content
Paykit
Esc
↑↓navigate↵open⌘Jpreview
On this page

Polar

Sell the catalog through Polar as merchant of record, with products and meters the deploy creates.

Polar sells as merchant of record: it calculates and collects tax, hosts the checkout and keeps the subscription. Paykit creates the products and meters the catalog states and records what Polar collects.

Deploy the vendor objects

PolarBilling creates a product per sold plan and a meter per metered feature. It returns the provider the billing stack validates, plus the ids the Worker needs:

import { PolarBilling, polarProviders } from "@potion.run/paykit-polar";
import * as Layer from "effect/Layer";

const providers = Layer.mergeAll(Cloudflare.providers(), Paykit.providers()).pipe(
  Layer.provideMerge(polarProviders({ server: "sandbox" })),
);

const billing = yield* PolarBilling("PolarBilling", {
  server: "sandbox",
  origin: "https://app.example.com",
  tiers: polarTiers,
});

const recorded = yield* Paykit.BillingStack("Billing", {
  catalog: billingCatalog,
  providers: [billing],
});

The deploy’s token comes from POLAR_API_KEY, or from the profile ALCHEMY_PROFILE selects (alchemy profile edit --provider Polar). Nothing is copied into the stack, and server: "sandbox" pins the base URL so a production token cannot create production objects.

polarTiers repeats the catalog’s prices in the shape Polar sells them: { pro: { price: proPrice, usage: { messages: messagePrice } } }.

billing.sold carries the ids: sold.products.pro and sold.meters.messages. billing.accessToken is the token the deploy resolved; bind it with the ids into the Worker’s environment as POLAR_API_KEY.

billing.webhookEndpoint is the endpoint the deploy created: resource is the vendor object, and secret is the signing key Polar minted at create. Bind the secret into the Worker’s environment — Polar never returns it again, so the endpoint’s state is the only copy:

const worker = yield* Cloudflare.Worker("Worker", {
  main: "./src/Worker.ts",
  env: {
    POLAR_API_KEY: billing.accessToken,
    POLAR_WEBHOOK_SECRET: billing.webhookEndpoint.secret,
  },
});

Build the runtime provider

The runtime provider takes the product id and the meter name from the environment:

import { Polar } from "@potion.run/paykit-polar";
import * as Redacted from "effect/Redacted";

export const provider = Polar({
  accessToken: env.POLAR_API_KEY,
  server: "sandbox",
  origin: "https://app.example.com",
  tiers: {
    pro: {
      price: proPrice,
      productId: env.POLAR_PRODUCT_PRO,
      usage: { messages: messagePrice },
    },
  },
  meters: { messages: env.POLAR_MESSAGES_METER },
  webhookSecret: Redacted.make(env.POLAR_WEBHOOK_SECRET),
});

The example pins server: "sandbox", so a production token cannot create production objects from it. A live deployment states its own server.

Deliveries

PolarBilling creates the endpoint at ${origin}/webhooks/polar — webhookPath moves it — and subscribes it to exactly the events the provider decodes, so the vendor’s event list cannot drift from what paykit handles. A delivery is verified with the secret the endpoint minted, decoded by the provider, and applied as a command. The account becomes active on the delivery, not on the redirect from checkout.

Destroying the stack deletes the endpoint: it is configuration, not financial history, and a retired deployment must stop receiving deliveries. A deployment that already owns one — a shared API, a secret in a vault — passes webhookSecret instead, and nothing is created.

client is for a test only. A deployment omits it, and Polar builds its own client from the token.

See Credits for a plan that grants a pool, and the Polar example for the whole loop: a deploy, a checkout, a signed delivery and a test that needs no Polar account.

The pages are Polar, PolarBilling, and PolarPrices.

Was this page helpful?