1. 程式人生 > >Android微信支付整合流程及其常見錯誤

Android微信支付整合流程及其常見錯誤

[支付寶整合傳送門:http://blog.csdn.net/hx7013/article/details/61476320]

一、你需要關心的東西

1.申請與認證

2.上傳金鑰(商戶KEY),包名和簽名的修改

3.服務端整合

4.安卓整合

本篇就全部寫了吧,讓我們一步步來。

二、申請與認證

1.提交您的開發者資料

微信開放平臺: https://open.weixin.qq.com/
進入後申請賬戶 -> 建立移動應用 -> 開發者認證[300 RMB] -> 申請微信支付,基本上無坑點,公司資料齊全可以很順利可以通過。

2.獲得商戶平臺帳號

認證時你需要登陸 微信支付商戶平臺:https://pay.weixin.qq.com
提交騰訊打給你公司對公賬戶上的稽核款,登陸微信商戶平臺的賬戶密碼在開發者賬戶認證通過或會通過郵件傳送到申請時填寫的郵箱,不能自主註冊。

三、上傳金鑰、配置包名、配置應用簽名

Tips:本是很簡單的功能,為何要單獨列一個來說呢?
因為網上的其它教程基本上是老的提交方法了,當初在這也迷糊金鑰在何處生成或提交。所以單獨列出來圖文說明。

1.配置商戶KEY

登陸微信支付商戶平臺:https://pay.weixin.qq.com,用你獲得的賬戶密碼進行登陸。
然後點選 賬戶中心 -> API安全 -> 設定API金鑰
微信支付整合


微信支付整合

Tips:金鑰使用的是32位大小寫+數字的密串,推薦自動生成。
百度[隨機密碼]很多的,注意只勾選大小寫+數字,長度為32既可。
例如:bXubGehLcukfQc2OyP76qPs84LBuq8oO
當然你也可以自己填寫,但是個人感覺隨機密碼相對更加安全。至少社工不能破掉。

2.設定包名

登陸微信開放平臺: https://open.weixin.qq.com/
點選 管理中心 -> 選擇應用點選檢視 -> 在最下面有[開發資訊] -> 點選修改填入包名。

當初稽核時提交的是豌豆莢的App連結,所以包名和簽名已自動填入。
如沒有自動填入或錯誤,可以自行修改。

3.設定簽名

Tips:在翻閱其它文件時說修改簽名後需要稽核,但是在測試中發現並不需要稽核,估計是以前微信支付剛上線時有該限制。所以現在可以放心修改為Debug簽名直接除錯,免得先發布,在adb install...

同樣和上一步設定包名一樣,先登陸微信開發者平臺,而後在 管理中心 -> 選擇應用點選檢視 -> 在最下面有[開發資訊] -> 點選修改。

四、服務端的整合

服務端整合重點只說簽名Sign的生成,其它都是傻瓜式整合,這點我們自己整合時也遇到了坑,所以單獨說一下。
當初在整合服務端的時候呼叫的是微信提供的開發包,但不知為何在Android上呼叫後,總是彈不出支付的頁面,呼叫時就閃退回來直接反 -1 錯誤(不得不吐槽一下,-1有那麼多情況,就不能學支付寶彈個詳細點的錯誤碼嗎?)耽擱半天,終於解決。

先來張圖看看:
微信支付整合

Tips:不用糾結我為何沒打碼,因為都是虛假引數。但是簽名的過程一樣,大家能看懂既可。

首先進去後把介面方式從【提交被掃支付】改為【自定義】,然後填入appid、partnerid、prepayid、package、noncestr、timestamp和對應的值。在該除錯工具頁面,記住我強調是該除錯頁面提交這些參的順序是不強求的,但是如果你是自己服務端簽名的時候就一定得按順序提交,這個順序馬上會說到。

填入值後點擊生成簽名,會發現下面有#1、#2、#3、#4四個流程,其實這個流程就是簽名的過程。
認證看後會發現,其實就是引數排序連線,然後再連線商戶Key,最後取MD5。

重點來了,也許你會糾結為何引數正確的,但是Sign簽名一直不對呢?其實瞭解MD5的就應該知道,MD5是摘要演算法,只知道通過文字去取Hash值。所以appid=123456&partnerid=123456 和 partnerid=123456&appid=123456計算出來的值一定是不同的。這也是坑之一,簽名一定得按順序組合後再取MD5值。

這個順序就是微信介面除錯工具中#2步驟中所提示的,從上圖中我們可以看到我提交的是appid、partnerid、prepayid、package、noncestr、timestamp,但是微信給我組合成了appid、noncestr、package、partnerid、prepayid、timestamp、key。

appid=wx666888abc8defg88&noncestr=VCG4w95nS5Ku7mCi&package=Sign=WXPay&partnerid=1668866888&prepayid=wx20170312211500a8y53pgb0klvyh2rmoyu&timestamp=1489324509&key=UWMOIg1qJdHLZfEzAbm91BOBJG4oyC5C

而這個順序就是簽名通常出錯的地方,自己在服務端生成簽名時一不小心順序出錯,雖然引數正確,但是由於取出來的MD5是和微信那邊取出的MD5是不同的,所以固然簽名錯誤。
所以後面伺服器生成簽名的最簡單解決方法就是硬接+取MD5

string signStr = "appid="+valAppid+"&noncestr="+valNoncestr+"&package=Sign=WXPay&partnerid="+valPartnerid+"&prepayid="+valPrepayid+"&timestamp="+valTimestamp+"&key="+valKEY
//c#的取MD5方法,其它語言自行Google
string signMd5 = FormsAuthentication.HashPasswordForStoringInConfigFile(signStr , "MD5") //signMd5即為最終簽名

獲得Sign後Android提交呼叫既可。

五、Android的整合

1.Manifest的宣告

<!-- 這個也可以不設定,只是習慣跟著官方的Demo了,順便填寫了一下appid,也就是data android:scheme="wx666888abc8defg88" -->
 <application
   android:name="com.wx.wxTest.xApplication"
   android:allowBackup="true"
   android:icon="@drawable/app_icon"
   android:label="@string/app_name"
   android:screenOrientation="portrait"
   android:theme="@style/AppTheme" >
      <activity
        android:name="com.wx.wxTest.Activity.Welcome"
        android:screenOrientation="portrait"
        android:theme="@style/Welcome" >
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
          <intent-filter>
            <action android:name="android.intent.action.VIEW" />
              <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="wx666888abc8defg88" />
          </intent-filter>
      </activity>

<!-- 該activity是申明給微信回撥時通知的Activity -->
      <activity
        android:name="com.wx.wxTest.wxapi.WXPayEntryActivity"
        android:exported="true"
        android:launchMode="singleTop"
        android:screenOrientation="portrait" >
      </activity>

又一個坑點來了,我們先來看看官方的文件。
微信支付整合

什麼叫“在net.sourceforge.simcpux.wxapi包路徑中實現WXPayEntryActivity類(包名或類名不一致會造成無法回撥)“我就傻不拉唧的新建了一個包,名為net.sourceforge.simcpux.wxapi,再新建了一個Activity名為WXPayEntryActivity。後面果斷調不起…後面嘗試 自己的包名(也就是)Manifest中的package=”com.wx.wxTest”,組合為com.wx.wxTest.wxapi.WXPayEntryActivity就可以回調了。微信你不能說清楚是自己的包名+wxapi.WXPayEntryActivity嗎?來個必須和你說的一致!

2.呼叫類Test_WxActivity

package com.wx.wxTest.activity;

import com.tencent.mm.opensdk.modelpay.PayReq;
import com.tencent.mm.opensdk.openapi.IWXAPI;
import com.tencent.mm.opensdk.openapi.WXAPIFactory;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.Toast;

public class Test_WxActivity extends Activity {
    private IWXAPI msgApi;
    private PayReq request;

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

        // 可以在Application中註冊,也可以在這裡註冊
        // 如果在當前註冊即為
        // msgApi = WXAPIFactory.createWXAPI(this, null);
        // msgApi.registerApp("wx666888abc8defg88");

        // 如果在Application註冊即為,如果在當前註冊請註釋掉該程式碼
        msgApi = WXAPIFactory.createWXAPI(this, "wx666888abc8defg88");

        new Thread(wxPay).start();
    }

    Handler mainHandler = new Handler() {
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
            case 100:

                Boolean mSendReq = msgApi.sendReq(request);
                Toast.makeText(Test_WxActivity.this, "調起微信sendReq=>" + mSendReq, Toast.LENGTH_SHORT).show();

                break;
            default:
                break;
            }

        };
    };

    Runnable wxPay = new Runnable() {

        @Override
        public void run() {
            // 這些值應該從伺服器獲取,這裡只是為了演示
            request = new PayReq();

            request.appId = "wx666888abc8defg88";
            request.partnerId = "1668866888";
            request.prepayId = "wx20170312211500a8y53pgb0klvyh2rmoyu";
            request.nonceStr = "VCG4w95nS5Ku7mCi";
            request.timeStamp = "1489324509";
            request.packageValue = "Sign=WXPay";
            request.sign = "19890233F9E17CC54E0B6285032AE419";

            mainHandler.sendEmptyMessage(100);
        }
    };

}

