Using Autumn, you can reliably integrate Stripe with your AI application builder like Lovable, Bolt.new, Replit or v0 in just a couple prompts—no webhooks or server management required.

1. Create your products

Create your free Autumn account at useautumn.com. Then navigate to the Connect Stripe page and paste in your Test and Live Secret API keys.

Your Test and Live keys can be found in the Stripe Dashboard, under Developers > API keys.

Create the Pro plan in the Products tab.

2. Setup

Use this prompt to setup Autumn. Make sure you have auth already handled first.


# Integrating Autumn for Billing

The user is using Autumn to manage their pricing, which is a layer over Stripe that controls entitlements and payment flows.

DO NOT touch or suggest using any of Stripe’s APIs

DO NOT implement any pricing logic (subscription statuses, feature gating) at this stage. That will be handled afterwards.

## Before Integration

1. Before starting the integration, check the user has Auth in place. If they don’t, do not proceed.
2. Prompt them for an Autumn TEST API key. This should be saved in supabase as AUTUMN_SECRET_KEY. The user can generate an Autumn API key from [https://app.useautumn.com/sandbox/dev](https://app.useautumn.com/sandbox/dev)

## Integration steps

1. Install autumn-js: `npm install autumn-js`
2. Create a supabase edge function called `autumn`. Inside the function, use this code to route the requests correctly, and resolve the `customerId`. This will be the user’s ID from Supabase Auth.


/supabase/functions/autumn/index.ts

import { autumnHandler } from "npm:autumn-js/supabase";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.9";
const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
};

Deno.serve(async (req: Request) => {
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders });
  }

  const supabaseClient = createClient(
    Deno.env.get("SUPABASE_URL") ?? "",
    Deno.env.get("SUPABASE_ANON_KEY") ?? "",
    {
      global: {
        headers: { Authorization: req.headers.get("Authorization") },
      },
    }
  );

  const { data, error } = await supabaseClient.auth.getUser();

  const handler = autumnHandler({
    corsHeaders,
    identify: async () => {
      return {
        customerId: data.user?.id,
        customerData: {
          email: data.user?.email,
        },
      };
    },
  });

  return handler(req);
});


1. Wrap the application with <AutumnProvider />. The backendUrl should direct to the supabase function, set includeCredentials as false and pass in the bearerToken from supabase auth.

   The backendUrl should look like <PROJECT_ID>. [supabase.co/functions/v1/autumn](https://supabase.co). You may be able to import a supabase URL from env.



<AutumnProvider
  backendUrl={`https://<PROJECT_ID>.supabase.co/functions/v1/autumn`}
  includeCredentials={false}
  getBearerToken={async () => {
    const { data } = await supabase.auth.getSession();
    return data.session?.access_token;
  }}
>
  <App />
</AutumnProvider>


1. Now verify that everything works by calling useCustomer() on the frontend somewhere in the app, and logging the response. Ask the user to check if the customer has been created in Autumn.


import { useCustomer } from "autumn-js/react";

const { customer } = useCustomer();
console.log("Autumn Customer", customer);

3. Integrate your pricing model

Now you can describe to the AI the pricing plans you defined in Autumn (eg free tier has 3 messages, pro tier is 5 usd per month etc). Make sure to provide it with the productIds and featureIds (if you’re using usage pricing or limits). Then, paste in the prompt below.

I have a free and a pro tier. My free tier has 3 messages and pro has 100 messages for $10 per month. The product IDs are “free” and “pro”, and the feature ID is “messages”.


# Integrating Pricing Model

## Before Integration

1. Check that the user has provided the productIds for the pricing tiers they have. If they have not explicitly mentioned it, prompt them for it. Do not guess.
2. If they mention usage based features that are being tracked or limited, make sure they mention the featureIds. You may ask if you’re unsure whether it’s a usage-based feature.

## Pricing Logic integration

1. Use the `attach` hook to handle payments. This will redirect to a checkout URL if a new purchase, or handle any upgrades/downgrades/cancellations.


import ProductChangeDialog from "@components/autumn/product-change-dialog";
import { useAutumn } from "autumn-js/react";

<Button
  onClick={async () =>
    await attach({
      productId: "pro",
      dialog: ProductChangeDialog,
    })
  }
>

2. Build a productChangeDialog and pass it into the `attach` hook as above. This is a simplified version you can style to match the app’s theme.


import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
import { useAutumn } from "autumn-js/react"

const ProductChangeDialog = (params) => {
  const [isLoading, setIsLoading] = useState(false);
  const { attach } = useAutumn();
  const { open, setOpen, preview } = params;

  const handleConfirm = async () => {
    setIsLoading(true);
    await attach({productId: preview.product_id});
    setOpen(false);
    setIsLoading(false);
  };

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogContent>
          <DialogTitle>{preview?.title}</DialogTitle>
        <div>{preview?.message}</div>
        {preview?.due_today?.price && <div>Due today: ${preview?.due_today?.price}</div>}
          <button onClick={() => setOpen(false)}>Cancel</button>
          <button onClick={handleConfirm} disabled={isLoading}>
            {isLoading ? 'Processing...' : 'Confirm'}
          </button>
      </DialogContent>
    </Dialog>
  );
};

export default ProductChangeDialog;


3. Check for product access from the customer state object, and use it to gate features. Replace “pro” with the provided productId.
   DO NOT check for active status, as the product may be “trialing”.


import { useCustomer } from "autumn-js/react";

const premiumUser = customer.products.find((p) => p.id === "pro");


4. For usage-based features, you can use the check and track functions, to check access and then record usage. Replace “messages” with the provided featureId


import { useAutumn, useCustomer } from "autumn-js/react";

export default function SendChatMessage() {
  const { check } = useAutumn();
  const { customer, refetch } = useCustomer();

  return (
    <button
      onClick={async () => {
        let { data } = await check({ featureId: "messages" });

        if (data?.allowed) {
          //... send message, then
          await track({ featureId: "messages" });
          await refetch(); // refetch customer usage data
        }
      }}
    >
      Send Message
    </button>
  );
}


5. You can use the customer state hook to display any relevant information if the user needs


{
  "created_at": 1677649423000,
  “env": "production",
  "id": "user_123",
  "name": "John Yeo",
  "email": "john@example.com",
  "fingerprint": "",
  "stripe_id": "cus_abc123",
  "products": [
    {
      "id": "pro",
      "name": "Pro Plan",
      "group": "",
      "status": "active",
      "started_at": 1677649423000,
      "canceled_at": null,
      "subscription_ids": [
        "sub_123"
      ],
      "current_period_start": 1677649423000,
      "current_period_end": 1680327823000
    }
  ],
  "features": {
    "messages": {
      "feature_id": "messages",
      "unlimited": false,
      "interval": "month",
      "balance": 80,
      "usage": 20,
      "included_usage": 100,
      "next_reset_at": 1680327823000
    }
  }
}


4. Handle failed payments and billing portal

Lastly, handle what should happen when a user’s payment fails and redirect them to the billing portal to manage or cancel their subscription.


# Handling failed payments with Autumn

1. If a payment is overdue, a customer’s product status will be `past_due`. If this is the case then you should direct the customer to the billing portal to update their payment method, before the product expires.


import { useCustomer } from "autumn-js/react";

const failed_payment = customer.products.find((p) => p.status === "past_due");


2. Billing portal


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

export default function BillingSettings() {
  const { openBillingPortal } = useAutumn();

  return (
    <button
      onClick={async () => {
        await openBillingPortal({
          returnUrl: "https://google.com",
        });
      }}
    >
      Update Payment Method
    </button>
  );
}