スキル一覧に戻る
OxFrancesco

implementing-autumn-billing

by OxFrancesco

0🍴 0📅 2026年1月24日
GitHubで見るManusで実行

SKILL.md


name: implementing-autumn-billing description: Implement Autumn billing and payments in applications. Use when building subscription systems, usage-based billing, paywalls, feature gates, or monetization features. Autumn is an open-source billing layer between your app and Stripe.

Implementing Autumn Billing

Autumn is an open-source billing management system that sits between your application and Stripe. It handles subscription state, usage metering, credit balances, and feature gating without requiring webhook management.

When to Use This Skill

  • Adding subscription/payment features to an app
  • Implementing usage-based billing (metered features)
  • Creating paywalls or feature gates
  • Building credit systems
  • Managing subscription tiers (Free/Pro/Enterprise)
  • Implementing per-seat or per-entity pricing

Installation

For Convex Projects

npm install @useautumn/convex autumn-js
# or
bun add @useautumn/convex autumn-js

For Next.js / Express Projects

npm install autumn-js
# or
bun add autumn-js

Environment Setup

Add to your .env or environment variables:

AUTUMN_SECRET_KEY=am_sk_your_secret_key

Get your secret key from https://app.useautumn.com

Pricing as Code (CLI)

Autumn supports defining features and products in code using autumn.config.ts:

1. Initialize Project

npx atmn init

2. Define Features and Products

Create autumn.config.ts:

import {
  feature,
  product,
  featureItem,
  priceItem,
} from "atmn";

// Features
const messages = feature({
  id: "messages",
  name: "Messages",
  type: "single_use", // Consumed on each use
});

const workspaces = feature({
  id: "workspaces",
  name: "Workspaces",
  type: "single_use",
});

// Products
const free = product({
  id: "free",
  name: "Free",
  is_default: true,
  items: [
    featureItem({
      feature_id: messages.id,
      included_usage: 50,
      interval: "month", // Resets monthly
    }),
    featureItem({
      feature_id: workspaces.id,
      included_usage: 2,
    }),
  ],
});

const pro = product({
  id: "pro",
  name: "Pro",
  items: [
    priceItem({
      price: 19,
      interval: "month",
    }),
    featureItem({
      feature_id: messages.id,
      included_usage: 500,
      interval: "month",
    }),
    featureItem({
      feature_id: workspaces.id,
      included_usage: 10,
    }),
  ],
});

const enterprise = product({
  id: "enterprise",
  name: "Enterprise",
  items: [
    featureItem({
      feature_id: messages.id,
      unlimited: true,
    }),
    featureItem({
      feature_id: workspaces.id,
      unlimited: true,
    }),
  ],
});

export default {
  features: [messages, workspaces],
  products: [free, pro, enterprise],
};

3. Push to Autumn

npx atmn push

CLI Commands

CommandDescription
npx atmn initInitialize project, pull existing config
npx atmn pushPush local config to Autumn
npx atmn pullPull remote config to local
npx atmn authAuthenticate with Autumn
npx atmn dashboardOpen Autumn dashboard

Backend Integration

Convex Setup

  1. Register the component in convex/convex.config.ts:
import { defineApp } from "convex/server";
import autumn from "@useautumn/convex/convex.config";

const app = defineApp();
app.use(autumn);

export default app;
  1. Initialize the client in convex/autumn.ts:
import { components } from "./_generated/api";
import { Autumn } from "@useautumn/convex";

export const autumn = new Autumn(components.autumn, {
  secretKey: process.env.AUTUMN_SECRET_KEY ?? "",
  identify: async (ctx: any) => {
    // Customize based on your auth provider
    const user = await ctx.auth.getUserIdentity();
    if (!user) return null;

    return {
      customerId: user.subject, // Unique user ID
      customerData: {
        name: user.name as string,
        email: user.email as string,
      },
    };
  },
});

// Export API functions
export const {
  track,
  cancel,
  query,
  attach,
  check,
  checkout,
  usage,
  setupPayment,
  createCustomer,
  listProducts,
  billingPortal,
  createReferralCode,
  redeemReferralCode,
  createEntity,
  getEntity,
} = autumn.api();

Next.js / Express Setup

// lib/autumn.ts
import { Autumn } from "autumn-js";

export const autumn = new Autumn({
  secretKey: process.env.AUTUMN_SECRET_KEY!,
});

// In API routes
export async function POST(req: Request) {
  const { userId } = await getAuth(req);

  const result = await autumn.check({
    customerId: userId,
    featureId: "api_calls",
  });

  if (!result.allowed) {
    return Response.json({ error: "Limit exceeded" }, { status: 403 });
  }

  // Process request...

  await autumn.track({
    customerId: userId,
    featureId: "api_calls",
    value: 1,
  });

  return Response.json({ success: true });
}

Core API Reference

Check Feature Access

Check if a customer can access a feature:

// Convex
const result = await autumn.check(ctx, {
  featureId: "messages",
  requiredBalance: 1, // Optional: minimum balance needed
});

if (result.allowed) {
  // Allow action
} else {
  // Show upgrade prompt
}

// Direct API
const response = await fetch("https://api.useautumn.com/v1/check", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${AUTUMN_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    customer_id: userId,
    feature_id: "messages",
    required_balance: 1,
  }),
});

Response:

{
  "allowed": true,
  "customer_id": "user_123",
  "balance": {
    "feature_id": "messages",
    "unlimited": false,
    "granted_balance": 100,
    "purchased_balance": 0,
    "current_balance": 75,
    "usage": 25,
    "overage_allowed": false,
    "reset": {
      "interval": "month",
      "interval_count": 1,
      "resets_at": 1735689600000
    },
    "plan_id": "pro"
  }
}