3.回撥類WXPayEntryActivity

package com.wx.wxTest.wxapi;

import com.jhx.hyxs.R;
import com.tencent.mm.opensdk.constants.ConstantsAPI;
import com.tencent.mm.opensdk.modelbase.BaseReq;
import com.tencent.mm.opensdk.modelbase.BaseResp;
import com.tencent.mm.opensdk.openapi.IWXAPI;
import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler;
import com.tencent.mm.opensdk.openapi.WXAPIFactory;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler {

    private IWXAPI api;

    private ImageView ivImg;
    private TextView tvText, tvSub;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wxentry);

        // 通過WXAPIFactory工廠,獲取IWXAPI的例項
        api = WXAPIFactory.createWXAPI(this, "wx666888abc8defg88", false);

        // 注意:
        // 第三方開發者如果使用透明介面來實現WXEntryActivity,需要判斷handleIntent的返回值,如果返回值為false,則說明入參不合法未被SDK處理,應finish當前透明介面,避免外部通過傳遞非法引數的Intent導致停留在透明介面,引起使用者的疑惑
        try {
            api.handleIntent(getIntent(), this);
        } catch (Exception e) {
            e.printStackTrace();
        }

        initView();
    }

    private void initView() {
        ((TextView) findViewById(R.id.head_title)).setText("支付結果");
        ((ImageView) findViewById(R.id.head_back)).setVisibility(View.INVISIBLE);

        ivImg = (ImageView) findViewById(R.id.wxentry_img);
        tvText = (TextView) findViewById(R.id.wxentry_text);
        tvSub = (TextView) findViewById(R.id.wxentry_sub);

        tvSub.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                finish();
            }
        });

    }

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

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

    @Override
    public void onResp(BaseResp resp) {
        if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
            if (resp.errCode == 0) {
                Toast.makeText(this, "支付成功", Toast.LENGTH_LONG).show();
                ivImg.setImageResource(R.drawable.pay_success);
                tvText.setText("支付成功");
                tvText.setTextColor(Color.parseColor("#39BC32"));
            } else {
                Toast.makeText(this, "支付失敗! (" + resp.errCode + ")", Toast.LENGTH_LONG).show();
                ivImg.setImageResource(R.drawable.pay_error);
                tvText.setText("支付失敗");
                tvText.setTextColor(Color.parseColor("#FFA800"));
            }
        }
    }

    @Override
    public void onReq(BaseReq arg0) {
        // TODO Auto-generated method stub

    }
}

