Flutter(安卓) - 实现支付宝、微信支付(2020.06.28更新)

声明:转载请指明内容出处,谢谢!

2020.06.28 22:32 更新说明

  1. 正式打包后,发现微信支付无法正常调用,经发现是由于Gson无法解析JSON字符串而造成微信参数错误,因此建议使用 JSONObject 处理支付参数的数据转换;
    改动代码 OnlinePayPlugin.kt
    /**
     * 启动在线支付
     * @param call 方法Call
     */
    private fun startOnlinePay(call: MethodCall, result: MethodChannel.Result) {
        callback = result //赋值结果反馈对接
        val payType = call.argument<String>("type")
        val payOrderInfo = call.argument<String>("payInfo")
        if (payOrderInfo.isNullOrEmpty()) {
            callback?.success(mapOf(
                    "type" to payType,
                    "state" to -1,
                    "description" to "错误:支付参数不能为空"))
            callback = null
            return
        }
        when (payType) {
            "ALIPAY" -> activity?.let {
                AliPayPlugin.getInstance()?.startPay(it, payOrderInfo)
            }
            // 微信支付的实体类使用JSONObject方式获取
            "WECHAT_PAY" ->
                WechatPayPlugin.getInstance()?.startPay(WechatPayParams.fromJsonObject(JSONObject(payOrderInfo)))
            else -> {
                result.error("ONLINE_PAY_TYPE_ERROR", "错误:未知支付类型", null)
                callback = null
            }
        }
    }

微信支付参数的实体类实现如下,WechatPayParams属于文件WechatPayPlugin.kt

data class WechatPayParams(
        @JvmField
        var appId: String? = BuildConfig.WX_APPID,
        @JvmField
        var packageValue: String? = "Sign=WXPay",
        @JvmField
        var partnerId: String?,
        @JvmField
        var prepayId: String?,
        @JvmField
        var sign: String? = null,
        @JvmField
        var nonceStr: String? = null,
        @JvmField
        var timeStamp: String? = null,
        @JvmField
        var signType: String? = null
) {

    override fun toString(): String = Gson().toJson(this)

    companion object {

        @JvmStatic
        fun fromJsonObject(jsonObject: JSONObject): WechatPayParams = WechatPayParams(
                jsonObject.optString("appId"),
                jsonObject.optString("packageValue"),
                jsonObject.optString("partnerId"),
                jsonObject.optString("prepayId"),
                jsonObject.optString("sign"),
                jsonObject.optString("nonceStr"),
                jsonObject.optString("timeStamp"),
                jsonObject.optString("signType")
        )

    }

}

2020.06.18 18:52 更新说明:

  1. 修复online_pay.dart文件中构造函数报错内容溢出问;
  2. 调整原生代码中支付参数为空的提示反馈方式,使用MethodChannel.Result来回调结果;
  3. 删除EventChannel,直接改为由startPay方法返回, 因EventChannel会导致支付结果信息缓存或者延迟;
  4. 示例代码中修改,添加输出结果展示。

原文内容

支付宝、微信支付是开发中经常需要用到的功能,那么如何集成支付功能到应用中呢?支付结果亦是异步的,又如何传递结果呢?带着这些疑问让我们一步步走进这学习探索之路。

在这探索之前,想问下看客朋友是否查看过官方的电池状态监控示例。如果还没有的朋友建议先看一下官方的源码!

当你对官方源码都有所了解后,那下面的内容就容易理解多了。一般情况下,原生与Flutter通讯,我们经常使用MethodChannel处理方法调用,然后用MethodChannel.Result来处理支付结果的返回。说到这里,头脑里是否有一个大致流程构思了?!

首先,集成支付SDK,这个就不多说了。

其次,把支付方法封装起来。

最后,把支付方法提供给Flutter调用,事件响应给Flutter。

1.支付宝支付辅助类代码

class AliPayPlugin private constructor() {

