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 Approved3D 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 gateWith 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 asINVALID_HASH; 3d/3d_host: the bank only authenticates the card. WhenmdStatusis accepted, the card is charged with the XML APIAuth(usingmd), 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 asINVALID_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.