Android集成微信支付

来源:互联网 发布:经典都市小说知乎 编辑:程序博客网 时间:2024/05/24 03:21

我个人将集成微信支付的过程分成4个步骤: 微信官方api文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
1.配置各种信息
2.拼凑预订单信息,访问微信服务器生成预订单,主要是为了得到prepared_id —– 建议在自己的服务器操作
3.根据得到的prepared_id及其他信息进行二次签名,调起微信sdk支付 — 前部分建议在服务器操作,后面部分在app端操作
4.根据回调的支付结果执行不同的逻辑

接下来具体说说各个步骤
1、配置各种信息 如在支付Activity中PayActivity(名字自己定)和微信回调的WXPayEntryActivity(这个类的名字不允许改变,包名固定为你应用的包名+wxapi)功能清单文件配置

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--微信支付回调界面-->
 
    <intent-filter>
         
        <category android:name="android.intent.category.DEFAULT">
        <data android:scheme="你的应用appid"> <!-- 需修改 -->
    </data></category></action></intent-filter>
</activity>
 
<!--微信支付界面-->
 
    <intent-filter>
         
        <category android:name="android.intent.category.DEFAULT">
        <data android:scheme="你的应用appid"> <!-- 需修改 -->
    </data></category></action></intent-filter>
</activity>

当然,权限什么的就不说了,别忘了哈

2.拼凑预订单信息,访问微信服务器生成预订单,主要是为了得到prepared_id,如下的代码是在客户端生成的实例,如果不需要在客户端操作,请无视
生成预订单有10个必选参数分别为 (注意参数名不能改变,post请求,以xml格式)
appid(应用id)、 mch_id(商户号)、 nonce_str(随机字符串,参照我下面的代码生成)、 sign(签名,参照我下面的代码签名)、 body(商品描述)、
out_trade_no(商户订单号,自己生成,唯一)、 total_fee(总金额,单位:分,不能像支付宝有0.01这种)、 spbill_create_ip(终端ip,参照下面ipv4的获取)、 notify_url(微信支付结果异步通知的地址)、 trade_type(交易类型,app的填APP即可)
官方的参数示例

?
1
2
3
4
5
6
7
8
9
10
11
12
    <xml>
   wx2421b1c4370ec43b</appid>
   支付测试</attach>APP支付测试
   <mch_id>10000100</mch_id>
   <nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str>
   <notify_url>http://wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php</notify_url>
   <out_trade_no>1415659990</out_trade_no>
   <spbill_create_ip>14.23.150.211</spbill_create_ip>
   <total_fee>1</total_fee>
   <trade_type>APP</trade_type>
   <sign>0CB01533B8C1EF103065174F50BCA001</sign>
</xml>

下面是几个参数的生成:
随机字符串的生成方式

?
1
2
3
4
5
6
7
8
/**
 * 生成随机字符串,算法自己考虑
 * @return
 */
privateString genNonceStr() {
Random random = newRandom();
 returnMD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
}

订单号的生成:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * get the out_trade_no for an order. 生成商户订单号,该值在商户端应保持唯一(可自定义格式规范)
 *
 */
privateString getWXOutTradeNo() {
    SimpleDateFormat format = newSimpleDateFormat("MMddHHmmss", Locale.getDefault());
    Date date = newDate();
    String key = format.format(date);
 
    Random r = newRandom();
    key = key + r.nextInt();
    key = key.substring(0,15);
    returnkey;
}

ipv4的获取方式:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * 获取自己手机的ipv4地址
 * @return
 */
publicString getIp() {
    try{
 
        for(Enumeration<networkinterface> en = NetworkInterface
 
                .getNetworkInterfaces(); en.hasMoreElements();) {
 
            NetworkInterface intf = en.nextElement();
 
            for(Enumeration<inetaddress> ipAddr = intf.getInetAddresses(); ipAddr
 
                    .hasMoreElements();) {
 
                InetAddress inetAddress = ipAddr.nextElement();
                // ipv4地址
                if(!inetAddress.isLoopbackAddress()
                        && InetAddressUtils.isIPv4Address(inetAddress
                        .getHostAddress())) {
 
                    Log.e("TAG","ipv4="+ inetAddress.getHostAddress());
                    returninetAddress.getHostAddress();
 
                }
 
            }
 
        }
 
    }catch(Exception ex) {
 
    }
 
    return"192.168.1.0";
 
}</inetaddress></networkinterface>

签名的生成:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * 生成package参数,就是签名参数
 * @param params
 * @return
 */
privateString genPackage(List<namevaluepair> params) {
    StringBuilder sb = newStringBuilder();
 
    for(inti = 0; i < params.size(); i++) {
        sb.append(params.get(i).getName());
        sb.append('=');
        sb.append(params.get(i).getValue());
        sb.append('&');
    }
    sb.append("key=");
    sb.append(Constants.API_KEY);// 注意:不能hardcode在客户端,建议genPackage这个过程都由服务器端完成
 
    returnMD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
}</namevaluepair>

生成xml字符串的方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 生成xml文件字符串
 * @param packageParams
 * @return
 */
