Signing

For security purposes, we require ALL GrubPay API requests to be signed.

This sign should be included as part of each API request.

Algorithm

Example: var ex_body= { "c":"cat", "a":"apple", "b":"boat", "d":""};

Step 1:

Remove all keys with empty values in copied body object

ex_body= { "c":"cat", "a":"apple", "b":"boat"};

Step 2:

Sort resulting body by key in ascending lexicographic order

ex_body= {"a":"apple" "b":"boat", "c":"cat"};

Step 3:

Concat resulting body into new string in URL parameter format "key1=value1&key2=value2"

mystr="a=apple&b=boat&c=cat"

Step 4:

Append your merchant key (assigned to you by GrubPay during onboarding) "&key={your_merchant_key}" to the end of mystr

mystr+="&key={your_merchant_key}"

Step 5:

MD5 hash mystr, then convert hash result into all UPPERCASE. This is your sign

sign = toUpper(md5(mystr));

Step 6:

Attach sign value to original request body

ex_body.sign = sign; // you can now send the signed request

// ex_body before sending request
{a:"apple" b:"boat", c:"cat", sign: "DAC619FA1BC9526EBDA688A9DC842B7A"};

Step 7 (Conditional):

You may need to perform an addition step if you meet ALL of the following conditions:

  • You have completed steps 1-6 and are still getting sign validation error

  • subject is present in request and is non-empty

  • subject contains non UTF8 encoded characters

    If all of the following apply, you should url encode the contents of subject and overwrite the original value; as below:

    ex_body.subject = url_encode(ex_body.subject);

Step 8 (Conditional):

Repeat step 7 for body field

Demo Codes

/** sign the string
	*$prestr string to be signed
	*return
 */
function md5Hash($prestr,$sign_type)
{
    $sign='';
    if($sign_type == 'MD5')
	{
        $sign = strtoupper(md5($prestr));//to upper case
    }
	else
	{
        die("only support MD5 for now".$sign_type);
    }
    return $sign;
}
/**
    *convert array to “key=value&key2=value2...” string
	*$array
	*return
*/
function create_linkstring($array)
{
    $arg  = "";
    while (list ($key, $val) = each ($array))
	{
       if($val!=''){
          $arg.=$key."=".$val."&";
		}
    }
    $arg = substr($arg,0,count($arg)-2);  //remove last '&'
    return $arg;
}
function build_mysign($sort_array,$key,$sign_type = "MD5")
{
    $prestr = create_linkstring($sort_array);
    $prestr = $prestr."&key=".$key;

    $mysgin = md5Hash($prestr,$sign_type);
    return $mysgin;
}

function arg_sort($array)
{
    ksort($array,SORT_NATURAL | SORT_FLAG_CASE);
    reset($array);
    return $array;
}

$arr = array(
    'mchId'=>$merchant_id,
    'mchOrderNo'=>$order_sn,
    'extra'=> $sceneInfo,
    'channelId'=>$channelId,
    'currency'=>'USD',
    'amount'=>intval($order_amount*100),
    'clientIp'=>$ip,
    'device'=>'WEB',
    'notifyUrl'=>$notifyUrl,
    'subject'=>$subject,
    'body'=>$body,
  );

$sort_array = arg_sort($arr);
$arr['sign']= build_mysign($sort_array,$merchant_key,"MD5");//generate signature
$arr['subject'] = urlencode($arr['subject'])  // urlencode after computing sign
$arr['body'] = urlencode($arr['body'])   // urlencode after computing sign
$param = json_encode($arr);

$resBody = request($url,$param);//submit request

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;


import com.alibaba.fastjson.JSON;

