1. 程式人生 > >Android支付開發(微信)

Android支付開發(微信)

微信接入支付流程跟支付寶差不多,這裡介紹一下接入流程以及注意事項。

接入流程:

1.1、到微信開放平臺新增移動應用,申請許可權

到微信開放平臺註冊開發者賬號,並新增應用,申請支付許可權,等待稽核,需要提前做,稽核需要時間,一般在7個工作日內。

獲取的引數配置:

這裡寫圖片描述

1.2、後臺設定

這裡寫圖片描述

這裡應用跟應用簽名和包名掛鉤,也就是說一個移動應用對應一個APPID,表明了配置引數的不可公用性。另外要注意除錯的時候簽名問題,可以先設定為debug簽名,等除錯成功換成正式簽名。一般除錯問題就是簽名問題。

支付流程

這裡寫圖片描述

由流程圖可知,客戶端只需要呼叫下單介面,拿到後臺簽名後的預支付訂單即可,客戶端根據預支付訂單去吊起微信支付

1.3、註冊吊起支付

 // 通過WXAPIFactory工廠,獲取IWXAPI的例項
 IWXAPI  api = WXAPIFactory.createWXAPI(this, Constants.APP_ID, false);
 // 將該app註冊到微信
 api.registerApp(Constants.APP_ID);   

商戶伺服器生成支付訂單,先呼叫統一下單API(詳見第7節)生成預付單,獲取到prepay_id後將引數再次簽名傳輸給APP發起支付,由伺服器端進行操作,簡化了客戶端的複雜度跟安全性。

訂單引數:

這裡寫圖片描述

這裡是伺服器端生成預支付訂單並加簽名返回。

呼叫方式:

 0) {
                        String content = new String(buf);
                        Log.e("get server pay params:",content);
                        JSONObject json = new JSONObject(content); 
                        if(null != json && !json.has("retcode") ){
                            PayReq req = new PayReq();
                            //req.appId = "wxf8b4f85f3a794e77";  
                            // 測試用appId
                            //訂單引數
                            req.appId           = json.getString("appid");
                            req.partnerId       = json.getString("partnerid");
                            req.prepayId        = json.getString("prepayid");
                            req.nonceStr        = json.getString("noncestr");
                            req.timeStamp       = json.getString("timestamp");
                            req.packageValue    = json.getString("package");
                            req.sign            = json.getString("sign");
                            req.extData         = "app data"; // optional
                            Toast.makeText(PayActivity.this, "正常調起支付", Toast.LENGTH_SHORT).show();
                            // 在支付之前,如果應用沒有註冊到微信,應該先呼叫IWXMsg.registerApp將應用註冊到微信
                            api.sendReq(req);
                        }else{
                            Log.d("PAY_GET", "返回錯誤"+json.getString("retmsg"));
                            Toast.makeText(PayActivity.this, "返回錯誤"+json.getString("retmsg"), Toast.LENGTH_SHORT).show();
                        }
                    }else{
                        Log.d("PAY_GET", "伺服器請求錯誤");
                        Toast.makeText(PayActivity.this, "伺服器請求錯誤", Toast.LENGTH_SHORT).show();
                    }
                }catch(Exception e){
                    Log.e("PAY_GET", "異常:"+e.getMessage());
                    Toast.makeText(PayActivity.this, "異常:"+e.getMessage(), Toast.LENGTH_SHORT).show();
                }
                payBtn.setEnabled(true);
            }
        }); " data-snippet-id="ext.e81c40480e05a2f627716c229015f412" data-snippet-saved="false" data-codota-status="done">api = WXAPIFactory.createWXAPI
(this, "wxb4ba3c02aa476ea1"); Button appayBtn = (Button) findViewById(R.id.appay_btn); appayBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //訂單介面 String url = "http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php?plat=android"; Button payBtn = (Button) findViewById(R.id.appay_btn); payBtn.setEnabled(false); Toast.makeText(PayActivity.this, "獲取訂單中...", Toast.LENGTH_SHORT).show(); try{ byte[] buf = Util.httpGet(url); if (buf != null && buf.length > 0) { String content = new String(buf); Log.e("get server pay params:",content); JSONObject json = new JSONObject(content); if(null != json && !json.has("retcode") ){ PayReq req = new PayReq(); //req.appId = "wxf8b4f85f3a794e77"; // 測試用appId //訂單引數 req.appId = json.getString("appid"); req.partnerId = json.getString("partnerid"); req.prepayId = json.getString("prepayid"); req.nonceStr = json.getString("noncestr"); req.timeStamp = json.getString("timestamp"); req.packageValue = json.getString("package"); req.sign = json.getString("sign"); req.extData = "app data"; // optional Toast.makeText(PayActivity.this, "正常調起支付", Toast.LENGTH_SHORT).show(); // 在支付之前,如果應用沒有註冊到微信,應該先呼叫IWXMsg.registerApp將應用註冊到微信 api.sendReq(req); }else{ Log.d("PAY_GET", "返回錯誤"+json.getString("retmsg")); Toast.makeText(PayActivity.this, "返回錯誤"+json.getString("retmsg"), Toast.LENGTH_SHORT).show(); } }else{ Log.d("PAY_GET", "伺服器請求錯誤"); Toast.makeText(PayActivity.this, "伺服器請求錯誤", Toast.LENGTH_SHORT).show(); } }catch(Exception e){ Log.e("PAY_GET", "異常:"+e.getMessage()); Toast.makeText(PayActivity.this, "異常:"+e.getMessage(), Toast.LENGTH_SHORT).show(); } payBtn.setEnabled(true); } });

1.4、支付結果回撥