    companion object {

        private var alipay: AliPayPlugin? = null

        @JvmStatic
        fun getInstance(): AliPayPlugin? {
            if (alipay == null) {
                synchronized(AliPayPlugin::class.java) {
                    alipay = AliPayPlugin()
                }
            }
            return alipay
        }

    }

    @Suppress("unchecked_cast")
    @SuppressLint("HandlerLeak")
    private val mHandler = object : Handler() {
        override fun handleMessage(msg: Message) {
            with(LocalBroadcastManager.getInstance(GenydfApplication.instance)) {
                val intent = Intent().apply {
                    action = OnlinePayPlugin.ACTION_ONLINE_PAY_RESULT_NOTIFIER
                    putExtra("payType", "ALIPAY")
                }
                when (msg.what) {
                    0x0000A -> with(PayResult(msg.obj as Map<String, String>)) {
                        /** 对于支付结果,请商户依赖服务端的异步通知结果。同步通知结果,仅作为支付结束的通知。 **/
                        val resultInfo = if (!TextUtils.isEmpty(result)) result else memo// 同步返回需要验证的信息
                        // 判断resultStatus 为9000则代表支付成功
                        when {
                            TextUtils.equals(resultStatus, "9000") -> intent.apply {
                                putExtra("message", "支付宝支付成功")
                                putExtra("state", 1)
                            }
                            TextUtils.equals(resultStatus, "6001") -> intent.apply {
                                putExtra("message", "您取消了支付宝支付")
                                putExtra("state", 0)
                            }
                            else -> intent.apply {
                                putExtra("message", "支付失败,原因:$resultInfo")
                                putExtra("state", -1)
                            }
                        }
                    }
                }
                sendBroadcast(intent)
            }
        }
    }

    /**
     * 支付宝支付业务示例
     * @param activity 上下文对象
     * @param orderInfo 订单信息,来自服务器
     */
    fun startPay(activity: Activity, orderInfo: String): Unit = Thread {
        val result = PayTask(activity).payV2(orderInfo, true)
        val msg = Message()
        msg.what = 0x0000A
        msg.obj = result
        mHandler.sendMessage(msg)
    }.start()

}

/**
 * 支付结果实体类
 * @param rawResult 支付Map数据结果
 */
class PayResult(rawResult: Map<String, String>?) {
    /**
     * @return the resultStatus
     */
    var resultStatus: String? = null
        private set
    /**
     * @return the result
     */
    var result: String? = null
        private set
    /**
     * @return the memo
     */
    var memo: String? = null
        private set

    init {
        rawResult?.let {
            for (key in rawResult.keys) {
                when {
                    TextUtils.equals(key, "resultStatus") -> resultStatus = rawResult[key]
                    TextUtils.equals(key, "result") -> result = rawResult[key]
                    TextUtils.equals(key, "memo") -> memo = rawResult[key]
                }
            }
        }
    }

    override fun toString(): String = "resultStatus={$resultStatus};memo={$memo};result={$result}"
}

2.微信支付辅助类代码

/**
 * 微信插件
 */
class WechatPlugin private constructor() {

    companion object {

        private var wechatPlugin: WechatPlugin? = null

        @JvmStatic
        fun getInstance(): WechatPlugin? {
            if (wechatPlugin == null) {
                synchronized(WechatPlugin::class.java) {
                    wechatPlugin = WechatPlugin()
                }
            }
            return wechatPlugin
        }
    }

    private val wxApi: IWXAPI? by lazy {
        val api = WXAPIFactory.createWXAPI(GenydfApplication.instance, BuildConfig.WX_APPID, false)
        api.registerApp(BuildConfig.WX_APPID)
        api
    }

    /** 微信APP是否已经安装 **/
    val isWXAppInstalled: Boolean by lazy { wxApi?.isWXAppInstalled ?: false }

    /**
     * 发送请求
     * @param req 要请求的对象数据
     */
    fun sendRequest(req: BaseReq?) = wxApi?.sendReq(req)

