SDK and React
The TypeScript client, the server handler for your billing page, and React hooks.
npm install @corpland/revenueServer 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.
| Option | Default | Meaning |
|---|---|---|
secretKey | REVENUE_SECRET_KEY | Your rv_sk_ key |
baseUrl | REVENUE_API_URL or the hosted API | Point it at a different host |
timeoutMs | 5000 | check waits at most 3000 |
retries | 2 | Retries 5xx, 429 and network errors with backoff. Writes carry an idempotency key, so retries never double count |
failOpen | true | On 5xx, timeouts and network errors, check answers allowed: true and track drops the event, both with failOpen: true and a warning |
onFailOpen | console warning | Send 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:
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;
},
});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
"use client";
import { RevenueProvider } from "@corpland/revenue/react";
export const Providers = ({ children }) => <RevenueProvider backendUrl="/api/revenue">{children}</RevenueProvider>;"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>
));
};checkin 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.attachredirects to checkout when payment is needed and refreshes the customer when it is not. Passredirect: falseto handle it yourself.previewAttachreturns the line items to show before the customer confirms.useEntity(entityId),useUsage({ featureId, range })anduseCheck({ featureId })cover seats, charts and single gates.