Skip to content
This repository was archived by the owner on Nov 23, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,28 @@ public void verifyAndProcessNotification(MultiValueMap<String, String> formData)
String md5sig = formData.getFirst("md5sig");
String paymentId = formData.getFirst("payment_id");

// Generate hash for verification
String hashString = merchantId + orderId + payhereAmount + payhereCurrency +
statusCode + payHereConfig.getMerchantSecret();
String generatedHash = PayHereHashUtil.getMd5(hashString);
// Format the received amount to 2 decimal places to match how hash was generated
String formattedAmount = payhereAmount;
try {
java.text.DecimalFormat df = new java.text.DecimalFormat("0.00");
java.math.BigDecimal bd = new java.math.BigDecimal(payhereAmount);
formattedAmount = df.format(bd);
} catch (Exception e) {
log.warn("Failed to format payhere_amount '{}', using raw value", payhereAmount);
}

// Validate presence of orderId before proceeding
if (orderId == null || orderId.isBlank()) {
log.warn("Missing order_id in PayHere notification, skipping processing");
return;
}

// Generate verification hash according to PayHere docs
// md5sig = MD5(merchant_id + order_id + payhere_amount + payhere_currency + status_code + MD5(merchant_secret)).toUpperCase()
String hashedSecret = PayHereHashUtil.getMd5(payHereConfig.getMerchantSecret());
String hashString = merchantId + orderId + formattedAmount + payhereCurrency +
statusCode + hashedSecret;
String generatedHash = PayHereHashUtil.getMd5(hashString);

// Verify signature
if (!generatedHash.equalsIgnoreCase(md5sig)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.techtorque.payment_service.util;

import org.junit.jupiter.api.Test;

import java.math.BigDecimal;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class PayHereHashUtilTest {

@Test
public void testGeneratePaymentHash_WithPlainSecret() {
String merchantId = "1231971";
String orderId = "ORDER-ABC-123";
BigDecimal amount = new BigDecimal("1000.00");
String currency = "LKR";
String merchantSecret = "plain-secret-123";

// Generate expected: hashedSecret = MD5(merchantSecret)
String hashedSecret = PayHereHashUtil.getMd5(merchantSecret);
String input = merchantId + orderId + "1000.00" + currency + hashedSecret;
String expected = PayHereHashUtil.getMd5(input);

String actual = PayHereHashUtil.generatePaymentHash(merchantId, orderId, amount, currency, merchantSecret);

assertEquals(expected, actual);
}

@Test
public void testGeneratePaymentHash_WithBase64Secret() {
String merchantId = "1231971";
String orderId = "ORD-BASE64";
BigDecimal amount = new BigDecimal("250.00");
String currency = "LKR";

// merchant secret base64 - for test use simple known string
String plainSecret = "super-secret";
String base64Secret = java.util.Base64.getEncoder().encodeToString(plainSecret.getBytes());

// The util should decode base64 then MD5
String hashedSecret = PayHereHashUtil.getMd5(plainSecret); // decode happens internally in generatePaymentHash
String input = merchantId + orderId + "250.00" + currency + hashedSecret;
String expected = PayHereHashUtil.getMd5(input);

String actual = PayHereHashUtil.generatePaymentHash(merchantId, orderId, amount, currency, base64Secret);
assertEquals(expected, actual);
}
}