Authentication

Guide

  • Alchemy Pay will issue a pair of merchantCode&privatekey for each partner once onboarded with Alchemy Pay. The merchantCode is used to identify the partner, while privatekey is for signature purposes.
  • merchantCode&privatekey will be delivered to the partner upon request from Alchemy Pay's operation team. There is currently no online service for self-apply.
  • Under no circumstances expose the privatekey in any API request.

Signature

Mutual authentication is implemented. For requests, the caller needs to compute a digital signature and add the signature as part of the HTTP body. Conversely, for responses, Alchemy Pay 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="wettvfele+=@#";

        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();
    }

}