npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@paykernel/gateway-hesabe

v0.1.1

Published

Portable Hesabe gateway adapter for @paykernel/core: encrypted checkout, transaction enquiry, merchant refunds, and enquiry-verified webhooks.

Readme

@paykernel/gateway-hesabe

Hesabe adapter for @paykernel/core, supporting KWD hosted payments, transaction enquiry, confirmed callbacks, enquiry-verified webhooks, and full/partial refunds. Register it as an external adapter; it does not extend core's built-in gateway names.

This adapter is published as 0.1.1. Live sandbox interoperability has not been validated — complete the sandbox acceptance checklist before production use.

Setup

bun add @paykernel/core @paykernel/gateway-hesabe
import { createPaymentClient, InMemoryIdempotencyStore, money } from "@paykernel/core";
import { hesabeGateway } from "@paykernel/gateway-hesabe";

const payments = createPaymentClient({
  gateways: {
    hesabe: hesabeGateway({
      merchantCode: process.env.HESABE_MERCHANT_CODE!,
      accessCode: process.env.HESABE_ACCESS_CODE!,
      encryptionKey: process.env.HESABE_ENCRYPTION_KEY!, // 32 UTF-8 bytes
      ivKey: process.env.HESABE_IV_KEY!, // 16 UTF-8 bytes
      username: process.env.HESABE_USERNAME!,
      password: process.env.HESABE_PASSWORD!,
      idempotencyStore: new InMemoryIdempotencyStore(), // single-process example
      live: false,
    }),
  },
  defaultGateway: "hesabe",
});
const hesabe = payments.gateway("hesabe");
const result = await hesabe.createPayment({
  amount: money("10.000", "KWD"),
  currency: "KWD",
  orderId: "order-123",
  idempotencyKey: "checkout-order-123", // retain this key for retries
  callbackUrl: "https://merchant.example/hesabe/callback",
});
if (result.outcome === "requires_action" && result.redirectUrl) {
  // Redirect the customer to result.redirectUrl. Payment is not yet confirmed.
}

Keep credentials on your backend. The runtime uses Web fetch and Web Crypto subtle; inject runtime dependencies through createPaymentClient({ runtime }) when needed. Defaults are sandbox hosts and a 30-second request timeout (timeoutMs). Merchant login is lazy and shared by concurrent refund requests on the same instance. Tokens refresh 60 seconds before expiry.

Checkout requires an order ID, idempotency key, positive KWD Money with at most three decimal places, and an HTTPS callback URL. It uses indirect payment type 0, checkout version 2.0. Optional fields on HesabeCreatePaymentParams are hesabeName, hesabeEmail, hesabeMobileNumber (eight digits without a country code), hesabeVariable1 through hesabeVariable5, hesabeWebhookUrl, and hesabeFailureUrl. The callback URL is also the failure URL by default. A config-level webhookUrl supplies the default notification URL.

Confirming a payment

Pass the encrypted callback query parameter to resolveCallback:

const confirmed = await hesabe.resolveCallback({ data: encryptedCallbackData });
if (confirmed.outcome === "succeeded" && confirmed.status === "paid") {
  // Check confirmed.orderId and confirmed.amount against your order before fulfillment.
}

The callback's transaction token, order reference, amount, and result are checked against transaction enquiry. A checkout result has a checkout:-prefixed ID and references.relatedIds.checkoutToken; it is not a transaction token. Use the confirmed transaction ID for subsequent operations:

const latest = await hesabe.getPayment({ gatewayPaymentId: confirmed.gatewayId });

Unknown provider statuses require reconciliation. Never fulfill from a checkout redirect, a callback alone, or a successful HTTP response.

Webhooks

Use payments.handleWebhook("hesabe", payload) or call verifyWebhookAsync(payload) before parseWebhookEvent(payload). Synchronous verifyWebhook always returns false because Hesabe does not document a webhook signature.

Async verification confirms token, order reference, KWD amount, and status through enquiry. This verifies transaction facts, not sender identity. Check that the order belongs to your application and that its amount matches before fulfillment. Mismatches return false; transport failures throw so the delivery can be retried. Unknown statuses are not accepted.

Events contain only the checked fields. Event IDs are deterministic across deliveries of the same facts. Payment success, failure, and processing dual-write the core PaymentEvent; untrusted timestamps and extra financial fields are ignored. Applications remain responsible for durable event deduplication.

Refunds

const refund = await hesabe.refundPayment({
  gatewayPaymentId: confirmed.gatewayId,
  idempotencyKey: "refund-order-123-part-1",
  amount: money("2.500", "KWD"),
  currency: "KWD",
});
const latestRefund = await hesabe.getRefund({ gatewayRefundId: refund.gatewayRefundId });

Omit amount for a full refund. The adapter enquires the original successful transaction before submitting either kind of refund. An explicit amount uses the documented partial refund method 2; full refunds use 1. The provider remains authoritative about the remaining refundable balance.

An accepted request returns pending. A refund becomes completed only when its entity status is 1 and it has a valid refund_at. totalRefunded is omitted because the returned amount describes one refund, not a cumulative total. Look up only a returned numeric refund ID; an indeterminate submission may not have one.

Retry and persistence contract

Every checkout and refund requires a stable idempotencyKey and the configured IdempotencyStore. Production deployments need a shared durable store whose reserve() is atomic across workers. InMemoryIdempotencyStore protects only one process and loses records on restart.

The adapter fingerprints effective provider parameters, rejects changed parameters or concurrent requests under the same key, and replays completed results. Eligible GET failures use the SDK’s bounded retry policy (up to three attempts, with backoff and Retry-After). It never automatically resubmits a checkout or refund. A failure after submission can return indeterminate with reconciliationRequired: true; its reservation stays blocked. A local persistence failure after provider acceptance also requires reconciliation.

Retain uncertain reservations beyond your retry horizon; do not let a generic TTL reopen an unresolved payment or refund. Reconcile with Hesabe and your order records before clearing a reservation or choosing a new mutation key. A checkout submission whose response was lost may require merchant-side investigation because the API has not returned a transaction token.

Supported capabilities

The adapter claims payments, immediateCapture, refunds, and partialRefunds. It does not support separate authorization/capture, voids, stored payment methods, customers, recurring payments, splits, disputes, payment links, or the core Checkout Session API.

Provider references and sandbox validation

Protocol references: indirect integration, transaction enquiry, webhooks, merchant login, refund requests, and refund details.

Crypto uses AES-256-CBC with PKCS#7 padding and hex encoding, matching the official JavaScript example. The encryption guide also includes a PHP example that pads plaintext to 32-byte boundaries; that can differ from standard AES block padding for some message lengths. An official published refund fixture is covered by an offline test, but live sandbox interoperability has not been validated. Complete the sandbox acceptance checklist before production use.

MIT licensed.