Configuration
Full configuration reference for betterPayment().
betterPayment() options
import { betterPayment, iyzico, paytr, akbank, parampos } from 'better-payment';
const payment = betterPayment({
providers: {
iyzico: iyzico({
apiKey: string,
secretKey: string,
baseUrl?: string, // default from mode
locale?: 'tr' | 'en',
}),
paytr: paytr({
merchantId: string,
merchantKey: string,
merchantSalt: string,
testMode?: boolean, // default: mode === 'sandbox'
timeoutLimit?: number, // iFrame timeout in minutes, default 30
}),
akbank: akbank({
merchantSafeId: string,
terminalSafeId: string,
secretKey: string,
subMerchantId?: string,
testMode?: boolean, // selects the test 3D gateway; default: mode === 'sandbox'
gateway3dUrl?: string, // override the securepay URL
}),
parampos: parampos({
clientCode: string,
clientUsername: string,
clientPassword: string,
guid: string,
}),
},
defaultProvider: 'iyzico', // optional; a single provider becomes the default
mode: 'sandbox', // 'sandbox' | 'production' (default)
logger, // optional, see below
retry, // optional, see below
fetch, // optional custom fetch (default: globalThis.fetch)
validate: true, // default; see Request Validation
handler, // optional HTTP handler options
plugins: [], // optional, see Plugins
});The keys of providers are the provider ids. They name the provider on the payment object (payment.iyzico, payment.use('iyzico')) and in the handler routes (/api/pay/iyzico/...). Any key works, so one provider can be configured twice, for example with two merchant accounts: { iyzicoTR: iyzico({...}), iyzicoEU: iyzico({...}) }. To leave a provider out, leave its key out.
Missing or empty credentials throw a ConfigurationError that lists the missing fields.
Default URLs
| Provider | sandbox | production |
|---|---|---|
| iyzico | https://sandbox-api.iyzipay.com | https://api.iyzipay.com |
| PayTR | https://www.paytr.com (with test_mode=1) | https://www.paytr.com |
| Akbank | https://apipre.akbank.com/api/v1/payment/virtualpos | https://api.akbank.com/api/v1/payment/virtualpos |
| Parampos | https://test-dmz.param.com.tr/turkpos.ws/service_turkpos_test.asmx | https://posws.param.com.tr/turkpos.ws/service_turkpos_prod.asmx |
| Kuveyt Türk | https://boatest.kuveytturk.com.tr/boa.virtualpos.services | https://sanalpos.kuveytturk.com.tr/ServiceGateWay |
| Sipay | https://provisioning.sipay.com.tr | https://app.sipay.com.tr |
EST (isbank, ziraat, halkbank, teb, sekerbank) | https://entegrasyon.asseco-see.com.tr | the bank's host, e.g. https://sanalpos.isbank.com.tr |
Set baseUrl in a provider's config to override its default.
Provider Access
payment.iyzico // Iyzico; only the configured providers exist
payment.paytr
payment.use('paytr') // throws ProviderNotEnabledError for unknown ids
payment.isProviderEnabled('iyzico') // boolean
payment.getEnabledProviders() // string[]: the keys of providersLogging
logger: {
debug: (message, meta) => {},
info: (message, meta) => {},
error: (message, error, meta) => {},
}The logger receives the HTTP method, URL and status. Request and response bodies, which contain card data and credentials, are never logged.
Custom fetch
Provider API calls use globalThis.fetch with a timeout (30 s; 60 s for Parampos). Pass fetch to use your own implementation, for example a proxy-aware fetch or a stub in tests. It can also be set per provider, in the provider's config.
fetch: (input, init) => myFetch(input, init),Retry
retry: {
attempts: 3, // total attempts including the first
delay: 1000, // ms between attempts
statusCodes: [429, 503], // retry on these HTTP statuses (network errors are always retried)
}Only idempotent requests are retried: status, BIN and installment queries. Payment, refund and cancel requests are never retried. If one of them fails without a response, it returns status: 'pending' with errorCode: 'NETWORK_ERROR' so that you check the outcome with getPayment().
Request Validation
Payment and refund requests are checked before anything is sent to the provider. An invalid request returns status: 'failure' with code: 'INVALID_REQUEST' and errorCode: 'VALIDATION_ERROR'. Its errorMessage lists every invalid field:
Invalid request: paymentCard.cardNumber is not a valid card number (Luhn check failed); basketItems prices add up to 1.00 but price is 5.00| Check | Applies to |
|---|---|
| Card number (12–19 digits, Luhn), holder name, expiry month 1–12, not expired, CVC 3–4 digits | Card payments: iyzico, PayTR non-3D, Parampos, Akbank |
price / paidPrice: positive, at most 2 decimals | All payments and refunds |
Basket item prices add up to price | iyzico |
| Non-empty basket | iyzico, PayTR |
| Buyer email, IPv4/IPv6 and GSM format (when present) | All payments |
Required fields: buyer id, name, surname, email, identityNumber, registrationAddress, city, country, ip; billing contactName, city, country, address | iyzico (including checkout form and PWI) |
Required fields: buyer email, ip, name, surname, gsmNumber | PayTR |
Required field: buyer ip | Parampos, Akbank |
The checks only reject what the provider would reject anyway. They do not require paidPrice >= price, because discounts can lower it, and they do not verify the TCKN checksum, because foreign customers and placeholder values are accepted by providers.
Turn validation off globally with validate: false, or for a single provider in its config:
betterPayment({
validate: false, // every provider
providers: {
iyzico: iyzico({ apiKey, secretKey, validate: true }), // override
},
});HTTP Handler
See BetterPaymentHandler for the options and the framework examples.