直接上代码,以后然后还要接应用宝什么的,免得重复踩坑。
// 准备请求参数
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。