本文導讀
能學到什么?
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中進行修改