在包名.wxapi包路徑中實現WXPayEntryActivity類(包名或類名不一致會造成無法回撥),在WXPayEntryActivity類中實現onResp函式,支付完成後,微信APP會返回到商戶APP並回調onResp函式,開發者需要在該函式中接收通知,判斷返回錯誤碼,如果支付成功則去後臺查詢支付結果再展示使用者實際支付結果。注意一定不能以客戶端返回作為使用者支付的結果,應以伺服器端的接收的支付通知或查詢API返回的結果為準。

清單檔案:

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

Activity設定:

package net.sourceforge.simcpux.wxapi;
import net.sourceforge.simcpux.Constants;
import net.sourceforge.simcpux.R;
import com.tencent.mm.sdk.constants.ConstantsAPI;
import com.tencent.mm.sdk.modelbase.BaseReq;
import com.tencent.mm.sdk.modelbase.BaseResp;
import com.tencent.mm.sdk.openapi.IWXAPI;
import com.tencent.mm.sdk.openapi.IWXAPIEventHandler;
import com.tencent.mm.sdk.openapi.WXAPIFactory;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler{

    private static final String TAG = "MicroMsg.SDKSample.WXPayEntryActivity";

    private IWXAPI api;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.pay_result);
        api = WXAPIFactory.createWXAPI(this, Constants.APP_ID);
        api.handleIntent(getIntent(), this);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        api.handleIntent(intent, this);
    }

    @Override
    public void onReq(BaseReq req) {
    }

    @Override
    public void onResp(BaseResp resp) {
        Log.d(TAG, "errCode = " + resp.errCode);
        if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.app_tip);
            builder.setMessage(getString(R.string.pay_result_callback_msg,            String.valueOf(resp.errCode)));
            builder.show();
        }
    }
}

errCode:

0 支付成功
-1 支付出錯,可能的原因:簽名錯誤、未註冊APPID、專案設定APPID不正確、註冊的APPID與設定的不匹配、其他異常等。
-2 使用者取消支付

這樣一個完整的流程就走完了,下面來說一下老版本關於在客戶端加簽的流程。因為現在要求加簽過程都扔到伺服器端進行操作。

舊版本的操作總結:

商戶系統和微信支付系統主要互動說明:
步驟1:使用者在商戶APP中選擇商品,提交訂單,選擇微信支付。
步驟2:商戶後臺收到使用者支付單,呼叫微信支付統一下單介面。
步驟3:統一下單介面返回正常的prepay_id,再按簽名規範重新生成簽名後,將資料傳輸給APP。參與簽名的欄位名為appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式為Sign=WXPay
步驟4:商戶APP調起微信支付。
步驟5:商戶後臺接收支付通知。
步驟6:商戶後臺查詢支付結果。

這裡主要講一下步驟2,步驟3,這兩步現在是放在伺服器端進行的操作,以前舊版本很多都是放在客戶端進行的。

步驟2:

需要呼叫微信的下單介面,那麼需要了解微信的介面規則。

這裡寫圖片描述

標註紅都是應該注意的地方。

簽名演算法:

private String genProductArgs(BaseOrder order) {
        StringBuffer xml = new StringBuffer();

        try {
            //生成隨機數演算法
            String nonceStr = genNonceStr();
            //第一步:對引數按照key=value的格式,並按照引數名ASCII字典序排序如下:
            xml.append("</xml>");
            List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
            packageParams.add(new BasicNameValuePair("appid", Constants.APP_ID));
            packageParams.add(new BasicNameValuePair("body", order.getTitle()));
            packageParams.add(new BasicNameValuePair("mch_id", Constants.MCH_ID));
            packageParams.add(new BasicNameValuePair("nonce_str", nonceStr));
            packageParams.add(new BasicNameValuePair("notify_url", order.getNotifyUrl()));
            packageParams.add(new BasicNameValuePair("out_trade_no", order.getOrderNo()));
            packageParams.add(new BasicNameValuePair("spbill_create_ip", "127.0.0.1"));
            packageParams.add(new BasicNameValuePair("total_fee", order.getWXTotalFee() + ""));
            packageParams.add(new BasicNameValuePair("trade_type", "APP"));

             //第二步:拼接API金鑰:
            String sign = genPackageSign(packageParams);
            packageParams.add(new BasicNameValuePair("sign", sign));
             //轉為成xml格式
            String xmlstring = toXml(packageParams);
             //有漢字時需要用ISO8859-1編碼,沒有時用utf-8
            return new String(xmlstring.toString().getBytes(), "ISO8859-1");

        } catch (Exception e) {
            Log.e(TAG, "genProductArgs fail, ex = " + e.getMessage());
            return null;
        }
    }
    //轉為成xml格式
 private 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(TAG, sb.toString());
        return sb.toString();
    }
     //生成隨機數演算法
 private String genNonceStr() {
        Random random = new Random();
        return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
    }

步驟三:

private void genPayReq() {
        req = new PayReq();
        req.appId = Constants.APP_ID;
        req.partnerId = Constants.MCH_ID;
        req.prepayId = resultunifiedorder.get("prepay_id");
        req.packageValue = "prepay_id=" + resultunifiedorder.get("prepay_id");
        req.nonceStr = genNonceStr();
        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 = genAppSign(signParams);

        sb.append("sign\n" + req.sign + "\n\n");
    }

    private long genTimeStamp() {
        return System.currentTimeMillis() / 1000;
    }

    private String genAppSign(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);

        this.sb.append("sign str\n" + sb.toString() + "\n\n");
        String appSign = MD5.getMessageDigest(sb.toString().getBytes());
        Log.e(TAG, appSign);
        return appSign;
    }

簽名比較麻煩啊,建議還是按照新版的來吧,省事,安全!