Better Payment

EST (İş Bankası, Ziraat, Halkbank, TEB, Şekerbank)

EST / NestPay (Asseco) virtual POS integration reference.

İş Bankası, Ziraat Bankası, Halkbank, TEB and Şekerbank share the EST (Asseco) virtual POS. One provider covers all of them: bank selects the production host. 3D Secure requests and callbacks are signed with hash version 3: base64(SHA-512(values sorted by field name, then the store key, joined with "|")).

Verified against the shared EST test environment with İş Bankası's test store: non-3D sale, void, refund, order status and all four 3D models end to end. The banks' production hosts have not been tested; try a small amount before going live.

Configuration

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

const payment = betterPayment({
  providers: {
    isbank: est({
      bank: 'isbank', // 'isbank' | 'ziraat' | 'halkbank' | 'teb' | 'sekerbank'
      clientId: process.env.EST_CLIENT_ID!,
      username: process.env.EST_API_USER!,
      password: process.env.EST_API_PASSWORD!,
      storeKey: process.env.EST_STORE_KEY!,
      // storeType?: '3d' | '3d_pay' | '3d_pay_hosting' | '3d_host' (default '3d')
      // department?: number (BOLUM, when the bank assigned one)
      // acceptedMdStatuses?: string[] (default ['1'], see below)
      // baseUrl: defaults from mode (shared EST test host / the bank's host)
    }),
  },
});

clientId is your store number. username and password belong to the API user (XML API), and storeKey is the 3D Secure store key; all come from the bank's merchant panel. storeType must match the 3D model the bank set up for your store.

paymentId is your order id: the conversationId you pass, or a generated id.

Direct Payment (non-3D)

const result = await payment.isbank.createPayment({ ...paymentRequest, conversationId: 'ORDER123' });
// XML API Auth; success when ProcReturnCode is 00 and Response is Approved

3D Secure

const init = await payment.isbank.initThreeDSPayment({
  ...paymentRequest,
  conversationId: 'ORDER123',
  callbackUrl: 'https://yoursite.com/api/pay/isbank/payment/complete-3ds',
});
// init.threeDSHtmlContent auto-submits a signed form to the bank's 3D gate

With 3d_pay_hosting and 3d_host the bank collects the card on its own page, so the request needs no paymentCard. The bank POSTs the result to callbackUrl:

const result = await payment.isbank.completeThreeDSPayment(callbackBody);

The hash covers the values of every returned field but not their names, so a valid hash alone does not prove which value is the order id. The result is confirmed with the bank:

  • a callback with an invalid hash, or for another clientid, is rejected as INVALID_HASH;
  • 3d / 3d_host: the bank only authenticates the card. When mdStatus is accepted, the card is charged with the XML API Auth (using md), and the bank's answer decides;
  • 3d_pay / 3d_pay_hosting: the bank charged the card. An approved callback is checked with an order status query; a payment the bank does not confirm is rejected as INVALID_HASH.

paymentId and conversationId are set only for a payment the bank confirmed, so a failure never carries an order id taken from the callback.

mdStatus

mdStatus 1 means the card was fully authenticated. 2, 3 and 4 are half-secure: the card or its bank is not enrolled in 3D Secure, or only an attempt was made. In the 3d and 3d_host models only 1 is charged by default; any other value fails with MD_STATUS_<value>. To charge half-secure payments too, list them in acceptedMdStatuses (for example ['1', '2', '3', '4']). Chargeback liability can then stay with you. In 3d_pay and 3d_pay_hosting the bank decides.

Refund, Cancel & Status

await payment.isbank.refund({ paymentId: 'ORDER123', price: '50.00', currency: 'TRY', ip: '1.2.3.4' }); // Credit
await payment.isbank.cancel({ paymentId: 'ORDER123', ip: '1.2.3.4' });                               // Void
const status = await payment.isbank.getPayment('ORDER123');                                          // ORDERSTATUS
// status: 'success' (captured sale), 'cancelled' (voided or refunded),
// 'failure' (declined or unknown order), 'pending' (anything else)

Browser Client

better-payment/client has no client.isbank property. Reach an EST provider by its key in providers:

const result = await client.use('isbank').initThreeDSPayment(request);

BIN and installment queries are not supported for EST: binCheck() and installmentInfo() throw NOT_SUPPORTED. Send installment in the payment request and compute paidPrice from your contracted rates.

On this page