Authentication

Guide

  • Alchemy Pay will issue a pair of merchantCode&privatekey for each partner once a partner has been on-boarded with Alchemy Pay. merchantCode is used to identify partner, Key is for sign purpose.
  • merchantCode&privatekey will be delivered to partner upon request from Alchemy Pay’s operation. There is currently no online service for self-apply.
  • In any circumstances, never expose privatekey in any API request.

Signature

Mutual authentication is implemented. For requests, the caller need to compute a digital signature and add the signature as part of HTTP body. Conversely, for responses, ACH provides its signature in the HTTP body in the response. The request signature is generated as follows:

StepDescriptionExample
1Sort all parameters in ascending order according to parameter namesParameter list: abc=value1 bcd=value2 bad=value3
Sort result: abc=value1 bad=value3 bcd=value2
2Connect all parameters with '&' with stringAabc=value1&bad=value3&bcd=value2
3Combine the stringA with ‘&’with key=privatekey to stringB. key is case sensitive must be with low caseabc=value1&bad=value3&bcd=value&key=privatekey.
4MD5 the stingB with upper caseMD5(stringB).toUpperCase();
5Save the signature in HTTP body

Sign demo

package com.achpay.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

public class Utils {

    // private static final Logger logger = LoggerFactory.getLogger(Utils.class);

    public static void main(String[] args) {

        String key="[email protected]#";

        Map map =new HashMap();
        map.put("merchantCode","ME1657708812740");
        map.put("unionId","9746630509196001");
        map.put("crypto","24353");


        System.out.println("========== sign =================: "+getSign(map,key));

    }


    /**
     * sign=MD5(accountAttr=accountAttr&accountName=accountName&accountNo=accountNo&amount=amount&bankName=bankName&bankNumber=bankNumber&city=city&mchId=mchId&mchOrderNo=mchOrderNo¬ifyUrl=notifyUrl&province=province&remark=remark&reqTime=reqTime&key=key).toUpperCase()
     * @param params
     * @param privatekey
     * @return
     */
    public static String getSign(Map<String, Object> params, String privatekey) {

        MapRemoveNullUtil.removeNullEntry(params);

        Map<String, Object> map = new TreeMap<>();
        map.putAll(params);
        //以key1 = value1&key2 = value2
        StringBuilder stringBuilder = new StringBuilder();
        for (Map.Entry<String, Object> s : map.entrySet()) {
            String key = s.getKey();
            Object value = s.getValue();
            stringBuilder.append(key).append("=").append(value).append("&");
        }
        stringBuilder.append("key="+privatekey);

        // logger.info("getSign{}", stringBuilder.toString());
        String sign = MD5Util.getMd5(stringBuilder.toString().getBytes()).toUpperCase();
        // logger.info("getSign-stringBuilder-sign为: {}, {}",stringBuilder, sign);
        return sign;
    }




    /**
     * @param obj
     * @return
     * @throws Exception
     */
    public static Map<String, Object> objectToMap(Object obj) throws Exception {
        if(obj == null)
            return null;

        Map<String, Object> map = new HashMap<String, Object>();

        BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor property : propertyDescriptors) {
            String key = property.getName();
            if (key.compareToIgnoreCase("class") == 0) {
                continue;
            }
            Method getter = property.getReadMethod();
            Object value = getter!=null ? getter.invoke(obj) : null;
            map.put(key, value);
        }

        return map;
    }



    /**
     * @param map
     */
    public static void removeNullEntry(Map map){
        removeNullKey(map);
        removeNullValue(map);
    }

    /**
     * @param map
     * @return
     */
    public static void removeNullKey(Map map){
        Set set = map.keySet();
        for (Iterator iterator = set.iterator(); iterator.hasNext();) {
            Object obj = (Object) iterator.next();
            remove(obj, iterator);
        }
    }


    /**
     * @param map
     * @return
     */
    public static void removeNullValue(Map map){
        Set set = map.keySet();
        for (Iterator iterator = set.iterator(); iterator.hasNext();) {
            Object obj = (Object) iterator.next();
            Object value =(Object)map.get(obj);
            remove(value, iterator);
        }
    }

    /**
     * @param obj
     * @param iterator
     */
    private static void remove(Object obj,Iterator iterator){

        if(obj instanceof String){
            String str = (String)obj;
            if(isEmpty(str.trim())){ //output map:{2=BB, 1=AA, 5=CC}
                iterator.remove();
            }
        }else if(obj instanceof Collection){
            Collection col = (Collection)obj;
            if(col==null||col.isEmpty()){
                iterator.remove();
            }
        }else if(obj instanceof Map){
            Map temp = (Map)obj;
            if(temp==null||temp.isEmpty()){
                iterator.remove();
            }
        }else if(obj instanceof Object[]){
            Object[] array =(Object[])obj;
            if(array==null||array.length<=0){
                iterator.remove();
            }
        }else{
            if(obj==null){
                iterator.remove();
            }
        }
    }
    public static boolean isEmpty(Object obj){
        return obj == null || obj.toString().length() == 0  || obj.equals("null");
    }


    /**
     */
    public static String getMd5(byte[] bytes) {
        byte abyte0[];
        MessageDigest messagedigest;
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nosuchalgorithmexception) {
            throw new IllegalArgumentException("no md5 support");
        }
        messagedigest.update(bytes);
        abyte0 = messagedigest.digest();
        return byte2hex(abyte0);

    }

    /**
     */
    public static String byte2hex(byte bytes[]) {
        StringBuffer stringBuffer = new StringBuffer(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            if ((bytes[i] & 0xff) < 16) {
                stringBuffer.append("0");
            }
            stringBuffer.append(Long.toString((long) bytes[i] & (long) 255, 16));
        }

        return stringBuffer.toString().toUpperCase();
    }

}