better-payment
Providers

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.

  1. Set the notification URL to https://yoursite.com/api/pay/paytr/callback.
  2. With the HTTP handler, the notification is verified, passed to onCallback, and answered with plain-text OK:
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 browser

Refund, 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 refunded

BIN & 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)

On this page