1. 程式人生 > >Android移動開發-在Android專案裡整合呼叫微信支付開發的實現

Android移動開發-在Android專案裡整合呼叫微信支付開發的實現

如今移動支付比較火,尤其是在中國的市場。移動支付也稱為手機支付,就是允許使用者使用其移動終端(通常是手機)對所消費的商品或服務進行賬務支付的一種服務方式。單位或個人通過移動裝置、網際網路或者近距離感測直接或間接向銀行金融機構傳送支付指令產生貨幣支付與資金轉移行為,從而實現移動支付功能。移動支付將終端裝置、網際網路、應用提供商以及金融機構相融合,為使用者提供貨幣支付、繳費等金融業務。
談到移動支付,不得不說阿里旗下的螞蟻金融的支付以及騰訊旗下的微信支付。那麼現在在就談談如何Android專案裡整合呼叫微信支付開發的實現方式。

首先訪問微信開放平臺官網,網址為:https://open.weixin.qq.com/

。然後用自己的郵箱註冊並認證為開發者,接著在平臺首頁依次點選“資源中心”——“Android資源下載”,即可在開啟的頁面中下載開發工具包jar包與Demo程式示例程式碼,下載地址為: https://res.wx.qq.com/open/zh_CN/htmledition/res/dev/download/sdk/WeChatSDK_Android221cbf.zip
注意這裡的開發包libmmsdk.jar同時集成了微信分享與微信支付的SDK。
使用微信支付需要先申請測試應用,在微信開放平臺登入成功後,依次單擊連結“管理中心”——“建立移動應用”,填寫應用相關資訊,提交成功後返回應用列表頁面。然後檢視應用詳情頁,在介面資訊欄目中發現預設已獲得微信分享的許可權,而微信支付許可權需要另外申請開通。

  • Android專案整合呼叫微信支付的流程

這裡寫圖片描述

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

  • 匯入開發資源

將下載的jar包放入商戶應用工程的libs目錄下。

  • 配置許可權宣告
<uses-permission android:name="android.permission.INTERNET"/>
  • 配置回撥activity
        <!-- 微信支付回撥頁面 -->
        <activity
            android:name="com.fukaimei.wechatpay.WXPayEntryActivity"
            android:exported="true"
            android:launchMode="singleTop">
        </activity>
  • GetPrepayIdTask.java邏輯程式碼如下:
package com.fukaimei.wechatpay.task;

import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Random;

import org.json.JSONObject;

import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;

//import com.alibaba.fastjson.JSONObject;
import com.fukaimei.wechatpay.bean.GetPrepayIdResult;
import com.fukaimei.wechatpay.bean.LocalRetCode;
import com.fukaimei.wechatpay.bean.WechatConstants;
import com.fukaimei.wechatpay.util.MD5;
import com.fukaimei.wechatpay.util.WechatUtil;
import com.tencent.mm.sdk.modelpay.PayReq;
import com.tencent.mm.sdk.openapi.IWXAPI;
import com.tencent.mm.sdk.openapi.WXAPIFactory;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;

public class GetPrepayIdTask extends AsyncTask<String, Void, GetPrepayIdResult> {
    private static final String TAG = "GetPrepayIdTask";
    private Context context;
    private ProgressDialog dialog;
    private String accessToken;
    private String[] goods_info;

    public GetPrepayIdTask(Context context, String accessToken) {
        this.context = context;
        this.accessToken = accessToken;
    }

    @Override
    protected void onPreExecute() {
        dialog = ProgressDialog.show(context, "提示", "正在獲取預支付訂單...");
    }

    @Override
    protected GetPrepayIdResult doInBackground(String... params) {
        goods_info = new String[] { params[0], params[1], params[2] };
        String url = String.format("https://api.weixin.qq.com/pay/genprepay?access_token=%s", accessToken);
        String entity = genProductArgs();
        Log.d(TAG, "doInBackground, url = " + url + ", entity = " + entity);

        GetPrepayIdResult result = new GetPrepayIdResult();
        byte[] buf = WechatUtil.httpPost(url, entity);
        if (buf == null || buf.length == 0) {
            result.localRetCode = LocalRetCode.ERR_HTTP;
            return result;
        }

        String content = new String(buf);
        Log.d(TAG, "doInBackground, response content = " + content);
        result.parseFrom(content);
        return result;
    }

