关于Android集成微信支付的过程

来源:互联网 发布:翻墙软件一点通 编辑:程序博客网 时间:2024/06/06 07:00

本来是不打算写关于微信支付的,网上有太多了,但是前些天一个哥们居然还问我微信支付怎么集成,所以准备做个记录。
以下内容是将微信支付的所有过程都放在在app端,这是不推荐的,实际中前几步由服务器来做比较安全些。当然,我这样写一个是为了展示微信支付的一个流程,二是服务器端哥们不愿写,老大让我全在客户端完成(汗一个),废话不多说了,进入正题。
我个人将集成微信支付的过程分成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)功能清单文件配置

<!--微信支付回调界面--><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><!--微信支付界面--><activity    android:name=".activity.mine.PayActivity"    android:theme="@style/AppTheme.NoActionBar"    >    <intent-filter>        <action android:name="android.intent.action.VIEW"/>        <category android:name="android.intent.category.DEFAULT"/>        <data android:scheme="你的应用appid"/> <!-- 需修改 -->    </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即可)
官方的参数示例

    <xml>   <appid>wx2421b1c4370ec43b</appid>   <attach>支付测试</attach>   <body>APP支付测试</body>   <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>

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

/** * 生成随机字符串,算法自己考虑 * @return */private String genNonceStr() {Random random = new Random(); return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());}

订单号的生成:

/** * get the out_trade_no for an order. 生成商户订单号,该值在商户端应保持唯一(可自定义格式规范) * */private String getWXOutTradeNo() {    SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss", Locale.getDefault());    Date date = new Date();    String key = format.format(date);    Random r = new Random();    key = key + r.nextInt();    key = key.substring(0, 15);    return key;}

ipv4的获取方式:

/** * 获取自己手机的ipv4地址 * @return */public String 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());                    return inetAddress.getHostAddress();                }            }        }    } catch (Exception ex) {    }    return "192.168.1.0";}

签名的生成:

