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"};
Sign Validator to confirm your sign result is correct
Recommended: UseStep 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-emptysubject
contains non UTF8 encoded charactersIf 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 Validator