Skip to main content

Examples

Complete integration examples for common use cases.

Basic E-commerce Checkout

A simple checkout flow for an e-commerce site.

Backend (Node.js/Express)

import express from 'express';
import { createCheckoutSession } from '@zkp2p-pay/sdk';

const app = express();
app.use(express.json());

const zkpayConfig = {
apiBaseUrl: process.env.ZKPAY_API_URL!,
apiKey: process.env.ZKPAY_API_KEY!,
checkoutBaseUrl: process.env.ZKPAY_CHECKOUT_URL!,
};

app.post('/api/checkout', async (req, res) => {
const { orderId, amount, customerId } = req.body;

try {
const response = await createCheckoutSession(
{
merchantId: process.env.MERCHANT_ID!,
amountUsdc: amount.toFixed(2),
destinationChainId: 8453,
destinationToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
recipientAddress: process.env.WALLET_ADDRESS!,
successUrl: `${process.env.APP_URL}/order/${orderId}/success`,
cancelUrl: `${process.env.APP_URL}/cart`,
metadata: {
orderId,
customerId,
},
},
zkpayConfig
);

res.json({
sessionId: response.session.id,
checkoutUrl: response.checkoutUrl,
});
} catch (error) {
res.status(500).json({ error: 'Failed to create checkout session' });
}
});

Frontend (React)

import { useState } from 'react';

function CheckoutButton({ orderId, amount, customerId }) {
const [loading, setLoading] = useState(false);

const handleCheckout = async () => {
setLoading(true);
try {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId, amount, customerId }),
});

const { checkoutUrl } = await response.json();
window.location.href = checkoutUrl;
} catch (error) {
console.error('Checkout failed:', error);
} finally {
setLoading(false);
}
};

return (
<button onClick={handleCheckout} disabled={loading}>
{loading ? 'Loading...' : 'Pay with Crypto'}
</button>
);
}

Subscription Payment

Creating a checkout for a subscription service.

async function createSubscriptionCheckout(
planId: string,
userId: string,
planPrice: number
) {
const response = await createCheckoutSession(
{
merchantId: process.env.MERCHANT_ID!,
amountUsdc: planPrice.toFixed(2),
destinationChainId: 8453,
destinationToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
recipientAddress: process.env.WALLET_ADDRESS!,
successUrl: `${process.env.APP_URL}/subscription/active`,
cancelUrl: `${process.env.APP_URL}/pricing`,
metadata: {
type: 'subscription',
planId,
userId,
billingCycle: 'monthly',
},
},
zkpayConfig
);

// Store session ID for webhook reconciliation
await db.subscriptions.create({
userId,
planId,
sessionId: response.session.id,
status: 'pending',
});

return response;
}

Handling Webhooks

Complete webhook handler with signature verification.

import express from 'express';
import crypto from 'crypto';

const app = express();

// Use raw body for signature verification
app.post(
'/webhooks/zkp2p',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-webhook-signature'] as string;
const timestamp = req.headers['x-webhook-timestamp'] as string;
const payload = req.body.toString();

// Verify signature
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update(`${timestamp}.${payload}`)
.digest('hex');

if (signature !== expectedSignature) {
console.error('Invalid webhook signature');
return res.status(401).send('Invalid signature');
}

// Check timestamp to prevent replay attacks
const webhookAge = Date.now() / 1000 - parseInt(timestamp);
if (webhookAge > 300) {
// 5 minutes
return res.status(401).send('Webhook too old');
}

const event = JSON.parse(payload);

// Respond immediately
res.status(200).send('OK');

// Process event asynchronously
await processWebhookEvent(event);
}
);

async function processWebhookEvent(event: WebhookPayload) {
const { session, order, txHash } = event.data;

switch (event.type) {
case 'order.created':
console.log(`Order created: ${order?.id}`);
break;

case 'order.payment_sent':
console.log(`Payment sent for order: ${order?.id}`);
await db.orders.update({
where: { id: session.metadata?.orderId },
data: { status: 'payment_pending' },
});
break;

case 'order.fulfilled':
console.log(`Order fulfilled: ${order?.id}, tx: ${txHash}`);
await db.orders.update({
where: { id: session.metadata?.orderId },
data: {
status: 'paid',
txHash,
paidAt: new Date(),
},
});
// Send confirmation email
await sendOrderConfirmation(session.metadata?.orderId);
break;

case 'order.failed':
console.error(`Order failed: ${order?.id}`, event.data.error);
await db.orders.update({
where: { id: session.metadata?.orderId },
data: {
status: 'failed',
errorMessage: event.data.error?.message,
},
});
break;

case 'order.expired':
await db.orders.update({
where: { id: session.metadata?.orderId },
data: { status: 'expired' },
});
break;
}
}

Next.js API Route

Using the SDK in a Next.js API route.

// pages/api/checkout.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { createCheckoutSession } from '@zkp2p-pay/sdk';

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}

const { amount, productId } = req.body;

try {
const response = await createCheckoutSession(
{
merchantId: process.env.MERCHANT_ID!,
amountUsdc: amount,
destinationChainId: 8453,
destinationToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
recipientAddress: process.env.WALLET_ADDRESS!,
successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/success`,
cancelUrl: `${process.env.NEXT_PUBLIC_APP_URL}/cart`,
metadata: { productId },
},
{
apiBaseUrl: process.env.ZKPAY_API_URL!,
apiKey: process.env.ZKPAY_API_KEY!,
}
);

res.json({ checkoutUrl: response.checkoutUrl });
} catch (error) {
console.error('Checkout error:', error);
res.status(500).json({ error: 'Failed to create checkout' });
}
}