/** * 生成package参数,就是签名参数 * @param params * @return */private String genPackage(List<NameValuePair> params) {    StringBuilder sb = new StringBuilder();    for (int i = 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这个过程都由服务器端完成    return MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();}

生成xml字符串的方法:

/** * 生成xml文件字符串 * @param packageParams * @return */private String toXml(List<NameValuePair> packageParams) {    StringBuffer xml =new StringBuffer("");    xml.append("<xml>");    for(int i = 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>");        }    }    return new String(xml);}

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

/**     * 生成预订单的信息     * @return     */    private String getProductArgs(){            String nonceStr = genNonceStr();//随机字符串            List<NameValuePair> packageParams = new LinkedList<NameValuePair>();            packageParams                    .add(new BasicNameValuePair("appid", Constants.APP_ID));            packageParams.add(new BasicNameValuePair("body", aliOrderBO.getName()));//商品名称//            packageParams.add(new BasicNameValuePair("input_charset", "UTF-8"));            packageParams.add(new BasicNameValuePair("detail", aliOrderBO.getDescribe()));//商品详情            packageParams                    .add(new BasicNameValuePair("mch_id", Constants.MCH_ID));//商户号            packageParams.add(new BasicNameValuePair("nonce_str", nonceStr));//随机字符串            packageParams.add(new BasicNameValuePair("notify_url", UrlStrings.getUrl(UrlIds.WXPAY_NOTIFY)));//接收服务器异步结果的url            String orderCode = getWXOutTradeNo();//生成商户订单号            ((MyApplication) getApplication()).setWxTradeNo(orderCode);//将订单号保存            packageParams.add(new BasicNameValuePair("out_trade_no",//商户订单号                    orderCode));            packageParams.add(new BasicNameValuePair("spbill_create_ip",getIp()));//用户终端IP            double totalFee = aliOrderBO.getPrice()*100;//价格,单位是分            packageParams.add(new BasicNameValuePair("total_fee", String.valueOf((int)totalFee)));//订单总金额            packageParams.add(new BasicNameValuePair("trade_type", "APP"));//交易类型            String packageValue = genPackage(packageParams);            packageParams.add(new BasicNameValuePair("sign", packageValue));        try {            return new String(toXml(packageParams).toString().getBytes(), "ISO8859-1");        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        }        return null;    }

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

String url = String.format("https://api.mch.weixin.qq.com/pay/unifiedorder");//微信预订单的地址,请参考微信官方new GetPrepayIdTask().execute(url, entry);//访问微信服务器//上传的类private class GetPrepayIdTask extends AsyncTask<String, Void, PrepareWXPayBean> {     @Override     protected void onPreExecute() {     }     @Override     protected void onPostExecute(PrepareWXPayBean result) {//这个PrepareWXPayBean是服务器返回的结果,自定义的类         if (result.localRetCode == PrepareWXPayBean.LocalRetCode.ERR_OK) {             sendPayReq(result);         } else {             Log.e("TAG", "error");         }     }     @Override     protected void onCancelled() {         super.onCancelled();     }     @Override     protected PrepareWXPayBean doInBackground(String... params) {         PrepareWXPayBean result = new PrepareWXPayBean();         if (params == null || params.length <= 0) {             result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_ARGU;             return result;         }         byte[] buf = Util.httpPost(params[0],params[1]);         if (buf == null || buf.length == 0) {             result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_HTTP;             return result;         }         String content = new String(buf);         result = decodeXML(content);         result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_OK;         return result;     } }

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

<xml>   <return_code><![CDATA[SUCCESS]]></return_code>   <return_msg><![CDATA[OK]]></return_msg>   <appid><![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>

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

/** * 解析xml * @param content * @return */private PrepareWXPayBean decodeXML(String content) {    XmlPullParser parser = Xml.newPullParser();    PrepareWXPayBean bean = new PrepareWXPayBean();    try {        parser.setInput(new StringReader(content));        int event = parser.getEventType();        while (event != XmlPullParser.END_DOCUMENT) {            switch (event) {                case XmlPullParser.START_DOCUMENT:                    break;                case XmlPullParser.START_TAG:                    if ("return_code".equals(parser.getName())) {                        parser.next();                        bean.setReturn_code(parser.getText());                    } else if ("return_msg".equals(parser.getName())) {                        parser.next();                        bean.setReturn_msg(parser.getText());                    }else if ("appid".equals(parser.getName())) {                        parser.next();                        bean.setAppid(parser.getText());                    }else if ("mch_id".equals(parser.getName())) {                        parser.next();                        bean.setMch_id(parser.getText());                    }else if ("nonce_str".equals(parser.getName())) {                        parser.next();                        bean.setNonce_str(parser.getText());                    }else if ("sign".equals(parser.getName())) {                        parser.next();                        bean.setSign(parser.getText());                    }else if ("result_code".equals(parser.getName())) {                        parser.next();                        bean.setResult_code(parser.getText());                    }else if ("prepay_id".equals(parser.getName())) {                        parser.next();                        bean.setPrepay_id(parser.getText());                    }else if ("trade_type".equals(parser.getName())) {                        parser.next();                        bean.setTrade_type(parser.getText());                    }                    break;                case XmlPullParser.END_TAG:                    break;            }            event = parser.next();        }    } catch (Exception e) {        e.printStackTrace();    }    return bean;}

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

private static final char SPLIT = '&';/** * 微信开放平台和商户约定的密钥 * * 注意:不能hardcode在客户端,建议genSign这个过程由服务器端完成 */private String genSign(PayReq req) {    StringBuilder sb = new StringBuilder();    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这个过程都由服务器端完成    return MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();}

生成时间戳的方法:

/** * 生成时间戳 * @return */private String genTimeStamp() {    return String.valueOf(System.currentTimeMillis() / 1000);}

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

private void 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 = new PayReq();    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

 @Override    public void onResp(BaseResp baseResp) {        Log.d("TAG", "onPayFinish, errCode = " + baseResp.errCode);        //Log.e("TAG", "info=" + baseResp.errStr + ",transaction=" + baseResp.transaction + ",openId=" + baseResp.openId);        Bundle bundle = new Bundle();        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,分享和登录的也是一样

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

1 0