Integrations
Next.js Integration
Integrating better-payment with the Next.js App Router.
1. Create the singleton
// lib/payment.ts
import { BetterPayment, ProviderType } from 'better-payment';
let _instance: BetterPayment | null = null;
export function getBetterPayment(): BetterPayment {
if (!_instance) {
_instance = new BetterPayment({
mode: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox',
defaultProvider: ProviderType.IYZICO,
providers: {
iyzico: {
enabled: true,
config: {
apiKey: process.env.IYZICO_API_KEY!,
secretKey: process.env.IYZICO_SECRET_KEY!,
},
},
},
handler: {
allowedActions: ['payment/init-3ds', 'payment/complete-3ds', 'callback'],
transformRequest: async (ctx) => buildPaymentRequestFromOrder(ctx.body.orderId, ctx.body.paymentCard),
onCallback: async (result) => markOrder(result.paymentId, result.status),
callbackRedirect: (result) => `/orders/${result.paymentId}?payment=${result.status}`,
},
});
}
return _instance;
}Initialize lazily (in a function, not with a module-level new BetterPayment(...)). Otherwise Next.js static generation can crash when the environment variables are not available.
2. Create the catch-all route handler
// app/api/pay/[...path]/route.ts
import { getBetterPayment } from '@/lib/payment';
export const runtime = 'nodejs'; // better-payment needs node:crypto
async function handler(req: Request): Promise<Response> {
const contentType = req.headers.get('content-type') ?? '';
let body: unknown;
if (req.method !== 'GET' && req.method !== 'HEAD') {
const text = await req.text();
body = contentType.includes('application/json') && text ? JSON.parse(text) : text;
}
const res = await getBetterPayment().handler.handle({
method: req.method,
url: req.url,
headers: Object.fromEntries(req.headers.entries()),
body,
});
// PayTR notifications must receive plain "OK"; redirects have an empty body
const payload = typeof res.body === 'string' ? res.body : JSON.stringify(res.body);
return new Response(res.status === 303 ? null : payload, {
status: res.status,
headers: res.headers,
});
}
export const GET = handler;
export const POST = handler;Form-urlencoded callbacks (iyzico, Param, Akbank, PayTR) are passed as raw strings; the handler parses them.
3. Start a payment from the frontend
const res = await fetch('/api/pay/iyzico/payment/init-3ds', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId, paymentCard }), // amounts come from transformRequest
});
const result = await res.json();
if (result.status === 'pending' && result.threeDSHtmlContent) {
document.open();
document.write(result.threeDSHtmlContent);
document.close();
}Or use the typed client:
import { createBetterPaymentClient } from 'better-payment/client';
const client = createBetterPaymentClient({ baseUrl: '/api/pay' });
const result = await client.iyzico.initThreeDSPayment(requestBuiltByTransformRequest);
// 422 (provider failure) resolves with status 'failure' instead of throwing4. Callback
Set callbackUrl to https://yoursite.com/api/pay/<provider>/payment/complete-3ds.
The handler verifies the callback, calls onCallback, and redirects the customer
using callbackRedirect.
For PayTR, set the notification URL in the PayTR panel to
https://yoursite.com/api/pay/paytr/callback.