Track Usage

Record usage for metered features:

// Convex
await autumn.track(ctx, {
  featureId: "api_calls",
  value: 1,
});

// Direct API
await fetch("https://api.useautumn.com/v1/track", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${AUTUMN_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    customer_id: userId,
    feature_id: "api_calls",
    value: 1,
    idempotency_key: "unique_request_id", // Optional: prevent duplicate tracking
  }),
});

Initiate Checkout

Start a checkout session for upgrades:

// Convex
const result = await autumn.checkout(ctx, {
  productId: "pro",
});
// Returns { url: "https://checkout.stripe.com/..." }

// Direct API
const response = await fetch("https://api.useautumn.com/v1/checkout", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${AUTUMN_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    customer_id: userId,
    product_id: "pro",
    success_url: "https://yourapp.com/success",
    cancel_url: "https://yourapp.com/pricing",
  }),
});

Billing Portal

Open Stripe billing portal for subscription management:

const result = await autumn.billingPortal(ctx, {});
// Returns { url: "https://billing.stripe.com/..." }

Frontend Integration

React Provider (Web)

// providers/autumn-provider.tsx
"use client";
import { AutumnProvider } from "autumn-js/react";
import { api } from "../convex/_generated/api";
import { useConvex } from "convex/react";

export function AutumnWrapper({ children }: { children: React.ReactNode }) {
  const convex = useConvex();

  return (
    <AutumnProvider convex={convex} convexApi={(api as any).autumn}>
      {children}
    </AutumnProvider>
  );
}

Using the Hook

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

function UpgradeButton() {
  const { customer, checkout, check } = useCustomer();

  const handleUpgrade = async () => {
    await checkout({
      productId: "pro",
      dialog: CheckoutDialog, // Optional: built-in dialog
    });
  };

  return <button onClick={handleUpgrade}>Upgrade to Pro</button>;
}

Pricing Table Component

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

function PricingPage() {
  return <PricingTable />;
}

Autumn Dashboard Configuration

1. Create Features

In the Autumn dashboard, define your metered features:

Feature IDTypeReset Interval
messagesMeteredMonthly
workspacesMeteredNone (cumulative)
api_callsMeteredMonthly
storage_gbMeteredNone

2. Create Products/Plans

Define your pricing tiers:

Free Plan:

  • messages: 50/month
  • workspaces: 2

Pro Plan ($19/mo):

  • messages: 500/month
  • workspaces: 10

Enterprise (Custom):

  • messages: Unlimited
  • workspaces: Unlimited

3. Connect Stripe

Link your Stripe account in the Autumn dashboard to enable payment processing.

Common Patterns

Paywall Before Action

async function sendMessage(ctx, args) {
  // 1. Check entitlement
  const result = await autumn.check(ctx, {
    featureId: "messages",
  });

  if (!result.allowed) {
    throw new Error("BILLING_LIMIT_EXCEEDED");
  }

  // 2. Perform action
  const message = await createMessage(args);

  // 3. Track usage
  await autumn.track(ctx, {
    featureId: "messages",
    value: 1,
  });

  return message;
}

Entity-Based Billing (Per-Seat)

// Track usage per entity (e.g., per team member)
await autumn.track(ctx, {
  featureId: "seats",
  entityId: teamMemberId,
  value: 1,
  entityData: {
    name: memberName,
    email: memberEmail,
  },
});

// Check entity-specific access
const result = await autumn.check(ctx, {
  featureId: "seats",
  entityId: teamMemberId,
});

Credit System

// Grant credits
await autumn.attach(ctx, {
  featureId: "credits",
  value: 100,
});

// Use credits
await autumn.track(ctx, {
  featureId: "credits",
  value: 10,
});

// Check remaining
const result = await autumn.check(ctx, {
  featureId: "credits",
});
console.log(result.balance.current_balance); // 90

Handling Billing Errors in UI

function ChatInput() {
  const [error, setError] = useState(null);
  const [showUpgrade, setShowUpgrade] = useState(false);

  const handleSend = async (message) => {
    try {
      await sendMessage({ content: message });
    } catch (err) {
      if (err.message.includes("BILLING_LIMIT_EXCEEDED")) {
        setShowUpgrade(true);
      } else {
        setError(err.message);
      }
    }
  };

  return (
    <>
      <Input onSubmit={handleSend} />
      {showUpgrade && <UpgradeModal onClose={() => setShowUpgrade(false)} />}
    </>
  );
}

Error Handling Best Practices

  1. Fail open on API errors: If Autumn API is unavailable, allow the action rather than blocking users.
try {
  const result = await autumn.check(ctx, { featureId: "messages" });
  if (!result.allowed) {
    throw new Error("BILLING_LIMIT_EXCEEDED");
  }
} catch (error) {
  if (error.message === "BILLING_LIMIT_EXCEEDED") {
    throw error; // Re-throw billing errors
  }
  // Log but continue on API errors
  console.error("[Autumn] Check failed:", error);
}
  1. Use idempotency keys for tracking to prevent duplicate charges.

  2. Track after success, not before, to avoid charging for failed operations.

Testing

Test Mode

Use test API keys (prefix am_sk_test_) for development:

AUTUMN_SECRET_KEY=am_sk_test_xxx

Mock Responses

For unit tests, mock the Autumn API responses:

jest.mock("autumn-js", () => ({
  check: jest.fn().mockResolvedValue({ allowed: true, balance: { current_balance: 50 } }),
  track: jest.fn().mockResolvedValue({ success: true }),
}));

Resources

スコア

総合スコア

35/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

0/5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です