最近软件的微支付突然不能用了,经紧急查找发现微支付的支付接口更换了(心中顿时千万只xx奔腾而过,微信太霸道了,更换了也不通知一声!!)于是就开始了我的微支付踩坑之路,更新微支付花了2天的时间,虽说不长但遇到了很多问题。作为一个有情怀的程序员,把这些坑记录一下,希望能帮助到大家!
1 简单介绍
新微支付和以前的变化还是很大的,上传数据和返回数据都采用了xml的方式。这里给出微支付的官方文档地址,方便大家查阅。微支付这个官方文档弄的真心不好,完全不是给初学的的看的!!我也是在同事的帮助下才看明白。
2 添加流程
我先说一下微支付的添加流程,大家心里有个数:
第一步 获取jar包
去官网下载一个demo,取出它里面的名字叫:libammsdk.jar的包。(注意:这个demo大家千万别废时间去想办法运行起来,它根本没用!)如下图:
第二步 注册APPID
// 通过WXAPIFactory工厂,获取IWXAPI的实例
IWXAPI api = WXAPIFactory.createWXAPI(this, Constants.APP_ID, false);
api.registerApp(Constants.APP_ID);
很多人调不起微支付,很多情况是忽略了这里。Constants.APP_ID要换成你们自己的app_id.
第三步 下单
这是最难的地方,我花时间最多的地方,踩过很多坑。先看一下它的官方地址。
总的思路是:
第一步.组合请求参数成xml字符串。
这里面有些东西大家一定要注意:
1.nonce_str的数据一定要和sign加密的nonce_str数据是一致的!
这里给出我的nonce_str生成算法:
public static String getNonceStr() {
// Random random = new Random();
// return MD5.getMessageDigest(String.valueOf(random.nextInt(10000))
// .getBytes());
final String[] Strarray = {"0","1","2","3","4","5","6","7","8","9",
"A","B","C","D","E","F","G","H","I","J",
"K","L","M","N","O","P","Q","R","S","T",
"U","V","W","X","Y","Z"};
StringBuffer nonceStr = new StringBuffer();
for(int i=0;i<15;i++){
nonceStr.append(Strarray[(int)(Math.random()*Strarray.length)]);
}
return nonceStr.toString();
}
2.sign签名的时候,要注意:
1.参数名一定要按照ASCII码从小到大排序!!
2.sign参数不参与签名.其他的都要参与签名!
3.签名的时候要严格按照人家的来,别弄错了!我封装了一个工具供大家使用。
//封装的签名工具
public static String getSign(String stringA){
String strDigest = stringA+"&key="+"";
return MD5.getMessageDigest(strDigest.getBytes()).toUpperCase();
}
使用方法 :
StringBuffer sb = new StringBuffer();
sb.append("appid=");
sb.append(Constants.APP_ID);
sb.append("&body=");
sb.append(subject);
sb.append("&mch_id=");
sb.append(Constants.PARTNER_ID);
sb.append("&nonce_str=");
sb.append(nonceStr);
sb.append("¬ify_url=");
sb.append(weixinNortifyUrl);
sb.append("&out_trade_no=");
sb.append(PayToActivityUtil.getWxPaysn(pay_sn));
sb.append("&spbill_create_ip=");
sb.append("172.16.1.99");
sb.append("&total_fee=");
sb.append(String.valueOf(ipri));
sb.append("&trade_type=");
sb.append("APP");
String sign = WXApiUtil.getSign(sb.toString();
第二步:发送组合好的xml字符串发送到接口地址。
接口地址人家已经给出来了,如下图:
发送的方法,我也已经封装好了,这里给大家发出代码:
/**
* 专门为微信支付封装的一个asynctask
*/
public class WXOpAsyncTask extends AsyncTask<Void, Void, HashMap<String,String>> {
public static final int WXOpAsyncTaskFlag1 = 0x010000;
public static final int WXOpAsyncTaskFlag2 = 0x010001;
public static final int WXOpAsyncTaskFlag3 = 0x010002;
private Context context;
private String fuction;
private ProgressDialog dialog;
private String url;
private String entity ;
private int wXOpAsyncTaskFlag = WXOpAsyncTask.WXOpAsyncTaskFlag1;
public interface WXOpAsyncCallBack{
public void onPostExecute(HashMap<String, String> strHashMap,int flag);
}
private WXOpAsyncCallBack wXOpAsyncCallBack;
public WXOpAsyncTask(Context context){
this.context = context;
}
public WXOpAsyncTask setFuction(String fuction) {
this.fuction = fuction;
return this;
}
public WXOpAsyncTask setwXOpAsyncTaskFlag(int wXOpAsyncTaskFlag) {
this.wXOpAsyncTaskFlag = wXOpAsyncTaskFlag;
return this;
}
public WXOpAsyncTask setUrl(String url) {
this.url = url;
return this;
}
public WXOpAsyncTask setEntity(String entity) {
this.entity = entity;
return this;
}
public WXOpAsyncTask setwXOpAsyncCallBack(WXOpAsyncCallBack wXOpAsyncCallBack) {
this.wXOpAsyncCallBack = wXOpAsyncCallBack;
return this;
}
@Override
protected void onPostExecute(HashMap<String, String> strHashMap) {
super.onPostExecute(strHashMap);
if (dialog != null) {
dialog.dismiss();
}
this.wXOpAsyncCallBack.onPostExecute(strHashMap,wXOpAsyncTaskFlag);
}
@Override
protected void onPreExecute() {
dialog = ProgressDialog.show(context, "提示", fuction);
}
@Override
protected HashMap<String, String> doInBackground(Void... params) {
byte[] buf = Util.httpXmlPost(url, entity);//这里是请求
return WChatXmlUtil.getInstance().getXmlData(new String(buf));//这里是解析
}
@Override
protected void onCancelled() {
super.onCancelled();
}
}
使用方法:
new WXOpAsyncTask(SubmitOrderDialog.this)
.setUrl(UrlManager.WXPlaceOrderUrl)
.setEntity(genProductArgs())//传入组合的xml参数
.setFuction("正在下单...")//加载提示语
.setwXOpAsyncCallBack(this)//设置回调,这里写一个this,所以acitivty需要实现WXOpAsyncTask.WXOpAsyncCallBack接口。
.setwXOpAsyncTaskFlag(WXOpAsyncTask.WXOpAsyncTaskFlag1)
.execute();
/*
回调
*/
@Override
public void onPostExecute(HashMap<String, String> strHashMap,int flag) {
switch (flag){
case WXOpAsyncTask.WXOpAsyncTaskFlag1:
if (strHashMap == null || NormalUtil.isEmpty(strHashMap.get("prepay_id"))) {
Toast.makeText(SubmitOrderDialog.this,"支付失败!",Toast.LENGTH_SHORT).show();
return ;
}
sendPayReq(strHashMap);
break;
}
}
下面就是其中用到的其他方法:
public static byte[] httpXmlPost(String urlStr, String entity) {
if (NormalUtil.isEmpty(entity)) {
DebugLogUtil.getInstance().Error("httpPost, url is null");
return null;
}
try {
URL url = new URL(urlStr);
byte[] xmlbyte = entity.toString().getBytes("UTF-8");
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");
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失败");
InputStream is = conn.getInputStream();// 获取返回数据
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
out.write(buf, 0, len);
}
String string = out.toString("UTF-8");
return out.toByteArray();
} catch (Exception e) {
DebugLogUtil.getInstance().Error("httpPost exception, e = " + e.getMessage());
e.printStackTrace();
return null;
}
}
/**
* Created by YuYuanDa on 2017-03-29.
* Administrator yyd
* 注意:本次封装只是针对微信支付封装的,支持简单格式xml.
* 作用:生成xml;
*/
public class WChatXmlUtil {
private StringWriter writer;
private XmlSerializer serializer;
private WChatXmlUtil(){
writer = new StringWriter();
serializer = Xml.newSerializer();
writer = new StringWriter();
}
/**
* 不是单例模式,此封装只是为了写代码方便一些。
* @return
*/
public static WChatXmlUtil getInstance(){
return new WChatXmlUtil();
}
public WChatXmlUtil start(){
try {
serializer.setOutput(writer);
serializer.startTag(null,"xml");
} catch (IOException e) {
e.printStackTrace();
}
return this;
}
public WChatXmlUtil addTag(String tag, String data){
try {
serializer.startTag(null,tag);
serializer.text(data);
serializer.endTag(null,tag);
} catch (IOException e) {
e.printStackTrace();
}
return this;
}
public WChatXmlUtil end(){
try {
serializer.endTag(null,"xml");
serializer.endDocument();
serializer.flush();
} catch (IOException e) {
e.printStackTrace();
}
return this;
}
public String getXmlByStr(){
return writer.toString();
}
/**
* 解析xml字符串
* @param data
* @return
* 请注意,这里会亦错误,请在博客最底下,下载jar包!!!!
*/
public HashMap<String,String> getXmlData(String data){
HashMap<String,String> map = new HashMap<String,String>();
Document doc = null;
try {
doc = DocumentHelper.parseText(data); // 将字符串转为XML
Element rootElt = doc.getRootElement(); // 获取根节点
List<Element> list = rootElt.elements();//获取根节点下所有节点
for (Element element : list) { //遍历节点
map.put(element.getName(), element.getText()); //节点的name为map的key,text为map的value
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
组合xml参数的代码如下:
String sendReqStr = WChatXmlUtil.getInstance()
.start()
.addTag("mch_id",Constants.PARTNER_ID)
.addTag("trade_type","APP")
.addTag("sign", WXApiUtil.getSign(sb.toString()))//不参与签名
.addTag("nonce_str",nonceStr)
.addTag("total_fee",String.valueOf(ipri))
.addTag("spbill_create_ip","172.16.1.99")
.addTag("notify_url",weixinNortifyUrl)
.addTag("body",subject)
.addTag("appid",Constants.APP_ID)
.addTag("out_trade_no",PayToActivityUtil.getWxPaysn(pay_sn))
.end()
.getXmlByStr();
代码确实有点乱哈,大家自己整理一下。
第三步:解析返回的数据,获取prepay_id。
微支付返回的东西不太符合我们的要求所以得解析一下,我已经在异步中解析成HashMap了,在回调方法onPostExecute中我们直接拿来用就行。然后在回调方法中调起支付
第四步 调起支付
调起支付不需要我们自己请求网络了,用微支付给我们提供的方法即可,但是里面还有一个签名,签名的方法和上面是一样的,只是签名的字段不一样而已。这个地方大家要仔细,别再弄错了。
String nonceStr = WXApiUtil.getNonceStr();
PayReq request = new PayReq();
request.appId = Constants.APP_ID;
request.partnerId = Constants.PARTNER_ID;
request.prepayId= strHashMap.get(“prepay_id”);
request.packageValue = “Sign=WXPay”;
request.nonceStr= nonceStr;
request.timeStamp = “”+ (System.currentTimeMillis()/1000);
request.sign= WXApiUtil.getSign(sb.toString());
api.sendReq(request);
第五步 回调结果
不管你调支付是否成功,微支付都会回调WXPayEntryActivity中的onResp()方法,代码如下:
@Override
public void onResp(BaseResp resp) {
if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
if(resp.errCode==BaseResp.ErrCode.ERR_OK){
Toast.makeText(this, "支付成功",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "支付失败",Toast.LENGTH_SHORT).show();
}
}else{
// Toast.makeText(this, "支付失败",Toast.LENGTH_SHORT).show();
}
}
第六步 结束
好了到此为止,相信你已给调起支付了。
3 注意
有一点需要注意,xml解析需要用到一个dom4j_1.6.1.jar包,点击这里下载。把这个解压后,只要里面的dom4j_1.6.1.jar文件就行。
4 结尾
好了就讲到这里吧,我封装了不少代码,代码贴的有点多,有点乱,如果有不懂的地方请留言告诉我。