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:

  1. Mobile Account (MA) — Customer enters their JazzCash mobile number and PIN. Money moves from wallet to merchant. Lowest friction for existing JazzCash users.
  2. 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.
  3. 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

  1. 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.
  2. Three credentials they send once approved:
    • Merchant ID
    • Password
    • Integrity Salt (the secret used for HMAC signing)
  3. A WordPress + WooCommerce install (WP 6.0+, WC 8.0+)
  4. SSL certificate on production domain — JazzCash rejects callbacks from non-HTTPS URLs
  5. 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 at https://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)

  1. Download the plugin ZIP from your JazzCash merchant portal.
  2. WP Admin → Plugins → Add New → Upload Plugin → select the ZIP → Install → Activate.
  3. WooCommerce → Settings → Payments. You’ll see JazzCash in the list — toggle it on.
  4. Click Manage. Fill in:
    • Merchant ID
    • Password
    • Integrity Salt
    • Return URL (WP auto-populates this; leave it)
    • Mode: Sandbox (for testing) or Live
  5. 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

  1. Start in sandbox mode. Use the test MSISDN and OTP from the JazzCash sandbox docs.
  2. Run three scenarios end-to-end: success, failure (wrong OTP), timeout (wait out the expiry).
  3. Confirm WooCommerce order status updates correctly in all three.
  4. Flip to live mode. Make a real PKR 1 transaction to yourself. If it completes, you’re live. Refund it.
  5. 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

ErrorReal causeFix
101 — Invalid Merchant IDSandbox creds pointed at live URL (or vice versa)Match mode + URL + credential set
114 — Invalid Secure HashBuilt hash from the wrong field setExclude 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 transactionHash verification skipped — callback spoofedAlways verify pp_SecureHash; never trust the response code alone
“Technical error — try again later” on a percentage of attemptsSandbox rate limitsNormal in sandbox; disappears on live

Security — the non-negotiable checklist

  • Store Integrity Salt in wp-config.php as a constant, not in the plugin settings UI (which saves to the DB in plain text).
  • Never log pp_Password or pp_SecureHash to 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.