Signature and Parameter Integrity

Introduction

TendoPay uses HMAC-SHA256 to sign all data in API communication with the merchant. Before sending a TendoPay Signature request, make sure the following conditions are met:

  • All parameters that start with tp_xxx in the request/response are all strings.
  • Sort key-value pairs in the payload in alphabetic order by keys.
  • Concatenate all key-value pairs to not have any space.
  • Make a hash with the payload string and client secret.
  • The message outputs should be a lowercase hexadecimal digit.

Cleanup Example

In the example below we will clean up the request and make sure it follows the conditions required in order to get a successful response.

  1. Make sure the necessary key / value pairs are met.
{
    "tp_amount": 1000,   
    "tp_currency": "PHP",
    "tp_merchant_order_id": "TEST_ORDER_ID_12345",
    "tp_redirect_url": "https://domain.com/redirect_url_path?query=string",
    "tp_merchant_user_id": "unique_user_id_in_merchant_side",
    "tp_description": "Test order",
    "some_other_value": "6789012"
}
  1. Only filter the Key / value pairs that start with tp_xxx.
   "tp_amount": 1000,   
   "tp_currency": "PHP",
   "tp_merchant_order_id": "TEST_ORDER_ID_12345",
   "tp_redirect_url": "https://domain.com/redirect_url_path?query=string",
   "tp_merchant_user_id": "unique_user_id_in_merchant_side",
   "tp_description": "Test order",
  1. Sort the keys alphabetically.
   "tp_amount": 1000,   
   "tp_currency": "PHP",
   "tp_description": "Test order",
   "tp_merchant_order_id": "TEST_ORDER_ID_12345",
   "tp_merchant_user_id": "unique_user_id_in_merchant_side",
   "tp_redirect_url": "https://domain.com/redirect_url_path?query=string",
  1. Trim all unnecessary quotations and spaces, like the example below.
tp_amount1000tp_currencyPHPtp_descriptionTest order
tp_merchant_order_idTEST_ORDER_ID_12345tp_merchant_user_idunique_user_id_in_merchant_side
tp_redirect_urlhttps://domain.com/redirect_url_path?query=string
  1. Make a hash with SHA256 with the client_secret. The response should output a hexadecimal digit like below.
67d0a6d3fa13679039826e64ee7a76bf2e8185c3184407914c0f76d793b222df

PHP Example

$payload = [
    "tp_amount" => 1000,   
    "tp_currency"=> "PHP",
    "tp_merchant_order_id" => "TEST_ORDER_ID_12345",
    "tp_redirect_url" => "https://domain.com/redirect_url_path?query=string",
    "tp_merchant_user_id" => "unique_user_id_in_merchant_side",
    "tp_description" => "Test order",
    "some_other_value" => "6789012" 
];
$client_secret = '1234567890';

ksort($payload);
$message = array_reduce(array_keys($payload), static function ($p, $k) use ($payload) {
    return strpos($k, 'tp_') === 0 ? $p.$k.trim($payload[$k]) : $p;
}, '');
$hash = hash_hmac('sha256', $message, $client_secret);

Node Example

const crypto = require('crypto');

const payload = {
  "tp_amount": 1000,   
  "tp_currency": "PHP",
  "tp_merchant_order_id": "TEST_ORDER_ID_12345",
  "tp_redirect_url": "https://domain.com/redirect_url_path?query=string",
  "tp_merchant_user_id": "unique_user_id_in_merchant_side",
  "tp_description": "Test order",
  "some_other_value": "6789012"
}
const client_secret = '1234567890'


const message = Object.keys(payload)
.sort()
.filter(p => p.indexOf('tp_') === 0)
.reduce((p, k) => (`${p}${k}${String(payload[k]).trim()}`), '')
var hash = crypto.createHmac('sha256', client_secret).update(message).digest('hex');

Go Example

package main

import (
    "fmt"
    "bytes"
    "strings"
    "sort"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
)

func main() {
    payload := map[string]string{
        "tp_amount": "1000",   
        "tp_currency": "PHP",
        "tp_merchant_order_id": "TEST_ORDER_ID_12345",
        "tp_redirect_url": "https://domain.com/redirect_url_path?query=string",
        "tp_merchant_user_id": "unique_user_id_in_merchant_side",
        "tp_description": "Test order",
        "some_other_value": "6789012",
    }
    client_secret := []byte("1234567890")

    // Make message
    var message bytes.Buffer
    
    keys := make([]string, 0, len(payload))
        for k := range payload {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    
    for _, k := range keys {
    
        if strings.Index(k, "tp_") != 0 {
            continue
        }
        message.WriteString(k)
        message.WriteString(strings.Trim(payload[k], " "))
    }
    
    // Make hash
    hashHmac := hmac.New(sha256.New, client_secret)
    hashHmac.Write(message.Bytes())
    hash := hex.EncodeToString(hashHmac.Sum(nil))
    
    fmt.Println(hash) 
}

Java Example

import java.util.HashMap; 
import java.util.TreeMap; 
import java.util.stream.Collectors;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;

class Main {
  public static void main(String[] args) {
    // Example
    String clientSecret = "1234567890";
    HashMap<String, String> payload = new HashMap<String, String>();
    payload.put("tp_amount", "1000");
    payload.put("tp_currency", "PHP");
    payload.put("tp_merchant_order_id", "TEST_ORDER_ID_12345");
    payload.put("tp_redirect_url", "https://domain.com/redirect_url_path?query=string");
    payload.put("tp_merchant_user_id", "unique_user_id_in_merchant_side");
    payload.put("tp_description", "Test order");
    payload.put("some_other_value", "6789012");

    // Make message
    TreeMap<String, String> sortedPayload = new TreeMap<>(payload);

    String message = sortedPayload.entrySet().stream()
          .filter(map -> map.getKey().indexOf("tp_") == 0)
          .map(map -> map.getKey() + map.getValue().trim())
          .collect(Collectors.joining());

    // Make hash
    byte[] hmacSha256 = null;
    String hash = null;
    try {
      Mac mac = Mac.getInstance("HmacSHA256");
      SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes("UTF-8"), "HmacSHA256");
      mac.init(secretKeySpec);
      hmacSha256 = mac.doFinal(message.getBytes("UTF-8"));
      hash = String.format("Hex: %032x", new BigInteger(1, hmacSha256)); 
    } 
    catch (Exception e) {}

    System.out.println(message);
    System.out.println(hash);
  }
}