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
import { betterPayment, paytr } from 'better-payment';
const payment = betterPayment({
providers: {
paytr: paytr({
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)
// threeDSMode: 'iframe' | 'direct' (default 'iframe', see Direct API 3D Secure)
}),
},
});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.
Direct API 3D Secure (your own card form)
Added in 0.12.0With threeDSMode: 'direct', initThreeDSPayment() takes the card in the request instead of
showing PayTR's iFrame, so you keep your own checkout design. It returns a form signed with the
Direct API token (non_3d=0) that the customer's browser posts to PayTR, as PayTR requires for
card data. PayTR runs 3D Secure, redirects to callbackUrl (or failUrl) and sends the result to
the notification URL, as with the iFrame.
const payment = betterPayment({
providers: {
paytr: paytr({ merchantId, merchantKey, merchantSalt, threeDSMode: 'direct' }),
},
});
const init = await payment.paytr.initThreeDSPayment({
...paymentRequest, // paymentCard comes from your own checkout form
conversationId: 'ORDER123',
callbackUrl: 'https://yoursite.com/orders/ORDER123',
// failUrl?: string (defaults to callbackUrl)
// saveCard?: true (PayTR stores the card; the utoken comes in the notification)
});
// init.threeDSHtmlContent auto-submits the signed card form to https://www.paytr.com/odemeThe Direct API must be enabled on your PayTR account. Stored cards always use this path, whatever
threeDSMode is.
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)