    @Override
    protected void onPostExecute(GetPrepayIdResult result) {
        if (dialog != null) {
            dialog.dismiss();
        }
        if (result.localRetCode == LocalRetCode.ERR_OK) {
            Toast.makeText(context, "獲取prepayid成功", Toast.LENGTH_LONG).show();
            payWithWechat(result);
        } else {
            Toast.makeText(context, "獲取prepayid失敗,原因" + result.localRetCode.name(), Toast.LENGTH_LONG).show();
        }
    }

    private IWXAPI mWeixinApi;

    // // 如果獲取token和預付標識在伺服器實現,只留下支付動作在客戶端實現,那麼下面要非同步呼叫
    // private void payWithWechat() {
    // final String payInfo = "";
    //
    // Runnable payRunnable = new Runnable() {
    // @Override
    // public void run() {
    // sendWXPayReq(payInfo);
    // }
    // };
    //
    // Thread payThread = new Thread(payRunnable);
    // payThread.start();
    // }

    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(WechatConstants.PARTNER_KEY); // 注意:不能hardcode在客戶端,建議genPackage這個過程都由伺服器端完成

        // 進行md5摘要前,params內容為原始內容,未經過url encode處理
        String packageSign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase(Locale.getDefault());
        return URLEncodedUtils.format(params, "utf-8") + "&sign=" + packageSign;
    }

    private String genNonceStr() {
        Random random = new Random();
        return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
    }

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

    private String getTraceId() {
        return "crestxu_" + genTimeStamp();
    }

    private String genOutTradNo() {
        Random random = new Random();
        return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
    }

    private long timeStamp;
    private String nonceStr, packageValue;

    private String genSign(List<NameValuePair> params) {
        StringBuilder sb = new StringBuilder();
        int i = 0;
        for (; i < params.size() - 1; i++) {
            sb.append(params.get(i).getName());
            sb.append('=');
            sb.append(params.get(i).getValue());
            sb.append('&');
        }
        sb.append(params.get(i).getName());
        sb.append('=');
        sb.append(params.get(i).getValue());

        String sha1 = WechatUtil.sha1(sb.toString());
        Log.d(TAG, "genSign, sha1 = " + sha1);
        return sha1;
    }

    private String genProductArgs() {
        JSONObject json = new JSONObject();

        try {
            json.put("appid", WechatConstants.APP_ID);
            String traceId = getTraceId(); // traceId
                                            // 由開發者自定義,可用於訂單的查詢與跟蹤,建議根據支付使用者資訊生成此id
            json.put("traceid", traceId);
            nonceStr = genNonceStr();
            json.put("noncestr", nonceStr);

            List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
            packageParams.add(new BasicNameValuePair("bank_type", "WX"));
            packageParams.add(new BasicNameValuePair("body", goods_info[0]));
            packageParams.add(new BasicNameValuePair("description", goods_info[1]));
            packageParams.add(new BasicNameValuePair("fee_type", "1"));
            packageParams.add(new BasicNameValuePair("input_charset", "UTF-8"));
            packageParams.add(new BasicNameValuePair("notify_url", "http://weixin.qq.com"));
            packageParams.add(new BasicNameValuePair("out_trade_no", genOutTradNo()));
            packageParams.add(new BasicNameValuePair("partner", WechatConstants.PARTNER_ID));
            packageParams.add(new BasicNameValuePair("spbill_create_ip", "196.168.1.1"));
            packageParams.add(new BasicNameValuePair("total_fee", ""
                    + (int) (Float.parseFloat(goods_info[2]) * 100)));
            packageValue = genPackage(packageParams);

            json.put("package", packageValue);
            timeStamp = genTimeStamp();
            json.put("timestamp", timeStamp);

            List<NameValuePair> signParams = new LinkedList<NameValuePair>();
            signParams.add(new BasicNameValuePair("appid", WechatConstants.APP_ID));
            signParams.add(new BasicNameValuePair("appkey", WechatConstants.APP_KEY));
            signParams.add(new BasicNameValuePair("noncestr", nonceStr));
            signParams.add(new BasicNameValuePair("package", packageValue));
            signParams.add(new BasicNameValuePair("timestamp", String.valueOf(timeStamp)));
            signParams.add(new BasicNameValuePair("traceid", traceId));
            json.put("app_signature", genSign(signParams));

            json.put("sign_method", "sha1");
        } catch (Exception e) {
            Log.e(TAG, "genProductArgs fail, ex = " + e.getMessage());
            return null;
        }

        return json.toString();
    }

    private void payWithWechat(GetPrepayIdResult result) {
        PayReq req = new PayReq();
        req.appId = WechatConstants.APP_ID;
        req.partnerId = WechatConstants.PARTNER_ID;
        req.prepayId = result.prepayId;
        req.nonceStr = nonceStr;
        req.timeStamp = String.valueOf(timeStamp);
        req.packageValue = "Sign=" + packageValue;

        List<NameValuePair> signParams = new LinkedList<NameValuePair>();
        signParams.add(new BasicNameValuePair("appid", req.appId));
        signParams.add(new BasicNameValuePair("appkey", WechatConstants.APP_KEY));
        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 = genSign(signParams);

        Log.d(TAG, "WXAPIFactory.createWXAPI");
        mWeixinApi = WXAPIFactory.createWXAPI(context, WechatConstants.APP_ID);
        // 在支付之前,如果應用沒有註冊到微信,應該先呼叫IWXMsg.registerApp將應用註冊到微信
        Log.d(TAG, "mWeixinApi.sendReq");
        mWeixinApi.sendReq(req);
    }
}
  • GetAccessTokenTask.java邏輯程式碼如下:
