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
Whether the organization has at least one active subscription
Polar customer ID if the organization exists in Polar, otherwise null
Estimated cost in cents across all active subscriptions and meters. Summed from all subscription meters.
Implementation
src/trpc/routers/billing.ts
Example Usage
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
Polar checkout URL to redirect the user to. Includes the configured product and links back to your app on success.
Implementation
src/trpc/routers/billing.ts
Example Usage
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
Polar customer portal URL where users can:
View and manage subscriptions
Update payment methods
View billing history and invoices
Cancel subscriptions
Implementation
src/trpc/routers/billing.ts
Example Usage
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
Code Description When It Occurs UNAUTHORIZEDUser not authenticated Missing or invalid session FORBIDDENMissing organization User not in an organization INTERNAL_SERVER_ERRORPolar API error Failed to create checkout/portal session
Subscription Flow
Check Status
Call billing.getStatus to determine if user has an active subscription
Create Checkout
If no subscription exists, call billing.createCheckout and redirect to the returned URL
User Subscribes
User completes payment on Polar’s checkout page
Webhook Notification
Polar sends webhook to your app (configure in Polar dashboard)
User Redirected
User returns to your app via the successUrl
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:
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.