Skip to main content

Overview

The Billing API integrates with Polar to handle subscription management, checkout flows, and usage tracking. All billing operations are organization-scoped.

Get Billing Status

Retrieve the current subscription status and estimated usage costs for an organization.
trpc.billing.getStatus.useQuery();

Response

hasActiveSubscription
boolean
required
Whether the organization has at least one active subscription
customerId
string | null
required
Polar customer ID if the organization exists in Polar, otherwise null
estimatedCostCents
number
required
Estimated cost in cents across all active subscriptions and meters. Summed from all subscription meters.

Implementation

getStatus: orgProcedure.query(async ({ ctx }) => {
  try {
    const customerState = await polar.customers.getStateExternal({
      externalId: ctx.orgId,
    });

    const hasActiveSubscription =
      (customerState.activeSubscriptions ?? []).length > 0;

    // Sum up estimated costs from all meters across active subscriptions
    let estimatedCostCents = 0;
    for (const sub of customerState.activeSubscriptions ?? []) {
      for (const meter of sub.meters ?? []) {
        estimatedCostCents += meter.amount ?? 0;
      }
    }

    return {
      hasActiveSubscription,
      customerId: customerState.id,
      estimatedCostCents,
    };
  } catch {
    // Customer doesn't exist yet in Polar
    return {
      hasActiveSubscription: false,
      customerId: null,
      estimatedCostCents: 0,
    };
  }
}),

Create Checkout Session

Create a Polar checkout session for subscribing to Resonance.
trpc.billing.createCheckout.useMutation();
This endpoint automatically creates a new customer in Polar if one doesn’t exist for the organization.

Response

checkoutUrl
string
required
Polar checkout URL to redirect the user to. Includes the configured product and links back to your app on success.

Implementation

creatCheckout: orgProcedure.mutation(async ({ ctx }) => {
  const result = await polar.checkouts.create({
    products: [env.POLAR_PRODUCT_ID],
    externalCustomerId: ctx.orgId,
    successUrl: process.env.APP_URL,
  });

  if (!result.url) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Failed to create checkout session",
    });
  }

  return { checkoutUrl: result.url };
}),

Create Portal Session

Create a Polar customer portal session for managing subscriptions, payment methods, and billing history.
trpc.billing.createPortalSession.useMutation();

Response

portalUrl
string
required
Polar customer portal URL where users can:
  • View and manage subscriptions
  • Update payment methods
  • View billing history and invoices
  • Cancel subscriptions

Implementation

createPortalSession: orgProcedure.mutation(async ({ ctx }) => {
  const result = await polar.customerSessions.create({
    externalCustomerId: ctx.orgId,
  });

  if (!result.customerPortalUrl) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Failed to create customer portal session",
    });
  }

  return { portalUrl: result.customerPortalUrl };
}),

Error Codes

CodeDescriptionWhen It Occurs
UNAUTHORIZEDUser not authenticatedMissing or invalid session
FORBIDDENMissing organizationUser not in an organization
INTERNAL_SERVER_ERRORPolar API errorFailed to create checkout/portal session

Subscription Flow

1

Check Status

Call billing.getStatus to determine if user has an active subscription
2

Create Checkout

If no subscription exists, call billing.createCheckout and redirect to the returned URL
3

User Subscribes

User completes payment on Polar’s checkout page
4

Webhook Notification

Polar sends webhook to your app (configure in Polar dashboard)
5

User Redirected

User returns to your app via the successUrl
6

Status Updates

billing.getStatus now returns hasActiveSubscription: true

Usage Metering

Resonance tracks TTS generation usage and reports it to Polar:
// Automatically called after each generation
polar.events.ingest({
  events: [{
    name: "tts_generation",
    externalCustomerId: ctx.orgId,
    metadata: { characters: input.text.length },
    timestamp: new Date(),
  }],
});

Metering Details

  • Event Name: tts_generation
  • Metric: Character count of the input text
  • Timing: Fire-and-forget after successful generation
  • Failure Handling: Silent failures to avoid blocking user experience

Viewing Usage

Estimated costs are calculated by summing all meter amounts across active subscriptions:
let estimatedCostCents = 0;
for (const sub of customerState.activeSubscriptions ?? []) {
  for (const meter of sub.meters ?? []) {
    estimatedCostCents += meter.amount ?? 0;
  }
}
The estimated cost is calculated in real-time based on current meter readings. Actual billing occurs according to your Polar product configuration.

Integration Setup

To configure billing, ensure these environment variables are set:
.env
POLAR_ACCESS_TOKEN=polar_at_xxx
POLAR_PRODUCT_ID=prod_xxx
APP_URL=https://your-app.com
And configure webhooks in the Polar dashboard to receive subscription events.

Build docs developers (and LLMs) love