Corpland Revenue

SDK and React

The TypeScript client, the server handler for your billing page, and React hooks.

npm install @corpland/revenue

Server client

import { Revenue } from "@corpland/revenue";

const revenue = new Revenue({ secretKey: process.env.REVENUE_SECRET_KEY });

Every operation in the API reference is a method, with camelCase fields: revenue.customers.getOrCreate, revenue.billing.previewAttach, revenue.balances.trackTokens, revenue.entities.create, revenue.invoices.list, revenue.rewards.redeem and the rest. revenue.check, revenue.track and revenue.attach are the short forms.

OptionDefaultMeaning
secretKeyREVENUE_SECRET_KEYYour rv_sk_ key
baseUrlREVENUE_API_URL or the hosted APIPoint it at a different host
timeoutMs5000check waits at most 3000
retries2Retries 5xx, 429 and network errors with backoff. Writes carry an idempotency key, so retries never double count
failOpentrueOn 5xx, timeouts and network errors, check answers allowed: true and track drops the event, both with failOpen: true and a warning
onFailOpenconsole warningSend fail open events to your logger or alerts

A 4xx is never failed open: it throws RevenueError with status, code and data.

Server handler

Your billing page needs to read the customer and start checkouts without holding the secret key. Mount one route and tell it who is signed in:

app/api/revenue/[action]/route.ts
import { nextHandler } from "@corpland/revenue/handler";
import { auth } from "@/auth";

export const { POST } = nextHandler({
  secretKey: process.env.REVENUE_SECRET_KEY,
  identify: async () => {
    const session = await auth();
    return session ? { customerId: session.user.id, customerData: { email: session.user.email, name: session.user.name } } : null;
  },
});
Express
import { expressHandler } from "@corpland/revenue/handler";

app.post("/api/revenue/:action", requireUser, expressHandler({ identify: (req) => ({ customerId: req.user.id }) }));

The browser can only act on the signed in customer, and only through these actions: customer, check, plans, preview_attach, attach, preview_update, update, portal, setup_payment, entities, entity, usage, redeem, referral_code, redeem_referral. Usage is never tracked from the browser.

React

app/providers.tsx
"use client";
import { RevenueProvider } from "@corpland/revenue/react";

export const Providers = ({ children }) => <RevenueProvider backendUrl="/api/revenue">{children}</RevenueProvider>;
Billing.tsx
"use client";
import { useCustomer, usePricingTable } from "@corpland/revenue/react";

export const Billing = () => {
  const { customer, check, attach, previewAttach, cancel, resume, openPortal } = useCustomer({ expand: ["invoices"] });
  const { plans } = usePricingTable();
  const { allowed } = check({ featureId: "premium_message" });

  return plans.map((plan) => (
    <button key={plan.id} disabled={plan.customerEligibility?.attachAction === "none"} onClick={() => attach({ planId: plan.id })}>
      {plan.customerEligibility?.attachAction === "upgrade" ? "Upgrade" : "Choose"} {plan.name}
    </button>
  ));
};
  • check in the hook answers from the cached customer with no network call, for showing and hiding things. Always check on the server before doing the work.
  • attach redirects to checkout when payment is needed and refreshes the customer when it is not. Pass redirect: false to handle it yourself.
  • previewAttach returns the line items to show before the customer confirms.
  • useEntity(entityId), useUsage({ featureId, range }) and useCheck({ featureId }) cover seats, charts and single gates.

On this page