Skip to content

Instantly share code, notes, and snippets.

@Pushkraj19
Last active February 2, 2026 09:04
Show Gist options
  • Select an option

  • Save Pushkraj19/15b972ad7340f969e9e51b3922ae254b to your computer and use it in GitHub Desktop.

Select an option

Save Pushkraj19/15b972ad7340f969e9e51b3922ae254b to your computer and use it in GitHub Desktop.
PayU Hash Generator Utility PHP Class

PayU Hash Generator Design Document

Overview

This document outlines the design for a comprehensive PayUHashGenerator utility class that handles all hash generation and validation scenarios for PayU India payment gateway integration.

Purpose

The PayUHashGenerator class provides a centralized, reusable solution for:

  1. Generating payment transaction hashes
  2. Validating response hashes (reverse hash) from callbacks
  3. Generating API operation hashes for PayU web services
  4. Supporting all UDF fields (udf1-udf10)
  5. Handling additional charges in hash calculations

Hash Formulas Reference

1. Payment Transaction Hash (Request Hash)

Used when initiating a payment request to PayU.

Standard Formula (API version < 19):

sha512(key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5||||||salt)

Extended Formula (API version 19):

sha512(key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5|udf6|udf7|udf8|udf9|udf10|user_token|offer_key|offer_auto_apply|cart_details|extra_charges|phone)

With Additional Charges (posted by merchant):

sha512(key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5||||||salt|additional_charges)

2. Response Validation Hash (Reverse Hash)

Used to validate responses from PayU callbacks (surl/furl).

Without Additional Charges:

sha512(salt|status||||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|key)

With Additional Charges:

sha512(additional_charges|salt|status||||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|key)

Extended with UDF6-UDF10 (if used in request):

sha512(salt|status|udf10|udf9|udf8|udf7|udf6|udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|key)

3. API Operation Hashes

Used for PayU server-to-server API calls.

General Formula:

sha512(key|command|var1|salt)
API Command var1 Value
verify_payment txnid
cancel_refund_transaction mihpayid
check_offer_status offer_key
get_transaction_details from_date|to_date
get_emi_amount_according_to_interest amount
capture_transaction mihpayid
check_action_status request_id
get_TDR txnid
payment_related_details_for_mobile_sdk user_credentials or "default"

Class Design

File Location

app/Services/Gateways/PayU/PayUHashGenerator.php

Class Structure

<?php

namespace App\Services\Gateways\PayU;

/**
 * PayU Hash Generator Utility Class
 * 
 * Handles all hash generation and validation scenarios for PayU payment gateway.
 * 
 * @see https://docs.payu.in/docs/generate-hash-merchant-hosted
 * @see https://docs.payu.in/docs/api-authentication-and-security
 */
class PayUHashGenerator
{
    // Hash sequence constants
    public const PAYMENT_HASH_SEQUENCE = 'key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5||||||';
    public const PAYMENT_HASH_SEQUENCE_V19 = 'key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5|udf6|udf7|udf8|udf9|udf10|user_token|offer_key|offer_auto_apply|cart_details|extra_charges|phone';
    
    // Reverse hash sequence (response validation)
    public const RESPONSE_HASH_SEQUENCE = 'status||||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|key';
    public const RESPONSE_HASH_SEQUENCE_EXTENDED = 'status|udf10|udf9|udf8|udf7|udf6|udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|key';
    
    // API command constants
    public const CMD_VERIFY_PAYMENT = 'verify_payment';
    public const CMD_CANCEL_REFUND = 'cancel_refund_transaction';
    public const CMD_CHECK_OFFER_STATUS = 'check_offer_status';
    public const CMD_GET_TRANSACTION_DETAILS = 'get_transaction_details';
    public const CMD_GET_EMI_AMOUNT = 'get_emi_amount_according_to_interest';
    public const CMD_CAPTURE_TRANSACTION = 'capture_transaction';
    public const CMD_CHECK_ACTION_STATUS = 'check_action_status';
    public const CMD_GET_TDR = 'get_TDR';
    public const CMD_PAYMENT_DETAILS_SDK = 'payment_related_details_for_mobile_sdk';
    