    /**
     * 微信登录
     * @param activity 上下文对象
     */
    fun login(activity: Activity) {
        if (wxApi?.isWXAppInstalled != true) {
            Toast.makeText(activity, "您的设备未安装微信,请安装后再登录!", Toast.LENGTH_SHORT).show()
            return
        }
        with(SendAuth.Req()) {
            scope = "snsapi_userinfo"
            state = "${System.currentTimeMillis()}"
            wxApi?.sendReq(this)
        }
    }

    /**
     * 回调,需要在WxEntryActivity中调用
     * @param intent
     * @param handler
     */
    fun handleIntent(intent: Intent?, handler: IWXAPIEventHandler) {
        wxApi?.handleIntent(intent, handler)
    }

}
/** 微信支付 **/
class WechatPayPlugin private constructor() {

    companion object {

        private var wechatPay: WechatPayPlugin? = null

        @JvmStatic
        fun getInstance(): WechatPayPlugin? {
            if (wechatPay == null) {
                synchronized(WechatPayPlugin::class.java) {
                    wechatPay = WechatPayPlugin()
                }
            }
            return wechatPay
        }

    }

    /**
     * 发送支付请求
     * @param params 支付请求参数
     */
    fun startPay(params: WechatPayParams): Unit = with(PayReq()) {
        appId = params.appId
        partnerId = params.partnerId
        prepayId = params.prepayId
        packageValue = params.packageValue
        nonceStr = params.nonceStr
        timeStamp = params.timeStamp
        sign = params.sign
        WechatPlugin.getInstance()?.sendRequest(this)
    }

}

/**
 * 微信支付参数实体类
 * @param appId APP-ID
 * @param packageValue 包名
 * @param partnerId 合作者ID
 * @param prepayId 准备的订单ID
 * @param sign 签名字符串
 * @param nonceStr 随机字符串
 * @param timeStamp 时间戳
 */
data class WechatPayParams(
        @JvmField
        var appId: String? = BuildConfig.WX_APPID,
        @JvmField
        var packageValue: String? = "Sign=WXPay",
        @JvmField
        var partnerId: String?,
        @JvmField
        var prepayId: String?,
        @JvmField
        var sign: String? = null,
        @JvmField
        var nonceStr: String? = null,
        @JvmField
        var timeStamp: String? = null
) {

    override fun toString(): String = Gson().toJson(this)

    companion object {

        @JvmStatic
        fun fromJsonObject(jsonObject: JSONObject): WechatPayParams = WechatPayParams(
                jsonObject.optString("appId"),
                jsonObject.optString("packageValue"),
                jsonObject.optString("partnerId"),
                jsonObject.optString("prepayId"),
                jsonObject.optString("sign"),
                jsonObject.optString("nonceStr"),
                jsonObject.optString("timeStamp"),
                jsonObject.optString("signType")
        )

    }

}
  1. 在线支付插件,按照V2版本插件编写的。
    下面代码中利用MethodChannel为Flutter提供调用方法,使用EventChannel为支付结果响应事件,其中这里用的是LocalBroadcastManager进行支付结果通知,我们可以把通知的优先级提高一点,防止收到通知太慢的情况发生。
class OnlinePayPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCallHandler {

    companion object {

        private const val M_NAME = "com.test.trade/online-pay-plugin"
        private const val E_NAME = "com.test.trade/online-pay-plugin/pay-result"

        /** 在线支付结果通知 **/
        const val ACTION_ONLINE_PAY_RESULT_NOTIFIER = "com.test.trade.online_pay.result"

    }

