Android对接微信支付

这几天给app对接了微信支付,官方的demo真是。。。第一次果然不免爬几个坑,记录一下,避免遗忘。

错误说明

  • 在保证提供的参数都正确的情况下,往往测试微信支付失败的原因是签名错误,这个时候应该仔细核对流程。就我自己而言碰到的问题就是搞错了最后发送订单的时候的签名sign参数。这个签名不是第一次组拼的签名,也不是生成预支付交易单,获取prepayId时返回的签名,而是获取到prepayId后用prepayId和其他参数第二次组拼成的sign。

流程说明

  • 详细的业务流程为微信官方文档里有,我这边以所有参数由客户端提供的方式简单说明一下请求流程。
  1. 获取必须参数,第一次签名。
    需要说明的是签名中的key参数是由我方自行在微信商户平台设置的。具体位置:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置

  2. 生成预支付交易单,获取prepayId。
    调用微信的接口“https://api.mch.weixin.qq.com/pay/unifiedorder”获取prepayId。另外返回信息中的sign并不是最后调起支付中的sign参数。

  3. 第二次签名。
    用"appid","noncestr","package","partnerid","prepayid","timestamp"生成第二次签名。签名方式与第一次相同,参数不同(注意参数顺序)。这样就拿到了调起微信支付所需的所有参数。

  4. 调起微信支付。

代码说明

public class WXPayUtils {

    private Context mContext;
    private PayReq req;
    private IWXAPI iwxapi; //微信支付api
    private Map<String, String> resultunifiedorder;

    public WXPayUtils() {
    }

