Skip to main content

TypeScript / Node.js SDK

The official TypeScript/Node.js SDK — @itpay/mcp — provides a fully typed client for the ItPay Protocol. It wraps the REST API with idiomatic TypeScript, supports both direct API usage and MCP transport integration, and includes built-in webhook signature verification.


Installation

npm install @itpay/mcp

Requirements: Node.js 18+ (native fetch and crypto module).


Quick Setup

Configure the client with your API key and optional defaults:

import { ItPayClient } from '@itpay/mcp';

const client = new ItPayClient({
apiKey: 'sk_liv_your_api_key_here',
channel: 'alipay',
});

The API key can also be loaded from the ITPAY_API_KEY environment variable, in which case the constructor argument is optional:

const client = new ItPayClient({ channel: 'alipay' });
// Reads ITPAY_API_KEY from process.env

Client Configuration

OptionTypeDefaultDescription
apiKeystringprocess.env.ITPAY_API_KEYYour secret API key
channelstringDefault payment channel (e.g. alipay)
baseUrlstringhttps://api.itpay.ai/v1API base URL
timeoutnumber30000Request timeout in milliseconds
webhookSecretstringprocess.env.ITPAY_WEBHOOK_SECRETSecret for verifying webhooks

Creating a PaymentIntent

The payment flow starts by creating a PaymentIntent, then generating a QR code so the user can scan and pay.

import { ItPayClient } from '@itpay/mcp';
import { randomUUID } from 'node:crypto';

const client = new ItPayClient({
apiKey: 'sk_liv_your_api_key_here',
channel: 'alipay',
});

async function createPayment(): Promise<void> {
// 1. Create the PaymentIntent
const intent = await client.paymentIntents.create({
id: `pi_${randomUUID()}`,
service_id: '01J7XYKZ1A2B3C4D5E6F7G8H9I',
type: 'one_time',
amount: {
currency: 'CNY',
value: 699, // 6.99 CNY in minor units
},
description: 'AI document summary (42 pages)',
payer: {
agent_id: 'agent_cli_a1b2c3d4',
human_id: 'user_abc_789',
},
metadata: {
session_id: 'sess_xyz_456',
},
});

console.log(`Intent created: ${intent.id} (status: ${intent.status})`);
// → Intent created: pi_01J7XZ1A2B3C4D5E6F7G8H9IK (status: pending)

// 2. Generate the QR code
const qrCharge = await client.qr.generate(intent.id);

console.log(`QR scan URL: ${qrCharge.scan_url}`);
console.log(`QR expires at: ${intent.expires_at}`);

// 3. Display the QR to the user
// In a web UI: <img src={qrCharge.scan_url} alt="Pay with Alipay" />
// In a terminal: open the URL via your system browser
// In a chat: send the image inline

// 4. Poll for completion (useful for development)
const result = await client.paymentIntents.waitForCompletion(intent.id, {
timeout: 300_000, // 5 minutes
pollInterval: 2_000, // poll every 2 seconds
});

if (result.status === 'succeeded') {
console.log('✓ Payment received!');
} else {
console.log(`✗ Payment failed: ${result.status}`);
}
}

createPayment().catch(console.error);

API Reference

client.paymentIntents.create(params)

FieldTypeRequiredDescription
idstringYesUnique UUIDv7 identifier (idempotency key)
service_idstringYesServiceManifest ID from registration
typestringYesone_time, cumulative, or subscription
amountMoneyYes{ currency: string, value: number }
descriptionstringNoHuman-readable payment label
payerPartyYes{ agent_id: string, human_id?: string }
metadataobjectNoArbitrary key-value data (max 4 KB)

client.qr.generate(paymentIntentId)

Generates a QR charge for the given PaymentIntent. The PaymentIntent must be in pending status. Returns a QRCharge object with a scan_url for rendering.

client.paymentIntents.waitForCompletion(id, options)

Polls the PaymentIntent until it reaches a terminal state (succeeded, expired, cancelled) or the timeout is hit. Accepts { timeout?: number, pollInterval?: number }.


Handling Webhooks

When a payment completes, the ItPay platform sends a signed webhook POST to your ServiceManifest's configured endpoint URL. Your handler must verify the HMAC-SHA256 signature and check for idempotency.

Here's a complete Express.js example:

import express, { Request, Response } from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';

const app = express();
const PORT = 8080;

// ⚠️ Must match the webhook secret from your ItPay dashboard
const WEBHOOK_SECRET = process.env.ITPAY_WEBHOOK_SECRET || 'whsec_xxxxxxxxxxxx';
const WEBHOOK_MAX_AGE_MS = 300_000; // 5 minutes

// Track processed events (use Redis in production)
const processedEvents = new Set<string>();

// --- Signature verification ---

function verifyWebhookSignature(
rawBody: string,
signatureHeader: string,
webhookSecret: string,
maxAgeMs: number = 300_000,
): boolean {
const headerParts = Object.fromEntries(
signatureHeader.split(',').map((p) => p.split('=', 2)),
);

const timestamp = headerParts['t'];
const expectedSig = headerParts['v1'];

if (!timestamp || !expectedSig) return false;

// Reject expired webhooks
if (Date.now() - Number(timestamp) * 1000 > maxAgeMs) {
return false;
}

const signedPayload = `${timestamp}.${rawBody}`;

const computedSig = createHmac('sha256', webhookSecret)
.update(signedPayload)
.digest('hex');

// Constant-time comparison — prevents timing side-channel attacks
return timingSafeEqual(
Buffer.from(computedSig),
Buffer.from(expectedSig),
);
}

