刚刚参加实习,很多东西都不懂,微信支付是最近才接触到的,开始去看官方文档的时候,一大堆文字东西说明看的有点蒙圈,好像很难的样子,但是随着慢慢的熟悉,微信支付其实也就那样(事实上我却做了两天,渣渣...),然后这个做了之后我觉得有必要把自己遇到的坑给记一下,再次做到微信支付的开发任务的时候便能够游刃有余了。
微信支付交互过程(这里我照抄了官方文档,原文在这里):
步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。
步骤2:商户后台收到用户支付单,调用微信支付统一下单接口。参见【统一下单API】。
步骤3:统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式为Sign=WXPay
步骤4:商户APP调起微信支付。api参见本章节【app端开发步骤说明】
步骤5:商户后台接收支付通知。api参见【支付结果通知API】
步骤6:商户后台查询支付结果。,api参见【查询订单API】
我踩的大坑
第一坑:调起微信支付接口没有反应,但是boolean result = api.sendReq(req);却返回了true
原因:在AndroidManifest.xml文件中没有注册WXPayEntryActivity,因此我们只要在将其在AndroidManifest.xml文件进行注册就可以了,如下示例
<activity android:name=".wxapi.WXPayEntryActivity" android:exported="true" android:launchMode="singleTop"/>
第二坑:从服务器后台成功获取了预支付单号prepayid之后,成功调起微信支付接口之后,resp.errCode 的值总是为 -1。
查了微信支付的开发文档之后,返回-1的原因可能是:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等。对于这样的说法真的是很懵逼,完全摸不着头脑,不过经过反复的才坑,我总结出了以下两个主要原因(网上有人说是微信缓存啊,appid阿或者要重新设置商户的key这些的,我试过了,不是........):
原因1:调起微信支付的参数签名sign不正确,对于这个很多人是没有仔细的看微信支付的开发文档的,像我就是其中的一个(瞎了),在开发文档中调起微信支付接口的关键代码是,在这里需要注意的有packageValue和timeStamp,packageValue的值只能填"Sign=WXPay",时间戳的单位是秒(固定10数),在Android中一般获取的系统当前时间是以毫秒为单位的,所以我们需要做下单位换算。
IWXAPI api;
PayReq request = new PayReq();
request.appId = "wxd930ea5d5a258f4f";
request.partnerId = "1900000109";
request.prepayId= "1101000000140415649af9fc314aa427",;
request.packageValue = "Sign=WXPay";
request.nonceStr= "1101000000140429eb40476f8896f4c9";
request.timeStamp= "1398746574";
request.sign= "7FFECB600D7157C5AA49810D2D8F28BC2811827B";
api.sendReq(req);
关于sign这个是什么呢?不清楚的看这里(戳戳戳),然后sign生成字段名列表是?戳(反正我是掉这坑了......),哦,对了,还有一个,怎么验证我们生成的sign是否正确的呢,其实很简单,微信已经给我们提供了一个在线验证sign是否正确的校验工具,啦在这里(校验工具),就是下面这张图
(1)生成sign方法
/**
* 生成签名
*
* @param characterEncoding
* @param parameters
* @return
*/
private static String createSign(String characterEncoding, SortedMap<Object, Object> parameters) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + ConstantOther.KEY);//这个key是商户key
String sign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
return sign;
}
MD5签名(这个在微信的官方demo中有):
package com.sans.taxigo.utils;
import java.security.MessageDigest;
public class MD5 {
private MD5() {}
/**
* 微信自带的MD5加密算法
* @param buffer
* @return
*/
public final static String getMessageDigest(byte[] buffer) {
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
try {
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(buffer);
byte[] md = mdTemp.digest();
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
}
(2)调起微信支付接口
String prepayId = response.getString("prepayId");
IWXAPI api = WXAPIFactory.createWXAPI(context, null);
PayReq req = new PayReq();
req.appId = ConstantOther.APP_ID;
req.partnerId = ConstantOther.MCH_ID;
req.prepayId = prepayId;
req.nonceStr = getNonceStr();
req.timeStamp = getTimeStamp();
req.packageValue = ConstantOther.PACKAGE_VALUE;
SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
parameters.put("appid", req.appId);
parameters.put("partnerid", req.partnerId);
parameters.put("noncestr", req.nonceStr);
parameters.put("prepayid", req.prepayId);
parameters.put("package", req.packageValue);
parameters.put("timestamp", req.timeStamp);
//生成签名
req.sign = createSign("UTF-8", parameters);//签名;
// 将该app注册到微信
api.registerApp(ConstantOther.APP_ID);
boolean result = api.sendReq(req);
原因2:应用的包签名不正确。这个坑踩的有点大,一开始在做微信支付的时候并没有发现,等到排查了其他的原因之后试着去验证包签名的正确性,结果问题真的就出在了这里,我们包的签名是在测试的时候和正式发布的时候是不一样的,而我一开始在测试的时候使用的包签名是正式发布后的包签名,这样在测试的时候自然不能够成功的调起微信支付接口,不知道包签名生成就看这里(这里),微信支付开发文档里写的很清楚,在调试的手机上安装一个签名工具后运行输入包名便可生成调试的包签名。生成的签名是填在下图的(图来自微信支付开发文档):
其实,对于我踩到的坑完全是可以避免的,由于我本身的经验不足以及没有很仔细的阅读官方文档才会导致我不断往坑里去,所以呢,抱着认真负责的态度去做事是可以少走很多弯路的(看文档要仔细、看文档要仔细、看文档要仔细)
下面是我的在实际项目中用到的代码,虽然代码不多,但其中肯定是有很多不足的,在这里我发出来给像我这样的新手借鉴一下,也希望有人可以指出其中的不足,好让我改正,谢谢........
package com.sans.taxigo.utils;
/**
* Created by chenjianrun on 2017/1/21.
*/
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.text.format.Formatter;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestParams;
import com.sans.taxigo.common.ConstantHttp;
import com.sans.taxigo.common.ConstantOther;
import com.sans.taxigo.http.MyHttpClient;
import com.tencent.mm.sdk.modelpay.PayReq;
import com.tencent.mm.sdk.openapi.IWXAPI;
import com.tencent.mm.sdk.openapi.WXAPIFactory;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import cz.msebera.android.httpclient.Header;
public class WeiXinPayUtil {
public static void sendToWinXinPay(Context context) {
// makePost(context);
askServerToPay(context,0.01f);
}
/**
* 向服务器后台发起下单请求成功后调起微信支付接口
* @param context
* @param totalFee
*/
private static void askServerToPay(final Context context, float totalFee){
String time = String.valueOf(System.currentTimeMillis());
RequestParams params = new RequestParams();
params.put("spbill_create_ip",getLocalIPAddress(context));
params.put("total_fee",totalFee);
params.put("userId", SharePreferenceHelper.getInt("ID",-1));
params.put("apiSendTime",time);
params.put("apiToken", ValidateAPITokenUtil.ctreatTokenStringByTimeString(time));
MyHttpClient.postNew(ConstantHttp.WEIXIN_URL, params, new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
try {
String tem= new String(responseBody);
JSONObject response = new JSONObject(tem);
String prepayId = response.getString("prepayId");
IWXAPI api = WXAPIFactory.createWXAPI(context, null);
PayReq req = new PayReq();
req.appId = ConstantOther.APP_ID;
req.partnerId = ConstantOther.MCH_ID;
req.prepayId = prepayId;
req.nonceStr = getNonceStr();
req.timeStamp = getTimeStamp();
req.packageValue = ConstantOther.PACKAGE_VALUE;
SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
parameters.put("appid", req.appId);
parameters.put("partnerid", req.partnerId);
parameters.put("noncestr", req.nonceStr);
parameters.put("prepayid", req.prepayId);
parameters.put("package", req.packageValue);
parameters.put("timestamp", req.timeStamp);
//生成签名
req.sign = createSign("UTF-8", parameters);//签名;
// 将该app注册到微信
api.registerApp(ConstantOther.APP_ID);
boolean result = api.sendReq(req);
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
ShowToast.Long("test");
}
@Override
public void onFinish() {
super.onFinish();
}
@Override
public void onUserException(Throwable error) {
super.onUserException(error);
}
});
}
/**
* 生成随机字符串
*
* @return
*/
private static String getNonceStr() {
int length = 16;
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
int len = base.length();
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(len);
sb.append(base.charAt(number));
}
return sb.toString();
}
/**
* 生成时间戳字符串
* 标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数
*
* @return
*/
private static String getTimeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000L);
}
/**
* 获取本地ip地址
*
* @param context
* @return
*/
private static String getLocalIPAddress(Context context) {
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String ipAddress = Formatter.formatIpAddress(wifiInfo.getIpAddress());
//String ipAddress = FormatIP(wifiInfo.getIpAddress());
return ipAddress;
}
/**
* 生成签名
*
* @param characterEncoding
* @param parameters
* @return
*/
private static String createSign(String characterEncoding, SortedMap<Object, Object> parameters) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + ConstantOther.KEY);
String sign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
//String sign = MD5.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
}