    public WXPayUtils(Context context) {
        this.mContext = context;
        req = new PayReq();
        iwxapi = WXAPIFactory.createWXAPI(context, null); //初始化微信api
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0: //获取参数及调起支付
                    getPayParameter();
                    sendPayReq();
                    break;
                default:
                    break;
            }
        }
    };

    /**
     * 第一次签名及生成prepayId
     **/
    public void toWXPayAndSign(int money) {
        PrePayIdAsyncTask prePayIdAsyncTask = new PrePayIdAsyncTask();
        prePayIdAsyncTask.execute(money);
    }

    private class PrePayIdAsyncTask extends AsyncTask<Integer, Void, Map<String, String>> {
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
        }

        @Override
        protected Map<String, String> doInBackground(Integer... params) {
            // TODO Auto-generated method stub
            String urlString = "https://api.mch.weixin.qq.com/pay/unifiedorder";
            int money = params[0];
            String entity = getProductArgs(money);
            Log.e("Simon", ">>>>" + entity);

            byte[] buf = Util.httpPost(urlString, entity);
            String content = new String(buf);
            Log.e("orion", "----" + content);
            Map<String, String> xml = decodeXml(content);
            return xml;
        }

        @Override
        protected void onPostExecute(Map<String, String> result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            resultunifiedorder = result;
            handler.sendEmptyMessage(0);
        }
    }

    /**
     * 生成xml参数,用于生成预支付交易单,获取prepayId
     * @param money
     * @return
     */
    private String getProductArgs(int money) {
        StringBuffer xml = new StringBuffer();
        try {
            DecimalFormat df = new DecimalFormat("#");
            String price = df.format(money * 100);
            String nonceStr = getNonceStr();
            xml.append("<xml>");
            List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
            packageParams.add(new BasicNameValuePair("appid", WXKeys.WX_APPID));
            packageParams.add(new BasicNameValuePair("body", "APP pay test"));
            packageParams.add(new BasicNameValuePair("mch_id", WXKeys.WX_MCH_ID));
            packageParams.add(new BasicNameValuePair("nonce_str", nonceStr));
            packageParams.add(new BasicNameValuePair("notify_url", "http://www.baidu.com/"));//你自己的的回调地址!!!
            packageParams.add(new BasicNameValuePair("out_trade_no", getOutTradeNo()));
            packageParams.add(new BasicNameValuePair("total_fee", price));
            packageParams.add(new BasicNameValuePair("trade_type", "APP"));

            String sign = getPackageSign(packageParams);
            packageParams.add(new BasicNameValuePair("sign", sign));
            String xmlString = toXml(packageParams);
            return xmlString;
        } catch (Exception e) {
            // TODO: handle exception
            return null;
        }
    }

    /**
     * 获取支付参数
     */
    private void getPayParameter() {
        req.appId = WXKeys.WX_APPID;
        req.partnerId = WXKeys.WX_MCH_ID;
        if (resultunifiedorder != null) {
            req.prepayId = resultunifiedorder.get("prepay_id");
        } else {
            Toast.makeText(mContext, "预支付交易单生成失败", Toast.LENGTH_SHORT).show();
        }
        req.packageValue = "Sign=WXPay";
        req.nonceStr = getNonceStr();
        req.timeStamp = String.valueOf(genTimeStamp());

        List<NameValuePair> signParams = new LinkedList<NameValuePair>();
        signParams.add(new BasicNameValuePair("appid", req.appId));
        signParams.add(new BasicNameValuePair("noncestr", req.nonceStr));
        signParams.add(new BasicNameValuePair("package", req.packageValue));
        signParams.add(new BasicNameValuePair("partnerid", req.partnerId));
        signParams.add(new BasicNameValuePair("prepayid", req.prepayId));
        signParams.add(new BasicNameValuePair("timestamp", req.timeStamp));

        req.sign = getPaySign(signParams);
    }

    /**
     * 调起微信支付
     */
    private void sendPayReq() {
        iwxapi.registerApp(WXKeys.WX_APPID);
        iwxapi.sendReq(req);
    }


    /**
     * 生成随机数
     * @return
     */
    private String getNonceStr() {
        // TODO Auto-generated method stub
        Random random = new Random();

        return getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
    }

    /**
     * 生成第一次签名
     * @param params
     * @return
     */
    private String getPackageSign(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(WXKeys.WX_PRIVATE_KEY);


        String packageSign = getMessageDigest(sb.toString().getBytes()).toUpperCase();
        Log.e("Simon", ">>>>" + packageSign);
        return packageSign;
    }

    /**
     * 生成第二次签名
     * @param params
     * @return
     */
    private String getPaySign(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(WXKeys.WX_PRIVATE_KEY);

        String appSign = getMessageDigest(sb.toString().getBytes());
        Log.e("Simon","----"+appSign);
        return appSign;
    }

    /**
     * 获取时间戳
     *
     * @return
     */
    private static long genTimeStamp() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * 获取设备ip地址
     * @return
     */
    private static String getIpAddressString() {
        try {
            for (Enumeration<NetworkInterface> enNetI = NetworkInterface
                    .getNetworkInterfaces(); enNetI.hasMoreElements(); ) {
                NetworkInterface netI = enNetI.nextElement();
                for (Enumeration<InetAddress> enumIpAddr = netI
                        .getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
                    InetAddress inetAddress = enumIpAddr.nextElement();
                    if (inetAddress instanceof Inet4Address && !inetAddress.isLoopbackAddress()) {
                        return inetAddress.getHostAddress();
                    }
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return "127.0.0.1";
    }

    /**
     * 获取外部订单号
     */
    String getOutTradeNo() {
        SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss");
        Date date = new Date();
        String strKey = format.format(date);

        java.util.Random r = new java.util.Random();
        strKey = strKey + r.nextInt();
        strKey = strKey.substring(0, 15);
        return strKey;
    }

    /**
     * 转换xml
     * @param params
     * @return
     */
    private static String toXml(List<NameValuePair> params) {
        StringBuilder sb = new StringBuilder();
        sb.append("<xml>");
        for (int i = 0; i < params.size(); i++) {
            sb.append("<" + params.get(i).getName() + ">");


            sb.append(params.get(i).getValue());
            sb.append("</" + params.get(i).getName() + ">");
        }
        sb.append("</xml>");

        Log.e("Simon", ">>>>" + sb.toString());
        return sb.toString();
    }

    /**
     * 转换xml
     * @param content
     * @return
     */
    private Map<String, String> decodeXml(String content) {

        try {
            Map<String, String> xml = new HashMap<String, String>();
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(new StringReader(content));
            int event = parser.getEventType();
            while (event != XmlPullParser.END_DOCUMENT) {

                String nodeName = parser.getName();
                switch (event) {
                    case XmlPullParser.START_DOCUMENT:

                        break;
                    case XmlPullParser.START_TAG:

                        if ("xml".equals(nodeName) == false) {
                            //实例化student对象
                            xml.put(nodeName, parser.nextText());
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        break;
                }
                event = parser.next();
            }

            return xml;
        } catch (Exception e) {
            Log.e("Simon", "----" + e.toString());
        }
        return null;

    }

    /**
     * md5加密
     *
     * @param buffer
     * @return
     */
    private static String getMessageDigest(byte[] buffer) {
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("MD5");
            mdTemp.update(buffer);
            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 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            return null;
        }
    }
}

参考

大致的流程就如上面所说,其他没有提到的大多在官方demo中有。需要详细的过程可以参考下面两个两篇(感谢原作者):
Android微信支付详解与Demo
详细介绍Android开发集成微信支付(二) --- 完整版本

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容