Android對(duì)接微信支付

這幾天給app對(duì)接了微信支付墅拭,官方的demo真是薇宠。。。第一次果然不免爬幾個(gè)坑陶因,記錄一下,避免遺忘垂蜗。

錯(cuò)誤說(shuō)明

  • 在保證提供的參數(shù)都正確的情況下楷扬,往往測(cè)試微信支付失敗的原因是簽名錯(cuò)誤,這個(gè)時(shí)候應(yīng)該仔細(xì)核對(duì)流程贴见。就我自己而言碰到的問(wèn)題就是搞錯(cuò)了最后發(fā)送訂單的時(shí)候的簽名sign參數(shù)烘苹。這個(gè)簽名不是第一次組拼的簽名,也不是生成預(yù)支付交易單片部,獲取prepayId時(shí)返回的簽名镣衡,而是獲取到prepayId后用prepayId和其他參數(shù)第二次組拼成的sign。

流程說(shuō)明

  • 詳細(xì)的業(yè)務(wù)流程為微信官方文檔里有档悠,我這邊以所有參數(shù)由客戶端提供的方式簡(jiǎn)單說(shuō)明一下請(qǐng)求流程廊鸥。
  1. 獲取必須參數(shù),第一次簽名辖所。
    需要說(shuō)明的是簽名中的key參數(shù)是由我方自行在微信商戶平臺(tái)設(shè)置的惰说。具體位置:微信商戶平臺(tái)(pay.weixin.qq.com)-->賬戶設(shè)置-->API安全-->密鑰設(shè)置

  2. 生成預(yù)支付交易單,獲取prepayId缘回。
    調(diào)用微信的接口“https://api.mch.weixin.qq.com/pay/unifiedorder”獲取prepayId吆视。另外返回信息中的sign并不是最后調(diào)起支付中的sign參數(shù)。

  3. 第二次簽名酥宴。
    用"appid"啦吧,"noncestr","package"拙寡,"partnerid"授滓,"prepayid","timestamp"生成第二次簽名倒庵。簽名方式與第一次相同褒墨,參數(shù)不同(注意參數(shù)順序)。這樣就拿到了調(diào)起微信支付所需的所有參數(shù)擎宝。

  4. 調(diào)起微信支付郁妈。

代碼說(shuō)明

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: //獲取參數(shù)及調(diào)起支付
                    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參數(shù),用于生成預(yù)支付交易單绍申,獲取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/"));//你自己的的回調(diào)地址X洹9苏谩!
            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;
        }
    }

    /**
     * 獲取支付參數(shù)
     */
    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, "預(yù)支付交易單生成失敗", 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);
    }

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


    /**
     * 生成隨機(jī)數(shù)
     * @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;
    }

    /**
     * 獲取時(shí)間戳
     *
     * @return
     */
    private static long genTimeStamp() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * 獲取設(shè)備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";
    }

    /**
     * 獲取外部訂單號(hào)
     */
    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;
    }

    /**
     * 轉(zhuǎn)換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();
    }

    /**
     * 轉(zhuǎn)換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) {
                            //實(shí)例化student對(duì)象
                            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;
        }
    }
}

參考

大致的流程就如上面所說(shuō)胃碾,其他沒(méi)有提到的大多在官方demo中有涨享。需要詳細(xì)的過(guò)程可以參考下面兩個(gè)兩篇(感謝原作者):
Android微信支付詳解與Demo
詳細(xì)介紹Android開(kāi)發(fā)集成微信支付(二) --- 完整版本

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市仆百,隨后出現(xiàn)的幾起案子厕隧,更是在濱河造成了極大的恐慌,老刑警劉巖俄周,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吁讨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡峦朗,警方通過(guò)查閱死者的電腦和手機(jī)建丧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)波势,“玉大人翎朱,你說(shuō)我怎么就攤上這事〕呦常” “怎么了拴曲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)迄埃。 經(jīng)常有香客問(wèn)我疗韵,道長(zhǎng),這世上最難降的妖魔是什么侄非? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任蕉汪,我火速辦了婚禮,結(jié)果婚禮上逞怨,老公的妹妹穿的比我還像新娘者疤。我一直安慰自己,他們只是感情好叠赦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布驹马。 她就那樣靜靜地躺著,像睡著了一般除秀。 火紅的嫁衣襯著肌膚如雪糯累。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天册踩,我揣著相機(jī)與錄音泳姐,去河邊找鬼。 笑死暂吉,一個(gè)胖子當(dāng)著我的面吹牛胖秒,可吹牛的內(nèi)容都是我干的缎患。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼阎肝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挤渔!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起风题,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤判导,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后俯邓,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體骡楼,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年稽鞭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片引镊。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朦蕴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弟头,到底是詐尸還是另有隱情吩抓,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布赴恨,位于F島的核電站疹娶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏伦连。R本人自食惡果不足惜雨饺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惑淳。 院中可真熱鬧额港,春花似錦、人聲如沸歧焦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)绢馍。三九已至向瓷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舰涌,已是汗流浹背猖任。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舵稠,地道東北人超升。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓入宦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親室琢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乾闰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容