    // UDF field constants
    public const UDF_FIELDS = ['udf1', 'udf2', 'udf3', 'udf4', 'udf5', 'udf6', 'udf7', 'udf8', 'udf9', 'udf10'];
    public const UDF_FIELDS_BASIC = ['udf1', 'udf2', 'udf3', 'udf4', 'udf5'];
}

Public Methods

1. generatePaymentHash()

Generates hash for payment initiation requests.

/**
 * Generate hash for payment transaction request.
 *
 * @param array $params Transaction parameters
 * @param string $salt Merchant salt
 * @param bool $useExtendedUdf Whether to use udf6-udf10 (default: false)
 * @param string|null $additionalCharges Additional charges string (optional)
 * @return string Generated hash (lowercase)
 */
public static function generatePaymentHash(
    array $params, 
    string $salt, 
    bool $useExtendedUdf = false,
    ?string $additionalCharges = null
): string;

Parameters Expected:

  • key - Merchant key (required)
  • txnid - Transaction ID (required)
  • amount - Transaction amount (required)
  • productinfo - Product information (required)
  • firstname - Customer first name (required)
  • email - Customer email (required)
  • udf1 through udf10 - User defined fields (optional)

Example Usage:

$hash = PayUHashGenerator::generatePaymentHash([
    'key' => 'merchant_key',
    'txnid' => 'TXN123456',
    'amount' => '100.00',
    'productinfo' => 'Premium Plan',
    'firstname' => 'John',
    'email' => '[email protected]',
    'udf1' => 'payment_uuid_123',
], $salt);

2. validateResponseHash()

Validates hash received from PayU callback responses.

/**
 * Validate response hash from PayU callback.
 *
 * @param array $responseParams Response parameters from PayU
 * @param string $salt Merchant salt
 * @param bool $useExtendedUdf Whether extended UDF fields were used in request
 * @return bool True if hash is valid, false otherwise
 */
public static function validateResponseHash(
    array $responseParams, 
    string $salt,
    bool $useExtendedUdf = false
): bool;

Parameters Expected from Response:

  • hash - Hash from PayU response (required)
  • status - Transaction status (required)
  • email - Customer email (required)
  • firstname - Customer first name (required)
  • productinfo - Product information (required)
  • amount - Transaction amount (required)
  • txnid - Transaction ID (required)
  • key - Merchant key (required)
  • udf1 through udf5 or udf10 - User defined fields
  • additionalCharges - Additional charges (optional)

Example Usage:

$isValid = PayUHashGenerator::validateResponseHash(
    $request->all(),
    $salt
);

if (!$isValid) {
    throw new PaymentProcessingException('Invalid response hash');
}

3. generateApiHash()

Generates hash for PayU API operations.

/**
 * Generate hash for PayU API operations.
 *
 * @param string $command API command name
 * @param string $var1 Variable parameter (differs by command)
 * @param string $key Merchant key
 * @param string $salt Merchant salt
 * @return string Generated hash (lowercase)
 */
public static function generateApiHash(
    string $command, 
    string $var1, 
    string $key, 
    string $salt
): string;

Example Usage:

// Verify payment
$hash = PayUHashGenerator::generateApiHash(
    PayUHashGenerator::CMD_VERIFY_PAYMENT,
    'TXN123456',  // txnid
    $merchantKey,
    $merchantSalt
);

// Refund transaction
$hash = PayUHashGenerator::generateApiHash(
    PayUHashGenerator::CMD_CANCEL_REFUND,
    '12345678901',  // mihpayid
    $merchantKey,
    $merchantSalt
);

4. computeHash()

Core hashing method (internal use).

/**
 * Compute SHA-512 hash from a string.
 *
 * @param string $hashString String to hash
 * @return string Lowercase SHA-512 hash
 */
