Files
cfw-autumn/apps/docs/mintlify/documentation/getting-started/display-billing.mdx
mintlify[bot] 9bd20cb2e9 Add changelog entry for checkout_session_params deep-merge fix (#999)
* docs improvements

* many changes

* prompt changes

* add refetch comment

* fix: checkout subscription data

* cus eligibility fixes

* rm cursor thing

---------

Co-authored-by: Ayush Rodrigues <joesj2905@gmail.com>
Co-authored-by: John Yeo <johnyeocx@gmail.com>
Co-authored-by: John Yeo <51376134+johnyeocx@users.noreply.github.com>
Co-authored-by: Ayush <74830628+ay-rod@users.noreply.github.com>
2026-03-17 17:33:52 +00:00

537 lines
13 KiB
Plaintext

---
title: "Build your billing page"
description: "Display usage and billing data in your app for your users"
---
Software applications typically ship with a billing page. This allows customers to change plan, cancel subscription and view their usage.
The customer endpoint returns the current state of the customer, including their active subscriptions, one-time purchases, and feature balances.
## Pricing table
When building a pricing table, you need to know what each plan means for the current customer — is it an upgrade, a downgrade, or their current plan? Is a free trial available?
Pass a `customerId` when listing plans and each plan will include a `customerEligibility` object:
| Field | Type | Description |
|-------|------|-------------|
| `attachAction` | `"activate"` \| `"upgrade"` \| `"downgrade"` \| `"purchase"` \| `"none"` | What happens when this plan is attached |
| `status` | `"active"` \| `"scheduled"` \| undefined | The customer's current relationship to this plan |
| `trialAvailable` | boolean | Whether the customer is eligible for the plan's free trial |
<CodeGroup>
```jsx React expandable
import { useListPlans, useCustomer } from "autumn-js/react";
const labels = {
activate: "Subscribe",
upgrade: "Upgrade",
downgrade: "Downgrade",
purchase: "Purchase",
};
const getLabel = (eligibility) => {
if (eligibility?.attachAction === "none") {
return eligibility.status === "scheduled" ? "Plan Scheduled" : "Current plan";
}
if (labels[eligibility?.attachAction]) {
return labels[eligibility.attachAction];
}
return "Get started";
};
export default function PricingPage() {
const { data: plans } = useListPlans();
const { attach } = useCustomer();
return plans?.map((plan) => (
<button
key={plan.id}
disabled={plan.customerEligibility?.attachAction === "none"}
onClick={() => attach({ planId: plan.id, redirectMode: "always" })}
>
{getLabel(plan.customerEligibility)}
</button>
));
}
```
```typescript TypeScript
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const { list: plans } = await autumn.plans.list({
customerId: "user_123",
});
for (const plan of plans) {
console.log(plan.name, plan.customerEligibility?.attachAction);
// e.g. "Free" "downgrade", "Pro" "none", "Enterprise" "upgrade"
}
```
```python Python
from autumn_sdk import Autumn
autumn = Autumn("am_sk_test_1234")
plans = await autumn.plans.list(customer_id="user_123")
for plan in plans.list:
print(plan.name, plan.customer_eligibility.attach_action)
```
```bash cURL
curl -X POST 'https://api.useautumn.com/v1/plans.list' \
-H 'Authorization: Bearer am_sk_test_1234' \
-H 'Content-Type: application/json' \
-d '{ "customer_id": "user_123" }'
# Each plan in the response includes customer_eligibility
```
</CodeGroup>
<Tip>
The React `useListPlans` hook automatically includes customer context from `AutumnProvider`, so `customerEligibility` is populated on every plan without extra configuration.
</Tip>
## Switching plans
Switching plans uses `billing.attach`. See [Attaching Plans](/documentation/customers/payment-flow) for the full guide.
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
export default function UpgradeButton() {
const { attach } = useCustomer();
return (
<button onClick={() => attach({ planId: "pro" })}>
Upgrade to Pro
</button>
);
}
```
```typescript TypeScript
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const response = await autumn.billing.attach({
customerId: "user_123",
planId: "pro",
});
// Redirect to complete payment or confirm plan change
redirect(response.paymentUrl);
```
```python Python
from autumn_sdk import Autumn
autumn = Autumn("am_sk_test_1234")
response = await autumn.billing.attach(
customer_id="user_123",
plan_id="pro",
)
# Redirect to response.payment_url
```
```bash cURL
curl -X POST 'https://api.useautumn.com/v1/attach' \
-H 'Authorization: Bearer am_sk_test_1234' \
-H 'Content-Type: application/json' \
-d '{
"customer_id": "user_123",
"plan_id": "pro"
}'
```
</CodeGroup>
## Cancelling a plan
Cancel a subscription using `billing.update` with a `cancelAction`. See [Subscription Lifecycle](/documentation/customers/subscription-lifecycle#cancellations) for the full guide on immediate vs end-of-cycle cancellations.
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
const { updateSubscription } = useCustomer();
// Cancel at end of billing cycle
await updateSubscription({
planId: "pro",
cancelAction: "cancel_end_of_cycle"
});
```
```typescript TypeScript
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
// Cancel at end of billing cycle
await autumn.billing.update({
customerId: "user_123",
planId: "pro",
cancelAction: "cancel_end_of_cycle",
});
```
```python Python
from autumn_sdk import Autumn
autumn = Autumn("am_sk_test_1234")
# Cancel at end of billing cycle
await autumn.billing.update(
customer_id="user_123",
plan_id="pro",
cancel_action="cancel_end_of_cycle",
)
```
```bash cURL
curl -X POST 'https://api.useautumn.com/v1/billing/update' \
-H 'Authorization: Bearer am_sk_test_1234' \
-H 'Content-Type: application/json' \
-d '{
"customer_id": "user_123",
"plan_id": "pro",
"cancel_action": "cancel_end_of_cycle"
}'
```
</CodeGroup>
## Uncancelling a plan
If a subscription has a pending cancellation, a scheduled downgrade, or a scheduled plan switch, you can reverse it with `cancelAction: "uncancel"`.
A subscription is pending cancellation when `canceledAt` is not null while the subscription is still `active`.
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
export default function BillingPage() {
const { data: customer, updateSubscription } = useCustomer();
const cancellingSub = customer?.subscriptions.find(
(sub) => sub.status === "active" && sub.canceledAt !== null
);
return cancellingSub ? (
<button onClick={() => updateSubscription({
planId: cancellingSub.planId,
cancelAction: "uncancel"
})}>
Keep plan
</button>
) : null;
}
```
```typescript TypeScript
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const customer = await autumn.customers.getOrCreate({
customerId: "user_123",
});
const cancellingSub = customer.subscriptions?.find(
(sub) => sub.status === "active" && sub.canceledAt !== null
);
if (cancellingSub) {
await autumn.billing.update({
customerId: "user_123",
planId: cancellingSub.planId,
cancelAction: "uncancel",
});
}
```
```python Python
from autumn_sdk import Autumn
autumn = Autumn("am_sk_test_1234")
customer = await autumn.customers.get_or_create(
customer_id="user_123"
)
cancelling_sub = next(
(s for s in customer.subscriptions
if s.status == "active" and s.canceled_at is not None),
None,
)
if cancelling_sub:
await autumn.billing.update(
customer_id="user_123",
plan_id=cancelling_sub.plan_id,
cancel_action="uncancel",
)
```
```bash cURL
curl -X POST 'https://api.useautumn.com/v1/billing/update' \
-H 'Authorization: Bearer am_sk_test_1234' \
-H 'Content-Type: application/json' \
-d '{
"customer_id": "user_123",
"plan_id": "pro",
"cancel_action": "uncancel"
}'
```
</CodeGroup>
## Active plans
Display the plan the user is currently on. Users can have multiple active subscriptions and purchases (e.g., main plan and add-ons).
- **`subscriptions`** - Free and paid recurring plans
- **`purchases`** - One-off plans (e.g., credit top-ups)
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
const { data: customer } = useCustomer();
const active = customer?.subscriptions.filter(
(sub) => sub.status === "active"
);
console.log(active?.map((sub) => sub.planId).join(", "));
```
```typescript TypeScript
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const customer = await autumn.customers.getOrCreate({
customerId: "user_123",
});
const active = customer.subscriptions?.filter(
(sub) => sub.status === "active"
);
console.log(active?.map((sub) => sub.planId).join(", "));
```
```python Python
from autumn_sdk import Autumn
autumn = Autumn("am_sk_test_1234")
customer = await autumn.customers.get_or_create(
customer_id="user_123"
)
active = [s for s in customer.subscriptions if s.status == "active"]
print([s.plan_id for s in active])
```
```bash cURL
curl -X POST 'https://api.useautumn.com/v1/customers' \
-H 'Authorization: Bearer am_sk_test_1234' \
-H 'Content-Type: application/json' \
-d '{ "customer_id": "user_123" }'
# Response includes subscriptions array
```
</CodeGroup>
## Usage balances
Metered features have `granted`, `usage`, and `remaining` fields. Use these to display current usage and remaining balance.
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
const { data: customer, refetch } = useCustomer();
const messages = customer?.balances.messages;
console.log(`${messages?.remaining} / ${messages?.granted}`);
// After tracking usage or changing plans, call refetch() to update balances
await refetch();
```
```typescript TypeScript
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const customer = await autumn.customers.getOrCreate({
customerId: "user_123",
});
const messages = customer.balances?.messages;
console.log(`${messages?.remaining} / ${messages?.granted}`);
```
```python Python
from autumn_sdk import Autumn
autumn = Autumn("am_sk_test_1234")
customer = await autumn.customers.get_or_create(
customer_id="user_123"
)
messages = customer.balances.get("messages")
print(f"{messages.remaining} / {messages.granted}")
```
```bash cURL
curl -X POST 'https://api.useautumn.com/v1/customers' \
-H 'Authorization: Bearer am_sk_test_1234' \
-H 'Content-Type: application/json' \
-d '{ "customer_id": "user_123" }'
# Response includes balances.[feature_id]
```
</CodeGroup>
## Stripe billing portal
The Stripe billing portal lets users manage their payment method, view past invoices, and cancel their plan.
<Warning>
Enable the billing portal in your [Stripe settings](https://dashboard.stripe.com/settings/billing/portal).
</Warning>
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
const { openCustomerPortal } = useCustomer();
// Opens Stripe billing portal in current tab
await openCustomerPortal({
returnUrl: "https://your-app.com/billing"
});
```
```typescript TypeScript
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const { url } = await autumn.billing.openCustomerPortal({
customerId: "user_123",
returnUrl: "https://your-app.com/billing",
});
redirect(url);
```
```python Python
from autumn_sdk import Autumn
autumn = Autumn("am_sk_test_1234")
response = await autumn.billing.open_customer_portal(
customer_id="user_123",
return_url="https://your-app.com/billing",
)
# Redirect to response.url
```
```bash cURL
curl -X POST 'https://api.useautumn.com/v1/billing.open_customer_portal' \
-H 'Authorization: Bearer am_sk_test_1234' \
-H 'Content-Type: application/json' \
-d '{
"customer_id": "user_123",
"return_url": "https://your-app.com/billing"
}'
```
</CodeGroup>
## Usage timeseries chart
Autumn provides aggregate time series queries for usage data. Pass the response to a charting library like Recharts.
<CodeGroup>
```jsx React
import { useAggregateEvents } from "autumn-js/react";
const { list, total } = useAggregateEvents({
featureId: "messages",
range: "30d",
});
// list: [{ period: 1234567890, values: { messages: 42 } }, ...]
// total: { messages: { count: 100, sum: 500 } }
```
```typescript TypeScript
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const { list, total } = await autumn.events.aggregate({
customerId: "user_123",
featureId: "messages",
range: "30d",
});
```
```python Python
from autumn_sdk import Autumn
autumn = Autumn("am_sk_test_1234")
response = await autumn.events.aggregate(
customer_id="user_123",
feature_id="messages",
range="30d",
)
# response.list, response.total
```
```bash cURL
curl -X POST 'https://api.useautumn.com/v1/events.aggregate' \
-H 'Authorization: Bearer am_sk_test_1234' \
-H 'Content-Type: application/json' \
-d '{
"customer_id": "user_123",
"feature_id": "messages",
"range": "30d"
}'
```
</CodeGroup>
You can also use the [`events.list`](/api-reference/events/listEvents) method to get the raw event data and display it in a table.