mp_flutter_wechat 0.3.0
mp_flutter_wechat: ^0.3.0 copied to clipboard
WeChat Mini Program APIs (login, payment, and more) for Flutter apps compiled with flutter_miniprogram; throws clear errors on other platforms.
mp_flutter_wechat #
在 mp-flutter 编译的微信小程序里调用微信登录、支付等原生能力; 在其它平台(Android/iOS/桌面,或普通浏览器)上调用会明确报错,而不是静默失败。
引入方式 #
尚未发布到 pub.flutter-io.cn。用 git 依赖引入本仓库(公开仓库,无需额外凭证;ref
建议固定到一个发布 tag,例如 v0.2.1,而不是 main):
dependencies:
mp_flutter_wechat:
git:
url: https://github.com/jackwangfeng/mp-flutter.git
path: packages/mp_flutter_wechat
ref: v0.2.1
在 mp-flutter 仓库内部开发(monorepo 内的 example/ 等)时,用相对路径的
path 依赖即可,不需要走 git:
dependencies:
mp_flutter_wechat:
path: ../mp_flutter/packages/mp_flutter_wechat
API #
| 方法 | 对应微信 API | 说明 |
|---|---|---|
MpWechat.isAvailable |
— | 当前环境是否存在微信桥(self.__mpWechat) |
MpWechat.login() |
wx.login |
返回 code;换 openid/session_key 须服务端调用 code2Session |
MpWechat.checkSession() |
wx.checkSession |
登录态是否仍有效;失败返回 false,不抛异常 |
MpWechat.requestPayment(params) |
wx.requestPayment |
params 来自服务端"统一下单"签名结果 |
MpWechat.chooseAddress() |
wx.chooseAddress |
拉起收货地址选择器 |
MpWechat.scanCode({onlyFromCamera}) |
wx.scanCode |
拉起扫码 |
MpWechat.setClipboardData(data) |
wx.setClipboardData |
写剪贴板 |
MpWechat.getClipboardData() |
wx.getClipboardData |
读剪贴板 |
MpWechat.getLocation({type}) |
wx.getLocation |
默认坐标系 gcj02 |
MpWechat.makePhoneCall(phoneNumber) |
wx.makePhoneCall |
拨打电话 |
MpWechat.setShareInfo({title, path, imageUrl, query}) |
— | 设置承载页 onShareAppMessage 使用的分享信息 |
MpWechat.menuButtonRect() |
wx.getMenuButtonBoundingClientRect |
胶囊按钮的位置,Future<Rect?>(逻辑像素);非小程序环境返回 null,见下文「避开胶囊按钮」 |
MpWechat.call(api, [params]) |
任意 wx.* |
通用入口,覆盖表里没有单独封装的接口;仅支持 success/fail 回调式异步接口——同步接口(*Sync)、事件订阅(on*/off*)、工厂/句柄(create*、*Manager)会立即报错,不会挂起 |
非小程序平台(stub)或 Web 上没有 __mpWechat 桥时:
isAvailable为falsecheckSession()返回false(不抛)setShareInfo(...)为空操作(不抛)- 其余方法(含
call)抛UnsupportedError('mp_flutter_wechat: <api> 仅在 mp-flutter 编译的微信小程序中可用')
服务端职责(敏感操作) #
以下操作需要 AppSecret 或商户密钥,绝不能放在客户端,必须由业务服务端完成:
code2Session(微信官方文档:auth.code2Session):用login()返回的code换取openid/unionid/session_key。- 统一下单 + 签名(微信支付官方文档:
统一下单 API+小程序调起支付 API的签名规则): 生成requestPayment()所需的timeStamp/nonceStr/package/signType/paySign。
用户取消的处理 #
失败(含用户取消)会抛出 MpWechatException,其 cancelled 字段区分"用户主动取消"
与其它失败:
try {
await MpWechat.requestPayment(params);
} on MpWechatException catch (e) {
if (e.cancelled) {
// 用户取消支付,静默返回即可
} else {
// 真正失败:e.api / e.errMsg 里有细节
showToast(e.errMsg);
}
}
非小程序平台行为 #
在 Android/iOS/桌面(走 channel_stub.dart)或普通浏览器(Web 上没有
self.__mpWechat,走 channel_web.dart 但 isAvailable 为 false)上,
先判断 MpWechat.isAvailable 再决定是否展示微信相关入口,避免调用抛出
UnsupportedError:
if (MpWechat.isAvailable) {
final code = await MpWechat.login();
// ...
}
避开胶囊按钮 #
mp-flutter 生成的页面是全屏画布(navigationStyle: custom),构建时会把小程序
安全区(状态栏/刘海、底部 Home 指示条)注入 MediaQuery.padding/viewPadding,
所以 SafeArea、Scaffold、AppBar 不用改就会让开内容。但右上角的胶囊按钮
不算进 padding(和原生 App 一样,padding 只描述系统占用的区域)。
页面顶部右侧想放按钮/文字又不想被胶囊盖住时,用 MpWechat.menuButtonRect()
取胶囊矩形自己让位:
final rect = await MpWechat.menuButtonRect(); // 非小程序环境为 null
final width = MediaQuery.sizeOf(context).width;
final rightGap = rect == null ? 16.0 : width - rect.left + 8; // 胶囊左边再留 8
Padding(padding: EdgeInsets.only(right: rightGap), child: header);
分享设置示例 #
每次 setShareInfo 整体替换已设置的分享信息(不是按字段合并):只传了 title
的下一次调用,会把之前设置过的 path/imageUrl/query 一并清空,承载页的
onShareAppMessage 只会看到这一次调用里传的字段。path 若传入且不以 /
开头,会在 Dart 侧立即抛出 ArgumentError(小程序页面路径必须以 / 开头)。
MpWechat.setShareInfo(
title: '限时特惠',
path: '/pages/flutter/flutter?sku=1',
imageUrl: 'https://example.com/share.png',
);
实现说明:Web 平台的异常映射 #
lib/src/channel_web.dart 里,JS 桥(wechat.js)reject 时抛出的 Error 带有
mpApi/mpErrMsg/mpCancelled 三个自定义属性。实测结论(微信开发者工具 +
dart2js 编译产物,accept-wx.js 的 pay 步骤):dart2js 把 JS reject 的 Error
原样交给 Dart 的 catch,e is JSObject 成立,不需要改走
JSPromise.then(onFulfilled, onRejected) 手工接收原始 reject 值。
即便如此,catch 里仍只在确实带有 mpErrMsg/mpApi(即真的来自 wechat.js
的 fail())时才映射成 MpWechatException,否则原样 rethrow——extension-type
的 cast 是无检查的,任何 JSObject 都能转成 _BridgeError 且不报错,只是取
不到的字段为 null;不加这道判断会把"任意 JS reject 值"都当成
MpWechatException,掩盖真正的 bug。