Autumn handles your billing flows, so you don’t need to build and maintain user flows like:

  • Purchasing a product
  • Inputting quantities for prepaid products
  • Upgrading, downgrading, cancelling or renewing a product’s subscription
  • Paywalling features
  • Failed payments [coming soon]

This page covers how to roll your own billing flows using our API or SDK. Alternatively, you can use our customizable shadcn/ui components to handle these out-of-the-box.

Product Change

Typically when charging a customer’s card, you’ll want them to confirm the payment beforehand. This is useful for upgrade, downgrade or add-on payment flows.

The check function has a with_preview parameter that can be used to get purchase preview information that can be displayed to the customer.

{
    "customer_id": "user_1234",
    "code": "product_found",
    "product_id": "ultra",
    "allowed": false,
    "preview": {
        "title": "Upgrade to Ultra",
        "message": "By clicking confirm, you will upgrade your plan to Ultra and the following amount will be charged to your card immediately:",
        "scenario": "upgrade",
        "product_id": "ultra",
        "product_name": "Ultra",
        "recurring": true,
        "next_cycle_at": 1750604867000,
        "current_product_name": "Pro",
        "items": [
            {
                "price": "-$3.53",
                "description": "Unused time on Pro after 31 May 2025"
            },
            {
                "price": "$7.06",
                "description": "Remaining time on Ultra after 31 May 2025"
            }
        ],
        "options": [
            {
                "feature_id": "seats",
                "feature_name": "seats",
                "billing_units": 1,
                "included_usage": 0,
                "price": 4
            }
        ],
        "due_today": {
            "price": 3.53,
            "currency": "USD"
        },
        "due_next_cycle": {
            "price": 10,
            "currency": "USD"
        },
        "product": {
            "id": "ultra",
            "name": "Ultra",
            "group": null,
            "env": "sandbox",
            "is_add_on": false,
            "is_default": false,
            "version": 1,
            "created_at": 1747926427301,
            "items": [
                {
                    "type": "price",
                    "feature_id": null,
                    "interval": "month",
                    "price": 10
                },
                {
                    "type": "priced_feature",
                    "feature_id": "seats",
                    "feature_type": "continuous_use",
                    "included_usage": 0,
                    "interval": "month",
                    "price": null,
                    "tiers": [
                        {
                            "to": "inf",
                            "amount": 4
                        }
                    ],
                    "usage_model": "prepaid",
                    "billing_units": 1,
                    "reset_usage_when_enabled": true
                },
                {
                    "type": "feature",
                    "feature_id": "chat_messages",
                    "feature_type": "single_use",
                    "included_usage": 20,
                    "interval": "month",
                    "reset_usage_when_enabled": true
                }
            ],
            "free_trial": null
        }
    }
}

There are two options for handling these flows:

  1. Pass in a component to the attach React hook
  2. Build your own flow from scratch

You can pass in a custom component (such as a dialog) to the attach() hook. This will automatically call check with with_preview: true and pass down a params object to the component.

Params contains the following properties:

  • open: A boolean that will be true if an input is required from the customer, such as to confirm a purchase or input a quantity.
  • setOpen: A function that will be used to close the dialog.
  • preview: The preview object returned from the check function.

If there is no preview object, open will be false and the customer will be directed directly to a checkout page.

import { useAutumn } from "autumn-js/react";
import CustomDialog from "./CustomDialog";

export default function PurchaseButton() {
  const { attach } = useAutumn();

  return (
    <button
      onClick={async () => {
        await attach({
          productId: product.id,
          dialog: CustomDialog,
        });
      }}
    >
      Upgrade to Pro
    </button>
  );
}

If preview is returned, it will contain a scenario enum, which can be one of the following:

ScenarioDescription
upgradeThe customer is upgrading their product
downgradeThe customer is downgrading to another paid tier
cancelThe customer is downgrading to a free product
renewThe customer is reactivating a product that was previously cancelled
scheduledThe product is already scheduled to start at a future date (eg, for downgrades), so no changes are needed
activeThe customer already has the product active, so no changes are needed

You can use this to control the dialog’s contents.

Paywall

A paywall that prompts users to upgrade to the next tier when they hit a usage limit, or don’t have access to a feature.

The check function has a with_preview parameter that can be used to get paywall preview information when a customer doesn’t have access to a feature. This will contain information about the next product tier (or an add-on) they should upgrade to, in order to access the feature.

{
  "customer_id": "user_1234",
  "feature_id": "chat_messages",
  "required_balance": 1,
  "code": "feature_found",
  "allowed": false,
  "unlimited": false,
  "balance": 0,
  "preview": {
    "title": "Upgrade to Pro",
    "message": "You have run out of messages. Please upgrade to Pro to continue using this feature.",
    "scenario": "usage_limit",
    "feature_id": "chat_messages",
    "feature_name": "messages",
    "products": [
      {
        "id": "pro",
        "name": "Pro",
        "group": null,
        "env": "sandbox",
        "is_add_on": false,
        "is_default": false,
        "version": 2,
        "created_at": 1748802907591,
        "items": [
          {
            "type": "price",
            "feature_id": null,
            "interval": "month",
            "price": 55
          },
          {
            "type": "feature",
            "feature_id": "chat_messages",
            "feature_type": "single_use",
            "included_usage": 300,
            "interval": "month",
            "reset_usage_when_enabled": true
          },
          {
            "type": "feature",
            "feature_id": "premium_support",
            "feature_type": "static"
          }
        ],
        "free_trial": null
      }
    ],
    "upgrade_product_id": "pro"
  }
}

There are two options for handling these flows:

  1. Pass in a component to the check React hook
  2. Build your own flow from scratch

You can pass in a custom component (such as a dialog) to the check() hook. This will automatically call check with with_preview: true and pass down a params object to the component.

Params contains the following properties:

  • open: A boolean that will be true if the feature is not allowed and a paywall should be shown.
  • setOpen: A function that will be used to close the dialog.
  • preview: The preview object returned from the check function.

If the feature is allowed, open will be false and no paywall will be shown.

import { useAutumn } from "autumn-js/react";
import PaywallDialog from "./PaywallDialog";

export default function FeatureButton() {
  const { check } = useAutumn();
  
  return (
    <button
      onClick={async () => {
        await check({
          featureId: "chat_messages",
          dialog: CustomPaywallDialog,
        });
      }}
    >
      Send Message
    </button>
  );
}

If preview is returned, it will contain a scenario enum, which can be one of the following:

ScenarioDescription
usage_limitThe customer has hit a usage limit for the feature
feature_flagThe customer doesn’t have access to the feature

You can use this to control the paywall’s contents.