PayTR
PayTR integration reference — iFrame payments, notifications, refunds, status, BIN and installments.
PayTR uses form-urlencoded requests. Every token is signed as
base64(HMAC-SHA256(data + merchant_salt, merchant_key)).
Configuration
paytr: {
enabled: true,
config: {
merchantId: process.env.PAYTR_MERCHANT_ID!,
merchantKey: process.env.PAYTR_MERCHANT_KEY!,
merchantSalt: process.env.PAYTR_MERCHANT_SALT!,
// testMode: defaults to mode === 'sandbox' (sends test_mode=1)
// timeoutLimit: 30 (minutes)
},
}Order ids
PayTR's merchant_oid may contain only letters and digits. Pass your order id
as conversationId. If you leave it out, an id such as BP1727180000000A1B2C3D4 is
generated and returned as paymentId / conversationId. Store it: refund,
cancel and getPayment use it.
iFrame payment (3D Secure)
const init = await payment.paytr.initThreeDSPayment({
...paymentRequest, // buyer.gsmNumber and billingAddress.address are sent to PayTR
conversationId: 'ORDER123',
callbackUrl: 'https://yoursite.com/orders/ORDER123', // where the customer returns
installment: undefined, // undefined: any; 1: single payment only; n: up to n
});
// init.threeDSHtmlContent — a page with the PayTR iframe
// init.redirectUrl — https://www.paytr.com/odeme/guvenli/<token>payment_amount is paidPrice (in kuruş). Basket items are sent with TL prices.
Notification (Bildirim URL)
callbackUrl does not carry the payment result. PayTR sends the result
server-to-server to the notification URL set in the PayTR merchant panel.
- Set the notification URL to
https://yoursite.com/api/pay/paytr/callback. - With the HTTP handler, the notification is verified, passed to
onCallback, and answered with plain-textOK:
handler: {
onCallback: async (result) => {
// result.paymentId === merchant_oid
// result.status is 'success' or 'failure'
await db.orders.updatePayment(result.paymentId, result.status);
},
}Without the handler:
const result = await payment.paytr.completeThreeDSPayment(req.body);
if (result.errorCode === 'INVALID_HASH') return res.status(400).send('bad hash');
await db.orders.updatePayment(result.paymentId, result.status); // make this idempotent
res.type('text/plain').send('OK');PayTR can send the same notification more than once. Make your order update idempotent.
Direct payment (non-3D)
const result = await payment.paytr.createPayment(paymentRequest);This uses the Direct API with non_3d=1 and sync_mode=1. Your PayTR account
must be approved for non-3D payments.
Stored cards (utoken / ctoken)
const init = await payment.paytr.createPaymentWithToken({
utoken, ctoken, cvv, price: '100.00',
callbackUrl: 'https://yoursite.com/orders/ORDER123',
conversationId: 'ORDER123',
buyer: { email, name, surname, ip, gsmNumber },
basketItems: [{ name: 'Item', price: '100.00', quantity: 1 }],
});
// init.threeDSHtmlContent auto-submits a Direct API form from the browserRefund, Cancel & Status
await payment.paytr.refund({ paymentId: 'ORDER123', price: '50.00', currency: 'TRY', ip: '1.2.3.4' });
// PayTR has no void; cancel() is a full refund. Without price, the paid amount is looked up.
await payment.paytr.cancel({ paymentId: 'ORDER123', ip: '1.2.3.4' });
const status = await payment.paytr.getPayment('ORDER123'); // /odeme/durum-sorgu
// 'success', or 'cancelled' when fully refundedBIN & Installments
const bin = await payment.paytr.binCheck('552879');
// bin.bankName, bin.cardFamily (brand), bin.cardAssociation (schema), bin.cardType
const info = await payment.paytr.installmentInfo({ binNumber: '552879', price: '100.00' });
// Totals use the commission rates defined on your PayTR account (/odeme/taksit-oranlari)