protected static function computeHash(string $hashString): string;

Helper Methods

5. buildPaymentHashString()

Builds the hash string for payment requests.

/**
 * Build hash string for payment request.
 *
 * @param array $params Transaction parameters
 * @param string $salt Merchant salt
 * @param bool $useExtendedUdf Whether to include udf6-udf10
 * @param string|null $additionalCharges Additional charges
 * @return string Hash string ready for hashing
 */
protected static function buildPaymentHashString(
    array $params, 
    string $salt,
    bool $useExtendedUdf = false,
    ?string $additionalCharges = null
): string;

6. buildResponseHashString()

Builds the reverse hash string for response validation.

/**
 * Build reverse hash string for response validation.
 *
 * @param array $params Response parameters
 * @param string $salt Merchant salt
 * @param bool $useExtendedUdf Whether extended UDF fields were used
 * @return string Hash string ready for hashing
 */
protected static function buildResponseHashString(
    array $params, 
    string $salt,
    bool $useExtendedUdf = false
): string;

7. getParamValue()

Safely extracts parameter value with default.

/**
 * Get parameter value with default empty string.
 *
 * @param array $params Parameters array
 * @param string $key Parameter key
 * @param string $default Default value
 * @return string Parameter value
 */
protected static function getParamValue(
    array $params, 
    string $key, 
    string $default = ''
): string;

Implementation Details

Hash String Construction

Payment Hash (Standard)

protected static function buildPaymentHashString(array $params, string $salt, bool $useExtendedUdf = false, ?string $additionalCharges = null): string
{
    $hashString = self::getParamValue($params, 'key') . '|';
    $hashString .= self::getParamValue($params, 'txnid') . '|';
    $hashString .= self::getParamValue($params, 'amount') . '|';
    $hashString .= self::getParamValue($params, 'productinfo') . '|';
    $hashString .= self::getParamValue($params, 'firstname') . '|';
    $hashString .= self::getParamValue($params, 'email') . '|';
    
    // UDF fields 1-5
    for ($i = 1; $i <= 5; $i++) {
        $hashString .= self::getParamValue($params, "udf{$i}") . '|';
    }
    
    if ($useExtendedUdf) {
        // UDF fields 6-10
        for ($i = 6; $i <= 10; $i++) {
            $hashString .= self::getParamValue($params, "udf{$i}") . '|';
        }
    } else {
        // Empty placeholders for udf6-udf10
        $hashString .= '|||||';
    }
    
    $hashString .= $salt;
    
    // Append additional charges if provided
    if ($additionalCharges !== null) {
        $hashString .= '|' . $additionalCharges;
    }
    
    return $hashString;
}

Response Hash (Reverse)

protected static function buildResponseHashString(array $params, string $salt, bool $useExtendedUdf = false): string
{
    $hashString = '';
    
    // Check for additional charges
    $additionalCharges = self::getParamValue($params, 'additionalCharges') 
                      ?? self::getParamValue($params, 'additional_charges');
    
    if (!empty($additionalCharges)) {
        $hashString .= $additionalCharges . '|';
    }
    
    $hashString .= $salt . '|';
    $hashString .= self::getParamValue($params, 'status') . '|';
    
    if ($useExtendedUdf) {
        // Reverse order: udf10 to udf1
        for ($i = 10; $i >= 1; $i--) {
            $hashString .= self::getParamValue($params, "udf{$i}") . '|';
        }
    } else {
        // Empty placeholders then udf5 to udf1
        $hashString .= '|||||'; // Empty placeholders
        for ($i = 5; $i >= 1; $i--) {
            $hashString .= self::getParamValue($params, "udf{$i}") . '|';
        }
    }
    
    $hashString .= self::getParamValue($params, 'email') . '|';
    $hashString .= self::getParamValue($params, 'firstname') . '|';
    $hashString .= self::getParamValue($params, 'productinfo') . '|';
    $hashString .= self::getParamValue($params, 'amount') . '|';
    $hashString .= self::getParamValue($params, 'txnid') . '|';
    $hashString .= self::getParamValue($params, 'key');
    
    return $hashString;
}