package com.fukaimei.wechatpay.task;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;

import com.fukaimei.wechatpay.bean.GetAccessTokenResult;
import com.fukaimei.wechatpay.bean.LocalRetCode;
import com.fukaimei.wechatpay.bean.WechatConstants;
import com.fukaimei.wechatpay.util.WechatUtil;

public class GetAccessTokenTask extends AsyncTask<String, Void, GetAccessTokenResult> {
    private static final String TAG = "GetAccessTokenTask";
    private Context context;
    private ProgressDialog dialog;
    private String[] goods_info;

    public GetAccessTokenTask(Context context) {
        this.context = context;
    }

    @Override
    protected void onPreExecute() {
        dialog = ProgressDialog.show(context, "提示", "正在獲取access token...");
    }

    @Override
    protected GetAccessTokenResult doInBackground(String... params) {
        goods_info = new String[]{params[0], params[1], params[2]};
        GetAccessTokenResult result = new GetAccessTokenResult();
        String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
                WechatConstants.APP_ID, WechatConstants.APP_SECRET);
        Log.d(TAG, "get access token, url = " + url);

        byte[] buf = WechatUtil.httpGet(url);
        if (buf == null || buf.length == 0) {
            result.localRetCode = LocalRetCode.ERR_HTTP;
            return result;
        }

        String content = new String(buf);
        result.parseFrom(content);
        return result;
    }

    @Override
    protected void onPostExecute(GetAccessTokenResult result) {
        if (dialog != null) {
            dialog.dismiss();
        }

        Log.d(TAG, "RetCode=" + result.localRetCode + ", errCode=" + result.errCode + ", errMsg=" + result.errMsg);
        if (result.localRetCode == LocalRetCode.ERR_OK) {
            Toast.makeText(context, "獲取access token成功, accessToken = " + result.accessToken, Toast.LENGTH_LONG).show();
            GetPrepayIdTask getPrepayId = new GetPrepayIdTask(context, result.accessToken);
            getPrepayId.execute(goods_info[0], goods_info[1], goods_info[2]);
        } else {
            Toast.makeText(context, "獲取access token失敗,原因: " + result.localRetCode.name(), Toast.LENGTH_LONG).show();
        }
    }
}
  • 在WechatConstants裡配置相關與支付寶簽約的商家金鑰和賬號等,WechatConstants.java邏輯程式碼如下:
package com.fukaimei.wechatpay.bean;

public class WechatConstants {
    // APP_ID 替換為你的應用從官方網站申請到的合法appId
    public static final String APP_ID = "";
    public static final String APP_SECRET = "";
    // wxd930ea5d5a258f4f 對應的支付金鑰
    public static final String APP_KEY = "";

    /** 商家向財付通申請的商家id */
    public static final String PARTNER_ID = "";
    public static final String PARTNER_KEY = "";

