Better Payment

Quick Start

Make your first payment in under 5 minutes.

1. Create the payment object

import { betterPayment, iyzico } from 'better-payment';

export const payment = betterPayment({
  mode: 'sandbox', // sandbox URLs and provider test modes; default is 'production'
  providers: {
    iyzico: iyzico({
      apiKey: process.env.IYZICO_API_KEY!,
      secretKey: process.env.IYZICO_SECRET_KEY!,
    }),
  },
});

2. Build a Payment Request

Build the request on the server from your own order data. Never take amounts from the browser.

import { Currency, BasketItemType } from 'better-payment';

const request = {
  price: '100.00',
  paidPrice: '100.00',
  currency: Currency.TRY,
  installment: 1,
  basketId: 'B67832',
  conversationId: 'ORDER123', // your order id (letters and digits only for PayTR)
  paymentCard: {
    cardHolderName: 'John Doe',
    cardNumber: '5528790000000008',
    expireMonth: '12',
    expireYear: '2030',
    cvc: '123',
  },
  buyer: {
    id: 'BY789',
    name: 'John',
    surname: 'Doe',
    gsmNumber: '+905350000000',
    email: 'johndoe@example.com',
    identityNumber: '74300864791',
    registrationAddress: 'Nidakule Göztepe, Merdivenköy Mah.',
    city: 'Istanbul',
    country: 'Turkey',
    ip: '85.34.78.112',
  },
  shippingAddress: {
    contactName: 'Jane Doe',
    city: 'Istanbul',
    country: 'Turkey',
    address: 'Nidakule Göztepe, Merdivenköy Mah.',
  },
  billingAddress: {
    contactName: 'Jane Doe',
    city: 'Istanbul',
    country: 'Turkey',
    address: 'Nidakule Göztepe, Merdivenköy Mah.',
  },
  basketItems: [
    {
      id: 'BI101',
      name: 'Binocular',
      category1: 'Collectibles',
      itemType: BasketItemType.PHYSICAL,
      price: '100.00',
    },
  ],
};

3. Start 3D Secure

const init = await payment.initThreeDSPayment({
  ...request,
  callbackUrl: 'https://yoursite.com/api/pay/iyzico/payment/complete-3ds',
});

if (init.status === 'pending') {
  // Render init.threeDSHtmlContent in the browser
  // (or redirect to init.redirectUrl when the provider returns one)
}

4. Complete 3D Secure

The bank POSTs the result to callbackUrl. Pass that POST body as it is:

const result = await payment.completeThreeDSPayment(callbackBody);

if (result.status === 'success') {
  // mark the order as paid
}

The library checks the callback with your credentials and, when the provider requires it, finalizes the payment. A forged or failed callback always returns failure. The HTTP handler does this for you.

5. Handle results

statusmeaning
successConfirmed by the provider
failureRejected; see errorCode / errorMessage
pendingWaiting for the customer, or the outcome is unknown (errorCode: 'NETWORK_ERROR')
cancelledVoided or fully refunded (status queries)

On NETWORK_ERROR the payment may still have gone through. Check it with getPayment(paymentId) before trying again. Payment, refund and cancel requests are never retried automatically.

Using Multiple Providers

const payment = betterPayment({
  providers: {
    iyzico: iyzico({ /* ... */ }),
    paytr: paytr({ /* ... */ }),
  },
  defaultProvider: 'iyzico',
});

await payment.createPayment(request);                 // default provider
await payment.paytr.initThreeDSPayment(paytrRequest); // specific provider
await payment.use('paytr').getPayment('ORDER123');

Update orders with events

Instead of checking every result, listen to payment events. They are emitted for every provider and flow, after the provider verified the result:

payment.on('payment.succeeded', async (event) => {
  await orders.markPaid(event.conversationId, event.paymentId);
});

payment.on('payment.failed', async (event) => {
  await orders.markFailed(event.conversationId, event.code);
});

On this page