Tabby Payment Gateway
The TabbyPaymentGateway component provides seamless integration with Tabby, enabling installment and deferred payment options directly within your checkout experience. Built for compatibility with the Akinon ProjectZero platform, this extension securely manages customer data and transaction flows while maintaining flexibility for localized currency and language support.
With minimal setup, the component generates the necessary context—including order, user, and historical data—to initiate secure transactions via Tabby. The extension also handles hash-based request validation and supports easy deployment through environment-based configuration.
A single storefront may serve several countries, and Tabby issues a separate merchant installation—its own extension URL and its own hash key—per country. This guide covers both cases: a single installation, and several installations selected by currency. If you operate in more than one country, read Multiple Tabby Installations before you deploy; a single-installation setup will appear to work in staging and then fail for every currency except the default one.
This guide covers installation, configuration, usage, and internal mechanics to help you implement Tabby in your checkout flow with confidence.
Installation Method
You can use the following command to install the extension with the latest plugins:
npx @akinon/projectzero@latest --pluginsProps
sessionId
string
Yes
The session identifier received from Tabby.
currency
string
Yes
Currency code used in the transaction (e.g., AED, SAR).
locale
string
Yes
Language/locale code used for translations (e.g., en, ar).
extensionUrl
string
Yes
The base URL for the Tabby extension server. The component does not read this from the environment—the page must resolve it and pass it in.
hashKey
string
Yes
Secret hash key used to generate a secure transaction hash. The component does not read this from the environment—the page must resolve it and pass it in.
If sessionId, currency or locale is missing, the component renders nothing and the checkout silently stops. Verify all three before debugging further.
Environment Variables
Single Installation
One country, one Tabby merchant. Add the following variables to your .env file:
Multiple Installations
One storefront, several countries. Append the uppercase currency code to each variable name:
Both variables are read on the server only. Do not prefix them with NEXT_PUBLIC_—the hash key is a secret and must never reach the browser.
Usage Example
Create a file in the path matching your routing mode:
usePzSegment: true (default)
src/app/[pz]/payment-gateway/tabby/page.tsx
Legacy (usePzSegment: false)
src/app/[commerce]/[locale]/[currency]/payment-gateway/tabby/page.tsx
For a single installation, pass the unsuffixed variables straight through:
Two pieces of the platform do the work here:
withSegmentDefaults resolves the Next.js params/searchParams promises and normalises searchParams into a URLSearchParams instance. Without it, searchParams.get is not available.
parsePzParams reads the locale and currency for the request. In pz-segment mode it decodes them out of the pz route segment; in legacy mode it reads params.locale and params.currency directly, falling back to the defaults in settings.js. Because it covers both, the page above is identical in either routing mode—only the file path changes.
Multiple Tabby Installations (Country / Currency Based)
Why This Is Needed?
Tabby onboards merchants per country. A storefront selling in both the UAE and Saudi Arabia holds two Tabby merchant accounts, each with its own extension URL and its own hash key. Because the extension resolves configuration from environment variables, those variables have to be distinguishable per country—otherwise every currency is sent to whichever single installation happens to be configured.
The currency is used as the discriminator, because it is already carried through the request by the platform.
Naming Convention
Append the uppercase currency code to the base variable name:
United Arab Emirates
AED
TABBY_EXTENSION_URL_AED
TABBY_HASH_KEY_AED
Saudi Arabia
SAR
TABBY_EXTENSION_URL_SAR
TABBY_HASH_KEY_SAR
Kuwait
KWD
TABBY_EXTENSION_URL_KWD
TABBY_HASH_KEY_KWD
The suffix is always uppercase, regardless of how the currency is written in settings.js or in the URL. The extension uppercases the active currency before building the variable name, so a currency configured as aed resolves to TABBY_EXTENSION_URL_AED.
Resolution Rules
The currency-specific variable is preferred; the unsuffixed variable is the fallback:
TABBY_HASH_KEY_AED only
Uses TABBY_HASH_KEY_AED
TABBY_HASH_KEY only
Uses TABBY_HASH_KEY
Both
Uses TABBY_HASH_KEY_AED
TABBY_HASH_KEY_AED set to an empty value
Falls back to TABBY_HASH_KEY
Neither
Availability endpoint returns HTTP 500
The extension URL follows the same rules. The two are resolved independently, so it is possible—and is a common misconfiguration—to end up with the URL of one installation and the hash key of another.
What Resolves Automatically vs. What Doesn’t
This is the single most important distinction in this page:
Check Availability API route
Yes, automatically
Reads the pz-currency cookie, uppercases it, and falls back to the unsuffixed variables.
TabbyPaymentGateway component
No
Receives extensionUrl and hashKey as props. Your page must perform the selection.
If you configure the suffixed variables but leave the gateway page passing process.env.TABBY_EXTENSION_URL, the availability check will correctly consult the per-country installation while the payment form posts to the fallback one. Availability then looks healthy and the payment itself fails—which is exactly the symptom that is hard to trace without knowing this asymmetry exists.
Resolving the Configuration on Your Page
Add a small helper to your application. It applies the same rules the API route applies—uppercase the currency, prefer the suffixed variable, fall back to the unsuffixed one:
Then use it in the gateway page, taking the currency from parsePzParams:
The currency parsePzParams returns and the pz-currency cookie the API route reads are written from the same resolved value in the same middleware pass, so the page and the availability endpoint always select the same installation. Keeping the fallback operator as ||—not ??—matches the route exactly: an empty value falls through to the unsuffixed variable in both.
Example Environment Files
Single Installation — United Arab Emirates Only
Multiple Installations — UAE & Saudi Arabia
Multiple Installations with a Default
Currencies without their own pair fall back to the unsuffixed values. Use this only when the fallback installation is genuinely correct for every remaining currency:
Define each installation's URL and hash key as a pair. Setting TABBY_EXTENSION_URL_SAR without TABBY_HASH_KEY_SAR sends SAR traffic to the Saudi installation while signing it with the fallback key, and every request is rejected.
API Routes
Check Availability API
To enable Tabby payment availability checks, create an API route at src/app/api/tabby-check-availability/route.ts:
This endpoint checks whether Tabby is available for a given order amount, email, phone number and currency. It reads the currency from the pz-currency cookie—set by the platform middleware—and selects the matching installation as described above. It then validates both the outgoing request and the incoming response with hash-based security measures.
Responses:
200
{ salt, hash, is_available }
Availability resolved and the response hash verified.
400
Currency not found in cookies
The pz-currency cookie is absent.
400
Missing required fields
One of amount, phone, email, name is missing.
400
Invalid response hash
The extension server signed its reply with a different hash key.
500
TABBY_HASH_KEY environment variable is not set
Neither the currency-specific nor the fallback key is defined.
500
TABBY_EXTENSION_URL environment variable is not set
Neither the currency-specific nor the fallback URL is defined.
The 500 messages name the unsuffixed variables even when a suffixed one was expected. Read them as "no value could be resolved for the active currency", not as "define TABBY_HASH_KEY".
Using checkTabbyAvailability Mutation
The extension provides a Redux mutation hook for availability checks:
The mutation returns:
is_available: boolean indicating whether Tabby payment is availablesalt: string used for hash verificationhash: string for response validation
The currency is not a parameter—it is taken from the cookie on the server. Sending a different currency in the request body has no effect on which installation is consulted.
Context Object (Auto-generated Internally)
The TabbyPaymentGateway component internally generates a context object using:
preOrderdata from the current checkout sessionuserProfileandwishlistdetailsHistorical orders and previous purchases
This context includes:
This object is passed to the FormComponent for completing the payment via Tabby. The commerce requests that build it are sent with an X-Currency header taken from the currency prop, so passing a currency that does not match the resolved installation produces a context describing one country and a signature belonging to another.
Hash Reference
All hashes are SHA-512 over the values joined by |. Knowing the inputs makes a mismatch straightforward to diagnose:
Payment gateway form
salt | sessionId | hashKey
Availability request
salt | amount | email | phone | hashKey
Availability response check
salt | "True" or "False" | hashKey
The hash key is the only secret in each of these. If a hash is rejected and the other inputs are demonstrably correct, the key belongs to a different installation.
Troubleshooting
Availability check succeeds, but the payment form is rejected by the extension server
The API route resolved the currency-specific installation while the gateway page passed the unsuffixed one.
Resolve extensionUrl and hashKey in the page as shown in Resolving the configuration in your page.
Customer is redirected to the wrong country's Tabby checkout
TABBY_EXTENSION_URL_<CURRENCY> is missing for that currency, so the fallback URL was used.
Define the pair for every currency you sell in, or confirm the fallback is correct for the remainder.
HTTP 400 Invalid response hash
The hash key does not match the installation the request was sent to.
Confirm the URL and key come from the same installation. A URL/key pair split across two installations produces exactly this.
HTTP 400 Currency not found in cookies
The pz-currency cookie was not set on the request.
Confirm the request passes through the platform middleware and that the storefront was reached on a currency-bearing route.
HTTP 500 ... environment variable is not set
Neither the suffixed nor the unsuffixed variable is defined for the active currency.
Add the pair for that currency, or an unsuffixed fallback pair.
The suffixed variable is defined but appears to be ignored
The suffix does not match the uppercased currency, or the value is an empty string.
Match the suffix to the uppercase currency code exactly, and give it a non-empty value—an empty value falls through to the fallback.
The gateway page renders nothing
sessionId, currency or locale is missing.
The component returns an empty fragment when any of the three is absent. Verify the query string and the routing mode.
Environment variable changes have no effect
The values are read at request time on the server, but the deployment was not restarted.
Restart the application after changing environment variables.
Last updated
Was this helpful?

