本文导读
能学到什么?
1、flutter和原生之间的通讯
2、澳门通sdk的使用方法
使用的语言
kotlin_version = '1.3.50'
Dart 2.13.4
Flutter 2.2.3
GitHub :https://github.com/InTheClodus/mpy_plugin
创建一个插件项目,当然也可以直接使用as创建
flutter create --org com.lf--template=plugin mpay_plugis
在android/ 下创建 lib
导入这三个包
分别是支付 、 澳门通和微信支付 的包,均可在gihub链接中下载,
在android/build.gradle中做如下配置,
android节点加上这两个声明 省略号是其他自带的,不用管,有这些配置才能添加aar包
android{ .... sourceSets { main { jniLibs.srcDirs = ['libs'] } } repositories { flatDir { dirs 'libs' } } }
dependencies节点修改成为如下,本来应该在第三行就可以导入aar包,但是不知道为什么运行之后就提示找不到alipay插件,所以就用了compileOnly 方法导入aar包,
两个网络请求模块是澳门通sdk必须用到的,所以也是需要导入,否则无法运行
dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation fileTree(include: ['*.jar'], dir: 'libs') // implementation fileTree(dir: 'libs', include: ['*.aar']) // 网络模块 implementation "com.squareup.okhttp3:okhttp:3.12.0" implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0' compileOnly fileTree(dir: "../libs", include: ["*.aar"]) }
开始编写代码。。。
编辑 mpy_plugin.dart文件,
ConfigType 是配置要初始化的支付方式,开始想法是集成支付宝微信和云闪付这些,不过mpay已经自带支付宝和微信支付了,所以暂时不弄这俩
在flutter插件项目里MethodChannel是最重要的一个方法,它负责进行通讯,需要在Flutter端和Native端两边做如下操作。
1.Flutter 端
static const MethodChannel _flutterPay = const MethodChannel('mpy_plugin');
2.Android端
MethodChannel(flutterPluginBinding.binaryMessenger, "mpy_plugin")
不过这些操作项目创建的时候就已经自动生成,所以不用管它,但是在Native和Flutter端注册的MethodChannel中的字符串值必须一样
接下来看代码,现在有三个方法,其中platformVersion是创建时自带的案例,不用管,它只是获取当前系统版本
init方法是初始化插件时调用,pay是支付时调用,等会会在使用案例写到怎么用
class ConfigType{ static String mPay = "config.mpay"; } class MpyPlugin { static const MethodChannel _flutterPay = const MethodChannel('mpy_plugin'); static Future<String?> get platformVersion async { final String? version = await _flutterPay.invokeMethod('getPlatformVersion'); return version; } static Future<dynamic> pay({required Map<String,dynamic> params})async{ return await _flutterPay.invokeMapMethod("pay",params); } /* * 初始化 */ static Future<void> init(String type,String methods) async { final Map<String, dynamic> params = <String, dynamic>{ "methods": methods, }; await _flutterPay.invokeListMethod(type, params); } }
右键项目,打开Android目录
接着创建一个支付适配器接口
interface PaymentAdapter<T> { val method: String fun pay(params: MethodCall, result: MethodChannel.Result) fun handleResult(result: T) }
一共三个接口,前面说了mpay的sdk集成了多个支付方式,所以定义一个 method 表示使用什么方式支付,pay 是支付的参数信息,handleResult 是支付返回值
然后还需要创建一个支付通道接口 channel暂时没用到,methods 还是和上面一样是支付信息,支付信息是在flutter端向服务器发起请求返回的数据,通过这个数据去进行支付操作
在pay接口中,我们需要判断一下适配器是否为空,避免不必要的错误,直接将错误信息返回给flutter端
interface PaymentChannel { val channel: String; fun methods(): HashMap<String, PaymentAdapter<PayResult>>; fun pay(method: String, params: MethodCall, result: MethodChannel.Result) { val adapter = methods()[method]; if (adapter == null) { result.error("500", "适配器是空的", params); } else { adapter.pay(params, result); } } }
新建一个 MPay的包,在MPay包下创建MPay.kt
定义MPay传入参数,并继承PaymentChannel,和澳门通SDk的OpenSdkInterfaces,按住alt+enter,实现全部方法,代码如下
class MPay(params: Map<String, String>, val mActivity: Activity): PaymentChannel, OpenSdkInterfaces { override val channel: String get() = TODO("Not yet implemented") override fun methods(): HashMap<String, PaymentAdapter<PayResult>> { TODO("Not yet implemented") } override fun OpenSDKInterfaces(p0: PayResult?) { TODO("Not yet implemented") } override fun AliPayInterfaces(p0: PayResult?) { TODO("Not yet implemented") } override fun MPayInterfaces(p0: PayResult?) { TODO("Not yet implemented") } override fun WeChatPayInterfaces(p0: PayResult?) { TODO("Not yet implemented") } }
暂时不实现里面的代码,我们来创建三个文件,三个都要继承支付适配器的方法(PaymentAdapter)
Alipay.kt、WeChat.kt、MPayModel.kt
以Alipay为例子,其他两个也一样
class Alipay(val mPay: MPay):PaymentAdapter<PayResult>{ override val method: String get() = TODO("Not yet implemented") override fun pay(params: MethodCall, result: MethodChannel.Result) { TODO("Not yet implemented") } override fun handleResult(result: PayResult) { TODO("Not yet implemented") } }
回到MPay,继续里面的代码,在注册MPay时需要传入几个参数,用来注册使用的支付方法
val _methods = params.getOrElse("methods"){ "" }.split(",").fold(HashMap<String, PaymentAdapter<PayResult>>(), { result, code -> when (code) { "alipay" -> { result[code] = Alipay(this) result } "wechat" -> { result[code] = WeChat(this) result } "mpay" -> { result[code] = MPayModel(this) result } else -> result } })
在 mthods 方法中调用这个方法
override fun methods(): HashMap<String, PaymentAdapter<PayResult>> { if (_methods.isEmpty()) return HashMap() return _methods }
然后实现pay方法,在该方法里,依旧要再判断适配器是否初始化,以免出现错误,由于我这里发起网络请求是在flutter端做的,传过来的是请求之后的数据,在返回
数据中有一个是 signData的key,它的value是MPay进行支付时所需的参数,如果这个参数是正常的,就调用 MPay OpenSdk.newPayAll 方法去进行真正的支付,其实到最后我们发现进行支付只需要这一行代码,之所以要写这么多代码有两个原因
1、为了将数据返回到flutter端,我们将数据返回都是靠 onMethodCall 的result,但是按照sdk 提供的demo,返回数据是在一个handle中,我们无法通过这一个handle去返回数据,所以我们才需要去定义接口,按照我们的方式返回数据
2、代码的整洁
var result : MethodChannel.Result? = null override fun pay(method: String, params: MethodCall, result: MethodChannel.Result) { this.result = result val adapter = methods()[method] if (adapter == null) { result.error("error", "适配器未初始化", params) return } val data = params.argument<String>("signData"); if (data is String) { OpenSdk.newPayAll(mActivity, data, this) return } result.error("error", "error", "error") }
我们还需要一个接收返回结果的方法,用于接收并返回支付结果,返回9000是支付正常,还有很多的支付异常的code,但是这里只判断成功和失败
fun handleResult(result : PayResult?) { val map = HashMap<String, String>(); map["message"] = result!!.result; if (result.resultStatus == "9000") { map["result"] = "Y" }else{ map["result"] = "N" } this.result?.success(map); }
我们继承OpenSdkInterfaces的时候需要实现三个方法,现在来实现具体的实现代码
override fun AliPayInterfaces(p0: PayResult?) { _methods["alipay"]?.handleResult(p0!!) } override fun MPayInterfaces(p0: PayResult?) { _methods["mpay"]?.handleResult(p0!!) } override fun WeChatPayInterfaces(p0: PayResult?) { _methods["wxpay"]?.handleResult(p0!!) }
接下来看看完整的MPay代码
class MPay(params: Map<String, String>, val mActivity: Activity): PaymentChannel, OpenSdkInterfaces { val _methods = params.getOrElse("methods"){ "" }.split(",").fold(HashMap<String, PaymentAdapter<PayResult>>(), { result, code -> when (code) { "alipay" -> { result[code] = Alipay(this) result } "wechat" -> { result[code] = WeChat(this) result } "mpay" -> { result[code] = MPayModel(this) result } else -> result } }) override val channel = "map" var result : MethodChannel.Result? = null override fun pay(method: String, params: MethodCall, result: MethodChannel.Result) { this.result = result val adapter = methods()[method] if (adapter == null) { result.error("error", "适配器未初始化", params) return } val data = params.argument<String>("signData"); if (data is String) { OpenSdk.newPayAll(mActivity, data, this) return } result.error("error", "error", "error") } fun handleResult(result : PayResult?) { val map = HashMap<String, String>(); map["message"] = result!!.result; if (result.resultStatus == "9000") { map["result"] = "Y" }else{ map["result"] = "N" } this.result?.success(map); } override fun methods(): HashMap<String, PaymentAdapter<PayResult>> { if (_methods.isEmpty()) return HashMap() return _methods } override fun OpenSDKInterfaces(p0: PayResult?) { } override fun AliPayInterfaces(p0: PayResult?) { _methods["alipay"]?.handleResult(p0!!) } override fun MPayInterfaces(p0: PayResult?) { _methods["mpay"]?.handleResult(p0!!) } override fun WeChatPayInterfaces(p0: PayResult?) { _methods["wxpay"]?.handleResult(p0!!) } }
现在来实现我们开始创建的 Alipay,MPayModel,WeChat那三个文件,只需要实现两个方法即可,下面还是用AliPay做为例子,另外两个只需要修改 method 为wechat,和mpay即可
class Alipay(val mPay: MPay): PaymentAdapter<PayResult> { override val method = "alipay"; override fun pay(params: MethodCall, result: MethodChannel.Result) { } override fun handleResult(result: PayResult) { mPay.handleResult(result); } }
接着进行 MpayPlugin 编写代码,由于我们在MPay中需要使用Activty,所以我们需要在MpayPlugin中得到 activity对象,想要在这里拿到activity,我们就需要去继承 ActivtyAware ,然后实现它的四个方法
在前面声明两个对象,一个是支付通道一个activty,后面会用到
private val channels = HashMap<String, PaymentChannel>() private var mActivity: Activity? = null
继承 ActivtyAware 方法后必须要实现的四个方法,只需要实现一个具体代码就可以了
/// 绑定 activty override fun onAttachedToActivity(binding: ActivityPluginBinding) { mActivity = binding.activity } override fun onDetachedFromActivityForConfigChanges() { } override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { } override fun onDetachedFromActivity() { }
修改 onMethodCall里面的代码,修改成为如下代码,在这里传入的MethodCall对象是map的格式,mthod就是每一次传入的map的key,如果这个key是等于 config.mpay 就去注册支付方式,这里没有写返回数据,如果需要通知flutter注册成功,可以加一句
return result.success("注册成功")
success 里返回的内容是Any 相当于 java的 object,是可以返回任意类型数据的。
因为目前就写了两个方法,要么是注册要么是支付,所以这里就直接用else了,至于method是哪来的,等会会在调用案例中说到,它就是支付方式,我们会在返回的信息中将它加进去,
判断语句 的意思就是 如果我们注册的支付通道里没有注册我们现在传递过来支付方式,就走默认result 为空,如果支付方式有注册才会调用支付result就是返回值
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { when (call.method) { "config.mpay" -> { channels["pay"] = MPay(call.arguments(), mActivity!!) } else -> { val payType = call.argument<String>("method") if (channels.containsKey(call.method) && payType != null) { channels[call.method]!!.pay(payType, call, result) return } result.notImplemented() } } }
再来看看全部代码,解释一下自带的一些方法的意思,
onAttachedToEngine
onMethodCall
onDetachedFromEngine
它们三个都是创建项目的时候就已经存在,其中 onAttachedToEngine 和 onDetachedFromEngine 尽量不要去动,这两个方法是flutter新的加载插件的方式.,既然是新的加载方法,
那自然还有个旧的加载方法,如果需要支持旧写法就加入下列代码,不过,话说回来,flutter2.0出来这么久了,应该不会还有人在用1.几的吧
companion object { private var registrar: PluginRegistry.Registrar? = null private var mActivity: Activity? = null var context: Context? = null private var mMethodChannel: MethodChannel? = null @JvmStatic fun registerWith(registrar: PluginRegistry.Registrar) { val channel = MethodChannel(registrar.messenger(), "mpy_plugin") channel.setMethodCallHandler(MpyPlugin()) context = registrar.context() MpyPlugin.registrar = registrar mActivity = registrar.activity() channel.setMethodCallHandler(MpyPlugin()) } }
全部的代码
class MpyPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private lateinit var channel : MethodChannel private val channels = HashMap<String, PaymentChannel>() private var mActivity: Activity? = null override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "mpy_plugin") channel.setMethodCallHandler(this) } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { when (call.method) { "config.mpay" -> { channels["pay"] = MPay(call.arguments(), mActivity!!) } else -> { val payType = call.argument<String>("method") if (channels.containsKey(call.method) && payType != null) { channels[call.method]!!.pay(payType, call, result) return } result.notImplemented() } } } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) } /// 绑定 activty override fun onAttachedToActivity(binding: ActivityPluginBinding) { mActivity = binding.activity } override fun onDetachedFromActivityForConfigChanges() { } override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { } override fun onDetachedFromActivity() { } }
example 案例
api请求参数不一定都是一样的按照自己的需求去修改即可
需要主要两个地方,这就是在MpayPlugin支付判断的method的所在位置,method 必须和请求参数里的 "pay_channel": "mpay" value一致
paramss.putIfAbsent("method", () => "mpay");
import 'dart:collection'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'dart:async'; import 'package:flutter/services.dart'; import 'package:mpy_plugin/mpy_plugin.dart'; void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override void initState() { super.initState(); MpyPlugin.init(ConfigType.mPay, "alipay,wechat,mpay"); } dynamic result; Future<void> pays() async { Map<String, dynamic> params = <String, dynamic>{ "org_id": "替换你的id", // 订单ID "sign_type": "MD5", // 加密方式 "sign": "替换你的密钥", /// 密钥 "pay_channel": "mpay", /// 支付方式 "total_fee": "0.10", // 金额 "body": "我的商品1", // 商品名 "sub_appid": "替换你的appid", // 微信支付appid "subject": "替换店铺",// 店铺 "extend_params": { "sub_merchant_name": "儒林教育", // app Name "sub_merchant_id": "替换你的编号", //z子商户编号 "sub_merchant_industry": "替换你的行业", //子商户行业 }, }; Options options = Options(headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", "Content-Type": "application/json", "accept": "*/*", "connection": "Keep-Alive" }); Response rep = await Dio().post( "替换你的后台api接口", data: params, options: options); print(rep.data); HashMap<String, dynamic> paramss = HashMap.from(rep.data); paramss.putIfAbsent("method", () => "mpay"); setState(() async{ result = await MpyPlugin.pay(params: paramss); print("====$result"); print("----------"); }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: Center( child:Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("$result"), TextButton( onPressed: pays, child: Text("测试"), ) ], ), ), ), ); } }
调用案例返回的数据就是这样,如果需要返回更多参数就在Mpay.kt中进行修改