    public static class ShowMsgActivity {
        public static final String STitle = "showmsg_title";
        public static final String SMessage = "showmsg_message";
        public static final String BAThumbData = "showmsg_thumb_data";
    }
}
  • layout/activity_wxpay.xml介面佈局程式碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp" >

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:src="@drawable/laoganma"
        android:scaleType="fitCenter" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="商品名稱:"
            android:textColor="@color/black"
            android:textSize="17sp" />

        <EditText
            android:id="@+id/et_goods_title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="5dp"
            android:background="@drawable/editext_selector"
            android:text="小米MIX 2"
            android:textColor="@color/black"
            android:textSize="17sp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="商品描述:"
            android:textColor="@color/black"
            android:textSize="17sp" />

        <EditText
            android:id="@+id/et_goods_desc"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="5dp"
            android:background="@drawable/editext_selector"
            android:text="全面屏2.0,驍龍835處理器,全陶瓷機身。"
            android:textColor="@color/black"
            android:textSize="17sp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="商品價格:"
            android:textColor="@color/black"
            android:textSize="17sp" />

        <EditText
            android:id="@+id/et_goods_price"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="5dp"
            android:background="@drawable/editext_selector"
            android:enabled="false"
            android:inputType="numberDecimal"
            android:text="0.01"
            android:textColor="@color/black"
            android:textSize="17sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="元"
            android:textColor="@color/black"
            android:textSize="17sp" />
    </LinearLayout>

    <Button
        android:id="@+id/btn_wechat"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="微信支付"
        android:textColor="@color/black"
        android:textSize="17sp" />

</LinearLayout>
  • WxpayActivity.java邏輯程式碼如下:
package com.fukaimei.wechatpay;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;

import com.fukaimei.wechatpay.task.GetAccessTokenTask;

public class WxpayActivity extends AppCompatActivity implements OnClickListener {
    private static final String TAG = "WxpayActivity";
    private EditText et_goods_title;
    private EditText et_goods_desc;
    private EditText et_goods_price;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wxpay);
        et_goods_title = (EditText) findViewById(R.id.et_goods_title);
        et_goods_desc = (EditText) findViewById(R.id.et_goods_desc);
        et_goods_price = (EditText) findViewById(R.id.et_goods_price);
        findViewById(R.id.btn_wechat).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_wechat) {
            String title = et_goods_title.getText().toString();
            String desc = et_goods_desc.getText().toString();
            String price = et_goods_price.getText().toString();
            new GetAccessTokenTask(this).execute(title, desc, price);
        }
    }
}
  • layout/activity_wxpay_result.xml介面佈局程式碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="5dp"
        android:text="這是微信支付結果頁面"
        android:textColor="@color/black"
        android:textSize="17sp" />

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:textColor="@color/black"
        android:textSize="17sp" />

</LinearLayout>
  • WXPayEntryActivity.java邏輯程式碼如下:
package com.fukaimei.wechatpay;

import com.fukaimei.wechatpay.bean.WechatConstants;
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.modelbase.BaseResp.ErrCode;
import com.tencent.mm.sdk.openapi.IWXAPI;
import com.tencent.mm.sdk.openapi.IWXAPIEventHandler;
import com.tencent.mm.sdk.openapi.WXAPIFactory;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

public class WXPayEntryActivity extends AppCompatActivity implements IWXAPIEventHandler {
    private static final String TAG = "WXPayEntryActivity";
    private IWXAPI api;
    private TextView tv_result;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wxpay_result);
        tv_result = (TextView) findViewById(R.id.tv_result);
        api = WXAPIFactory.createWXAPI(this, WechatConstants.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, "onResp, errCode = " + resp.errCode);
        String result = "";
        if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
            switch (resp.errCode) {
            case ErrCode.ERR_OK:
                result = "微信支付成功";
                break;
            case ErrCode.ERR_COMM:
                result = "微信支付失敗:" + resp.errCode + "," + resp.errStr;
                break;
            case ErrCode.ERR_USER_CANCEL:
                result = "微信支付取消:" + resp.errCode + "," + resp.errStr;
                break;
            default:
                result = "微信支付未知異常:" + resp.errCode + "," + resp.errStr;
                break;
            }
        }
        Toast.makeText(this, result, Toast.LENGTH_LONG).show();
        tv_result.setText(result);
    }
}