If you run a WooCommerce store in Pakistan, JazzCash is almost certainly on your customers’ phones. It’s Pakistan’s largest mobile wallet — 45+ million active accounts at the last SBP count — and customers who see it as a checkout option convert noticeably better than those pushed toward card or bank transfer alone.
This guide walks you through two ways to add JazzCash to your WooCommerce checkout: the fast plugin route (live in under an hour) and the custom-code route (more work, more control). Whichever you pick, you’ll finish with a working Pakistan-friendly payment option and a sandbox workflow so you can test safely before going live.
Who this guide is for
- Store owners on WooCommerce who want JazzCash alongside COD and bank transfer
- Developers building a Pakistani client’s store
- Anyone who’s tried a paid plugin and hit the “invalid secure hash” wall
You don’t need to be a PHP expert to follow the plugin route. For the custom route, comfort reading a WordPress plugin file helps.
The three JazzCash merchant products
Before touching code, know what you’re integrating. JazzCash for business offers three checkout types through their Direct Payment API:
- Mobile Account (MA) — Customer enters their JazzCash mobile number and PIN. Money moves from wallet to merchant. Lowest friction for existing JazzCash users.
- Voucher — Customer gets a 3N voucher code at checkout and pays at any JazzCash retailer within 24 hours. Useful for buyers without a wallet who still want to pay locally.
- Card — JazzCash-hosted page accepts Visa/Mastercard, including from customers without a JazzCash account.
Most Pakistani DTC stores enable MA + Card together and skip Voucher (it adds customer-support load). That’s the combo we’ll wire up.
What you’ll need before writing a line of code
- JazzCash merchant account — apply at jazzcash.com.pk/business. Approval takes 5–10 working days. You’ll need your CNIC/NTN, business bank account, and a signed merchant agreement.
- Three credentials they send once approved:
Merchant IDPasswordIntegrity Salt(the secret used for HMAC signing)
- A WordPress + WooCommerce install (WP 6.0+, WC 8.0+)
- SSL certificate on production domain — JazzCash rejects callbacks from non-HTTPS URLs
- A sandbox set of credentials — ask the merchant onboarding team separately; they don’t send sandbox by default
Heads-up: JazzCash’s sandbox uses
https://sandbox.jazzcash.com.pk/...endpoints and the production URLs live athttps://payments.jazzcash.com.pk/.... Keep the two sets of credentials clearly labelled — mixing them is the #1 reason “invalid merchant” errors show up.
Option 1: The plugin route
If your scope is “I just want JazzCash to work on Monday”, use a plugin.
Plugin options worth trying
- JazzCash Payment Gateway for WooCommerce — the official-ish plugin some merchants get a link to during onboarding. Supports MA + Card, sandbox toggle, and basic callback handling. Free.
- PayFast Pakistan — a third-party aggregator that bundles JazzCash, Easypaisa, HBL, and Meezan into one integration. Free to install; they take a small per-transaction cut. Good if you want multiple gateways from one plugin.
- Custom-built plugins — several Pakistani dev studios sell WooCommerce JazzCash plugins for around PKR 5,000–15,000. Worth it only if they include proper callback handling and order-status sync.
Installation (using the official plugin)
- Download the plugin ZIP from your JazzCash merchant portal.
- WP Admin → Plugins → Add New → Upload Plugin → select the ZIP → Install → Activate.
- WooCommerce → Settings → Payments. You’ll see JazzCash in the list — toggle it on.
- Click Manage. Fill in:
- Merchant ID
- Password
- Integrity Salt
- Return URL (WP auto-populates this; leave it)
- Mode: Sandbox (for testing) or Live
- Save.
Place a test order with the sandbox Mobile Account number the JazzCash docs provide (usually 03123456789 with OTP 1234). Watch the order flow — on successful payment, WooCommerce should mark the order Processing; on failure, Failed.
Where plugins fall short
The free plugins work, but they share three common limits:
- No partial-refund support — you’ll do refunds manually from the JazzCash dashboard.
- Cart abandonment on gateway redirect — if the customer hits back from JazzCash, the order sometimes stays stuck “Pending payment” forever.
- No saved wallet — every purchase re-enters credentials.
If any of those matter, go custom.
Option 2: The custom-code route
This is a stripped-down version of how JazzCash MA integration actually works underneath. Drop it into a file at wp-content/plugins/asad-jazzcash/asad-jazzcash.php and activate.
Step 1: Register the gateway class
<?php
/**
* Plugin Name: Asad — JazzCash WooCommerce Gateway
* Description: Custom JazzCash Mobile Account gateway for WooCommerce.
* Version: 1.0.0
*/
add_action('plugins_loaded', 'asad_jc_init_gateway', 11);
function asad_jc_init_gateway() {
if (!class_exists('WC_Payment_Gateway')) return;
class Asad_JazzCash_Gateway extends WC_Payment_Gateway {
public function __construct() {
$this->id = 'asad_jazzcash';
$this->method_title = 'JazzCash (Mobile Account)';
$this->method_description = 'Accept JazzCash wallet payments in Pakistan.';
$this->has_fields = false;
$this->init_form_fields();
$this->init_settings();
$this->title = $this->get_option('title');
$this->merchant_id = $this->get_option('merchant_id');
$this->password = $this->get_option('password');
$this->integrity_salt = $this->get_option('integrity_salt');
$this->sandbox = $this->get_option('sandbox') === 'yes';
add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options'));
add_action('woocommerce_api_asad_jazzcash_callback', array($this, 'handle_callback'));
}
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array('title' => 'Enable', 'type' => 'checkbox', 'default' => 'no'),
'title' => array('title' => 'Checkout label', 'type' => 'text', 'default' => 'JazzCash Mobile Account'),
'merchant_id' => array('title' => 'Merchant ID', 'type' => 'text'),
'password' => array('title' => 'Password', 'type' => 'password'),
'integrity_salt' => array('title' => 'Integrity Salt', 'type' => 'password'),
'sandbox' => array('title' => 'Sandbox mode', 'type' => 'checkbox', 'default' => 'yes'),
);
}
public function process_payment($order_id) {
$order = wc_get_order($order_id);
$url = $this->get_post_url();
return array(
'result' => 'success',
'redirect' => add_query_arg(
$this->build_payload($order),
$url
),
);
}
}
}
add_filter('woocommerce_payment_gateways', function ($gateways) {
$gateways[] = 'Asad_JazzCash_Gateway';
return $gateways;
});
Step 2: Build the signed payload
JazzCash rejects any request whose pp_SecureHash doesn’t match. The hash is HMAC-SHA256 of an alphabetically-sorted, pipe-delimited string of all pp_* fields, salted with your Integrity Salt.
private function build_payload($order) {
$txn_ref = 'T' . time() . $order->get_id();
$amount = number_format((float) $order->get_total(), 2, '.', '') * 100; // paisa
$data = array(
'pp_Version' => '1.1',
'pp_TxnType' => 'MWALLET',
'pp_Language' => 'EN',
'pp_MerchantID' => $this->merchant_id,
'pp_Password' => $this->password,
'pp_TxnRefNo' => $txn_ref,
'pp_Amount' => (string) $amount,
'pp_TxnCurrency' => 'PKR',
'pp_TxnDateTime' => date('YmdHis'),
'pp_BillReference' => 'billRef' . $order->get_id(),
'pp_Description' => 'Order #' . $order->get_id(),
'pp_TxnExpiryDateTime'=> date('YmdHis', strtotime('+1 hour')),
'pp_ReturnURL' => home_url('/wc-api/asad_jazzcash_callback/'),
'ppmpf_1' => (string) $order->get_id(),
);
$data['pp_SecureHash'] = $this->build_secure_hash($data);
return $data;
}
private function build_secure_hash($data) {
ksort($data);
$values = array();
foreach ($data as $key => $value) {
if ($value !== '' && $key !== 'pp_SecureHash') {
$values[] = $value;
}
}
$string = $this->integrity_salt . '&' . implode('&', $values);
return strtoupper(hash_hmac('sha256', $string, $this->integrity_salt));
}
private function get_post_url() {
return $this->sandbox
? 'https://sandbox.jazzcash.com.pk/CustomerPortal/transactionmanagement/merchantform/'
: 'https://payments.jazzcash.com.pk/CustomerPortal/transactionmanagement/merchantform/';
}
Step 3: Handle the callback
When the customer finishes on JazzCash’s hosted page, they get redirected back to pp_ReturnURL with a pp_ResponseCode and a fresh pp_SecureHash. You must verify the hash before marking the order paid — otherwise someone can fake a success by crafting a URL.
public function handle_callback() {
$data = $_POST ?: $_GET;
if (empty($data['pp_TxnRefNo'])) { exit; }
$expected_hash = $this->build_secure_hash($data);
if (!hash_equals($expected_hash, strtoupper($data['pp_SecureHash'] ?? ''))) {
wp_die('JazzCash hash mismatch — possible tampering.', 403);
}
$order_id = isset($data['ppmpf_1']) ? (int) $data['ppmpf_1'] : 0;
$order = wc_get_order($order_id);
if (!$order) { wp_die('Order not found.', 404); }
if ($data['pp_ResponseCode'] === '000') {
$order->payment_complete($data['pp_TxnRefNo']);
$order->add_order_note('JazzCash payment successful. Ref: ' . $data['pp_TxnRefNo']);
wp_safe_redirect($this->get_return_url($order));
} else {
$order->update_status('failed', 'JazzCash declined: ' . ($data['pp_ResponseMessage'] ?? 'unknown'));
wp_safe_redirect(wc_get_checkout_url());
}
exit;
}
That’s a working MA integration. It’s ~120 lines, gives you full control, and — crucially — the hash-verification step closes the biggest security hole plugins often leave open.
Testing workflow
- Start in sandbox mode. Use the test MSISDN and OTP from the JazzCash sandbox docs.
- Run three scenarios end-to-end: success, failure (wrong OTP), timeout (wait out the expiry).
- Confirm WooCommerce order status updates correctly in all three.
- Flip to live mode. Make a real PKR 1 transaction to yourself. If it completes, you’re live. Refund it.
- Monitor the first week of real orders daily — this is when edge cases (network drop mid-callback, double-click checkout, browser back button) surface.
Common failures and how to fix them
| Error | Real cause | Fix |
|---|---|---|
101 — Invalid Merchant ID | Sandbox creds pointed at live URL (or vice versa) | Match mode + URL + credential set |
114 — Invalid Secure Hash | Built hash from the wrong field set | Exclude empty values and pp_SecureHash itself from the string |
| Customer redirected back but order stays “Pending payment” | Callback URL blocked by security plugin (Wordfence rules) | Whitelist /wc-api/asad_jazzcash_callback/ or switch callback to a REST route |
| Order marked paid but JazzCash shows no transaction | Hash verification skipped — callback spoofed | Always verify pp_SecureHash; never trust the response code alone |
| “Technical error — try again later” on a percentage of attempts | Sandbox rate limits | Normal in sandbox; disappears on live |
Security — the non-negotiable checklist
- Store Integrity Salt in
wp-config.phpas a constant, not in the plugin settings UI (which saves to the DB in plain text). - Never log
pp_Passwordorpp_SecureHashto server logs. A leaked log file becomes a leaked merchant account. - Run HTTPS everywhere. JazzCash’s production endpoints reject non-HTTPS callbacks silently — you’ll see “paid” orders that never settle.
- Keep a daily reconciliation cron: pull your JazzCash transaction list via the Status Enquiry API and compare against WooCommerce orders. Catches missed callbacks within 24 hours.
Going further
What this guide doesn’t cover — and which you’ll likely want within 3 months of going live:
- Status Enquiry (
pp_TxnType = STATUS) to reconcile missed callbacks - Refunds via the Refund API
- Card checkout (
pp_TxnType = AUTH+CAPTURE) — different flow, separate merchant product - Voucher for customers without a wallet
- Recurring billing (subscriptions) — JazzCash supports it but the API is separate and needs extra merchant onboarding
Happy to help with any of those.
FAQ
How much does JazzCash charge merchants in Pakistan?
Rates vary by business type and volume — typically 1.5%–2.5% per transaction for SMB merchants. Larger merchants negotiate custom rates. Always get a signed fee schedule before going live; verbal quotes change.
Can I offer JazzCash without a registered business?
No — JazzCash merchant onboarding requires an NTN (tax number) and a business bank account. If you’re running a one-person Instagram store, you can still register as a sole proprietor at FBR, which takes a day.
Is JazzCash API the same as Easypaisa API?
No — different companies (Jazz vs Telenor Microfinance Bank), different APIs, different credentials, different response codes. Integrating Easypaisa will be covered in a separate post. Customers generally appreciate both being available.
What if a customer pays and the callback fails?
That’s what the Status Enquiry API + a daily reconciliation cron is for. Money is safe on JazzCash’s side — the job is syncing that status back into WooCommerce without requiring manual finance work.
Do I need a plugin if I already use PayFast or 2C2P?
If those aggregators already route JazzCash for you, no — a direct JazzCash integration is redundant. The direct route saves the aggregator fee (typically 0.5–1% on top of JazzCash’s own rate), which matters above ~PKR 500k/month in volume.
Need this wired up for your store?
I build and support WooCommerce stores across Pakistan — in Karachi, Lahore, Islamabad, and Rawalpindi. JazzCash, Easypaisa, HBL, Meezan, and courier integrations are bread-and-butter work.
Send me a short brief on WhatsApp and I’ll come back with a fixed quote in PKR — usually the same day.