npm install @kvell/kvell-nodeimport Kvell from '@kvell/kvell-node';
const kvell = new Kvell({
apiKey: process.env.KVELL_API_KEY!,
secretKey: process.env.KVELL_SECRET_KEY!,
environment: 'sandbox', // switch to 'production' when you go live
});
export default kvell;
import Kvell from '@kvell/kvell-node';
const kvell = new Kvell({
apiKey: process.env.KVELL_API_KEY!,
secretKey: process.env.KVELL_SECRET_KEY!,
environment: 'sandbox',
});
async function createCheckoutSession() {
// Amounts are always integers in kuruş (1/100 TRY) — 15000 = 150.00 TRY
const session = await kvell.paymentSessions.create({
transactionId: 'ORDER-2026-001',
amount: 15000,
currency: 'TRY',
description: 'Order #1234',
returnUrl: 'https://merchant.example.com/return',
});
console.log('Redirect the buyer to: ' + session.checkoutUrl);
return session.checkoutUrl;
}
createCheckoutSession();
import Kvell, { KvellError } from '@kvell/kvell-node';
const kvell = new Kvell({
apiKey: process.env.KVELL_API_KEY!,
secretKey: process.env.KVELL_SECRET_KEY!,
environment: 'sandbox',
});
async function chargeCard() {
try {
const payment = await kvell.payments.charge({
transactionId: 'ORDER-2026-001',
amount: 15000, // kuruş
currency: 'TRY',
cardToken: 'tok_9f8e7d6c5b4a',
});
console.log('Captured payment ' + payment.paymentId);
return payment;
} catch (err) {
if (err instanceof KvellError) {
switch (err.code) {
case 'KVELL-PAY-4001':
console.warn('Card declined (requestId=' + err.requestId + ')');
break;
case 'KVELL-GW-1002':
console.error('Signature invalid, check your secret key (requestId=' + err.requestId + ')');
break;
default:
console.error('Kvell error ' + err.code + ' (requestId=' + err.requestId + ')');
}
}
throw err;
}
}
import crypto from 'crypto';
import express from 'express';
const app = express();
const seenEventIds = new Set<string>(); // swap for Redis/DB in production
app.post('/webhooks/kvell', express.raw({ type: 'application/json' }), (req, res) => {
const signatureHeader = req.header('X-Kvell-Signature') || '';
const eventId = req.header('X-Kvell-Event-Id') || '';
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.KVELL_WEBHOOK_SECRET!)
.update(req.body) // raw Buffer, never the parsed JSON
.digest('hex');
const valid = signatureHeader.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
if (!valid) return res.status(401).send('invalid signature');
if (seenEventIds.has(eventId)) return res.status(200).send('duplicate');
seenEventIds.add(eventId);
const event = JSON.parse(req.body.toString('utf8'));
switch (event.eventType) {
case 'payment.captured':
// Fulfil the order for event.payload.paymentId asynchronously
break;
}
res.status(200).send('ok');
});