Billing
Resolve UseAutumn feature checks and subscription issues
Billing issues stem from UseAutumn configuration, feature ID mismatches, or environment variable problems. Verify configuration in this order: environment variables, feature IDs, customer identification.
Feature Check Returns False
Symptoms: ctx.check({ featureId })
returns allowed: false
for users with active subscriptions.
Diagnosis: Feature ID mismatch, configuration not pushed, or customer not identified in UseAutumn.
Solution:
Verify feature ID matches autumn.config.ts
:
export const dashboard = feature({
id: "dashboard", // Must match exactly in ctx.check()
name: "Dashboard Access",
type: "boolean",
});
Push configuration to UseAutumn:
npx atmn push
Verify AUTUMN_SECRET_KEY
is set:
# Check local env
grep AUTUMN_SECRET_KEY .env.local
# Sync to Convex
npx convex env set AUTUMN_SECRET_KEY <same-value>
Customer Not Identified
Symptoms: Feature checks fail even after checkout. UseAutumn dashboard shows no customer record.
Diagnosis: identify
function in convex/autumn.ts
not returning customer data.
Solution:
Verify identify function returns proper structure:
export const autumn = new Autumn(components.autumn, {
secretKey: process.env.AUTUMN_SECRET_KEY,
identify: async (ctx) => {
const user = await ctx.auth.getUserIdentity();
if (!user) return null;
return {
customerId: user.subject, // BetterAuth user ID
customerData: {
name: user.name,
email: user.email,
},
};
},
});
Checkout Not Working
Symptoms: billing.checkout()
throws errors or redirects to wrong URL.
Diagnosis: Product ID doesn't exist in UseAutumn or environment not configured.
Solution:
Verify product exists in autumn.config.ts
:
export const paid = product({
id: "starterapp-demo-paid", // Use this exact ID
name: "Paid",
items: [
priceItem({ price: 10, interval: "month" }),
featureItem({ feature_id: dashboard.id, included_usage: 1 }),
],
});
Push to UseAutumn:
npx atmn push
Use correct product ID in code:
await billing.checkout({
productId: "starterapp-demo-paid", // Must match autumn.config.ts
successUrl: window.location.origin + "/dashboard",
});
Usage Tracking Not Working
Symptoms: ctx.track()
doesn't decrement usage limits. Users can exceed quotas.
Diagnosis: Feature not configured as type: "single_use"
or tracking not called after check.
Solution:
Configure feature for usage tracking:
export const apiCalls = feature({
id: "api_calls",
name: "API Calls",
type: "single_use", // Required for usage tracking
});
Track usage after successful action:
export const callApi = userAction({
handler: async (ctx) => {
const result = await ctx.check({ featureId: "api_calls" });
if (!result.data?.allowed) {
throw new AppError("USAGE_EXCEEDED", "API call limit reached");
}
// Perform action
const data = await externalApiCall();
// Track usage AFTER successful action
await ctx.track({ featureId: "api_calls", value: 1 });
return data;
},
});
Subscription Status Stale
Symptoms: billing.useCustomer()
shows old subscription data after changes.
Diagnosis: Client-side cache not invalidating after subscription updates.
Solution:
UseAutumn updates happen via webhooks. Verify webhook endpoint is accessible:
Check Convex logs for webhook deliveries. Force refresh customer data by signing out and back in.
Related Documentation
- Billing Overview - UseAutumn integration architecture
- Feature Gating - Server-side access control patterns
- Usage-Based Billing - Tracking and limits