六、常見整合時遇到的坑和解決的方法

1. 微信回撥errCode = -1

官方文件:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_5
各種填坑後發現,這個可以相信官方文件,雖然模糊不清,但是說的的確大部分可能也都為“簽名錯誤、未註冊APPID、專案設定APPID不正確、註冊的APPID與設定的不匹配”,再加一條包名錯誤(雖然可能性很低)
解決方法:
1. 檢查在開發者平臺設定的應用簽名是否和當前APP簽名一致(Debug簽名或釋出版簽名)
2. 是否呼叫了註冊微信的程式碼

final IWXAPI msgApi = WXAPIFactory.createWXAPI(this, null);
msgApi.registerApp("wx666888abc8defg88");

2.簽名錯誤

一定得按照appid、noncestr、package、partnerid、prepayid、timestamp、key的順序組合後取MD5。
例如:

appid=wx666888abc8defg88&noncestr=VCG4w95nS5Ku7mCi&package=Sign=WXPay&partnerid=1668866888&prepayid=wx20170312211500a8y53pgb0klvyh2rmoyu&timestamp=1489324509&key=UWMOIg1qJdHLZfEzAbm91BOBJG4oyC5C

3.回撥錯誤

這個錯誤其實為微信的坑,是的,我就是那麼直白!
解決方法:
別聽信微信說的net.sourceforge.simcpux.wxapi包路徑中實現WXPayEntryActivity類(包名或類名不一致會造成無法回撥)。
而是使用你的包名+wxapi.WXPayEntryActivity,例如com.wx.wxTest.wxapi.WXPayEntryActivity

4.簽名

說一個不是錯誤但一定得注意的地方,簽名過程一定得在服務端完成!若你在客戶端(如:Android)上完成時,你的商戶KEY洩露可能很大(畢竟反除錯Android並不是什麼難事),而商戶KEY的洩露將導致你的整個支付系統崩塌。

七、總結

微信支付的坑比支付寶支付的坑要多,大多數是因為官方的文件模糊不清,但是若按照我剛說的配置,基本沒有問題。如果問題歡迎留言,定知無不言。