    private var applicationContext: Context? = null
    private var activity: Activity? = null
    private var payResultNotifierReceiver: BroadcastReceiver? = null
    private var methodChannel: MethodChannel? = null
    private var callback: MethodChannel.Result? = null

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
    }

    private fun onAttachedToEngine(applicationContext: Context, messenger: BinaryMessenger) {
        this.applicationContext = applicationContext
        payResultNotifierReceiver = newBroadcastReceiverInstance()
        LocalBroadcastManager.getInstance(applicationContext)
                .registerReceiver(payResultNotifierReceiver!!,
                        IntentFilter(ACTION_ONLINE_PAY_RESULT_NOTIFIER))
        methodChannel = MethodChannel(messenger, M_NAME).apply {
            setMethodCallHandler(this@OnlinePayPlugin)
        }
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        if (payResultNotifierReceiver != null) {
            LocalBroadcastManager.getInstance(applicationContext!!)
                    .unregisterReceiver(payResultNotifierReceiver!!)
            payResultNotifierReceiver = null
        }
        methodChannel?.setMethodCallHandler(null)
        methodChannel = null
    }

    override fun onAttachedToActivity(binding: ActivityPluginBinding) {
        onAttachedToActivity(binding.activity)
    }

    private fun onAttachedToActivity(activity: Activity) {
        this.activity = activity
    }

    override fun onDetachedFromActivity() {}

    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}

    override fun onDetachedFromActivityForConfigChanges() {}

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        when (call.method) {
            "startPay" -> startOnlinePay(call, result)
            else -> result.notImplemented()
        }
    }

    // 构建一个新的消息接收实例
    private fun newBroadcastReceiverInstance() = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action == ACTION_ONLINE_PAY_RESULT_NOTIFIER) {
                callback?.success(mapOf(
                        "type" to intent.getStringExtra("payType"),
                        "state" to intent.getIntExtra("state", -1),
                        "description" to intent.getStringExtra("message")))
                callback = null
            }
        }
    }

    /**
     * 启动在线支付
     * @param call 方法Call
     */
    private fun startOnlinePay(call: MethodCall, result: MethodChannel.Result) {
        callback = result //赋值结果反馈对接
        val payType = call.argument<String>("type")
        val payOrderInfo = call.argument<String>("payInfo")
        if (payOrderInfo.isNullOrEmpty()) {
            callback?.success(mapOf(
                    "type" to payType,
                    "state" to -1,
                    "description" to "错误:支付参数不能为空"))
            callback = null
            return
        }
        when (payType) {
            "ALIPAY" -> activity?.let {
                AliPayPlugin.getInstance()?.startPay(it, payOrderInfo)
            }
            "WECHAT_PAY" -> WechatPayPlugin.getInstance()?.startPay(WechatPayParams.fromJsonObject(JSONObject(payOrderInfo)))
            else -> {
                result.error("ONLINE_PAY_TYPE_ERROR", "错误:未知支付类型", null)
                callback = null
            }
        }
    }

}

微信WXPayEntryActivity .java的处理

public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler {

    private WechatPlugin wechatPlugin;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        wechatPlugin = WechatPlugin.getInstance();
        if (wechatPlugin != null)
            wechatPlugin.handleIntent(getIntent(), this);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        if (wechatPlugin == null)
            wechatPlugin = WechatPlugin.getInstance();
        if (wechatPlugin != null)
            wechatPlugin.handleIntent(getIntent(), this);
    }

    @Override
    public void onReq(BaseReq req) {
    }

    @Override
    public void onResp(BaseResp resp) {
        if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
            Intent intent = new Intent();
            intent.setAction(OnlinePayPlugin.ACTION_ONLINE_PAY_RESULT_NOTIFIER);
            intent.putExtra("payType", "WECHAT_PAY");
            switch (resp.errCode) {
                case 0: //支付成功
                    intent.putExtra("message", "微信支付成功");
                    intent.putExtra("state", 1);
                    break;
                case -1: //支付错误
                    intent.putExtra("message", "微信支付失败, 错误:" + resp.errStr);
                    intent.putExtra("state", -1);
                    break;
                case -2: //用户取消
                    intent.putExtra("message", "您已取消微信支付");
                    intent.putExtra("state", 0);
                    break;
            }
            LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
        }
        finish();
    }
}

4.Flutter部分实现,对接MethodChannel和EventChannel,类使用单例模式。

/// 在线支付插件, 单例类
class OnlinePayPlugin {
  static const MethodChannel _methodChannel =
      const MethodChannel('com.test.trade/online-pay-plugin');

