關(guān)于接入微信淫奔、支付寶支付山涡,看這篇就夠了

前言

由于公司項(xiàng)目需要,安排我負(fù)責(zé)接入微信唆迁、支付寶支付功能鸭丛。從最開始的申請(qǐng)賬號(hào)到最后的功能完成,全程參與其中√圃穑現(xiàn)在功能完成了鳞溉,正好寫篇總結(jié)文檔。順便寫了個(gè)Android端的demo鼠哥,把整個(gè)功能都整合了一下熟菲。里面包括獲取訂單看政,簽名,驗(yàn)證抄罕,調(diào)起支付允蚣,支付完成同步回調(diào)整個(gè)流程。配合總結(jié)文檔食用最佳呆贿,歡迎star ~

接入支付寶支付

支付寶接入相對(duì)而言比較簡單嚷兔,按照官方文檔和demo基本沒什么大問題。先看下支付寶支付的流程圖做入。


alipay2.png

體驗(yàn)demo

如果你已經(jīng)按照官方教程完成了接入支付寶的準(zhǔn)備工作冒晰,那么用申請(qǐng)的appid和生成的公鑰私鑰替換demo中PayConstants的相關(guān)屬性,就可以直接體驗(yàn)支付寶支付了竟块。這其中需要注意RSA_PRIVATE 和 RSA2_PRIVATE 都是支付寶私鑰壶运,分別對(duì)應(yīng)著RSA和RSA2簽名算法。
目前新建應(yīng)用只能使用RSA2簽名算法彩郊,老應(yīng)用還是可以使用RSA簽名算法前弯,不過在調(diào)用支付時(shí)需要標(biāo)注是否是使用RSA2簽名算法。

    /**
     * RSA_PRIVATE 和支付寶私鑰
     */
    public static final String RSA2_PRIVATE = "XXXXXXX";
    /**
     * 支付寶APP_ID
     */
    public static final String ALI_APP_ID = "2018083061113973";

接入支付SDK

下載最新版的支付SDK秫逝,將其復(fù)制到你項(xiàng)目的libs文件夾中恕出。在項(xiàng)目的build.gradle中添加如下代碼:

allprojects {
    repositories {

        // 添加下面的內(nèi)容
        flatDir {
            dirs 'libs'
        }

        // ... jcenter() 等其他倉庫
    }
}

在APP的build.gradle中,添加支付SDK的aar包依賴:

    compile (name: 'alipaySdk-15.5.9-20181123210601', ext: 'aar')

在AndroidManifest中添加以下權(quán)限:


    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

    <uses-permission android:name="android.permission.INTERNET" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

以上就完成了接入SDK的工作违帆。這其中需要注意浙巫,WRITE_EXTERNAL_STORAGE和READ_PHONE_STATE權(quán)限需要在代碼中動(dòng)態(tài)獲取。

獲取簽名訂單

按照接入流程圖所示刷后,第一步是先獲取簽名訂單的畴。為了防止應(yīng)用的支付寶私鑰暴露,這一步都是在服務(wù)器中完成尝胆。demo中為了方便方便體驗(yàn)丧裁,直接寫在了客戶端中。如果是開發(fā)線上APP含衔,不建議這么做煎娇。支付寶獲取簽名訂單需要以下幾個(gè)步驟:

  • 將請(qǐng)求參數(shù)按照key=value&key=value方式拼接的未簽名原始字符串。
  • 再對(duì)原始字符串進(jìn)行簽名
  • 最后對(duì)請(qǐng)求字符串的所有一級(jí)value(biz_content作為一個(gè)value)進(jìn)行encode贪染,編碼格式按請(qǐng)求串中的charset為準(zhǔn)缓呛,沒傳charset按UTF-8處理,獲得最終的請(qǐng)求字符串

