想和应用宝sig签名谈谈

首先,一般签名的英文是sign,然而应用宝的是sig,跟应用宝SDK接触久了你会发现应用宝自成一家的地方非常非常多,OK,既然不能改变别人,那我们就只能自己认了。

应用宝的文档非常多,重要的是还非常乱,很多相关联的文档并没有链接指向,全靠自己领悟。

直接上代码,以后然后还要接应用宝什么的,免得重复踩坑。

// 准备请求参数
long time = System.currentTimeMillis()/1000;
String uri = null;
String noHostUri = "/mpay/get_balance_m";
if ("test".equals(env)) {
	// 沙箱环境
	uri = ConstantUtil.GET_BALANCE_TEST;
} else {
	// 正式环境
	uri = ConstantUtil.GET_BALANCE_RELEASE;
}
// 加密源串
StringBuilder signSource = new StringBuilder();
signSource.append("GET&");
signSource.append(URLEncoder.encode(noHostUri)+"&");
signSource.append(URLEncoder.encode("appid="+appid+"&format="+"json"+"&openid="+openId+"&openkey="+openKey+"&pay_token="+pay_token
		+"&pf="+pf+"&pfkey="+pfKey+"&ts="+time+"&zoneid="+1));
BaseUtil.logDebugMsg(ConstantUtil.MSDK_TAG, "加密源串=" + signSource.toString());
HashMap params = new HashMap();
params.put("appid", appid);
params.put("format", "json");
params.put("openid", openId);
params.put("openkey", openKey);
params.put("pay_token", pay_token);
params.put("pf", pf);
params.put("pfkey", pfKey);
params.put("ts", time+"");
params.put("zoneid", "1");

String payKey = msdkKey + "&";
String sign = null;
try {
	sign = SnsSigCheck.makeSig("get", noHostUri, params, payKey);
} catch (OpensnsException e1) {
	Toast.makeText(LeitingActivityUtils.getActivity(), "支付签名异常!", Toast.LENGTH_LONG).show();
	e1.printStackTrace();
}
BaseUtil.logDebugMsg(ConstantUtil.MSDK_TAG, "Base64后sign=" + sign);

// 	请求参数
StringBuilder url = new StringBuilder();
url.append(uri);
url.append("?appid="+appid);
url.append("&format=json");
url.append("&openid="+openId);
url.append("&openkey="+openKey);
url.append("&pay_token="+pay_token);
url.append("&pf="+pf);
url.append("&pfkey="+pfKey);
url.append("&ts="+time);
url.append("&zoneid="+1);
url.append("&sig="+SnsSigCheck.encodeValue(sign));
BaseUtil.logDebugMsg(ConstantUtil.MSDK_TAG, "查询余额接口请求参数="+url.toString());
// 向雷霆服务器发送请求
HttpReturnBean returnBean = HttpUtil.httpGet(url.toString(),sessionId,sessionType);
Message msg = mHandler.obtainMessage();
if (returnBean == null || !"0".equals(returnBean.getStatus())) {
	// 查询余额失败
	Toast.makeText(LeitingActivityUtils.getActivity(), "查询余额失败!", Toast.LENGTH_LONG).show();
	// 直接拉起支付界面
	msg.what = ConstantUtil.MSG_PAY;
	mHandler.sendMessage(msg);
	return;
}
JSONObject obj = null;
try {
	obj = new JSONObject(returnBean.getMessage());
} catch (JSONException e) {
	e.printStackTrace();
}
int balance = obj.optInt("balance");

// 验证成功
Bundle data = new Bundle();
data.putInt("balance", balance);
msg.what = ConstantUtil.MSG_GET_BALANCE_SUCCESS;
msg.setData(data);
mHandler.sendMessage(msg);

这里最坑的一个地方是HMAC-SHA1加密算法+Base64生成签名(应用宝说编程语言自带HMAC-SHA1),一开始到处上网找开源代码,然而每次签名验证都失败,并不知道是不是签名代码的问题,然后偶然在一篇百度的文章中发现说应用宝有提供签名的工具类(既然有为什么不早说嘛),根据不同的语言自行下载:http://wiki.open.qq.com/wiki/SDK%E4%B8%8B%E8%BD%BD。

注意:拼接URL请求时顺序也要注意一下。

为了以防万一,我还是把工具类的代码也贴上来。

package com.leiting.sdk.channel.msdk.util;


// urlencode
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.io.UnsupportedEncodingException;
import java.net.*;

// hmacsha1 
import java.security.MessageDigest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

// base64
import biz.source_code.base64Coder.Base64Coder;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

 /**
 * 生成签名类
 *
 * @version 3.0.1
 * @since jdk1.5
 * @author open.qq.com
 * @copyright © 2012, Tencent Corporation. All rights reserved.
 * @History:
 *               3.0.1 | 2012-08-28 17:34:12 | support cpay callback sig verifictaion.
 *               3.0.0 | nemozhang | 2012-03-21 12:01:05 | initialization
 *
 */
 
public class SnsSigCheck 
{

    /** 
     * URL编码 (符合FRC1738规范)
     *
     * @param input 待编码的字符串
     * @return 编码后的字符串
     * @throws OpensnsException 不支持指定编码时抛出异常。
     */
    public static String encodeUrl(String input) throws OpensnsException
    {
        try
        {
            return URLEncoder.encode(input, CONTENT_CHARSET).replace("+", "%20").replace("*", "%2A");
        }
        catch(UnsupportedEncodingException e)
        {
            throw new OpensnsException(ErrorCode.MAKE_SIGNATURE_ERROR, e);
        }
    }

