import { createHash } from 'crypto';
/**
* URL encodes a value using PayFast's specific requirements:
* - Uses encodeURIComponent for encoding
* - Replaces %20 with + for spaces (PayFast requirement)
*
* @param value - The value to encode
* @returns The URL-encoded value with spaces as +
*/
function urlEncodeForPayFast(value: string): string {
return encodeURIComponent(value).replace(/%20/g, '+');
}
/**
* Generates a URL-encoded parameter string for PayFast signature generation.
*
* Process:
* 1. Filters out empty/null/undefined values
* 2. Sorts parameters alphabetically by key
* 3. Creates URL-encoded parameter string: key1=value1&key2=value2&...
* 4. If passphrase is provided, appends &passphrase=<encoded_passphrase>
*
* @param params - Object containing key-value parameters
* @param passphrase - Optional PayFast passphrase
* @returns URL-encoded parameter string ready for MD5 hashing
*
* @example
* ```typescript
* const params = {
* merchant_id: '10000100',
* merchant_key: '46f0cd694581a',
* amount: '100.00',
* item_name: 'Test Product'
* };
* const paramString = generateParamString(params, 'myPassphrase');
* // Returns: "amount=100.00&item_name=Test+Product&merchant_id=10000100&merchant_key=46f0cd694581a&passphrase=myPassphrase"
* ```
*/
export function generateParamString(
params: Record<string, string>,
passphrase?: string
): string {
// Filter out empty, null, or undefined values
const filteredParams = Object.entries(params).filter(([_, value]) => {
return value !== null && value !== undefined && value !== '';
});
// Sort parameters alphabetically by key
filteredParams.sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
// Create URL-encoded parameter string
const paramString = filteredParams
.map(([key, value]) => `${key}=${urlEncodeForPayFast(value)}`)
.join('&');
// Append passphrase if provided
if (passphrase && passphrase !== '') {
return `${paramString}&passphrase=${urlEncodeForPayFast(passphrase)}`;
}
return paramString;
}
/**
* Generates an MD5 signature for PayFast API requests.
*
* This function creates the signature required by PayFast for secure API communication.
* The signature is generated by:
* 1. Creating a URL-encoded parameter string (via generateParamString)
* 2. Generating an MD5 hash of the complete string
* 3. Returning the hash as a lowercase hexadecimal string
*
* @param params - Object containing key-value parameters for the PayFast request
* @param passphrase - Optional PayFast passphrase (recommended for production)
* @returns MD5 signature as a lowercase hex string
*
* @example
* ```typescript
* const params = {
* merchant_id: '10000100',
* merchant_key: '46f0cd694581a',
* return_url: 'https://example.com/return',
* cancel_url: 'https://example.com/cancel',
* notify_url: 'https://example.com/notify',
* amount: '100.00',
* item_name: 'Test Product'
* };
*
* const signature = generateSignature(params, 'mySecretPassphrase');
* // Returns: "9cd7a8c6b8c6e5c8a1b2c3d4e5f6a7b8" (example hash)
* ```
*
* @remarks
* PayFast-specific requirements:
* - Empty string values are excluded from signature calculation
* - Parameters are sorted alphabetically by key
* - Values are URL-encoded with spaces as + (not %20)
* - Passphrase is appended at the end (after sorting other params)
* - The final hash must be lowercase hexadecimal
*/
export function generateSignature(
params: Record<string, string>,
passphrase?: string
): string {
// Generate the parameter string
const paramString = generateParamString(params, passphrase);
// Generate MD5 hash
const hash = createHash('md5');
hash.update(paramString);
// Return as lowercase hex string
return hash.digest('hex').toLowerCase();
}