// --- Webhook endpoint ---

// ⚠️ IMPORTANT: Use express.raw() BEFORE the JSON parser for webhooks.
// The raw body is needed for HMAC verification.
app.use('/itpay/v1/webhook', express.raw({ type: 'application/json' }));

app.post('/itpay/v1/webhook', (req: Request, res: Response) => {
// 1. Read signature headers
const signatureHeader = req.headers['x-itpay-signature'] as string;
if (!signatureHeader) {
res.status(401).json({ error: 'Missing X-ItPay-Signature header' });
return;
}

// 2. Verify the HMAC signature
const rawBody = (req.body as Buffer).toString('utf-8');
if (!verifyWebhookSignature(rawBody, signatureHeader, WEBHOOK_SECRET, WEBHOOK_MAX_AGE_MS)) {
res.status(401).json({ error: 'Invalid signature' });
return;
}

// 3. Parse the event
const event = JSON.parse(rawBody);
const eventId: string = event.id;
const eventType: string = event.type;

// 4. Idempotency check — skip events already processed
if (processedEvents.has(eventId)) {
res.status(200).json({ status: 'already_processed' });
return;
}
processedEvents.add(eventId);

// 5. Handle the event
switch (eventType) {
case 'payment_intent.succeeded': {
const intent = event.data;
console.log(`✓ Payment succeeded — Intent: ${intent.id}`);
console.log(` Amount: ${intent.amount.value} ${intent.amount.currency}`);
console.log(` Channel: ${intent.channel}`);
console.log(` Metadata: ${JSON.stringify(intent.metadata)}`);

// TODO: Deliver the service to the user
// - Look up session_id from intent.metadata
// - Unlock content or grant access
break;
}

case 'payment_intent.failed': {
const intent = event.data;
console.log(`✗ Payment failed — ${intent.id}: ${intent.failure_reason || 'unknown'}`);
break;
}

case 'payment_intent.expired': {
const intent = event.data;
console.log(`⏰ Payment expired — ${intent.id}`);
break;
}

default:
console.log(`ℹ Unhandled event type: ${eventType}`);
}

// 6. Acknowledge receipt
res.status(200).json({ status: 'received' });
});

app.listen(PORT, () => {
console.log(`Webhook handler listening on port ${PORT}`);
});

Testing the Webhook Locally

# Start your webhook handler
npx tsx webhook-handler.ts

# Expose it to the internet with ngrok
ngrok http 8080
# → Forwarding https://abc123.ngrok.io → http://localhost:8080

# Update your ServiceManifest's endpoint to the ngrok URL
# https://abc123.ngrok.io/itpay/v1/webhook

MCP Server Integration

The @itpay/mcp package can also be used as a Model Context Protocol (MCP) server, exposing ItPay payment capabilities as MCP tools. This allows any MCP-compatible AI agent to discover and use payment tools automatically.

As an MCP Server (stdio transport)

import { ItPayMCPServer } from '@itpay/mcp/server';

const server = new ItPayMCPServer({
apiKey: 'sk_liv_your_api_key_here',
channel: 'alipay',
});

await server.start();
// Listens on stdin/stdout as MCP stdio transport
// Tools are auto-discovered by MCP clients:
// - create_payment_intent
// - generate_qr
// - check_payment_status

Configure in an MCP Client

Add @itpay/mcp to your MCP client configuration:

{
"mcpServers": {
"itpay": {
"command": "npx",
"args": ["-y", "@itpay/mcp"],
"env": {
"ITPAY_API_KEY": "sk_liv_your_api_key_here",
"ITPAY_CHANNEL": "alipay"
}
}
}
}

Once configured, your AI agent can invoke payment tools using natural language:

The user wants to pay ¥6.99 for the document summary.
→ Agent calls create_payment_intent and generate_qr
→ QR code is displayed to the user
→ Agent waits for webhook confirmation
→ Service is delivered

Available MCP Tools

Tool NameDescription
create_payment_intentCreate a new PaymentIntent for receiving funds
generate_qrGenerate a QR code for an existing intent
check_payment_statusPoll the status of a PaymentIntent
list_manifestsList registered ServiceManifests

Error Handling

The SDK throws typed errors for predictable error handling:

import { ItPayError, ItPayAuthenticationError, ItPayValidationError } from '@itpay/mcp';

try {
const intent = await client.paymentIntents.create({ /* ... */ });
} catch (err) {
if (err instanceof ItPayAuthenticationError) {
console.error('Invalid API key — check your credentials');
} else if (err instanceof ItPayValidationError) {
console.error(`Validation failed: ${err.message}`);
} else if (err instanceof ItPayError) {
console.error(`ItPay API error: ${err.statusCode}${err.message}`);
} else {
console.error(`Network error: ${err}`);
}
}

Error Classes

ClassHTTP StatusDescription
ItPayAuthenticationError401Invalid or missing API key
ItPayValidationError422Request validation failed
ItPayRateLimitError429Rate limit exceeded
ItPayAPIError5xxServer-side error (retryable)
ItPayErrorBase class for all ItPay errors

Best Practices

  • Use UUIDv7 for PaymentIntent id values — they are time-sortable and unique.
  • Store the webhook secret as an environment variable, never in source code.
  • Always verify webhook signatures using timingSafeEqual for constant-time comparison.
  • Use idempotency — the id field on PaymentIntent creation is the idempotency key. Reusing the same id is safe.
  • Set reasonable timeouts — QR payments typically complete within 2–5 minutes.
  • Prefer webhooks over polling in production. The built-in waitForCompletion helper is convenient for development but webhooks are more reliable at scale.

Next Steps