@paytrigo-net/sdk
v0.1.4
Published
payTrigo SDK client and OpenAPI types
Readme
@paytrigo-net/sdk
Official PayTrigo SDK (runtime client + TypeScript types). PayTrigo is non-custodial: funds go directly to the merchant wallet. The SDK helps you create invoices, fetch status/intent, and submit payment intents with full type safety.
Install
npm install @paytrigo-net/sdk
# or
pnpm add @paytrigo-net/sdk
# or
yarn add @paytrigo-net/sdkRequirements
- A runtime with
fetch(Node 18+ or a polyfill) - TypeScript recommended (types are included)
Quickstart (Merchant)
import { createPaytrigoMerchantClient } from "@paytrigo-net/sdk";
const client = createPaytrigoMerchantClient({
auth: { type: "apiKey", token: process.env.PAYTRIGO_API_KEY! },
});
const invoice = await client.invoices.create({
amount: "29.00",
// options omitted -> all enabled Address Book entries are exposed
metadata: { orderId: "order_123" },
});
console.log(invoice.invoiceId, invoice.payUrl);Configuration
Environment Variables
PAYTRIGO_API_KEY=sk_live_your_api_key_here
PAYTRIGO_PLATFORM_KEY=sk_live_your_platform_key_here
PAYTRIGO_API_URL=https://api.paytrigo.net
PAYTRIGO_WEBHOOK_SECRET=whsec_...Base URL
The SDK defaults to https://api.paytrigo.net and automatically calls /v1/*. Do not include /v1 in baseUrl.
const client = createPaytrigoMerchantClient({
baseUrl: process.env.PAYTRIGO_API_URL,
auth: { type: "apiKey", token: process.env.PAYTRIGO_API_KEY! },
});Timeouts, User-Agent, Retry
const client = createPaytrigoMerchantClient({
auth: { type: "apiKey", token: process.env.PAYTRIGO_API_KEY! },
timeoutMs: 10000,
userAgent: "my-app/1.2.3",
retry: { max: 2, backoffMs: 500 },
});Hosted Checkout (Recommended)
Create the invoice on your server, then redirect the user to payUrl.
Server: Create Invoice
import { createPaytrigoMerchantClient } from "@paytrigo-net/sdk";
const client = createPaytrigoMerchantClient({
auth: { type: "apiKey", token: process.env.PAYTRIGO_API_KEY! },
});
export async function createPayment(orderId: string, amount: string) {
const invoice = await client.invoices.create({
amount,
metadata: { orderId },
});
return {
invoiceId: invoice.invoiceId,
payUrl: invoice.payUrl,
checkoutToken: invoice.checkoutToken,
};
}Client: Redirect
const response = await fetch("/api/create-payment", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amount: "29.00" }),
});
const { payUrl } = await response.json();
window.location.href = payUrl;Merchant vs Platform Keys
Merchant keys are for checkout integrations. They cannot set recipientAddress and rely on Address Book entries configured in the dashboard.
Platform keys are for server-side marketplace flows. They must set recipientAddress for each invoice (or per option).
Security note: never expose API keys in the browser. Only use checkoutToken client-side.
Merchant: Multi-option (Multi-token)
await client.invoices.create({
amount: "100.00",
options: [
{ chain: "base", token: "usdc", isDefault: true },
{ chain: "base", token: "usdt" },
],
});Platform: Single Option (Server-side only)
import { createPaytrigoPlatformClient } from "@paytrigo-net/sdk";
const platformClient = createPaytrigoPlatformClient({
auth: { type: "apiKey", token: process.env.PAYTRIGO_PLATFORM_KEY! },
});
await platformClient.invoices.create({
amount: "49.99",
chain: "base",
token: "usdc",
recipientAddress: "0xSellerWallet...",
});Platform: Multi-option (Each option needs recipientAddress)
await platformClient.invoices.create({
amount: "49.99",
options: [
{ chain: "base", token: "usdc", recipientAddress: "0xSellerWallet..." },
{ chain: "base", token: "usdt", recipientAddress: "0xSellerWallet..." },
],
});Checkout Token Flow (Pay Page)
Use the checkout token to build a pay page that only has access to the specific invoice.
const invoice = await client.invoices.create({ amount: "29.00" });
const payClient = createPaytrigoMerchantClient({
auth: { type: "checkoutToken", token: invoice.checkoutToken },
});
const intent = await payClient.invoices.getIntent(invoice.invoiceId);
const options = await payClient.invoices.getOptions(invoice.invoiceId);Render Options (Chain/Token Selector)
const options = await payClient.invoices.getOptions(invoice.invoiceId);
const labels = options.options.map((option) => ({
id: `${option.chain}-${option.token}`,
label: `${option.chainName} ${option.tokenMeta.symbol}`,
routerAddress: option.routerAddress,
isDefault: option.isDefault,
}));API-Only Flow (Intent + Submit)
Use this when you build your own wallet UI instead of Hosted Checkout.
import { createPaytrigoMerchantClient } from "@paytrigo-net/sdk";
const client = createPaytrigoMerchantClient({
auth: { type: "apiKey", token: process.env.PAYTRIGO_API_KEY! },
});
// 1) Create invoice
const invoice = await client.invoices.create({
amount: "50.00",
chain: "base",
token: "usdc",
});
// 2) Get intent (router, amountAtomic, steps, etc.)
const intent = await client.invoices.getIntent(invoice.invoiceId, {
chain: "base",
token: "usdc",
});
// 3) Build and send transaction with your wallet UI...
// 4) Submit txHash for verification
await client.invoices.submitPaymentIntent(invoice.invoiceId, {
txHash: "0x...",
chain: "base",
token: "usdc",
});Check Invoice Status
const invoice = await client.invoices.get("inv_123");
console.log(invoice.status, invoice.amount, invoice.chain);Webhooks (Signature Verification)
Webhooks are the recommended way to confirm payments in production. Always verify the signature and dedupe on event.id or X-Paytrigo-Event-Id.
import crypto from "crypto";
type WebhookEvent = {
id: string;
type:
| "invoice.created"
| "invoice.confirmed"
| "invoice.expired"
| "invoice.invalid"
| "invoice.updated";
createdAt: string;
data: {
invoiceId: string;
status: string;
[key: string]: unknown;
};
};
export async function handleWebhook(request: Request) {
const rawBody = await request.text();
const timestamp = request.headers.get("X-Paytrigo-Timestamp") ?? "";
const signature = request.headers.get("X-Paytrigo-Signature") ?? "";
const eventId = request.headers.get("X-Paytrigo-Event-Id") ?? "";
const expected =
"v1=" +
crypto
.createHmac("sha256", process.env.PAYTRIGO_WEBHOOK_SECRET!)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
if (!timingSafeEquals(signature, expected)) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(rawBody) as WebhookEvent;
// Deduplicate by eventId (store in DB)
if (await alreadyProcessed(eventId)) {
return new Response("ok");
}
await markProcessed(eventId);
await processEvent(event);
return new Response("ok");
}
function timingSafeEquals(a: string, b: string) {
try {
return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
} catch {
return false;
}
}
async function alreadyProcessed(_eventId: string) {
return false;
}
async function markProcessed(_eventId: string) {}
async function processEvent(event: WebhookEvent) {
if (event.type === "invoice.confirmed") {
// fulfill order
}
}Next.js Webhook Route (App Router)
// app/api/webhooks/paytrigo/route.ts
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
export async function POST(request: NextRequest) {
const rawBody = await request.text();
const timestamp = request.headers.get("X-Paytrigo-Timestamp") ?? "";
const signature = request.headers.get("X-Paytrigo-Signature") ?? "";
const expected =
"v1=" +
crypto
.createHmac("sha256", process.env.PAYTRIGO_WEBHOOK_SECRET!)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
if (!timingSafeEquals(signature, expected)) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
// Parse and process event
return NextResponse.json({ received: true });
}
function timingSafeEquals(a: string, b: string) {
try {
return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
} catch {
return false;
}
}Polling (Not Recommended)
Polling is acceptable for testing or UI fallback, but webhooks are strongly recommended in production.
const terminalStates = new Set(["confirmed", "expired", "invalid"]);
async function pollInvoice(invoiceId: string) {
while (true) {
const invoice = await client.invoices.get(invoiceId);
if (terminalStates.has(invoice.status)) return invoice;
await new Promise((resolve) => setTimeout(resolve, 5000));
}
}Idempotency
For create and submit APIs, you can pass an idempotencyKey. If retry is enabled, the SDK will automatically set an idempotency key for POST requests.
await client.invoices.create({
amount: "29.00",
idempotencyKey: `pay_attempt_${crypto.randomUUID()}`,
});Errors
import { PaytrigoError, PaytrigoValidationError } from "@paytrigo-net/sdk";
try {
await client.invoices.create({ amount: "29.00" });
} catch (error) {
if (error instanceof PaytrigoValidationError) {
console.error(error.issues);
} else if (error instanceof PaytrigoError) {
console.error(error.status, error.code, error.message);
}
}Types
import type { components } from "@paytrigo-net/sdk";
type InvoiceDetail = components["schemas"]["InvoiceDetailResponseDto"];
type CreateInvoiceRequest = components["schemas"]["CreateInvoiceDto"];Note: webhook payload types are not part of the OpenAPI spec yet. You can define your own type based on the docs:
type WebhookEvent = {
id: string;
type:
| "invoice.created"
| "invoice.confirmed"
| "invoice.expired"
| "invoice.invalid"
| "invoice.updated";
data: Partial<components["schemas"]["InvoiceDetailResponseDto"]> & {
invoiceId: string;
status: string;
};
};Note: CreateInvoiceDto.token is a string token ID (e.g. "usdc"), while invoice responses and webhooks return a token descriptor object.
Utility Type Patterns
type InvoiceSummary = Pick<
components["schemas"]["InvoiceDetailResponseDto"],
"invoiceId" | "status" | "amount" | "chain"
>;
type ClientSafeInvoice = Omit<
components["schemas"]["InvoiceDetailResponseDto"],
"metadata"
>;OpenAPI JSON
// Requires resolveJsonModule in tsconfig
import openapi from "@paytrigo-net/sdk/openapi.json";Platform Fee Helpers
import { getPlatformFeeSummary, expectPlatformFee } from "@paytrigo-net/sdk";
const summary = getPlatformFeeSummary(invoice);
expectPlatformFee(invoice, { bps: 50 });You can also assert platform fees at creation time:
await client.invoices.create({
amount: "29.00",
expectPlatformFee: { bps: 50, recipient: "0xPlatformFeeWallet..." },
});Escape Hatch (Raw Request)
const data = await client.request("get", "/v1/invoices/{invoiceId}", {
params: { path: { invoiceId: "inv_..." } },
});Next.js API Route Example
// app/api/create-payment/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createPaytrigoMerchantClient } from "@paytrigo-net/sdk";
export async function POST(request: NextRequest) {
const { amount } = await request.json();
const client = createPaytrigoMerchantClient({
auth: { type: "apiKey", token: process.env.PAYTRIGO_API_KEY! },
});
const invoice = await client.invoices.create({ amount });
return NextResponse.json({
invoiceId: invoice.invoiceId,
payUrl: invoice.payUrl,
});
}Next.js Pages Router (API Route)
// pages/api/create-payment.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { createPaytrigoMerchantClient } from "@paytrigo-net/sdk";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") return res.status(405).end();
const { amount } = req.body as { amount: string };
const client = createPaytrigoMerchantClient({
auth: { type: "apiKey", token: process.env.PAYTRIGO_API_KEY! },
});
const invoice = await client.invoices.create({ amount });
res
.status(200)
.json({ invoiceId: invoice.invoiceId, payUrl: invoice.payUrl });
}Development (Repo Maintainers)
- OpenAPI JSON and schema types are generated. Do not edit
openapi.jsonorsrc/schema.d.tsmanually. - To regenerate from the API codebase:
pnpm sdk:syncRepository
Source: https://github.com/paytrigo/paytrigo-sdk
License
MIT. See LICENSE.