按照官方的請(qǐng)求參數(shù)說明杭隙,demo中的代碼示例如下:

    /**
     * 生成map格式訂單
     *
     * @param app_id  支付平臺(tái)中你APP的app_id
     * @param rsa2    是否是使用RSA2簽名算法
     * @param payBean 包括訂單金額哟绊,訂單描述,訂單詳細(xì)內(nèi)容痰憎,創(chuàng)建訂單時(shí)間戳
     * @param version 接入的支付寶SDK版本號(hào)
     * @return
     */
    public static Map<String, String> buildOrderParamMap(String app_id, boolean rsa2, PayBean payBean, String version) {
        Map<String, String> keyValues = new HashMap<>();
        //支付寶的amount單位為元
        float amount = (payBean.getAmount()) / 100;

        keyValues.put("app_id", app_id);
        /**
         * biz_content參數(shù)包括所有的訂單信息
         */
        keyValues.put("biz_content", "{\"timeout_express\":\"30m\",\"product_code\":\"QUICK_MSECURITY_PAY\",\"total_amount\":\"" + amount + "\",\"subject\":\"" + payBean.getBody() + "\",\"body\":\"我是測試數(shù)據(jù)\",\"out_trade_no\":\"" + getOutTradeNo() + "\"}");

        keyValues.put("charset", "utf-8");

        keyValues.put("method", "alipay.trade.app.pay");

        keyValues.put("sign_type", rsa2 ? "RSA2" : "RSA");

        keyValues.put("notify_url",payBean.getNotify_url());

        keyValues.put("timestamp", getDateToString(payBean.getTime(), "yyyy-MM-dd HH:mm:ss"));

        keyValues.put("version", version);

        return keyValues;
    }

    
        /**
     * 將map轉(zhuǎn)換為string票髓,構(gòu)造原始支付訂單參數(shù)信息
     *
     * @param map 支付訂單參數(shù)
     * @return
     */
    public static String buildOrderParam(Map<String, String> map) {
        List<String> keys = new ArrayList<String>(map.keySet());

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < keys.size() - 1; i++) {
            String key = keys.get(i);
            String value = map.get(key);
            sb.append(buildKeyValue(key, value, true));
            sb.append("&");
        }

        String tailKey = keys.get(keys.size() - 1);
        String tailValue = map.get(tailKey);
        sb.append(buildKeyValue(tailKey, tailValue, true));

        return sb.toString();
    }

