關(guān)于本文支付相關(guān)的源碼詳見我的開源項目MobilePayment
前言
移動支付其實是非常簡單的,因為只要按照第三方的文檔來就行了仔引,所以在本次分享中扔仓,其實更像是一次開發(fā)的紀要,當然也有一些看點咖耘。做過支付的人都知道支付的難點其實是在第三方文檔和demo上(集中體現(xiàn)文檔陳舊翘簇、demo容易誤導人、槽點太多)儿倒,那就不得不先來吐槽下微信的開發(fā)文檔和示例版保,我相信大部分人都被坑過,沒有對比就沒有傷害夫否,相對而言彻犁,支付寶的的文檔就好很多,下面我先說重點再談支付流程凰慈。
開發(fā)優(yōu)化要點
- 微信回調(diào)返回當前頁面部分機型會產(chǎn)生一閃而過的黑屏現(xiàn)象汞幢,測試機型三星S8,解決方案為在微信回調(diào)頁面增加透明主題微谓,如下:
<!--解決微信支付回調(diào)部分機型黑屏閃爍的問題-->
<style name="wxPayTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
</style>
<!--回調(diào)頁面WXPayEntryActivity關(guān)閉finish的時候增加-->
overridePendingTransition(0, 0);
<!--回調(diào)頁面WXPayEntryActivity配置-->
<activity
android:name=".wxapi.WXPayEntryActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/wxPayTheme"
android:windowSoftInputMode="adjustPan|stateAlwaysHidden" />
<!--另外一種解決方案-->
android:launchMode="singleTop"改為android:launchMode="singleInstance"
建議優(yōu)先采取第一種方案森篷,該方案作為備選,畢竟微信推薦使用"singleTop"啟動模式豺型。
- DialogFragment內(nèi)存泄漏問題仲智,google雖然推薦使用DialogFragment替代Dialog,但是內(nèi)存泄漏問題并未解決触创,試過很多方案坎藐,并未完美解決泄漏問題,故更改使用Activity結(jié)合動畫實現(xiàn)底部支付彈窗的效果哼绑。
<!--底部彈框支付Activity主題樣式-->
<style name="PayTranslucentTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowAnimationStyle">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item> <!-- 無標題 -->
<item name="android:windowContentOverlay">@null</item>
<item name="android:backgroundDimEnabled">true</item><!-- 半透明 -->
</style>
<!-- R.anim.push_bottom_in 進入動畫-->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromYDelta="100%p"
android:toYDelta="0" />
</set>
<!-- R.anim.push_bottom_out 淡出動畫-->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromYDelta="0"
android:toYDelta="100%p" />
</set>
<!-- R.anim.push_bottom_silent 原點-->
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromYDelta="0"
android:toYDelta="0" />
<!-- 底部彈框-->
startActivity(intent);
overridePendingTransition(R.anim.push_bottom_in,R.anim.push_bottom_silent);
<!-- 關(guān)閉底部彈框-->
finish();
overridePendingTransition(R.anim.push_bottom_silent,R.anim.push_bottom_out);
<!-- 實現(xiàn)透明狀態(tài)欄-->
setContentView(R.layout.activity_dialog_pay_custom);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(Color.TRANSPARENT);
}
效果圖
微信支付
流程
沒錯,這就是官方的業(yè)務(wù)流程圖碉咆,黑糊糊抖韩,感覺就是一張盜版圖,下面我以客戶端開發(fā)為主疫铜,簡化一下流程:
- 客戶端App選擇購買的商品后茂浮,選擇微信支付,接口請求服務(wù)器,服務(wù)器此時需要生成預支付訂單(調(diào)用微信統(tǒng)一下單接口)席揽,其中返回的參數(shù)便是我們調(diào)起微信支付所需要的參數(shù)顽馋。(統(tǒng)一下單)
- 客戶端拿到服務(wù)器返回的預支付訂單參數(shù),通過微信SDK調(diào)起微信支付頁面
- 客戶端收到微信支付的狀態(tài)回調(diào)
- 通過服務(wù)器查詢支付訂單的狀態(tài)幌羞,注意:以服務(wù)器查詢的結(jié)果為準寸谜,不要使用微信返回給客戶端的支付狀態(tài)作為支付依據(jù)。(訂單查詢)
集成
- 微信開放平臺注冊及配置
1.申請應(yīng)用獲取微信平臺生成的唯一APPID(管理中心-創(chuàng)建應(yīng)用)
2.配置簽名属桦,獲取應(yīng)用的簽名(簽名工具)
3.配置應(yīng)用包名熊痴,此處文檔太老舊,應(yīng)該為buile.gradle中的applicationId
- 注冊APPID
IWXAPI api = WXAPIFactory.createWXAPI(this, APP_ID);
- 調(diào)用SDK發(fā)起支付
/**
* 微信支付
*
* @param payInfo 預支付信息
*/
private void wechatPay(final String payInfo) {
try {
JSONObject json = new JSONObject(payInfo);
if (!json.has("retcode")) {
PayReq req = new PayReq();
// 測試用appId
req.appId = APP_ID;
// 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";
Toast.makeText(PayDialogActivity.this, "正常調(diào)起支付", Toast.LENGTH_SHORT).show();
// 在支付之前聂宾,如果應(yīng)用沒有注冊到微信果善,應(yīng)該先調(diào)用IWXMsg.registerApp將應(yīng)用注冊到微信
api.sendReq(req);
}
} catch (JSONException e) {
e.printStackTrace();
showToast(this, "異常:" + e.getMessage());
}
}
- 微信回調(diào)界面
1.目錄結(jié)構(gòu)配置,需要在包名下新建wxapi目錄系谐,并增加回調(diào)界面WXPayEntryActivity巾陕,如下圖:
2.回調(diào)界面示例
/**
* @author hule
* @date 2019/7/29 15:58
* description: 微信支付回調(diào)
*/
public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler {
private IWXAPI api;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//此處如果需要顯示界面需要設(shè)置setContentView();
//一般情況下會都不會去設(shè)置界面,拿到回調(diào)后直接關(guān)閉界面纪他,發(fā)送通知處理
api = WXAPIFactory.createWXAPI(this, PayDialogActivity.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) {
if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
//通知我們回調(diào)鄙煤,我們拿來查詢訂單的狀態(tài)
EventBus.getDefault().post(new WXPayEntryEntity(resp.errCode));
}
// 清除動畫,有助于防止黑屏閃爍
overridePendingTransition(0, 0);
finish();
}
}
3.回調(diào)頁面配置
<!--解決微信支付回調(diào)部分機型黑屏閃爍的問題-->
<style name="wxPayTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
</style>
<!--回調(diào)頁面WXPayEntryActivity關(guān)閉finish的時候增加-->
overridePendingTransition(0, 0);
<activity
android:name=".wxapi.WXPayEntryActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/wxPayTheme"
android:windowSoftInputMode="adjustPan|stateAlwaysHidden" />
- 查詢訂單支付結(jié)果止喷,并提示給用戶
@Subscribe(threadMode = ThreadMode.MAIN)
public void payment(WXPayEntryEntity wxPayEntryEntity) {
//TODO 當客戶端收到微信支付成功的回調(diào)后馆类,繼續(xù)向后臺服務(wù)器進行驗證為最終的結(jié)果
// .......此處省略調(diào)用服務(wù)器接口,查詢結(jié)果邏輯......
// 此處以下代碼只是作為測試使用弹谁,這里需要去服務(wù)器異步查詢結(jié)果
switch (wxPayEntryEntity.getPayStatus()) {
case BaseResp.ErrCode.ERR_OK:
showToast(this, "支付成功");
break;
case BaseResp.ErrCode.ERR_AUTH_DENIED:
showToast(this, "微信授權(quán)失敗");
break;
case BaseResp.ErrCode.ERR_COMM:
showToast(this, "訂單支付失斍伞!");
break;
case BaseResp.ErrCode.ERR_SENT_FAILED:
showToast(this, "微信發(fā)送失敗");
break;
case BaseResp.ErrCode.ERR_UNSUPPORT:
showToast(this, "微信不支持");
break;
case BaseResp.ErrCode.ERR_USER_CANCEL:
showToast(this, "用戶點擊取消并返回");
break;
default:
showToast(this, "訂單支付失斣し摺沟于!");
break;
}
}
- 混淆配置
# 微信混淆
-keep class com.tencent.mm.opensdk.** { *; }
-keep class com.tencent.wxop.** { *; }
-keep class com.tencent.mm.sdk.** { *; }
支付寶支付
流程
[圖片上傳失敗...(image-92d9c1-1567066470689)]
其實流程和微信差不多,對于APP端只需要簡單的幾個流程就能完成支付寶支付
- 下單后植康,選擇支付寶支付旷太,客戶端向服務(wù)器發(fā)送請求,服務(wù)器產(chǎn)生預支付訂單销睁,并返回給客戶端
- 客戶端拿到預支付訂單的相關(guān)參數(shù)供璧,通過支付寶SDK喚起支付
- 客戶端拿到支付寶返回的支付結(jié)果,向商戶后臺服務(wù)器查詢最終的訂單完成信息冻记。
集成
- 接入支付寶SDK
1.下載SDK
2.將alipaySdk-15.6.5-20190718211148.aar
復制到libs
目錄下
3.工程目錄的build.gradle添加
allprojects {
repositories {
// 支付寶 SDK AAR 包所需的配置
flatDir {
dirs 'libs'
}
google()
jcenter()
}
}
4.Module項目的build.gradle增加依賴
// 支付寶 SDK AAR 包所需的配置
implementation (name: 'alipaySdk-15.6.5-20190718211148', ext: 'aar')
- 添加權(quán)限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
- 調(diào)用支付
/**
* 支付寶支付
*
* @param payInfo 預支付信息
*/
private void aliPay(final String payInfo) {
// 1.去服務(wù)器拿支付訂單
final Runnable payRunnable = new Runnable() {
@Override
public void run() {
PayTask aliPay = new PayTask(PayDialogActivity.this);
Map<String, String> result = aliPay.payV2(payInfo, true);
Log.d(TAG, result.toString());
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
payHandler.sendMessage(msg);
}
};
// 必須異步調(diào)用
Thread payThread = new Thread(payRunnable);
payThread.start();
}
- 支付寶回調(diào)
/**
* 支付寶回調(diào)
*/
static class PayHandler extends Handler {
private final WeakReference<PayDialogActivity> payDialogActivityWrf;
private PayHandler(PayDialogActivity payDialogActivityWrf) {
this.payDialogActivityWrf = new WeakReference<>(payDialogActivityWrf);
}
@Override
public void handleMessage(Message msg) {
if (SDK_PAY_FLAG == msg.what) {
@SuppressWarnings("unchecked")
PayResult payResult = new PayResult((Map<String, String>) msg.obj);
//對于支付結(jié)果睡毒,請商戶依賴服務(wù)端的異步通知結(jié)果。同步通知結(jié)果,僅作為支付結(jié)束的通知。
String resultStatus = payResult.getResultStatus();
// 判斷resultStatus 為9000則代表支付成功
if (TextUtils.equals(resultStatus, CODE_9000)) {
// TODO 該筆訂單是否真實支付成功蓉冈,需要依賴[服務(wù)端的異步通知]赞警。
// 該筆訂單是否真實支付成功胎许,需要依賴服務(wù)端的異步通知钟沛。
if (payDialogActivityWrf.get() != null) {
payDialogActivityWrf.get().showAlert(payDialogActivityWrf.get(), "支付成功驮审!");
}
} else {
// TODO 該筆訂單是否真實支付成功译株,需要依賴[服務(wù)端的異步通知]棉钧。
if (payDialogActivityWrf.get() != null) {
payDialogActivityWrf.get().showAlert(payDialogActivityWrf.get(), "支付失斢炱辍!");
}
}
}
}
}
- 關(guān)于混淆
由于新版的支付寶SDK采用的是aar替換了原來舊版的jar包掰盘,默認aar中已經(jīng)幫你做出了混淆摄悯,故新版的支付寶無需手動混淆
,以下是aar中解壓出來的混淆說明
# 這個 ProGuard 文件被指定為 consumerProguardFiles愧捕。
# 如此一來奢驯,AAR 包的使用者在其應(yīng)用進行 ProGuard 混淆時,將自動附加下列規(guī)則次绘,
# 省去了接入 JAR 時手動在 ProGuard 規(guī)則文件中加入支付寶 SDK 規(guī)則的步驟瘪阁。
-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IAlixPay$Stub{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{ public *;}
-keep class com.alipay.sdk.app.AuthTask{ public *;}
-keep class com.alipay.sdk.app.H5PayCallback {
<fields>;
<methods>;
}
-keep class com.alipay.android.phone.mrpc.core.** { *; }
-keep class com.alipay.apmobilesecuritysdk.** { *; }
-keep class com.alipay.mobile.framework.service.annotation.** { *; }
-keep class com.alipay.mobilesecuritysdk.face.** { *; }
-keep class com.alipay.tscenter.biz.rpc.** { *; }
-keep class org.json.alipay.** { *; }
-keep class com.alipay.tscenter.** { *; }
-keep class com.ta.utdid2.** { *;}
-keep class com.ut.device.** { *;}
# SDK 包可能不包含 utdid
-dontwarn com.ta.utdid2.**
-dontwarn com.ut.device.**
# SDK 包可能不包含 securitysdk
-dontwarn com.alipay.mobilesecuritysdk.**
總結(jié)
關(guān)于支付大概就是這么多了,總而言之邮偎,對于移動支付管跺,APP客戶端流程只需要做到以下幾點就能完成支付:
- 商戶服務(wù)器產(chǎn)生預支付訂單
- 客戶端通過預訂單參數(shù)調(diào)用支付SDK客戶端
- 拿到支付客戶端返回的訂單信息并以商戶后臺服務(wù)器查詢的訂單為最終結(jié)果