  static const EventChannel _eventChannel =
      const EventChannel('com.test.trade/online-pay-plugin/pay-result');

  factory OnlinePay() {
    if (_instance == null) _instance = OnlinePay._internel();
    return _instance;
  }

  OnlinePay._internel();

  /// 开始支付
  /// + `info` 支付信息
  Future<OnlinePayResultInfo> startPay(OnlinePayInfo info) async {
    var result = await _methodChannel.invokeMethod('startPay', {
      'type': info.type == PayType.AliPay ? 'ALIPAY' : 'WECHAT_PAY',
      'payInfo': info.payArguments is String
          ? info.payArguments
          : json.encode(info.payArguments)
    });
    return OnlinePayResultInfo.fromJson(result);
  }
}

/// 支付类型
enum PayType {
  ///支付宝支付
  AliPay,

  /// 微信支付
  WechatPay
}

///支付信息
class OnlinePayInfo {
  /// 支付类型
  PayType type;

  /// 支付参数
  dynamic payArguments;

  OnlinePayInfo({@required this.type, @required this.payArguments});

  Map<String, dynamic> toJson() => {
        'type': type.toString(),
        'payArguments':
            payArguments is String ? payArguments : json.encode(payArguments)
      };
}

/// 微信支付参数
class WechatPayArgumentsInfo {
  /// 应用ID
  String appId;

  /// 常量值:Sign=WXPay
  String packageValue;

  /// 合作者ID
  String partnerId;

  /// 预付订单ID
  String prepayId;

  /// 签名字符串
  String sign;

  /// 随机字符串
  String nonceStr;

  /// 时间戳
  String timeStamp;

  WechatPayArgumentsInfo();

  factory WechatPayArgumentsInfo.fromJson(Map<String, dynamic> json) =>
      WechatPayArgumentsInfo()
        ..appId = json['appId'] as String
        ..packageValue = json['packageValue'] as String
        ..partnerId = json['partnerId'] as String
        ..prepayId = json['prepayId'] as String
        ..sign = json['sign'] as String
        ..nonceStr = json['nonceStr'] as String
        ..timeStamp = json['timeStamp'] as String;

  Map<String, dynamic> toJson() => {
        'appId': appId,
        'packageVakue': packageValue,
        'partnerId': partnerId,
        'prepayId': prepayId,
        'sign': sign,
        'nonceStr': nonceStr,
        'timeStamp': timeStamp
      };
}

/// 支付结果状态
enum OnlinePayResultState {
  /// 支付成功
  Success,

  /// 支付失败
  Fail,

  /// 支付被取消
  Cancel
}

/// 在线支付结果信息
class OnlinePayResultInfo {
  /// 支付类型
  PayType type;

  /// 支付状态
  OnlinePayResultState state;

  /// 支付结果描述
  String description;

  OnlinePayResultInfo();

  factory OnlinePayResultInfo.fromJson(Map<String, dynamic> json) =>
      OnlinePayResultInfo()
        ..type = json['type'] == 'ALIPAY' ? PayType.AliPay : PayType.WechatPay
        ..state = json['state'] == 1
            ? OnlinePayResultState.Success
            : (json['state'] == -1
                ? OnlinePayResultState.Fail
                : OnlinePayResultState.Cancel)
        ..description = json['description'] as String;

  Map<String, dynamic> toJson() => {
        'type': type.toString(),
        'state': state.toString(),
        'description': description
      };
}

5.调用测试

void test() async {
    debugPrint('------>开始支付:');
    var result = await OnlinePay()
        .startPay(OnlinePayInfo(type: PayType.AliPay, payArguments: ''));
    debugPrint('------>结果:${result.toJson()}');
    debugPrint('------->支付结束');
}

输出结果:

I/flutter ( 7857): ------>开始支付:
I/flutter ( 7857): ------>结果:{type: PayType.AliPay, state: OnlinePayResultState.Fail, description: 支付失败,原因:系统繁忙,请稍后再试}
I/flutter ( 7857): ------->支付结束
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容