先根據(jù)用戶支付的訂單參數(shù)攀涵,如訂單價(jià)格、訂單描述等炬称,構(gòu)建原始map格式訂單汁果。這其中需要注意,所有的訂單參數(shù)(total_amount玲躯,timeout_express据德,subject)都一起存入biz_content中。然后將map格式原始訂單參數(shù)跷车,進(jìn)行排序棘利,轉(zhuǎn)換為string格式。這其中還需要傳入一個(gè)notify_url參數(shù)朽缴,后面會(huì)用到善玫。

    /**
     * 將訂單信息簽名,encode
     *
     * @param orderParam    待簽名的訂單信息
     * @param rsaKey 支付寶私鑰
     * @param rsa2   是否使用RSA2簽名算法
     * @return
     */
    public static String getSign(Map<String, String> map, String rsaKey, boolean rsa2) {

        List<String> keys = new ArrayList<String>(map.keySet());
        // key排序
        Collections.sort(keys);

        StringBuilder authInfo = new StringBuilder();
        for (int i = 0; i < keys.size() - 1; i++) {
            String key = keys.get(i);
            String value = map.get(key);
            authInfo.append(buildKeyValue(key, value, false));
            authInfo.append("&");
        }

        String tailKey = keys.get(keys.size() - 1);
        String tailValue = map.get(tailKey);
        authInfo.append(buildKeyValue(tailKey, tailValue, false));

        //將排序后的序列進(jìn)行簽名
        String oriSign = sign(authInfo.toString(), rsaKey, rsa2);
        String encodedSign = "";

        try {
            encodedSign = URLEncoder.encode(oriSign, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "sign=" + encodedSign;
    }
    

    /**
     * 簽名
     *
     * @param content    待簽名信息
     * @param privateKey 支付寶私鑰
     * @param rsa2       是否使用RSA2簽名算法
     * @return
     */
    public static String sign(String content, String privateKey, boolean rsa2) {
        try {
            PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
                    Base64.decode(privateKey));
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, "BC");
            PrivateKey priKey = keyFactory.generatePrivate(priPKCS8);

            java.security.Signature signature = java.security.Signature
                    .getInstance(getAlgorithms(rsa2));

            signature.initSign(priKey);
            signature.update(content.getBytes(DEFAULT_CHARSET));

            byte[] signed = signature.sign();

            return Base64.encode(signed);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

然后將排序后的訂單信息進(jìn)行簽名和encode密强,獲取最終請(qǐng)求支付的字符串茅郎。

請(qǐng)求支付

支付寶請(qǐng)求支付,只需要調(diào)用alipay.payV2方法或渤。

        Runnable payRunnable = new Runnable() {

            @Override
            public void run() {
                PayTask alipay = new PayTask(MainActivity.this);
                String  orderInfo = AliPayUtil.getKidoOrderInfo(getPayBean(),alipay.getVersion());
                Map<String, String> result = alipay.payV2(orderInfo, true);
                Log.i("msp", result.toString());
                Message msg = new Message();
                msg.what = SDK_PAY_FLAG;
                msg.obj = result;
                mHandler.sendMessage(msg);
            }
        };

        Thread payThread = new Thread(payRunnable);
        payThread.start();

其中系冗,同步支付結(jié)果通過message傳遞出來。這其中需要注意薪鹦,支付結(jié)果必須通過服務(wù)器端返回的異步結(jié)果來進(jìn)行最終確認(rèn)掌敬。不能僅根據(jù)本地結(jié)果來顯示給用戶。

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @SuppressWarnings("unused")
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SDK_PAY_FLAG: {
                    @SuppressWarnings("unchecked")
                    PayResult payResult = new PayResult((Map<String, String>) msg.obj);
                    /**
                     對(duì)于支付結(jié)果池磁,請(qǐng)商戶依賴服務(wù)端的異步通知結(jié)果奔害。同步通知結(jié)果,僅作為支付結(jié)束的通知地熄。
                     */
                    String resultInfo = payResult.getResult();// 同步返回需要驗(yàn)證的信息
                    String resultStatus = payResult.getResultStatus();
                    // 判斷resultStatus 為9000則代表支付成功
                    if (TextUtils.equals(resultStatus, "9000")) {
                        // 該筆訂單是否真實(shí)支付成功华临,需要依賴服務(wù)端的異步通知。
                        paymentSucceed();
                    } else {
                        // 該筆訂單真實(shí)的支付結(jié)果端考,需要依賴服務(wù)端的異步通知雅潭。
                        paymentFailed();
                    }
                    break;
                }
                default:
                    break;
            }
        }
    };

前面?zhèn)魅氲闹Ц秴?shù)中,有一個(gè)notify_url跛梗。支付寶服務(wù)器會(huì)把異步支付結(jié)果通過調(diào)用這個(gè)notify_url來返回給我們服務(wù)器,然后通過服務(wù)器返回給APP棋弥,APP通過與本地結(jié)果對(duì)比核偿,來反饋給用戶支付結(jié)果。

以上顽染,整個(gè)支付流程就走完了漾岳。

微信支付

微信支付接入起來稍微有點(diǎn)復(fù)雜轰绵。官方文檔上面也有相應(yīng)介紹。先看下官方流程圖尼荆。

wxpay.png

總結(jié)下就是:

  • 先準(zhǔn)備本地下單信息左腔,對(duì)下單信息進(jìn)行排序、簽名和XML格式化捅儒。
  • 用本地下單信息調(diào)用微信統(tǒng)一下單接口液样,獲取統(tǒng)一下單信息。并對(duì)該信息進(jìn)行簽名驗(yàn)證巧还。
  • 將微信返回的統(tǒng)一下單信息進(jìn)行解析鞭莽,并轉(zhuǎn)化為調(diào)用微信支付所需格式。
  • 調(diào)用微信支付麸祷,同步返回支付結(jié)果澎怒。
  • 微信服務(wù)器調(diào)用傳入的notify_url,異步返回支付結(jié)果阶牍。本地服務(wù)器將結(jié)果返回給APP喷面,反饋給用戶。

接入微信SDK

首先在官網(wǎng)上下載SDK走孽,并將其復(fù)制到libs文件夾中惧辈。在APP的build.gradle中添加如下代碼(一般Android studio會(huì)自動(dòng)生成這段代碼):

    implementation fileTree(dir: 'libs', include: ['*.jar'])