privateString toXml(List<namevaluepair> packageParams) {
    StringBuffer xml =newStringBuffer("");
    xml.append("<xml>");
    for(inti = 0;i < packageParams.size();i++){
        NameValuePair nameValuePair = packageParams.get(i);
        xml.append("<"+ nameValuePair.getName() + ">"+ nameValuePair.getValue() + "<!--"+ nameValuePair.getName() + "-->");
        if(i == packageParams.size() - 1){
            xml.append("</xml>");
        }
    }
    returnnew String(xml);
}</namevaluepair>

接下来就可以将上面的10个参数生成xml文件字符串上传

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
     * 生成预订单的信息
     * @return
     */
    privateString getProductArgs(){
 
            String nonceStr = genNonceStr();//随机字符串
 
            List<namevaluepair> packageParams = newLinkedList<namevaluepair>();
            packageParams
                    .add(newBasicNameValuePair("appid", Constants.APP_ID));
 
            packageParams.add(newBasicNameValuePair("body", aliOrderBO.getName()));//商品名称
//            packageParams.add(new BasicNameValuePair("input_charset", "UTF-8"));
            packageParams.add(newBasicNameValuePair("detail", aliOrderBO.getDescribe()));//商品详情
            packageParams
                    .add(newBasicNameValuePair("mch_id", Constants.MCH_ID));//商户号
            packageParams.add(newBasicNameValuePair("nonce_str", nonceStr));//随机字符串
 
            packageParams.add(newBasicNameValuePair("notify_url", UrlStrings.getUrl(UrlIds.WXPAY_NOTIFY)));//接收服务器异步结果的url
 
            String orderCode = getWXOutTradeNo();//生成商户订单号
            ((MyApplication) getApplication()).setWxTradeNo(orderCode);//将订单号保存
 
            packageParams.add(newBasicNameValuePair("out_trade_no",//商户订单号
                    orderCode));
            packageParams.add(newBasicNameValuePair("spbill_create_ip",getIp()));//用户终端IP
            doubletotalFee = aliOrderBO.getPrice()*100;//价格,单位是分
 
            packageParams.add(newBasicNameValuePair("total_fee", String.valueOf((int)totalFee)));//订单总金额
            packageParams.add(newBasicNameValuePair("trade_type","APP"));//交易类型
 
            String packageValue = genPackage(packageParams);
 
            packageParams.add(newBasicNameValuePair("sign", packageValue));
        try{
            returnnew String(toXml(packageParams).toString().getBytes(), "ISO8859-1");
        }catch(UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        returnnull;
    }</namevaluepair></namevaluepair>

上传预订单信息给微信服务器

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
String url = String.format("https://api.mch.weixin.qq.com/pay/unifiedorder");//微信预订单的地址,请参考微信官方
 
newGetPrepayIdTask().execute(url, entry);//访问微信服务器
 
//上传的类
privateclass GetPrepayIdTask extendsAsyncTask<string, preparewxpaybean=""> {
 
     @Override
     protectedvoid onPreExecute() {
 
     }
 
     @Override
     protectedvoid onPostExecute(PrepareWXPayBean result) {//这个PrepareWXPayBean是服务器返回的结果,自定义的类
 
         if(result.localRetCode == PrepareWXPayBean.LocalRetCode.ERR_OK) {
 
             sendPayReq(result);
         }else{
 
             Log.e("TAG","error");
         }
     }
 
     @Override
     protectedvoid onCancelled() {
         super.onCancelled();
     }
 
     @Override
     protectedPrepareWXPayBean doInBackground(String... params) {
         PrepareWXPayBean result = newPrepareWXPayBean();
 
         if(params == null|| params.length <= 0) {
             result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_ARGU;
             returnresult;
         }
 
         byte[] buf = Util.httpPost(params[0],params[1]);
         if(buf == null|| buf.length == 0) {
             result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_HTTP;
             returnresult;
         }
 
         String content = newString(buf);
 
         result = decodeXML(content);
 
         result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_OK;
         returnresult;
     }
 }
</string,>

3.解析返回的预订单信息再次签名,然后调起微信sdk(前面建议的服务器完成,如果不需要,请无视)
返回结果示例(请求成功的结果,其他情况请参考api):

?
1
2
3
4
5
6
7
8
9
10
11
<xml>
   <return_code><!--[CDATA[SUCCESS]]--></return_code>
   <return_msg><!--[CDATA[OK]]--></return_msg>
   <!--[CDATA[wx2421b1c4370ec43b]]--></appid>
   <mch_id><!--[CDATA[10000100]]--></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>

解析结果的方法(这只是个示例,具体方式请自行考虑):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
 * 解析xml
 * @param content
 * @return
 */
privatePrepareWXPayBean decodeXML(String content) {
    XmlPullParser parser = Xml.newPullParser();
    PrepareWXPayBean bean = newPrepareWXPayBean();
    try{
        parser.setInput(newStringReader(content));
        intevent = parser.getEventType();
        while(event != XmlPullParser.END_DOCUMENT) {
            switch(event) {
                caseXmlPullParser.START_DOCUMENT:
                    break;
                caseXmlPullParser.START_TAG:
                    if("return_code".equals(parser.getName())) {
                        parser.next();
                        bean.setReturn_code(parser.getText());
                    }elseif ("return_msg".equals(parser.getName())) {
                        parser.next();
                        bean.setReturn_msg(parser.getText());
                    }elseif ("appid".equals(parser.getName())) {
                        parser.next();
                        bean.setAppid(parser.getText());
                    }elseif ("mch_id".equals(parser.getName())) {
                        parser.next();
                        bean.setMch_id(parser.getText());
                    }elseif ("nonce_str".equals(parser.getName())) {
                        parser.next();
                        bean.setNonce_str(parser.getText());
                    }elseif ("sign".equals(parser.getName())) {
                        parser.next();
                        bean.setSign(parser.getText());
                    }elseif ("result_code".equals(parser.getName())) {
                        parser.next();
                        bean.setResult_code(parser.getText());
                    }elseif ("prepay_id".equals(parser.getName())) {
                        parser.next();
                        bean.setPrepay_id(parser.getText());
                    }elseif ("trade_type".equals(parser.getName())) {
                        parser.next();
                        bean.setTrade_type(parser.getText());
                    }
                    break;
                caseXmlPullParser.END_TAG:
                    break;
            }
            event = parser.next();
        }
    }catch(Exception e) {
        e.printStackTrace();
    }
    returnbean;
}

解析完成后进行二次签名,签名方法如下 key就是api秘钥 key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
privatestatic final char SPLIT = '&';
/**
 * 微信开放平台和商户约定的密钥
 *
 * 注意:不能hardcode在客户端,建议genSign这个过程由服务器端完成
 */
 
privateString genSign(PayReq req) {
    StringBuilder sb = newStringBuilder();
    sb.append("appid=");
    sb.append(req.appId);
    sb.append(SPLIT);
 
    sb.append("noncestr=");
    sb.append(req.nonceStr);
    sb.append(SPLIT);
 
    sb.append("package=");
    sb.append(req.packageValue);
    sb.append(SPLIT);
 
    sb.append("partnerid=");
    sb.append(req.partnerId);
    sb.append(SPLIT);
 
    sb.append("prepayid=");
    sb.append(req.prepayId);
    sb.append(SPLIT);
 
    sb.append("timestamp=");
    sb.append(req.timeStamp);
    sb.append(SPLIT);
 
    sb.append("key=");
    sb.append(Constants.API_KEY);// 注意:不能hardcode在客户端,建议genSign这个过程都由服务器端完成
 
    returnMD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
}

生成时间戳的方法:

?
1
2
3
4
5
6
7
/**
 * 生成时间戳
 * @return
 */
privateString genTimeStamp() {
    returnString.valueOf(System.currentTimeMillis() / 1000);
}

然后就可以调起微信sdk支付啦

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
privatevoid sendPayReq(PrepareWXPayBean result) {
    if("FAIL".equals(result.getResult_code())) {
        Log.e(TAG,"sendPayReq fail, retCode = " + result.getResult_code() + ", retmsg = " + result.getReturn_msg());
        return;
    }
 
    String prepare_id = result.getPrepay_id();
    Log.d(TAG,"sendPayReq, prepare_id = " + prepare_id + ", sign = " + result.getSign());
 
    if(prepare_id == null|| prepare_id.length() == 0) {
        Log.e(TAG,"sendPayReq fail, prepare_id is empty");
        return;
    }
 
    PayReq req = newPayReq();
    req.appId = result.getAppid();
    req.partnerId = result.getMch_id();//商户号
    req.prepayId = prepare_id;
    req.nonceStr = result.getNonce_str();//随机字符串
    req.timeStamp = genTimeStamp();//时间戳
    req.packageValue = "Sign=WXPay";//固定字符串
    req.sign = genSign(req);//签名
    //req.extData = "app data"; // optional,微信不处理该字段,会在PayResp结构体中回传该字段
 
    // 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信
    api.registerApp(Constants.APP_ID);
    api.sendReq(req);
}

4.处理回调 再次强调这个类 微信回调的WXPayEntryActivity(这个类的名字不允许改变,包名固定为你应用的包名+wxapi

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
   publicvoid onResp(BaseResp baseResp) {
       Log.d("TAG","onPayFinish, errCode = " + baseResp.errCode);
       //Log.e("TAG", "info=" + baseResp.errStr + ",transaction=" + baseResp.transaction + ",openId=" + baseResp.openId);
       Bundle bundle = newBundle();
       if(baseResp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
           if(baseResp.errCode == 0){
               Toast.makeText(WXPayEntryActivity.this,"支付成功", Toast.LENGTH_SHORT).show();
 
 
           }else{
               Toast.makeText(WXPayEntryActivity.this,"支付失败", Toast.LENGTH_SHORT).show();
           }
 
       }

最后再强调一个,微信sdk想要成功调起,必须用正式签名,正式签名,正式签名。不只是支付sdk,分享和登录的也是一样

大体的流程就这样了,小弟能力有限,如果有什么错误欢迎指正!

原创粉丝点击