public class TestSign {
	private static String encodingCharset = "UTF-8";
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("mchId", "10000XXX");
		map.put("mchOrderNo", "6199200000006");
		map.put("channel", "CC_CARD");
		map.put("currency", "USD");
		map.put("amount", "1");
		map.put("clientIp", "127.0.0.1");
		map.put("notifyUrl", "http://www.xxxx.ca/getnotify");
		map.put("subject", "test");
		map.put("body", "test body");
		String merchantKey = "xxxxxxxxxxxxxxxxxxxx";
		String sign = getSign(map, merchantKey);
		System.out.println("sign is [" + sign + "]");
		//sign is [E7F9184356482199D44C9FC20704B798]
		map.put("sign", sign);
		try {
			String strSubjectEncode = URLEncoder.encode(map.get("subject")+"", StandardCharsets.UTF_8.toString());
			map.put("subject", strSubjectEncode);
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			String strBodyEncode = URLEncoder.encode(map.get("body")+"", StandardCharsets.UTF_8.toString());
			map.put("body", strBodyEncode);
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		String strParam = JSON.toJSONString(map);

		String strOutputParams = strParam;
		String url = "https://api.grubpay.io/v1/create_order";
		String result = Http(url, strOutputParams);

	}
	public static String Http(String url, String param) {
		String result = "";
		//please add your http request here.
		return result;
	}
	public static String toHex(byte input[]) {
		if (input == null)
			return null;
		StringBuffer output = new StringBuffer(input.length * 2);
		for (int i = 0; i < input.length; i++) {
			int current = input[i] & 0xff;
			if (current < 16)
				output.append("0");
			output.append(Integer.toString(current, 16));
		}

		return output.toString();
	}
	public static String md5(String value, String charset) {
		MessageDigest md = null;
		try {
			byte[] data = value.getBytes(charset);
			md = MessageDigest.getInstance("MD5");
			byte[] digestData = md.digest(data);
			return toHex(digestData);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return null;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null;
		}
	}
	public static String getSign(Map<String,Object> map, String key){
		ArrayList<String> list = new ArrayList<String>();
		for(Map.Entry<String,Object> entry:map.entrySet()){
			if(!"".equals(entry.getValue())){
				list.add(entry.getKey() + "=" + entry.getValue() + "&");
			}
		}
		int size = list.size();
		String [] arrayToSort = list.toArray(new String[size]);
		Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < size; i ++) {
			sb.append(arrayToSort[i]);
		}
		String result = sb.toString();
		result += "key=" + key;
		result = md5(result, encodingCharset).toUpperCase();
		return result;
	}
}

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace demo_sign
{
    class Program
    {
        //get the merchantKey from GrubPay
        static string merchantKey = "xxxxxxxxxxxxxxxxxxx";

        public static string GetMD5Hash(string str)
        {
            StringBuilder sb = new StringBuilder();
            using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
            {
                byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
                int length = data.Length;
                for (int i = 0; i < length; i++)
                    sb.Append(data[i].ToString("X2"));
            }
            return sb.ToString();
        }

        static string create_linkstring(SortedDictionary<string, string> dict)
        {
            string arg = "";
            foreach (KeyValuePair<string, string> kv in dict)
            {
                if (kv.Value != "")
                {
                    arg += kv.Key + "=" + kv.Value + "&";
                }

            }
            arg = arg.TrimEnd('&');
            return arg;

        }

        static void Main(string[] args)
        {
            //add all parameters into sorted dict, order by key name in lexicographic order
            SortedDictionary<string, string> para_array = new SortedDictionary<string, string>();
            para_array.Add("mchId", "10000XXX");
            para_array.Add("mchOrderNo", "6199200000006");
            para_array.Add("channel", "CC_CARD");
            para_array.Add("currency", "USD");
            para_array.Add("amount", "1");
            para_array.Add("clientIp", "127.0.0.1");
            para_array.Add("notifyUrl", "http://www.xxxx.ca/getnotify");
            para_array.Add("subject", "test");
            para_array.Add("body", "test body");

            Console.WriteLine(para_array.Count);
            //convert the dict to url string
            string link_str = create_linkstring(para_array);
            //append the merchant key as the last parameter
            link_str += ("&key="+merchantKey);
            Console.WriteLine(link_str);
            //get md5 sign value
            string sign = GetMD5Hash(link_str);
            Console.WriteLine(sign);

        }
    }
}
// Param1 arr: request body, object
// Param2: merchant key, string
// Return: request body with sign attached
const getSignedBody = (arr, merchantKey) => {
  // step 1 + 2:  Remove empty keys; sort request body by key in ascending lexical order
  const sorted = Object.keys(arr)
    .sort()
    .reduce((accumulator, key) => {
      if (arr[key] != '') {
        accumulator[key] = arr[key];
        return accumulator;
      } else {
        // skip empty fields
        return accumulator;
      }
    }, {});
  arr = sorted;
  // step 3: Concat resulting body into new string in URL parameter format
  var queryString = Object.keys(arr)
    .map((key) => key + '=' + arr[key])
    .join('&');

  // step 4: Append merchant key to the end of queryString
  queryString += `&key=${merchantKey}`;

  // step 5: md5 queryString and uppercase hashed string
  let sign = md5(queryString).toUpperCase();

  // step 6: Attach sign value to original request body
  arr.sign = sign;

  // step 7 + 8 (conditional): refer to guide for details
  // if ("body" in arr) {
  //   arr.body = encodeURIComponent(arr.body);
  // }
  // if ("subject" in arr) {
  //   arr.subject = encodeURIComponent(arr.subject);
  // }
  return arr;
};

Online Sign Validator

Visit our Online Sign Validatoropen in new window

Last Updated:
Contributors: grant, Edward, Grant, Howard, Edward Yuan, Grant Yao, H