Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.pylonsync.com/llms.txt

Use this file to discover all available pages before exploring further.

@pylonsync/stripe replaces the ~400 lines every Pylon app rewrites when adding Stripe: customer creation, checkout session minting, billing portal, webhook signature verification, plan derivation, subscription state. One stripe({plans, hooks}) block returns a manifest fragment + handler factories.

Install

bun add @pylonsync/stripe

Config

import { buildManifest, app, entity, field } from "@pylonsync/sdk";
import { stripe } from "@pylonsync/stripe";

export const Org = entity("Org", {
  name: field.string(),
  slug: field.string().unique(),
  createdBy: field.id("User"),
  createdAt: field.string(),
  stripeCustomerId: field.string().optional(),
});

export const billing = stripe({
  referenceType: "org", // or "user"
  plans: [
    { name: "starter", priceId: process.env.STRIPE_PRICE_STARTER!, limits: { recordings: 50 } },
    { name: "pro",     priceId: process.env.STRIPE_PRICE_PRO!,     limits: { recordings: 500 } },
    { name: "scale",   priceId: process.env.STRIPE_PRICE_SCALE!,   limits: { recordings: -1 } },
  ],
  hooks: {
    onSubscriptionActivate: async (ctx, { referenceId, plan }) => {
      // analytics, welcome email
    },
    onInvoice: async (ctx, { eventType, invoice }) => {
      // record audit, slack alert on payment_failed
    },
  },
});

export default app({
  name: "myapp",
  entities: [Org /* + your entities */, ...billing.manifest.entities],
  actions:  [...billing.manifest.actions],
  queries:  [...billing.manifest.queries],
  policies: [...billing.manifest.policies],
});

Wrapper files

Pylon loads function handlers by file. Create one-line wrappers under functions/:
// functions/createCheckoutSession.ts
export { createCheckoutSession as default } from "../billing";
// functions/createBillingPortalSession.ts
export { createBillingPortalSession as default } from "../billing";
…and one per handler key exposed by billing.handlers (cancelSubscription, restoreSubscription, stripeWebhook, plus the _pylonStripe* internals).

Required env

VariablePurpose
STRIPE_SECRET_KEYAPI requests (set per Pylon Cloud project’s Secrets UI).
STRIPE_WEBHOOK_SECRETSignature verification on /api/webhooks/stripeWebhook.
PYLON_PUBLIC_URLAuto-set by Pylon Cloud. Drives URL allowlist for success_url/cancel_url.

Webhook endpoint

Configure in Stripe Dashboard → Developers → Webhooks → Add endpoint:
URL:    https://api.<your-app>.com/api/webhooks/stripeWebhook
Events: customer.subscription.created
        customer.subscription.updated
        customer.subscription.deleted
        invoice.created
        invoice.finalized
        invoice.paid
        invoice.payment_failed
        invoice.voided
Copy the signing secret (whsec_...) → set as STRIPE_WEBHOOK_SECRET on your Pylon machine.

Lifecycle hooks

HookFires
onCustomerCreateAfter a Stripe customer is created (first checkout).
onSubscriptionActivatecustomer.subscription.created event.
onSubscriptionUpdatecustomer.subscription.updated event.
onSubscriptionCancelcustomer.subscription.deleted event.
onInvoiceAny invoice event.
onEventCatch-all for unhandled Stripe events.
getCheckoutSessionParamsInject tax/promo/idempotency-key params into checkout creation.

RBAC

The authorizeReference hook gates subscription mutations. Defaults:
  • referenceType: "org" — caller must be the org’s owner or admin.
  • referenceType: "user" — caller must be the user themselves.
  • referenceType: "custom" — you must supply your own authorizeReference.

Security

  • Constant-time signature verification with 5-minute replay window and multi-secret rotation (supports overlap window during secret rotation).
  • URL allowlist auto-derived from PYLON_PUBLIC_URL + PYLON_CORS_ORIGIN — no hardcoded host strings.
  • Three-signal plan resolver (lookup_key → nickname → priceId match) — Stripe occasionally elides the first two on webhook payloads.
  • Double-trial guard — prior Subscription rows for the same reference disable the trial period regardless of plan config.
  • Idempotent webhook upsert via stripeSubscriptionId lookup, so Stripe retries produce the same state.