API Hash

public static function generateApiHash(string $command, string $var1, string $key, string $salt): string
{
    $hashString = "{$key}|{$command}|{$var1}|{$salt}";
    return self::computeHash($hashString);
}

Edge Cases & Handling

1. Missing UDF Fields

  • Default to empty string when UDF fields are not provided
  • Ensure pipe delimiters are always present

2. Additional Charges Variations

  • Parameter may be named additionalCharges or additional_charges
  • Check both keys when validating responses
  • Only include in hash if non-empty

3. Amount Format

  • PayU expects decimal format (e.g., "100.00")
  • Application uses cents internally
  • Conversion should happen before calling hash generation

4. Case Sensitivity

  • All hashes should be lowercase
  • Use strtolower() on the final hash

5. Empty vs Null Parameters

  • Treat null as empty string
  • Use ?? operator for safe access

Integration with PayUGateway

Current Implementation Issues

The existing PayUGateway.php has inline hash generation that:

  1. Uses hardcoded hash sequence with UDF1-UDF10
  2. Has debugging code (\dd()) in response validation
  3. References undefined $data variable (bug at line 339)

Recommended Integration

Replace inline methods with utility class:

// In PayUGateway.php

protected function generateHash(array $params): string
{
    return PayUHashGenerator::generatePaymentHash(
        $params, 
        $this->merchantSalt
    );
}

protected function validateResponseHash(array $params): bool
{
    return PayUHashGenerator::validateResponseHash(
        $params, 
        $this->merchantSalt
    );
}

// For API calls
public function verifyPayment(string $txnid): array
{
    $hash = PayUHashGenerator::generateApiHash(
        PayUHashGenerator::CMD_VERIFY_PAYMENT,
        $txnid,
        $this->merchantKey,
        $this->merchantSalt
    );
    
    // Make API call with hash...
}

Class Diagram

classDiagram
    class PayUHashGenerator {
        <<utility>>
        +string PAYMENT_HASH_SEQUENCE$
        +string PAYMENT_HASH_SEQUENCE_V19$
        +string RESPONSE_HASH_SEQUENCE$
        +string CMD_VERIFY_PAYMENT$
        +string CMD_CANCEL_REFUND$
        +array UDF_FIELDS$
        +generatePaymentHash(params, salt, useExtendedUdf, additionalCharges)$ string
        +validateResponseHash(responseParams, salt, useExtendedUdf)$ bool
        +generateApiHash(command, var1, key, salt)$ string
        #buildPaymentHashString(params, salt, useExtendedUdf, additionalCharges)$ string
        #buildResponseHashString(params, salt, useExtendedUdf)$ string
        #computeHash(hashString)$ string
        #getParamValue(params, key, default)$ string
    }
    
    class PayUGateway {
        -string merchantKey
        -string merchantSalt
        #generateHash(params) string
        #validateResponseHash(params) bool
        +verifyPayment(txnid) array
        +refundTransaction(mihpayid, amount) array
    }
    
    PayUGateway --> PayUHashGenerator : uses
Loading

Flow Diagrams

Payment Hash Generation Flow

flowchart TD
    A[Start: Generate Payment Hash] --> B{Extended UDF?}
    B -->|Yes| C[Include UDF1-UDF10]
    B -->|No| D[Include UDF1-UDF5 + empty placeholders]
    C --> E{Additional Charges?}
    D --> E
    E -->|Yes| F[Append additional_charges to hash string]
    E -->|No| G[Build standard hash string]
    F --> H[Compute SHA-512]
    G --> H
    H --> I[Return lowercase hash]
Loading

Response Validation Flow

