Project Structure
app/
├── api/
│ ├── payment/
│ │ ├── create/route.ts # POST — create session
│ │ └── webhook/route.ts # POST — receive webhook
│ └── payment-status/
│ └── [sessionId]/route.ts # GET — poll status
├── pay/
│ └── page.tsx # Payment UI
Create Session Route
// app/api/payment/create/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const { orderId, amount } = await req.json();
const res = await fetch('https://paychainly.com/api/v1/addresses/start-session', {
method: 'POST',
headers: {
'x-api-key': process.env.PAYCHAINLY_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({ userId: String(orderId), amount }),
});
const session = await res.json();
return NextResponse.json(session);
}
Webhook Route
// app/api/payment/webhook/route.ts
import { createHmac, timingSafeEqual } from 'crypto';
export async function POST(req: NextRequest) {
const raw = await req.text();
const payload = JSON.parse(raw);
const sig = req.headers.get('x-signature') ?? '';
const msg = [payload.event, payload.txHash, payload.fromAddress,
payload.toAddress, payload.amount, payload.blockNumber,
payload.timestamp, payload.userId].join('|');
const expected = createHmac('sha256', process.env.PAYCHAINLY_WEBHOOK_SECRET!)
.update(msg).digest('hex');
if (!timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Process payment...
return NextResponse.json({ ok: true });
}
Environment Variables
# .env.local
PAYCHAINLY_API_KEY=your_key
PAYCHAINLY_WEBHOOK_SECRET=your_secret