    /* 生成签名
     *
     * @param method HTTP请求方法 "get" / "post"
     * @param url_path CGI名字, eg: /v3/user/get_info
     * @param params URL请求参数
     * @param secret 密钥
     * @return 签名值
     * @throws OpensnsException 不支持指定编码以及不支持指定的加密方法时抛出异常。
     */
    public static String makeSig(String method, String url_path, HashMap params, String secret) throws OpensnsException
    {
        String sig = null;
        try
        {
            Mac mac = Mac.getInstance(HMAC_ALGORITHM);

            SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(CONTENT_CHARSET), mac.getAlgorithm());

            mac.init(secretKey);

            String mk = makeSource(method, url_path, params);
            
            byte[] hash = mac.doFinal(mk.getBytes(CONTENT_CHARSET));
    
            // base64
            sig = new String(Base64Coder.encode(hash));
        }
        catch(NoSuchAlgorithmException e)
        {
            throw new OpensnsException(ErrorCode.MAKE_SIGNATURE_ERROR, e);
        }
        catch(UnsupportedEncodingException e)
        {
            throw new OpensnsException(ErrorCode.MAKE_SIGNATURE_ERROR, e);
        }
        catch(InvalidKeyException e)
        {
            throw new OpensnsException(ErrorCode.MAKE_SIGNATURE_ERROR, e);
        }
        return sig;
    }

    /* 生成签名所需源串
     *
     * @param method HTTP请求方法 "get" / "post"
     * @param url_path CGI名字, eg: /v3/user/get_info
     * @param params URL请求参数
     * @return 签名所需源串
     */
    public static String makeSource(String method, String url_path, HashMap params) throws OpensnsException
    {
        Object[] keys = params.keySet().toArray();

        Arrays.sort(keys);  

        StringBuilder buffer = new StringBuilder(128);

        buffer.append(method.toUpperCase()).append("&").append(encodeUrl(url_path)).append("&");

        StringBuilder buffer2= new StringBuilder();

        for(int i=0; i params, String secret, String sig) throws OpensnsException
    {
        // 确保不含sig
        params.remove("sig");

        // 按照发货回调接口的编码规则对value编码
        codePayValue(params);

        // 计算签名
        String sig_new = makeSig(method, url_path, params, secret);

        // 对比和腾讯返回的签名
        return sig_new.equals(sig);
    }

    /**
     * 应用发货URL接口对腾讯回调传来的参数value值先进行一次编码方法,用于验签 
     * (编码规则为:除了 0~9 a~z A~Z !*() 之外其他字符按其ASCII码的十六进制加%进行表示,例如“-”编码为“%2D”)
     * 参考 <回调发货URL的协议说明_V3>
     * 
     * @param params
     *            腾讯回调传参Map (key,value);
     */
    public static void codePayValue(Map params) 
    {
        Set keySet = params.keySet();
        Iterator itr = keySet.iterator();

        while (itr.hasNext()) 
        {
            String key = (String) itr.next();
            String value = (String) params.get(key);
            value = encodeValue(value);
            params.put(key, value);
        }
    }

    /**
     * 应用发货URL接口的编码规则
     * @param s
     * @return
     */
    public static String encodeValue(String s) 
    {
        String rexp = "[0-9a-zA-Z!*\\(\\)]";
        StringBuffer sb = new StringBuffer(s);
        StringBuffer sbRtn = new StringBuffer();
        Pattern p = Pattern.compile(rexp);
        char temp;
        String tempStr;

        for (int i = 0; i < sb.length(); i++) 
        {
            temp = sb.charAt(i);
            tempStr = String.valueOf(temp);
            Matcher m = p.matcher(tempStr);

            boolean result = m.find();
            if (!result) {
                tempStr = hexString(tempStr);
            }
            sbRtn.append(tempStr);
        }

        return sbRtn.toString();
    }

    /**
     * 应用发货URL 十六进制编码 
     * @param s
     * @return
     */
    private static String hexString(String s) 
    {
        byte[]b = s.getBytes();
        String retStr = "";
        for (int i = 0; i < b.length; i++) 
        {
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            retStr = "%"+hex.toUpperCase();
        }
        return retStr;
    }
    
    // 编码方式
    private static final String CONTENT_CHARSET = "UTF-8";
   
    // HMAC算法
    private static final String HMAC_ALGORITHM = "HmacSHA1";
}


其他注意:

1、在沙箱支付时,提示“系统繁忙,请稍后再试 1001-1007-0”。

解决方案:openkey 客户端 手Q登录传pay_token,微信登录传accesstoken,也就是客户端和服务端的openkey是不一样的。还有就是区组Id要传对。

2、QQ登录后支付提示:系统繁忙,微信登录后提示:openkey不能为空。

根据上面规则检查openkey发现没有问题,最后竟然是zoneId错误,一定要传应用宝后台配置的zoneId,不能就会出现乱七八糟的错误。

3、QQ登录时支付可以,微信登录支付不行,返回错误编码1016

参考文档:http://wiki.mg.open.qq.com/index.php?title=%E9%94%99%E8%AF%AF%E7%A0%81%E6%9F%A5%E8%AF%A2#.E9.94.99.E8.AF.AF.E7.A0.81.EF.BC.9A1018

服务端接口appid使用错误;特别注意的是微信支付后台接口使用的也是手Qappid。



你可能感兴趣的:(android学习,android,应用宝,签名)