Better Payment

Changelog

Release history and breaking changes for better-payment.

The version history was reset. 0.0.1 is the first release of the reworked package. Releases 1.x–3.x had incorrect provider integrations and callback checks that could be bypassed. Do not use them. See What's New in 0.0.1 for the reasoning and a step-by-step migration guide.

0.6.0

Added

  • Elysia adapter: better-payment/elysia mounts the handler on Elysia (Bun) in one line: app.all('/api/pay/*', toElysiaHandler(payment), { parse: 'none' }). Thanks to @ScottHallchico.

0.5.2

Added

0.5.1

Added

Changed

0.5.0

Plugins and typed payment events. Breaking: betterPayment() replaces new BetterPayment(), and providers are created with factories. See Migrating to 0.5.

Changed (breaking)

  • betterPayment({ providers, plugins }) replaces the BetterPayment class, and providers are created with iyzico(), paytr(), akbank() and parampos(). The enabled flag is gone.
  • Any key of providers is a provider id; payment.<id> is typed per provider. getEnabledProviders() returns string[].

Added

  • Plugins: before/after hooks for provider operations, typed methods, handler endpoints and error codes. Write your own in a few lines.
  • Payment events: one typed listener for every provider and flow, emitted only for verified results.
  • Localized error messages: the first official plugin, in better-payment/plugins. Customer-facing messages in English and Turkish.
  • Custom providers with defineProvider(), and WebCrypto helpers for signing.

0.4.0

Testing without a sandbox and one-line framework integration. Existing 0.3.0 code keeps working.

Added

  • better-payment/testing: MockProvider, an in-memory provider for application tests, with no credentials and no network access. Magic card numbers cover declines, 3D Secure failures, lost responses and failing refunds; 3D Secure callbacks go through the real handler.
  • Framework adapters: better-payment/next, better-payment/express, better-payment/fastify and better-payment/hono mount the handler in one line; toFetchHandler covers Workers, Deno and Bun.
  • The documentation is available in Turkish.

Changed

  • PROVIDER_DEFAULT_URLS is keyed by RemoteProviderType (every provider except mock).

0.3.0

Edge runtimes and zero runtime dependencies. Application code keeps working unchanged.

Added

  • Edge runtime support: the same package runs on Node.js 20+, Vercel Edge, Cloudflare Workers, Deno and Bun. It only uses fetch and WebCrypto.
  • fetch option for a custom fetch implementation (proxy, test stubs).
  • HttpClient and HttpError are exported for custom providers.

Changed

  • No runtime dependencies: axios is removed. Timeouts, logging and retry rules are unchanged.
  • Custom providers that extend PaymentProvider use createHttpClient() instead of setupAxiosLogging() / setupAxiosRetry().

0.2.0

Pre-authorization and stored cards. Existing 0.1.0 code keeps working. Both features are tested against the real iyzico sandbox every night.

Added

  • Pre-authorization: authorize() / initThreeDSAuthorize() block an amount, capture() charges it (partial capture supported) and voidAuthorization() releases it. iyzico, Parampos and Akbank; PayTR is not supported yet.
  • Stored cards: save a card with saveCard during a payment, pay later with storedCard: { customerToken, cardToken }, and manage cards with saveCard(), listCards() and deleteCard(). iyzico and PayTR; Parampos and Akbank are not supported yet.
  • Handler routes and browser client methods for both. Capture, void and the card routes require an authorize hook.

Changed

  • PaymentRequest.paymentCard is optional in the types; one of paymentCard or storedCard is required at runtime.
  • The handler answers 400 instead of 500 when a provider does not support an operation.

0.1.0

The first feature release after the reset. Existing 0.0.1 code keeps working; behavior changes are listed under "Changed". The iyzico integration is now tested against the real iyzico sandbox every night.

Added

  • Unified error codes: every failed result carries code (INSUFFICIENT_FUNDS, CARD_DECLINED, THREEDS_FAILED, INVALID_HASH, ...). errorCode keeps the provider's raw code.
  • Request validation before the provider is called. Invalid requests return code: 'INVALID_REQUEST' with every invalid field listed. Turn it off with validate: false.
  • Parampos installments: installmentInfo(), calculatePaidPrice() and getInstallmentRates() use your Param rates.
  • Duplicate callbacks are processed once, and mutating handler routes accept an Idempotency-Key header. The IdempotencyStore is in-memory by default; use Redis or a database with several instances.
  • PaymentErrorCode and PaymentStatus are exported from better-payment/client.

Changed

  • Request validation errors return errorCode: 'VALIDATION_ERROR'; errorCode used to be empty.
  • Stricter public types, with no any: rawResponse and the handler bodies are unknown, and onCallback / callbackRedirect receive a PaymentResponse.
  • NETWORK_ERROR messages name the transport error (ECONNRESET, timeout, ...).
  • The published build is minified: about 19 kB gzip instead of 29.6 kB.

0.0.1

Security

  • Parampos: the 3D callback hash is verified only with the GUID from your configuration. Earlier versions read the GUID from the callback payload, which let anyone forge a successful payment.
  • Parampos / Akbank: a 3D callback no longer counts as a successful payment on its own. Parampos finalizes with TP_WMD_Pay; Akbank uses the 3D_PAY model with an HMAC-signed result. A missing result code is no longer treated as success.
  • HTTP handler: secure by default. By default it exposes only callbacks and card queries. Refund, cancel, payment lookup and subscription management require an authorize hook.
  • Retries only re-send idempotent requests, so a timeout can no longer cause a double charge or double refund.
  • Signatures are compared in constant time.

Provider fixes

  • PayTR: requests are signed with merchant_key, with merchant_salt appended to the signed data. user_basket is base64 JSON with TL prices. merchant_oid must be alphanumeric. test_mode follows sandbox mode. Refund amounts are in TL. Status queries use /odeme/durum-sorgu. Notifications are answered with plain OK. Secrets are no longer sent in request bodies.
  • Parampos: uses TP_WMD_UCD / TP_WMD_Pay, comma-decimal amounts and the documented hash. Refunds and cancels use TP_Islem_Iptal_Iade_Kismi2, status queries TP_Islem_Sorgulama4 and BIN lookups BIN_SanalPos. Made-up installment rates were removed.
  • Akbank: rewritten against the Akbank Sanal POS JSON API.
  • iyzico: Checkout Form and PWI results report the payment's own status (paymentStatus). 3DS completion requires mdStatus=1. retrieveSubscription uses GET.

Breaking changes (compared with 3.x)

AreaBeforeNow
PayTR configapiKey, secretKey, merchantId, merchantSaltmerchantId, merchantKey, merchantSalt, testMode?
Akbank configmerchantId, terminalId, storeKey, …merchantSafeId, terminalSafeId, secretKey
Parampos configalso required apiKey/secretKeyclientCode, clientUsername, clientPassword, guid
paymentId (PayTR/Akbank/Parampos)provider token / transaction GUIDyour order id
Timeoutsfailurepending + errorCode: 'NETWORK_ERROR'
Handlerevery route public, always HTTP 200opt-in routes, authorize, 422 on failure
Unsupported currencysilently TRYerror

On this page