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.
The PayUHashGenerator class provides a centralized, reusable solution for:
- Generating payment transaction hashes
- Validating response hashes (reverse hash) from callbacks
- Generating API operation hashes for PayU web services
- Supporting all UDF fields (udf1-udf10)
- Handling additional charges in hash calculations
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)
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)
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" |
app/Services/Gateways/PayU/PayUHashGenerator.php
<?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'];
}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)udf1throughudf10- 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);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)udf1throughudf5orudf10- User defined fieldsadditionalCharges- Additional charges (optional)
Example Usage:
$isValid = PayUHashGenerator::validateResponseHash(
$request->all(),
$salt
);
if (!$isValid) {
throw new PaymentProcessingException('Invalid response hash');
}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
);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;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;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;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;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;
}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;
}public static function generateApiHash(string $command, string $var1, string $key, string $salt): string
{
$hashString = "{$key}|{$command}|{$var1}|{$salt}";
return self::computeHash($hashString);
}- Default to empty string when UDF fields are not provided
- Ensure pipe delimiters are always present
- Parameter may be named
additionalChargesoradditional_charges - Check both keys when validating responses
- Only include in hash if non-empty
- PayU expects decimal format (e.g., "100.00")
- Application uses cents internally
- Conversion should happen before calling hash generation
- All hashes should be lowercase
- Use
strtolower()on the final hash
- Treat null as empty string
- Use
??operator for safe access
The existing PayUGateway.php has inline hash generation that:
- Uses hardcoded hash sequence with UDF1-UDF10
- Has debugging code (
\dd()) in response validation - References undefined
$datavariable (bug at line 339)
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...
}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
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]
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]
-
Payment Hash Generation
- Test with all required fields
- Test with missing UDF fields
- Test with extended UDF fields
- Test with additional charges
-
Response Validation
- Test valid response hash
- Test invalid response hash
- Test with additional charges
- Test with missing fields
-
API Hash Generation
- Test each command type
- Test with special characters in var1
// 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)- Salt Protection: Never log or expose the merchant salt
- Server-Side Only: Hash generation must happen on the server
- Constant-Time Comparison: Use
hash_equals()for hash comparison to prevent timing attacks - Input Validation: Sanitize all input parameters before hash generation
| 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" |
- PayU Generate Hash Documentation
- PayU API Authentication
- PayU Additional Charges
- PayU Hash Verification Tool
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2026-02-02 | Initial design document |