flowchart TD
    A[Start: Validate Response Hash] --> B[Extract hash from response]
    B --> C{Has additionalCharges?}
    C -->|Yes| D[Prepend additionalCharges to hash string]
    C -->|No| E[Build standard reverse hash string]
    D --> F[salt + status + empty + udf5..1 + email + firstname + productinfo + amount + txnid + key]
    E --> F
    F --> G[Compute SHA-512]
    G --> H{Computed == Response hash?}
    H -->|Yes| I[Return true - Valid]
    H -->|No| J[Return false - Invalid]
Loading

Testing Considerations

Unit Tests Required

  1. Payment Hash Generation

    • Test with all required fields
    • Test with missing UDF fields
    • Test with extended UDF fields
    • Test with additional charges
  2. Response Validation

    • Test valid response hash
    • Test invalid response hash
    • Test with additional charges
    • Test with missing fields
  3. API Hash Generation

    • Test each command type
    • Test with special characters in var1

Test Data Example

// Test data from PayU documentation
$testParams = [
    'key' => 'gtKFFx',
    'txnid' => '123456789',
    'amount' => '10.00',
    'productinfo' => 'Test Product',
    'firstname' => 'John',
    'email' => '[email protected]',
];
$testSalt = 'your_test_salt';

// Expected: sha512(gtKFFx|123456789|10.00|Test Product|John|[email protected]||||||||||your_test_salt)

Security Considerations

  1. Salt Protection: Never log or expose the merchant salt
  2. Server-Side Only: Hash generation must happen on the server
  3. Constant-Time Comparison: Use hash_equals() for hash comparison to prevent timing attacks
  4. Input Validation: Sanitize all input parameters before hash generation

API Command Reference

Command Description var1 Parameter
verify_payment Check transaction status Transaction ID (txnid)
cancel_refund_transaction Initiate refund PayU ID (mihpayid)
check_offer_status Check offer validity Offer key
get_transaction_details Get transactions in date range from_date|to_date
get_emi_amount_according_to_interest Calculate EMI Amount
capture_transaction Capture authorized transaction PayU ID (mihpayid)
check_action_status Check async action status Request ID
get_TDR Get TDR details Transaction ID (txnid)
payment_related_details_for_mobile_sdk SDK payment options User credentials or "default"

References


Version History

