(一)调用前准备
申请微信开发者账号,添加应用及申请开通微信支付功能,如
查看开通流程注意:
在申请支付功能时会开通一个微信商户号,请注意是app的商户号,不是公众号的商户号,审核成功后会发到你邮箱里,微信支付商户号(不是商户平台登录号)后面会用到,另外,申请开通微信支付功能需要300元/年(土豪请忽略)。
添加的应用填写的包名根据manifests的package上写;
(二)开发步骤:
1 、SDK下载
①方法:
Android Studio环境下:
在build.gradle文件中,添加如下依赖即可:
dependencies {
compile 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+'
}
或
dependencies {
compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
}
(其中,前者包含统计功能)
②方法:
依赖官网里的微信Demo里的libs的SDK文件,
注意:此方法可能会有出现警告并且无法在模拟器运行的情况 (sdk太老,不兼容Android,或者前面你已经导入的第三方Jar包和微信支付jar冲突)
此时可以尝试使用真机运行;不过,最好的办法就是找到最新版的微信demo里的SDK(如:微信开发者平台-->资源中心-->移动应用-->android 开发文档-->SDK下载 )
因为Demo文件的sdk更新不太及时,最好还是使用build.gradle文件添加依赖方法好。
2、添加权限
- AndroidManifest.xml 设置
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3、注册到微信
4、开始调用微信支付
① 第一步,统一下单(一般是服务器做好的) 下单接口文档
注意事项:
务必提交必须的字段:appid,body,mch_id,nonce_str,notify_url,
out_trade_no,spbill_create_ip,total_fee,trade_type,sign(都是小写);提交到微信接口时以xml格式提交sign为前面提交的参数按照参数名ASCII码从小到大排序签名拼接起来然后进行MD5运算,再将得到的字符串所有字符转换为大写得到的,如签名生成算法
参与生成sign的key为商户账号的密钥,key设置路径如下:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置
- 下面是具体代码(如若查看你的sign生成及提交的xml是否正确可以点击如下:签名生成工具(不要用QQ浏览器看,打不开的))
//拼接字段,顺序不能变
String A = "appid=你的appID" +
"&body=jinshi" +
"&mch_id=你的商户号" +
"&nonce_str=" + nonce_str +
"¬ify_url=http://www.szgsip.com/" +
"&out_trade_no=" + trade_no +
"&spbill_create_ip=192.168.1.1" +
"&total_fee=1" +
"&trade_type=APP";
String key = "你的密钥";
String temp = A + "&key=" + key;
// 生成sign
String sign = MD5.md5(temp).toUpperCase();
- 其中nonce_str为随机字符串,trade_no 为订单号,可以用下面方法模拟生成
(其实可以写固定的,不过只能下单一次)
String nonce_str = getNonce_str(5);//5为填多长的字符串,不长于32位即可
String trade_no = getNonce_str(8);
public static String getNonce_str(int length) {
String base = "QWERTYUIOPLKJHGFDSAZXCVBNM0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
- 接下来提交到微信的接口上
StringBuilder xml = new StringBuilder();//组建xml数据,请保持和上面参与生成算法的参数一致
xml.append("<xml>\n");
xml.append("<appid>你的appID</appid>\n");
xml.append("<body>jinshi</body>\n");
xml.append("<mch_id>你的商户号</mch_id>\n");
xml.append("<nonce_str>" + nonce_str + "</nonce_str>\n");
xml.append("<notify_url>http://www.szgsip.com/</notify_url>\n");
xml.append("<out_trade_no>" + trade_no + "</out_trade_no>\n");
xml.append("<spbill_create_ip>192.168.1.1</spbill_create_ip>\n");
xml.append("<total_fee>1</total_fee>\n");
xml.append("<trade_type>APP</trade_type>\n");
xml.append("<sign>" + sign + "</sign>\n");
xml.append("</xml>");
byte[] xmlbyte = new byte[0];
try {
xmlbyte = xml.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Log.e("-----xml------", xml + "");
try {
URL url = new URL(BaseApp.uri); //提交到微信服务器的接口https://api.mch.weixin.qq.com/pay/unifiedorder
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setDoOutput(true);// 允许输出
conn.setDoInput(true);
conn.setUseCaches(false);// 不使用缓存
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");// 维持长连接
conn.setRequestProperty("Charset", "UTF-8");//如果上面参数body是中文,就不要用UTF—8
conn.setRequestProperty("Content-Length",
String.valueOf(xmlbyte.length));
conn.setRequestProperty("Content-Type", "text/xml; charset=UTF-8");
// conn.setRequestProperty("X-ClientType", "2");//发送自定义的头信息
conn.getOutputStream().write(xmlbyte);
conn.getOutputStream().flush();
conn.getOutputStream().close();
if (conn.getResponseCode() != 200) {
throw new RuntimeException("请求url失败");
} else {
}
//也可以用 HttpClient类提交网络请求,不过Android6.0及以上已经删除这个类了
- 返回结果处理
- 返回结果报错,给的信息都比较详细,错误处理就不一一解释了
- 接下来是返回结果正确的处理:
- 解析
//两个return_code 为SUCCESS即为正确
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<mch_id><![CDATA[2434546]]></mch_id>
<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
<sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
<trade_type><![CDATA[APP]]></trade_type>
</xml>
- 解析xml,其中上面的appid,mch_id,prepay_id是我们需要的,nonce_str可以按照上面getNonce_str()得到,sign值我也不知道有什么卵用,(注意:千万不要使用这个sign到下一个接口中,下面接口的sign要重新生成,不是用这个)
//第一步
//使用dom4j类解析,先在build.gradle添加依赖
compile 'org.dom4j:dom4j:2.0.0'
//第二步
public void dom(String xml) {
Document doc = null;
try {
doc = DocumentHelper.parseText(xml);
Element root = doc.getRootElement();// 指向根节点
// 解析
Element appid = root.element("appid");
Element mch_id = root.element("mch_id");
Element prepay_id = root.element("prepay_id");
final String appidName = appid.getStringValue();
final String mch_idName = mch_id.getStringValue();
final String prepay_idName = prepay_id.getStringValue();
} catch (DocumentException e) {
e.printStackTrace();
}
}
② 第二步,调起支付接口(也是服务器做好的)最坑爹的一个接口文档
注意事项:
文档中的参数sign也是拼接该接口字段重新生成的,绝对不是上个接口返回的sign,这点要注意
在调用该接口之前还需要做一步:
找到微信Demo文件的WXPayEntryActivity类,连同wxapi包一起复制,其中不需要WXEntryActivity类;
WXPayEntryActivity.java路径必须要放到“App包名.wxapi”的package中,否则无法响应回调(包名可以在manifests中package查看);
WXPayEntryActivity 在manifests配置为:
<activity
android:name=".wxapi.WXPayEntryActivity"
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="你的appid"/>
</intent-filter>
</activity>
- 接下来可以调用支付接口了
//生成时间戳,部分系统取到的值为毫秒级,需要转换成秒(10位数字)。
String time_str = System.currentTimeMillis() / 1000 + "";
//生成sign
String finalSign = getSign(mch_idName, prepay_idName, "Sign=WXPay", nonce_str, time_str, BaseApp.key);
//提交参数
PayReq payReq = new PayReq();
payReq.appId = "你的appID";
payReq.partnerId = "你的mch_id";
payReq.prepayId = "下单接口返回的prepayId";
payReq.nonceStr = nonce_str;
payReq.timeStamp = time_str;//时间戳
payReq.packageValue = "Sign=WXPay";//微信官方固定写法
payReq.sign = finalSign;
//发送请求
api.sendReq(payReq);
//该方法生成sign
private String getSign(String partnerId, String prepayId, String packageValue, String nonceStr, String timeStamp, String key) {
String stringA =
"appid=" + BaseApp.app_ID
+ "&noncestr=" + nonceStr
+ "&package=" + packageValue
+ "&partnerid=" + partnerId
+ "&prepayid=" + prepayId
+ "×tamp=" + timeStamp;
String stringSignTemp = stringA + "&key=" + key;
String sign = MD5.md5(stringSignTemp).toUpperCase();
return sign;
}
然后,在WXPayEntryActivity类中会有回调
- 在这个类中需要注意的地方有三个地方:
- 这个类中的布局是可以自定义的,如果你不需要展示什么布局,删了即可
-
回调结果的处理,下面是官方的处理方式,直接给了一个dialog,很多人会摸不着头脑,如果你不需要这个dialog,直接删除就好了,不需要把官方demo中的布局和资源都复制过来
最后这个才是我们需要关注的地方:
- 在这个类中需要注意的地方有三个地方:
@Override
public void onResp(BaseResp resp) {
if (!api.isWXAppInstalled()) {
Toast.makeText(this, "您没有安装微信", Toast.LENGTH_SHORT).show();
}
if (!api.isWXAppSupportAPI()) {
Toast.makeText(this, "当前版本不支持支付", Toast.LENGTH_SHORT).show();
}
Log.e("---resp.errStr------", resp.errStr + "" + "-----------------------");
Log.e("回调微信支付结果", "errCode=" + resp.errCode);
if (resp.errCode == 0) {
Toast.makeText(this, "支付成功!", Toast.LENGTH_SHORT).show();
} else if (resp.errCode == -1) {
Toast.makeText(this, "支付失败!", Toast.LENGTH_SHORT).show();
finish();
} else if (resp.errCode == -2) {
Toast.makeText(this, "取消支付!", Toast.LENGTH_SHORT).show();
finish();
}
很多人都会遇到resp.errCode == -1支付失败的问题,官方只有这么一句话告诉你
这才是最坑的地方,这么大一个网络公司,就不能多写几句。。。还有,打印resp.errStr=null,这真TM。。。
- 支付失败最主要的原因其实有两个,
一个就是参数sign生成出错,拼接字符串时参数名没有小写,参数没有按AscII码顺序排序,而且两个接口都要重新生成Sign,这个问题认真按照官方写的步骤就不会出错了;
第二个问题,其实是没有打包,或者打包后app的签名和官方注册的不一致,yi
查看app签名步骤如下:
- 运行进入控制台
在弹出的控制台窗口中输入 cd .android 定位到 .android 文件夹。
- 继续在控制台输入命令。
- 开发模式使用 debug.keystore,(使用eclipse)命令为:keytool -list -v -keystore debug.keystore(或者keytool -list -v -keystore debug.jks 使用studio)
- 发布模式使用 apk 对应的 keystore,命令为:keytool -list -v -keystore apk的keystore
具体路径的情况下:
- 提示输入密钥库密码,开发模式默认密码是 android,发布模式的密码是为 apk 的 keystore 设置的密码。输入密钥后回车(如果没设置密码,可直接回车),此时可在控制台显示的信息中获取 MD5值(微信是需要MD5值,像百度地图是要SHA1值),如下图所示:(右击标记即可复制MD5值)
讲了,那么多,其实最简单的方法就是用微信的签名生成工具
还有个问题,为了保证debug测试运行和打包签名后运行时签名一致呢?其实可以 这样:
首先 ,打开如下
添加签名文件
3.设置debug和release的默认签名
4.最后,在gradle查看是否设置成功
另外,可以查看debug运行时签名:
md5就是微信支付的签名信息(百度地图则是SHA1)
以上是微信支付最主要的问题,如果还有解决不了的,直接发邮件给微信邮箱吧
(记得写上你的商户号+用户名称和详细的开发问题,一般一天内回复你的)
wepayTS@tencent.com