前言:本文主要说明如何在Android项目中接入微信支付,介绍微信支付在项目中的配置,分析微信支付数据与其交互流程,分享个人遇到的坑,以帮助有需要的朋友能更快的在项目中进行微信支付的接入。
正文:
1,开发资质申请:
这个过程在本文中不详细介绍,总的来说需要企业资质申请微信支付功能以及微信开发者,最终是为了得到APPID和商户密钥(后面会详讲这个两个值怎么使用)
2,流程总体介绍
微信支付总体结构图:
订单信息:简单来讲就是对订单的封装,其中包含了支付金额,支付时间,订单号,签名信息等等订单信息。这些信息在项目中一般是由服务端构建的,交付给APP转发给微信服务器即可。但是实际情况中服务端可能也没有做这个工作,那么订单信息也有可能在APP端合成。所以开发时务必和后端开发人员协商好关于订单信息构造的工作。(订单构造详情见下文)
支付请求:即APP在程序中调用支付SDK将订单信息发送给微信服务器,这个过程在代码中是很简单的,微信封装好了这个请求接口执行一行代码即可调用(详情见下文)
支付结果反馈:微信会在特定回调接口通知支付接口
总体上这就是微信支付的结构,操作上稍微复杂的是订单信息的构造,容易导致出错的微信SDK的配置。那么接下来就具体的一一介绍每个点的实现。
3,下载官方demo
官网SDK以及demo下载地址:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419319167&token=&lang=zh_CN
官方文档地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=1_1
下载完成后将libammsdk.jar导入到项目中,至于官方demo,建议大家看看就好,不要太相信。因为写的是在太烂了(不知道为什么微信这么大个平台,开发的demo是如此的不考虑用户的感受)
4,实现
a,导入jar包
b,在工程包名下根目录新建wxapi包,这个wxapi包的位置一定要是包名根目录,包名也不能错。(这让对包名有强迫症的开发者会很不爽,但是没办法)
c,在该包下新建回调类WXPayEntryActivity,该类的名字一定是在wxapi包下,名字也一定是WXPayEntryActivity。因为他是微信回调的接口,如果改变位置或名字,就会导致微信无法将支付结果反馈,从而导致支付失败。
(图中WXEntryActivity是用作微信分享或其他功能的,只做微信支付的朋友可以忽略)
d,WXPayEntryActivity的代码实现以及使用说明:
该类是不需要开发者自己调用的,他是在微信支付后SDK为了回调通知APP的接口,SDK内部会调用该Activity,该Activity的界面布局可以自定义的设置。
其具体的回调接口是该类中的onResp函数,在该函数中可判断是否支付成功。
public class WXPayEntryActivity extends AppCompatActivity implements IWXAPIEventHandler {
private IWXAPI api;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wxpay_entry);
api = WXAPIFactory.createWXAPI(this, "wxAPPID");//这里填入自己的微信APPID
api.handleIntent(getIntent(), this);
}
@Override
public void onReq(BaseReq baseReq) {
}
@Override
public void onResp(BaseResp baseResp) {
Log.d("coyc", "onPayFinish, errCode = " + baseResp.errCode);
if (baseResp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
int errCord = baseResp.errCode;
if (errCord == 0) {
App.getInstance().tos("支付成功!");
} else {
App.getInstance().tos("支付失败");
}
//这里接收到了返回的状态码可以进行相应的操作,如果不想在这个页面操作可以把状态码存在本地然后finish掉这个页面,这样就回到了你调起支付的那个页面
//获取到你刚刚存到本地的状态码进行相应的操作就可以了
finish();
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
api.handleIntent(intent, this);
}
}
e,AndroidManifest.xml配置:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 微信支付 -->
<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="自己的wxAPPID" />
</intent-filter>
</activity>
f,调用支付接口:
IWXAPI api= WXAPIFactory.createWXAPI(context, "wxAPPID",false);//填写自己的APPID
api.registerApp("wxAPPID");//填写自己的APPID,注册本身APP
PayReq req = new PayReq();//PayReq就是订单信息对象
//给req对象赋值
req.appId = appid;//APPID
req.partnerId = partnerid;// 商户号
req.prepayId = prepayid;// 预付款ID
req.nonceStr = getRoundString();//随机数
req.timeStamp = getTimeStamp();//时间戳
req.packageValue = "Sign=WXPay";//固定值Sign=WXPay
req.sign = sign;//签名
api.sendReq(req);//将订单信息对象发送给微信服务器,即发送支付请求
那么写到这里,上面的PayReq 对象其实是未被赋值的
req.appId = appid;//APPID
req.partnerId = partnerid;// 商户号
req.prepayId = prepayid;// 预付款ID
req.nonceStr = getRoundString();//随机数
req.timeStamp = getTimeStamp();//时间戳
req.packageValue = "Sign=WXPay";//固定值Sign=WXPay
req.sign = sign;//签名
而且这7个参数是缺一不可的,如果这些参数都是在服务端生成的,那么在Android开发人员就不用做什么工作了,将服务端给的值给其赋值就好了,那么整个微信支付流程也就做完了(一般来讲也确实是这个样子)。但是有些情况下服务端的同事没有做这些工作,比如说签名工作等等。那么这么这些工作就要在APP本地实现。
下面我给出随机数获取算法,时间戳获取算法,签名算法。
private String getRoundString() {
Random random = new Random();
return random.nextInt(10000) + "";
}
private String getTimeStamp() {
return new Date().getTime() / 10 + "";
}
private String getSign() {
Map<String, String> map = new HashMap<>();
map.put("appid", req.appId);
map.put("partnerid", req.partnerId);
map.put("prepayid", req.prepayId);
map.put("package", req.packageValue);
map.put("noncestr", req.nonceStr);
map.put("timestamp", req.timeStamp);
ArrayList<String> sortList = new ArrayList<>();
sortList.add("appid");
sortList.add("partnerid");
sortList.add("prepayid");
sortList.add("package");
sortList.add("noncestr");
sortList.add("timestamp");
sort(sortList);
String md5 = "";
int size = sortList.size();
for (int k = 0; k < size; k++) {
if (k == 0) {
md5 += sortList.get(k) + "=" + map.get(sortList.get(k));
} else {
md5 += "&" + sortList.get(k) + "=" + map.get(sortList.get(k));
}
}
String stringSignTemp = md5+"&key=商户密钥";//这里填写自己的商户密钥,所以说如果签名工作实在服务端完成的,商户密钥在APP端是用不到的
String sign=MD5.Md5(stringSignTemp).toUpperCase();
return sign;
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.android.pc.util;
import java.security.MessageDigest;
public class MD5 {
public MD5() {
}
public static final String Md5(String s) {
char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] e = s.getBytes("UTF-8");
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(e);
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 & 15];
str[k++] = hexDigits[byte0 & 15];
}
return new String(str);
} catch (Exception var10) {
return null;
}
}
}
商户号以及预付款ID这个必须是服务端同事给定的值,在APP端是无法代码生成的。
那么整个微信支付的代码工作到这里就结束了。这里我将自己封装的支付类贴出来,仅供参考。
public class WX_Pay {
public IWXAPI api;
private PayReq req;
public WX_Pay(Context context) {
api = WXAPIFactory.createWXAPI(context, "wxAPPID",false);
}
/**
* 向微信服务器发起的支付请求
*/
public void pay(String appid,String partnerid,String prepayid) {
req = new PayReq();
req.appId = appid;//APPID
req.partnerId = partnerid;// 商户号
req.prepayId = prepayid;// 预付款ID
req.nonceStr = getRoundString();//随机数
req.timeStamp = getTimeStamp();//时间戳
req.packageValue = "Sign=WXPay";//固定值Sign=WXPay
String sign = getSign();
req.sign = sign;//签名
api.registerApp("wxAPPID");
api.sendReq(req);
}
@NonNull
private String getSign() {
Map<String, String> map = new HashMap<>();
map.put("appid", req.appId);
map.put("partnerid", req.partnerId);
map.put("prepayid", req.prepayId);
map.put("package", req.packageValue);
map.put("noncestr", req.nonceStr);
map.put("timestamp", req.timeStamp);
ArrayList<String> sortList = new ArrayList<>();
sortList.add("appid");
sortList.add("partnerid");
sortList.add("prepayid");
sortList.add("package");
sortList.add("noncestr");
sortList.add("timestamp");
sort(sortList);
String md5 = "";
int size = sortList.size();
for (int k = 0; k < size; k++) {
if (k == 0) {
md5 += sortList.get(k) + "=" + map.get(sortList.get(k));
} else {
md5 += "&" + sortList.get(k) + "=" + map.get(sortList.get(k));
}
}
String stringSignTemp = md5+"&key=商户密钥";
String sign=MD5.Md5(stringSignTemp).toUpperCase();
return sign;
}
private String getRoundString() {
Random random = new Random();
return random.nextInt(10000) + "";
}
private String getTimeStamp() {
return new Date().getTime() / 10 + "";
}
private static void sort(ArrayList<String> strings) {
Collections.sort(strings);
}
}
那么在APP中这样使用就好
WX_Pay pay = new WX_Pay(getContext());
pay.pay(str1,str2,str3);
5,错误小结:本人在开发中遇到过两个错误
a,无法起调微信支付界面:错误原因是没有将WXPayEntryActivity 类放在在wxapi包下。
b,支付失败:错误原因是在签名算法中忘记将商户密钥签名。
这里基本可以总结下错误类型:
1,起调失败:工程配置错误
2,支付订单报错:订单信息错误
c,千万注意微信支付要编译为release版才能通过支付测试