Version Date Changes
1.0.0 2026-02-02 Initial design document
<?php
namespace App\Services\Gateways\PayU;
/**
* PayU Hash Generator Utility Class
*
* Handles all hash generation and validation scenarios for PayU payment gateway.
*
* @see https://docs.payu.in/docs/generate-hash-merchant-hosted
* @see https://docs.payu.in/docs/api-authentication-and-security
*/
class PayUHashGenerator
{
// Hash sequence constants
public const PAYMENT_HASH_SEQUENCE = 'key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5|udf6|udf7|udf8|udf9|udf10';
public const UDF_COUNT = 5;
// API command constants
public const CMD_VERIFY_PAYMENT = 'verify_payment';
public const CMD_CANCEL_REFUND = 'cancel_refund_transaction';
public const CMD_CHECK_OFFER_STATUS = 'check_offer_status';
public const CMD_GET_TRANSACTION_DETAILS = 'get_transaction_details';
public const CMD_GET_EMI_AMOUNT = 'get_emi_amount_according_to_interest';
public const CMD_CAPTURE_TRANSACTION = 'capture_transaction';
public const CMD_CHECK_ACTION_STATUS = 'check_action_status';
public const CMD_GET_TDR = 'get_TDR';
public const CMD_PAYMENT_DETAILS_SDK = 'payment_related_details_for_mobile_sdk';
/**
* Generate hash for payment transaction request.
*
* Formula: sha512(key|txnid|amount|productinfo|firstname|email|udf1|...|udf10||||||salt)
*
* @param array $params Transaction parameters
* @param string $salt Merchant salt
* @return string Generated hash (lowercase)
*/
public static function generatePaymentHash(array $params, string $salt): string
{
$hashString = self::buildPaymentHashString($params, $salt);
return self::computeHash($hashString);
}
/**
* Validate response hash from PayU callback (reverse hash).
*
* Formula: sha512([additionalCharges|]salt|status||||||udf10|...|udf1|email|firstname|productinfo|amount|txnid|key)
*
* @param array $responseParams Response parameters from PayU
* @param string $salt Merchant salt
* @return bool True if hash is valid, false otherwise
*/
public static function validateResponseHash(array $responseParams, string $salt): bool
{
if (empty($responseParams['hash'])) {
return false;
}
$hashString = self::buildResponseHashString($responseParams, $salt);
$computedHash = self::computeHash($hashString);
// Use constant-time comparison to prevent timing attacks
return hash_equals($computedHash, strtolower($responseParams['hash']));
}
/**
* Generate hash for PayU API operations.
*
* Formula: sha512(key|command|var1|salt)
*
* @param string $command API command name
* @param string $var1 Variable parameter (differs by command)
* @param string $key Merchant key
* @param string $salt Merchant salt
* @return string Generated hash (lowercase)
*/
public static function generateApiHash(string $command, string $var1, string $key, string $salt): string
{
$hashString = "{$key}|{$command}|{$var1}|{$salt}";
return self::computeHash($hashString);
}
/**
* Build hash string for payment request.
*
* @param array $params Transaction parameters
* @param string $salt Merchant salt
* @return string Hash string ready for hashing
*/
private static function buildPaymentHashString(array $params, string $salt): string
{
$hashString = self::getSafeValue($params, 'key').'|';
$hashString .= self::getSafeValue($params, 'txnid').'|';
$hashString .= self::getSafeValue($params, 'amount').'|';
$hashString .= self::getSafeValue($params, 'productinfo').'|';
$hashString .= self::getSafeValue($params, 'firstname').'|';
$hashString .= self::getSafeValue($params, 'email').'|';
// UDF fields 1-10
for ($i = 1; $i <= self::UDF_COUNT; $i++) {
$hashString .= self::getSafeValue($params, "udf{$i}").'|';
}
// 6 empty pipes for reserved fields (required by PayU)
$hashString .= '|||||';
$hashString .= $salt;
return $hashString;
}
/**
* Build reverse hash string for response validation.
*
* @param array $params Response parameters
* @param string $salt Merchant salt
* @return string Hash string ready for hashing
*/
private static function buildResponseHashString(array $params, string $salt): string
{
$hashString = '';
// Check for additional charges (may be named differently)
$additionalCharges = self::getSafeValue($params, 'additionalCharges')
?: self::getSafeValue($params, 'additional_charges');
if (! empty($additionalCharges)) {
$hashString .= $additionalCharges.'|';
}
$hashString .= $salt.'|';
$hashString .= self::getSafeValue($params, 'status').'|';
// 6 empty pipes for reserved fields (required by PayU)
$hashString .= '|||||';
// UDF fields in reverse order: udf10 to udf1
for ($i = self::UDF_COUNT; $i >= 1; $i--) {
$hashString .= self::getSafeValue($params, "udf{$i}").'|';
}
$hashString .= self::getSafeValue($params, 'email').'|';
$hashString .= self::getSafeValue($params, 'firstname').'|';
$hashString .= self::getSafeValue($params, 'productinfo').'|';
$hashString .= self::getSafeValue($params, 'amount').'|';
$hashString .= self::getSafeValue($params, 'txnid').'|';
$hashString .= self::getSafeValue($params, 'key');
return $hashString;
}
/**
* Compute SHA-512 hash from a string.
*
* @param string $hashString String to hash
* @return string Lowercase SHA-512 hash
*/
private static function computeHash(string $hashString): string
{
return strtolower(hash('sha512', $hashString));
}
/**
* Get safe value (empty string if null/missing).
*
* @param array $params Parameters array
* @param string $key Parameter key
* @return string Parameter value or empty string
*/
private static function getSafeValue(array $params, string $key): string
{
return isset($params[$key]) ? (string) $params[$key] : '';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment