移动支付那点事儿

image

关于本文支付相关的源码详见我的开源项目MobilePayment

前言

移动支付其实是非常简单的,因为只要按照第三方的文档来就行了,所以在本次分享中,其实更像是一次开发的纪要,当然也有一些看点。做过支付的人都知道支付的难点其实是在第三方文档和demo上(集中体现文档陈旧、demo容易误导人、槽点太多),那就不得不先来吐槽下微信的开发文档和示例,我相信大部分人都被坑过,没有对比就没有伤害,相对而言,支付宝的的文档就好很多,下面我先说重点再谈支付流程。

开发优化要点

  • 微信回调返回当前页面部分机型会产生一闪而过的黑屏现象,测试机型三星S8,解决方案为在微信回调页面增加透明主题,如下:
    <!--解决微信支付回调部分机型黑屏闪烁的问题-->
    <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>
    
    <!--回调页面WXPayEntryActivity关闭finish的时候增加-->
     overridePendingTransition(0, 0);
    
    <!--回调页面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"
     建议优先采取第一种方案,该方案作为备选,毕竟微信推荐使用"singleTop"启动模式。
  • DialogFragment内存泄漏问题,google虽然推荐使用DialogFragment替代Dialog,但是内存泄漏问题并未解决,试过很多方案,并未完美解决泄漏问题,故更改使用Activity结合动画实现底部支付弹窗的效果。
 <!--底部弹框支付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);

<!-- 关闭底部弹框-->
finish();
overridePendingTransition(R.anim.push_bottom_silent,R.anim.push_bottom_out);

<!-- 实现透明状态栏-->
setContentView(R.layout.activity_dialog_pay_custom);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    getWindow().setStatusBarColor(Color.TRANSPARENT);
    }

效果图

image

微信支付

流程

image

没错,这就是官方的业务流程图,黑糊糊,感觉就是一张盗版图,下面我以客户端开发为主,简化一下流程:

  • 客户端App选择购买的商品后,选择微信支付,接口请求服务器,服务器此时需要生成预支付订单(调用微信统一下单接口),其中返回的参数便是我们调起微信支付所需要的参数。(统一下单
  • 客户端拿到服务器返回的预支付订单参数,通过微信SDK调起微信支付页面
  • 客户端收到微信支付的状态回调
  • 通过服务器查询支付订单的状态,注意:以服务器查询的结果为准,不要使用微信返回给客户端的支付状态作为支付依据。(订单查询

集成

  • 微信开放平台注册及配置

image

1.申请应用获取微信平台生成的唯一APPID(管理中心-创建应用
2.配置签名,获取应用的签名(签名工具
3.配置应用包名,此处文档太老旧,应该为buile.gradle中的applicationId

  • 注册APPID
 IWXAPI api = WXAPIFactory.createWXAPI(this, APP_ID);
  • 调用SDK发起支付
  /**
     * 微信支付
     *
     * @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, "正常调起支付", Toast.LENGTH_SHORT).show();
                // 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信
                api.sendReq(req);
            }
        } catch (JSONException e) {
            e.printStackTrace();
            showToast(this, "异常:" + e.getMessage());
        }
    }
  • 微信回调界面
    1.目录结构配置,需要在包名下新建wxapi目录,并增加回调界面WXPayEntryActivity,如下图:
    image

    2.回调界面示例
/**
 * @author hule
 * @date 2019/7/29 15:58
 * description: 微信支付回调
 */
public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler {
    private IWXAPI api;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //此处如果需要显示界面需要设置setContentView();
        //一般情况下会都不会去设置界面,拿到回调后直接关闭界面,发送通知处理
        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) {
            //通知我们回调,我们拿来查询订单的状态
            EventBus.getDefault().post(new WXPayEntryEntity(resp.errCode));
        }
        // 清除动画,有助于防止黑屏闪烁
        overridePendingTransition(0, 0);
        finish();
    }
}

3.回调页面配置

    <!--解决微信支付回调部分机型黑屏闪烁的问题-->
    <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>
    
    <!--回调页面WXPayEntryActivity关闭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" />
  • 查询订单支付结果,并提示给用户
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void payment(WXPayEntryEntity wxPayEntryEntity) {
        //TODO 当客户端收到微信支付成功的回调后,继续向后台服务器进行验证为最终的结果
        // .......此处省略调用服务器接口,查询结果逻辑......
        // 此处以下代码只是作为测试使用,这里需要去服务器异步查询结果
        switch (wxPayEntryEntity.getPayStatus()) {
            case BaseResp.ErrCode.ERR_OK:
                showToast(this, "支付成功");
                break;
            case BaseResp.ErrCode.ERR_AUTH_DENIED:
                showToast(this, "微信授权失败");
                break;
            case BaseResp.ErrCode.ERR_COMM:
                showToast(this, "订单支付失败!");
                break;
            case BaseResp.ErrCode.ERR_SENT_FAILED:
                showToast(this, "微信发送失败");
                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端只需要简单的几个流程就能完成支付宝支付

  • 下单后,选择支付宝支付,客户端向服务器发送请求,服务器产生预支付订单,并返回给客户端
  • 客户端拿到预支付订单的相关参数,通过支付宝SDK唤起支付
  • 客户端拿到支付宝返回的支付结果,向商户后台服务器查询最终的订单完成信息。

集成

  • 接入支付宝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')
  • 添加权限
    <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" />
  • 调用支付
    /**
     * 支付宝支付
     *
     * @param payInfo 预支付信息
     */
    private void aliPay(final String payInfo) {
        // 1.去服务器拿支付订单
        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);
            }
        };
        // 必须异步调用
        Thread payThread = new Thread(payRunnable);
        payThread.start();
    }
  • 支付宝回调
  /**
     * 支付宝回调
     */
    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);
                //对于支付结果,请商户依赖服务端的异步通知结果。同步通知结果,仅作为支付结束的通知。
                String resultStatus = payResult.getResultStatus();
                // 判断resultStatus 为9000则代表支付成功
                if (TextUtils.equals(resultStatus, CODE_9000)) {
                    // TODO 该笔订单是否真实支付成功,需要依赖[服务端的异步通知]。
                    // 该笔订单是否真实支付成功,需要依赖服务端的异步通知。
                    if (payDialogActivityWrf.get() != null) {
                        payDialogActivityWrf.get().showAlert(payDialogActivityWrf.get(), "支付成功!");
                    }
                } else {
                    // TODO 该笔订单是否真实支付成功,需要依赖[服务端的异步通知]。
                    if (payDialogActivityWrf.get() != null) {
                        payDialogActivityWrf.get().showAlert(payDialogActivityWrf.get(), "支付失败!");
                    }
                }
            }
        }
    }
  • 关于混淆
    由于新版的支付宝SDK采用的是aar替换了原来旧版的jar包,默认aar中已经帮你做出了混淆,故新版的支付宝无需手动混淆
    ,以下是aar中解压出来的混淆说明
# 这个 ProGuard 文件被指定为 consumerProguardFiles。
# 如此一来,AAR 包的使用者在其应用进行 ProGuard 混淆时,将自动附加下列规则,
# 省去了接入 JAR 时手动在 ProGuard 规则文件中加入支付宝 SDK 规则的步骤。

-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.**

总结

关于支付大概就是这么多了,总而言之,对于移动支付,APP客户端流程只需要做到以下几点就能完成支付:

  • 商户服务器产生预支付订单
  • 客户端通过预订单参数调用支付SDK客户端
  • 拿到支付客户端返回的订单信息并以商户后台服务器查询的订单为最终结果
image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 支付 1.支付宝和银联的支付流程 常用的支付方式有: 1、支付宝支付 https://openhome.alipa...
    丶逐渐阅读 1,670评论 3 11
  • 支付宝简介文档 (适用于ydm-java接口与后台,如有误入,但愿也能给您带来帮助) 此文档写于2017年3月,只...
    隔壁付叔叔阅读 17,044评论 3 19
  • 实现支付宝支付的准备工作: 1.向支付宝签约,成为支付宝的商户 签约完成后,支付宝会提供一些必要的数据给我们 商户...
    Anson杨春安阅读 8,181评论 0 6
  • 准备工作: 需要公司的营业执照,税务信息,等老板的身份证信息等,我记得,用这些材料,去支付宝注册一个商家账户(审核...
    Hevin_Chen阅读 6,795评论 0 9
  • 微信支付逻辑? 1、顾客选择商品 2、顾客选到心怡的商品找店员结算 3、店员拿着顾客交给他的商品生成预付单(后台服...
    Carden阅读 559评论 0 0