@billing-io/sdk
v2.0.0
Published
Official billing.io JavaScript/TypeScript SDK -- non-custodial crypto checkout API
Maintainers
Readme
@billing-io/sdk
Official JavaScript/TypeScript SDK for the billing.io non-custodial crypto checkout API.
- Zero runtime dependencies -- uses the built-in
fetchAPI (Node 18+, Deno, Cloudflare Workers, browsers) - Full TypeScript type safety derived from the OpenAPI v2 specification
- Automatic cursor-based pagination via
asynciterators - Webhook signature verification using the Web Crypto API
Installation
npm install @billing-io/sdkyarn add @billing-io/sdkpnpm add @billing-io/sdkQuick start
Create a checkout and poll for status
import { BillingIO } from "@billing-io/sdk";
const billing = new BillingIO({ apiKey: "sk_live_..." });
// 1. Create a checkout
const checkout = await billing.checkouts.create({
amount_usd: 49.99,
chain: "tron",
token: "USDT",
metadata: { order_id: "ord_12345" },
});
console.log("Deposit to:", checkout.deposit_address);
console.log("Checkout ID:", checkout.checkout_id);
// 2. Poll for status
let status = await billing.checkouts.getStatus(checkout.checkout_id);
while (status.status !== "confirmed" && status.status !== "expired") {
await new Promise((r) => setTimeout(r, status.polling_interval_ms));
status = await billing.checkouts.getStatus(checkout.checkout_id);
console.log(
`Status: ${status.status} (${status.confirmations}/${status.required_confirmations})`,
);
}
if (status.status === "confirmed") {
console.log("Payment confirmed!", status.tx_hash);
}Webhook verification
import { verifyWebhookSignature } from "@billing-io/sdk";
// In your webhook handler (e.g. Express, Next.js Route Handler, etc.)
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get("x-billing-signature")!;
try {
const event = await verifyWebhookSignature(
body,
signature,
process.env.WEBHOOK_SECRET!, // whsec_...
);
switch (event.type) {
case "checkout.completed":
console.log("Payment completed:", event.data.checkout_id);
// fulfill the order ...
break;
case "checkout.expired":
console.log("Checkout expired:", event.data.checkout_id);
break;
}
return new Response("ok", { status: 200 });
} catch (err) {
console.error("Webhook verification failed:", err);
return new Response("Invalid signature", { status: 400 });
}
}You can also adjust the timestamp tolerance (default is 300 seconds / 5 minutes):
const event = await verifyWebhookSignature(body, signature, secret, {
tolerance: 600, // 10 minutes
});Error handling
import { BillingIO, BillingIOError } from "@billing-io/sdk";
const billing = new BillingIO({ apiKey: "sk_live_..." });
try {
await billing.checkouts.create({
amount_usd: -1, // invalid
chain: "tron",
token: "USDT",
});
} catch (err) {
if (err instanceof BillingIOError) {
console.error("API error:", {
type: err.type, // "invalid_request"
code: err.code, // e.g. "invalid_amount"
message: err.message, // human-readable description
statusCode: err.statusCode, // 400
param: err.param, // "amount_usd"
});
// Specific error types
switch (err.type) {
case "authentication_error":
// bad API key
break;
case "rate_limited":
// back off and retry
break;
case "idempotency_conflict":
// idempotency key was reused with different params
break;
case "not_found":
// resource does not exist
break;
}
}
}Pagination
Manual pagination
let page = await billing.checkouts.list({ limit: 10 });
for (const checkout of page.data) {
console.log(checkout.checkout_id, checkout.status);
}
// Fetch the next page
if (page.has_more && page.next_cursor) {
page = await billing.checkouts.list({
limit: 10,
cursor: page.next_cursor,
});
}Auto-pagination
Use the autoPaginate helper to iterate across all pages automatically:
for await (const checkout of billing.checkouts.list.autoPaginate({
status: "confirmed",
})) {
console.log(checkout.checkout_id, checkout.amount_usd);
}This works for all list endpoints:
// All events for a specific checkout
for await (const event of billing.events.list.autoPaginate({
checkout_id: "co_abc123",
})) {
console.log(event.type, event.created_at);
}
// All webhook endpoints
for await (const endpoint of billing.webhooks.list.autoPaginate()) {
console.log(endpoint.webhook_id, endpoint.url);
}
// All active customers
for await (const customer of billing.customers.list.autoPaginate({
status: "active",
})) {
console.log(customer.customer_id, customer.email);
}
// All subscriptions for a customer
for await (const sub of billing.subscriptions.list.autoPaginate({
customer_id: "cus_abc123",
})) {
console.log(sub.subscription_id, sub.status);
}Customers
// Create a customer
const customer = await billing.customers.create({
email: "[email protected]",
name: "Alice",
metadata: { tier: "enterprise" },
});
// List customers
const customers = await billing.customers.list({ status: "active" });
// Get a customer
const cust = await billing.customers.get("cus_abc123");
// Update a customer
await billing.customers.update("cus_abc123", { name: "Alice Smith" });Payment Methods
// Create a payment method
const pm = await billing.paymentMethods.create({
customer_id: "cus_abc123",
type: "wallet",
chain: "tron",
address: "T...",
label: "Main Wallet",
});
// List payment methods for a customer
const methods = await billing.paymentMethods.list({ customer_id: "cus_abc123" });
// Set as default
await billing.paymentMethods.setDefault("pm_abc123");
// Delete
await billing.paymentMethods.delete("pm_abc123");Payment Links
// Create a payment link
const link = await billing.paymentLinks.create({
amount_usd: 99.99,
chain: "tron",
token: "USDT",
});
console.log("Share this link:", link.url);
// List payment links
const links = await billing.paymentLinks.list({ status: "active" });Subscriptions
// Create a plan
const plan = await billing.subscriptionPlans.create({
name: "Pro Monthly",
amount_usd: 29.99,
interval: "monthly",
token: "USDT",
chain: "tron",
});
// Create a subscription
const sub = await billing.subscriptions.create({
customer_id: "cus_abc123",
plan_id: plan.plan_id,
payment_method_id: "pm_abc123",
});
// Cancel a subscription
await billing.subscriptions.update(sub.subscription_id, { status: "cancelled" });
// List renewals
const renewals = await billing.subscriptionRenewals.list({
subscription_id: sub.subscription_id,
});
// Retry a failed renewal
await billing.subscriptionRenewals.retry("ren_abc123");Entitlements
// Create an entitlement
await billing.entitlements.create({
subscription_id: "sub_abc123",
feature_key: "api_calls",
value: "10000",
});
// Check if a customer is entitled to a feature
const check = await billing.entitlements.check({
customer_id: "cus_abc123",
feature_key: "api_calls",
});
if (check.entitled) {
console.log("Allowed:", check.value);
}
// Delete an entitlement
await billing.entitlements.delete("ent_abc123");Payouts
// Create a payout intent
const payout = await billing.payoutIntents.create({
amount_usd: 500.0,
chain: "arbitrum",
token: "USDC",
destination_address: "0x...",
});
// Execute the payout
await billing.payoutIntents.execute(payout.payout_id);
// List settlements
const settlements = await billing.settlements.list({
payout_id: payout.payout_id,
});Revenue
// List revenue events
const events = await billing.revenueEvents.list({ type: "payment_received" });
// Get accounting summary
const accounting = await billing.revenueEvents.accounting({
start_date: "2025-01-01T00:00:00Z",
end_date: "2025-12-31T23:59:59Z",
});
console.log("Net revenue:", accounting.net_revenue_usd);
// Create an adjustment
await billing.adjustments.create({
amount_usd: 10.0,
type: "credit",
description: "Goodwill credit",
customer_id: "cus_abc123",
});
// List adjustments
const adjustments = await billing.adjustments.list({ type: "credit" });Managing webhook endpoints
// Create
const endpoint = await billing.webhooks.create({
url: "https://example.com/webhooks/billing",
events: ["checkout.completed", "checkout.expired"],
description: "Production webhook",
});
console.log("Secret (store this!):", endpoint.secret);
// List
const endpoints = await billing.webhooks.list();
// Get
const ep = await billing.webhooks.get("we_abc123");
// Delete
await billing.webhooks.delete("we_abc123");Idempotent requests
Pass an idempotencyKey to safely retry checkout creation:
import { randomUUID } from "node:crypto";
const checkout = await billing.checkouts.create(
{
amount_usd: 49.99,
chain: "tron",
token: "USDT",
},
{ idempotencyKey: randomUUID() },
);If a request with the same key was already processed, the original response is returned. Keys expire after 24 hours.
Health check
const health = await billing.health.get();
console.log(health.status); // "healthy"
console.log(health.version); // "1.0.0"Configuration
const billing = new BillingIO({
// Required -- your secret API key
apiKey: "sk_live_...",
// Optional -- override the base URL (useful for local development)
baseUrl: "http://localhost:8080/v1",
});TypeScript
The SDK is written in TypeScript and ships with full type declarations. All request parameters, response objects, and error types are fully typed.
import type {
Checkout,
CheckoutStatus,
CreateCheckoutRequest,
Customer,
Subscription,
SubscriptionPlan,
PayoutIntent,
EventType,
Chain,
Token,
} from "@billing-io/sdk";
const params: CreateCheckoutRequest = {
amount_usd: 100,
chain: "arbitrum",
token: "USDC",
};Requirements
- Node.js 18+ (or any runtime with a global
fetchandcrypto.subtle) - No runtime dependencies
License
MIT