微信支付的本地結(jié)果回調(diào),需要在項(xiàng)目中新建wxapi文件夾融求,在其中添加WXPayEntryActivity咬像,并繼承IWXAPIEventHandler接口。當(dāng)支付完成后生宛,結(jié)果會(huì)同步到WXPayEntryActivity的onResp(BaseResp baseResp)方法中县昂。

public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler {


    private IWXAPI api;

    private static WXPayListenter mListener;

    public static void setmListener(WXPayListenter listener) {
        mListener = listener;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        api = WXAPIFactory.createWXAPI(this, PayConstants.WX_APP_ID, false);

        try {
            api.handleIntent(getIntent(), this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        setIntent(intent);
        api.handleIntent(intent, this);
    }


    @Override
    public void onReq(BaseReq baseReq) {

    }

    @Override
    public void onResp(BaseResp baseResp) {
        //支付結(jié)果還需要發(fā)送給服務(wù)器確認(rèn)支付狀態(tài)
        if (baseResp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
            if (mListener != null) {
                if (baseResp.errCode == 0) {
                    mListener.paymentSucceed();
                } else if (baseResp.errCode == -2) {
                    mListener.paymentCanceled();
                } else {
                    mListener.paymentFailed();
                }
            }
            finish();
        }

    }

    public interface WXPayListenter {
        void paymentSucceed();

        void paymentCanceled();

        void paymentFailed();
    }

}

在AndroidManifest中添加WXPayEntryActivity

        <activity
            android:name=".wxapi.WXPayEntryActivity"
            android:exported="true"
            android:launchMode="singleTop" />

以上就完成了接入SDK的工作。如果已經(jīng)接入過微信分享SDK陷舅,那么下載SDK和依賴這一步就可以跳過了倒彰,只需要再添加WXPayEntryActivity就行了。

準(zhǔn)備本地下單信息

自我感覺莱睁,這一步是最麻煩的待讳。首先我們需要本地訂單信息進(jìn)行排序和簽名。

    
    /**
     * 生成訂單信息
     *
     * @param ip      當(dāng)前手機(jī)IP
     * @param orderId 當(dāng)前生成的外部訂單號(hào)
     * @return
     */
    public static SortedMap<String, Object> prepareOrder(String ip, String orderId,PayBean payBean) {

        Map<String, Object> oparams = new LinkedHashMap<String, Object>();
        oparams.put("appid", PayConstants.WX_APP_ID);// 服務(wù)號(hào)的應(yīng)用號(hào)
        oparams.put("body",payBean.getDescribe());// 商品描述
        oparams.put("mch_id", PayConstants.WX_CHD_ID);// 商戶號(hào)
        oparams.put("nonce_str", CreateNoncestr());// 16隨機(jī)字符串(大小寫字母加數(shù)字)
        oparams.put("out_trade_no", orderId);// 商戶訂單號(hào)
        oparams.put("total_fee", payBean.getAmount());// 支付金額 單位分 注意:前端負(fù)責(zé)傳入分
        oparams.put("spbill_create_ip", ip);// IP地址
        oparams.put("notify_url", payBean.getNotify_url()); // 微信回調(diào)地址
        oparams.put("trade_type", PayConstants.TRADE_TYPE);// 支付類型 app
        return sortMap(oparams);
    }
    
    /**
     * 對(duì)map根據(jù)key進(jìn)行排序 ASCII 順序
     *
     * @param
     * @return
     */
    public static SortedMap<String, Object> sortMap(Map<String, Object> map) {

        List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(
                map.entrySet());
        // 排序
        Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
            @Override
            public int compare(Map.Entry<String, Object> o1,
                               Map.Entry<String, Object> o2) {
                // return (o2.getValue() - o1.getValue());//value處理
                return (o1.getKey()).toString().compareTo(o2.getKey());
            }
        });
        // 排序后
        SortedMap<String, Object> sortmap = new TreeMap<String, Object>();
        for (int i = 0; i < infoIds.size(); i++) {
            String[] split = infoIds.get(i).toString().split("=");
            sortmap.put(split[0], split[1]);
        }
        return sortmap;
    }
    
    /**
     * 簽名工具
     *
     * @param characterEncoding 編碼格式 UTF-8
     * @param parameters        請(qǐng)求參數(shù)
     * @return
     */
    public static String createSign(String characterEncoding,
                                    Map<String, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        Iterator<Map.Entry<String, Object>> it = parameters.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next();
            String key = (String) entry.getKey();
            Object value = entry.getValue();//去掉帶sign的項(xiàng)
            if (null != value && !"".equals(value) && !"sign".equals(key)
                    && !"key".equals(key)) {
                sb.append(key + "=" + value + "&");
            }
        }
        sb.append("key=" + PayConstants.WX_KEY);
        return MD5Encode(sb.toString(), characterEncoding).toUpperCase();
    }

    /**
     * MD5
     *
     * @param origin
     * @param charsetname
     * @return
     */
    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname)) {
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            } else {
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
            }
        } catch (Exception exception) {
        }
        return resultString;
    }

這一部分仰剿,上面代碼中已經(jīng)備注的很詳細(xì)创淡,就不展開解釋了。簽名之后南吮,將本地下單信息進(jìn)行xml格式化。

    /**
     * 將請(qǐng)求參數(shù)轉(zhuǎn)換為xml格式的string
     *
     * @param parameters 請(qǐng)求參數(shù)
     * @return
     */
    public static String getRequestXml(SortedMap<String, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Iterator<Map.Entry<String, Object>> iterator = parameters.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Object> entry = (Map.Entry<String, Object>) iterator.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key)
                    || "sign".equalsIgnoreCase(key)) {
                sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">");
            } else {
                sb.append("<" + key + ">" + value + "</" + key + ">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }

這其中需要注意,attach箱锐,body,sign三個(gè)參數(shù)加了CDATA標(biāo)簽幢哨,用于說明數(shù)據(jù)不被XML解析器解析。以上我們就準(zhǔn)備好了本地下單信息岸售,可以直接調(diào)用微信統(tǒng)一下單接口,獲取統(tǒng)一下單訂單信息了袱院。

獲取統(tǒng)一下單信息

先調(diào)用接口獲取統(tǒng)一下單信息。我這里使用的是最基本的httpsRequest腻惠。

    /**
     * 發(fā)送https請(qǐng)求
     *
     * @param requestUrl    請(qǐng)求地址
     * @param requestMethod 請(qǐng)求方式(GET集灌、POST)
     * @param outputStr     提交的數(shù)據(jù)
     * @return 返回微信服務(wù)器響應(yīng)的信息
     * @throws Exception
     */
    public static String httpsRequest(String requestUrl, String requestMethod,
                                      String outputStr) throws Exception {
        try {

            URL url = new URL(requestUrl);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 設(shè)置請(qǐng)求方式(GET/POST)
            conn.setRequestMethod(requestMethod);
            conn.setRequestProperty("content-type",
                    "application/x-www-form-urlencoded");
            // 當(dāng)outputStr不為null時(shí)向輸出流寫數(shù)據(jù)
            if (null != outputStr) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意編碼格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }
            // 從輸入流讀取返回內(nèi)容
            InputStream inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(
                    inputStream, "UTF-8");
            BufferedReader bufferedReader = new BufferedReader(
                    inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            // 釋放資源
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            conn.disconnect();
            return buffer.toString();
        } catch (ConnectException ce) {
            Log.e(TAG, "連接超時(shí):{}" + ce);
            throw new RuntimeException("鏈接異常" + ce);
        } catch (Exception e) {
            Log.e(TAG, "https請(qǐng)求異常:{}" + e);
            throw new RuntimeException("https請(qǐng)求異常" + e);
        }

    }

服務(wù)器返回的下單信息是xml格式梯找,我們需要先進(jìn)行格式轉(zhuǎn)換驯鳖。

    /**
     * @param xml
     * @return Map
     * @description 將xml字符串轉(zhuǎn)換成map
     */
    public static Map<String, Object> readStringXmlOut(String xml) {
        Map<String, Object> map = new HashMap<String, Object>();
        Document doc = null;
        try {
            doc = DocumentHelper.parseText(xml); // 將字符串轉(zhuǎn)為XML
            Element rootElt = doc.getRootElement(); // 獲取根節(jié)點(diǎn)
            @SuppressWarnings("unchecked")
            List<Element> list = rootElt.elements();// 獲取根節(jié)點(diǎn)下所有節(jié)點(diǎn)
            for (Element element : list) { // 遍歷節(jié)點(diǎn)
                map.put(element.getName(), element.getText()); // 節(jié)點(diǎn)的name為map的key,text為map的value
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

檢驗(yàn)API返回的數(shù)據(jù)里面的簽名是否合法,避免數(shù)據(jù)在傳輸?shù)倪^程中被第三方篡改。

    /**
     * 檢驗(yàn)API返回的數(shù)據(jù)里面的簽名是否合法盛正,避免數(shù)據(jù)在傳輸?shù)倪^程中被第三方篡改
     *
     * @param map API返回的下單數(shù)據(jù)
     * @return API簽名是否合法
     * @throws
     * @throws
     * @throws
     */
    public static boolean checkIsSignValidFromResponseString(Map<String, Object> map) {

        try {
            String signFromAPIResponse = map.get("sign").toString();
            if ("".equals(signFromAPIResponse) || signFromAPIResponse == null) {
                Log.d(TAG, "API返回的數(shù)據(jù)簽名數(shù)據(jù)不存在豪筝,有可能被第三方篡改!!!");
                return false;
            }

            //清掉返回?cái)?shù)據(jù)對(duì)象里面的Sign數(shù)據(jù)(不能把這個(gè)數(shù)據(jù)也加進(jìn)去進(jìn)行簽名),然后用簽名算法進(jìn)行簽名
            map.put("sign", "");
            //將API返回的數(shù)據(jù)根據(jù)用簽名算法進(jìn)行計(jì)算新的簽名续崖,用來跟API返回的簽名進(jìn)行比較
            String signForAPIResponse = createSign(Charsets.UTF_8.toString(), map);
            Log.d(TAG, "服務(wù)器回包里面的簽名是:" + signFromAPIResponse + "==服務(wù)器回包數(shù)據(jù)簽名是:" + signForAPIResponse);
            if (!signForAPIResponse.equals(signFromAPIResponse)) {
                //簽名驗(yàn)不過团搞,表示這個(gè)API返回的數(shù)據(jù)有可能已經(jīng)被篡改了
                Log.d(TAG, "API返回的數(shù)據(jù)簽名驗(yàn)證不通過,有可能被第三方篡改!!!");
                return false;
            }
            Log.d(TAG, "恭喜逻恐,API返回的數(shù)據(jù)簽名驗(yàn)證通過!!!");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

最后像吻,還需要對(duì)返回的訂單信息進(jìn)行整理,來得到我們需要的最終調(diào)用微信支付所需要的數(shù)據(jù)拨匆。

    /**
     * 提取微信服務(wù)器返回的下單數(shù)據(jù)涮雷,組合成調(diào)用微信支付所需的map
     * 對(duì)map進(jìn)行排序簽名
     *
     * @param resultMap
     * @return
     */
    private static Map<String, Object> getPayResultMap(Map<String, Object> resultMap) {
        Map<String, Object> map = new LinkedHashMap<String, Object>();
        map.put("appid", resultMap.get("appid"));
        map.put("partnerid", resultMap.get("mch_id"));
        map.put("prepayid", resultMap.get("prepay_id"));
        map.put("package", "Sign=WXPay");
        map.put("noncestr", resultMap.get("nonce_str"));
        map.put("timestamp", getTimeStamp());
        //對(duì)map進(jìn)行排序
        SortedMap<String, Object> sortedMap = sortMap(map);
        //對(duì)map進(jìn)行簽名仑扑,并將簽名加入map
        sortedMap.put("sign", createSign(Charsets.UTF_8.toString(), sortedMap));
        return sortedMap;
    }

這其中需要注意,我們還是需要將最終的訂單信息進(jìn)行重新排序,然后重新簽名。這里的簽名方式一定要與統(tǒng)一下單接口使用的一致俱济。具體可以參照官方簽名文檔嘶是。

調(diào)用支付

總算是走到這一步了,維信調(diào)用支付需要先判斷當(dāng)前手機(jī)是否有安裝維信蛛碌,維信版本是否支持支付功能聂喇。

    /**
     * 判斷當(dāng)前手機(jī)是否支持維信支付
     * @return
     */
    private boolean wxCanPay(){
        try{
            if(!iwxapi.isWXAppInstalled()){
                Toast.makeText(MainActivity.this,"請(qǐng)安裝微信客戶端", Toast.LENGTH_SHORT).show();
                return false;
            }else if(!iwxapi.isWXAppSupportAPI()){
                Toast.makeText(MainActivity.this, "當(dāng)前微信版本不支持支付", Toast.LENGTH_SHORT).show();
                return false;
            }
        }catch (Exception e){
            e.printStackTrace();
            Toast.makeText(MainActivity.this, "請(qǐng)安裝最新微信客戶端", Toast.LENGTH_SHORT).show();
            return false;
        }
        return true;
    }

調(diào)用微信支付

        Runnable wxPayRunnable = new Runnable() {
            @Override
            public void run() {
                try{
                    Map<String,Object> orderInfo = WXPayUtil.getWXOrderInfo(getPayBean(),MainActivity.this);
                    PayReq req = new PayReq();
                    req.appId = (String)orderInfo.get("appid");
                    req.partnerId = (String)orderInfo.get("partnerid");
                    req.prepayId = (String)orderInfo.get("prepayid");
                    req.nonceStr = (String)orderInfo.get("noncestr");
                    req.timeStamp = (String)orderInfo.get("timestamp");
                    req.packageValue = (String)orderInfo.get("package");
                    req.sign = (String)orderInfo.get("sign");
                    iwxapi.sendReq(req);
                }catch (Exception e ){
                    e.printStackTrace();
                }


            }
        };
        Thread payThread = new Thread(wxPayRunnable);
        payThread.start();

按照第一步接入準(zhǔn)備中所說,支付結(jié)果會(huì)返回到WXPayEntryActivity的onResp(BaseResp baseResp)蔚携。我們需要獲取到結(jié)果后希太,finish掉這個(gè)activity。然后在支付結(jié)果頁面等待服務(wù)器返回異步支付結(jié)果酝蜒。

總結(jié)

接入兩個(gè)支付誊辉,收獲還是有的。設(shè)計(jì)到支付相關(guān)亡脑,安全是最重要的堕澄。所以簽名加密等所有操作都是在服務(wù)器端進(jìn)行的,最終落實(shí)到APP端霉咨,也只是寫個(gè)界面奈偏,調(diào)個(gè)接口而已。但是相關(guān)的流程和邏輯還是有必要理一理躯护,總結(jié)一下的惊来。最后再附上demo的地址,歡迎大家star

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棺滞,一起剝皮案震驚了整個(gè)濱河市裁蚁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌继准,老刑警劉巖枉证,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異移必,居然都是意外死亡室谚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門崔泵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秒赤,“玉大人,你說我怎么就攤上這事憎瘸∪肜海” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵幌甘,是天一觀的道長潮售。 經(jīng)常有香客問我痊项,道長,這世上最難降的妖魔是什么酥诽? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任鞍泉,我火速辦了婚禮,結(jié)果婚禮上肮帐,老公的妹妹穿的比我還像新娘塞弊。我一直安慰自己,他們只是感情好泪姨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著饰抒,像睡著了一般肮砾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袋坑,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天仗处,我揣著相機(jī)與錄音,去河邊找鬼枣宫。 笑死婆誓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的也颤。 我是一名探鬼主播洋幻,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼翅娶!你這毒婦竟也來了文留?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤竭沫,失蹤者是張志新(化名)和其女友劉穎燥翅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜕提,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡森书,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谎势。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凛膏。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖脏榆,靈堂內(nèi)的尸體忽然破棺而出译柏,到底是詐尸還是另有隱情,我是刑警寧澤姐霍,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布鄙麦,位于F島的核電站典唇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏胯府。R本人自食惡果不足惜介衔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骂因。 院中可真熱鬧炎咖,春花似錦、人聲如沸寒波。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俄烁。三九已至绸栅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間页屠,已是汗流浹背粹胯。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辰企,地道東北人风纠。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像牢贸,于是被迫代替她去往敵國和親竹观。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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