}
}


页面端是这样调用的

WechatPayment paymentUtils = new WechatPayment();
paymentUtils.wxPay(
state.model.wxPayModel,
onError: (String err) {
if (!mounted) return;
// 微信支付错误,设置支付状态为false,弹框即可
isPaying = false;
SchedulerBinding.instance.addPostFrameCallback((
) {
CommonUtils.showToast(err, backgroundColor: Theme.of(context).errorColor);
});
},
onSuccess:(){
_isPaying = true;
},
onWxPaying: () {
// 启动微信支付,设置支付状态为true,关闭加载框
isPaying = true;
SchedulerBinding.instance.addPostFrameCallback((
) {
Navigator.pop(context);
});
},
);


但是需要注意,微信的回调是异步的,并且有很多种情况是接收不到回调的,以下是确定收不到会调的情况。

微信调起支付页面时,其实是跳转到新的应用,对于我们的应用而言是触发了前后台切换的生命周期。
因此在检测到应用返回前台,并且支付状态还在进行中时,可以证明是收不到微信的支付状态回调,需要特殊处理下。
收不到的情况有:
// ① 弹出支付框后使用系统返回键关闭;
// ② 进入微信支付密码框后不输入使用系统导航切回app或者系统返回键返回;
// ③ 进入微信后直接返回桌面再回到应用;
// ④ 弹出支付框后锁屏再开屏;
// ⑤ 弹出支付款后下拉任务栏;
// ⑥ 输入密码成功后,直接返回桌面或者使用系统导航或者使用返回键返回app
// ⑦ 退出微信登录,进行支付后直接登录微信,在登录过程中回到app
// ⑧ 在系统应用管理中双开微信后,调起支付后不点击任一个微信端,而是点击取消


现在主流的做法是再支付页面监听app的生命周期,即由后台切回前台的时候,检测下状态,若还在支付中,直接进入查询结果页面,由后台去检验订单,拿到结果显示即可。**(后台主动查询理论上还是存在微信服务端延时的问题,因此后台进行查询的时候,建议采取轮询机制,若是没有支付成功的话,延时5秒后再确认下更保险)**

class _XXXPageState extends State with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); //添加观察者
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this); //销毁观察者
super.dispose();
}

/// 应用状态监听
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
{
if (Platform.isAndroid && _isPaying) {
_isPaying = false;
// 监听到时安卓设备并且支付还在进行中,程序员要根据业务做一下处理
break;
}
default:
break;
}
super.didChangeAppLifecycleState(state);
}
}


到此,微信支付很愉快的解决了,以上代码是抽象出来的工具类,可以直接使用;**但是不涉及任何业务流程的开发,这个需要使用者自己去补充。** 综上,微信支付流程主线可简单粗暴总结为:服务端生成订单 → 客户端调起支付 → 客户端通知服务端核验订单 → 客户端拿到最终结果 → 客户端final支付。 整个过程形成闭环,有理有据,数据都由后端去操作安全合理。(最重点是前端工作量简直不要太少)。> _**可是,iOS就不一样了,简直不要太恶心!**_iOS IAP应用内支付
------------*   IAP,即in-app Purchase,苹果推出的App内购买虚拟商品的方式,基于AppStore账户的支付方式。由于iOS整个体系都是基于自己的一套系统的(不像上面的微信支付,是第三方支付平台),因此在开发之前,我们需要到Apple开发者中心完成以下步骤:1\. 签署协议和银行业务 2. 在后台创建App内购买项目,这里所有的价格都是Apple规定好的,我们只有选择的资格,没办法自定价格。**创建完成后,每个项目会有sku和productId** 3. 添加沙盒测试员Apple [以上步骤参考内容引自站内大神:]( )Geniune*   支付流程:应用通过sku向服务端获取商品列表 → 列表中取出对应产品请求支付 → 进入appStore支付 → 页面监听支付回调拿到验证票据 → 业务后台拿到应用接收到的票据后去Apple官网进行校验即可。流程很简单,简单到几乎不用跟业务后台打交代,但是坑却随之而来:

① 支付数据完全依赖前端应用,很难跟业务后台的订单系统一一对应;
② 针对①的问题,IAP支付支持传递skPayment对象,里面的applicationUsername经常用来保存系统的OrderId;
但是应用支付成功后收到的回调中,applicationUsername却偶尔会出现为null的情况,没有了对应关系,就没办法核销业务系统中的订单从而为用户充值;
③ iOS支付回调非常不稳定,有时延迟严重;且没有任何注定查询的方法;
④ iOS应用内支付有很多异常情况要处理,最常见的就是没有登录、没有同意最新的iOS支付协议等,都会发送给app支付失败的回调;
但是当用户登录或是同意后,iOS系统又会触发新的支付,导致旧的附带业务订单号的支付无效,莫名又多出一个没有订单号的新支付;
⑤ 国内网上资料极度缺乏,基本都是19年以前的,Flutter的文章更是少的可怜,可参考性不强。
⑥ 测试文档对于中断购买的测试流程有巨坑,后面菜单一定不要错过~


> 通过查看文档和不断调试,我们发现:① 支付错误的回调,基本能马上收到; ② 上面流程说到IAP支付需要手动结束支付流程。同时iOS规定不能对同一个skuId重复发起多次支付的,只要当前skuId有没有final的支付,再次发起都会失败; ② 无论支付成功或失败,只要app没有主动对当前支付进行final,每次启动app后,app都会收到这个支付信息的通知; ③ 关于applicationUsername,只有在支付完成马上收到回调的情况下,回调信息才会有这个信息;到②中的情况,肯定不会返回applicationUsername; ④ 没有applicationUsername就意味着订单对不上,因此我们需要进行凑单机制。> 综上,我们对异常处理有了确定方案:① app发起支付后,需要将业务OrderId和skuId进行持久化存储(即卸载应用都不会删除的数据); ②只要持久化存储不为空,启动app就需要马上启动监听,以接收iOS系统的订单推送; ③ 支付出错可以final当前支付,但是支付成功必须明确接收到iOS推送并且后台核验成功后,才能final,并删除持久化存储。* * *最终,结合到业务系统和特殊情况的处理后,支付流程应该如下:1.  业务后台返回商品列表时,**需要附加返回对应的skuId**
2.  app通过skuId请appStore请求商品信息
3.  app对商品发起支付,并**将业务订单号存储在applicationUsername中,发起成功写入持久化存储,状态为pending**
4.  接收iOS系统回调,**失败马上final支付,更改对应持久化存储状态为cancle**;成功拿到**票据和业务OrderId**发送给后台
5.  后台调取Apple服务端接口,传入票据(票据其实储存着最新的时间,appStore用户信息等)
6.  后台获取到Apple返回的当前appStore用户所有支付的前100条记录,拿到productId到数据库有中匹配该用户是否有未核销的订单,并对应修改业务订单状态
7.  app确认核销成功,**final支付,并且删除持久化存储**同时还需要做一些特殊处理:1.  app刚启动时,若是持久化存储不为空,需要马上启动iOS支付订阅监听,以接收iOS对未完成订单的推送;
2.  由于iOS限制了同一个skuId不能重复发起支付,因此持久化存储中,一个skuId永远只会有一条记录。因此当app接收到的支付推送applicationUsername为null,采取凑单机制,原则是:通过skuId找到存储记录,拿到其对应的OrderId,发给后台核验。*   接下来进入开发,Futter采用的是in\_app\_purchase插件,官方提供的,支持google和IAP支付;而持久化存储用的是flutter\_secure\_storage插件。依据上面的流程,我同样封装了工具类。而且由于可能会在多个地方调用起监听,所有必须是单例模式,代码如下:

import ‘dart:async’;

import ‘package:flutter_secure_storage/flutter_secure_storage.dart’;
import ‘package:in_app_purchase/in_app_purchase.dart’;

// iOS支付单一实例
final iOSPayment = IOSPayment();

class IOSPayment {
/// 单例模式
static final IOSPayment _iosPayment = IOSPayment.init();

factory IOSPayment() {
return _iosPayment;
}

IOSPayment.init();

// 应用内支付实例
InAppPurchaseConnection purchaseConnection = InAppPurchaseConnection.instance;
FlutterSecureStorage storage = new FlutterSecureStorage();

// iOS订阅监听
StreamSubscription<List> subscription;

/// 判断是否可以使用支付
Future isAvailable() async => await purchaseConnection.isAvailable();

// 开始订阅
void startSubscription() async {
if (subscription != null) return;
print(’>>> start subscription’);
// 支付消息订阅
Stream purchaseUpdates = purchaseConnection.purchaseUpdatedStream;
subscription = purchaseUpdates.listen(
(purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
print(’>>> pending’);
// 业务代码略:有订单开始支付,向外部发出通知,并记录到缓存中;
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
print(’>>> error’);
// 业务代码略:有订单支付错误,向外部发出通知
// 下面是删除
String value = await storage.read(key: purchaseDetails.productID);
String orderId = value.split(‘¥’)[0];
writeStorage(purchaseDetails.productID, orderId, ‘cancel’);
finalTransaction(purchaseDetails);
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
print(’>>> purchased’);
String orderId = purchaseDetails.skPaymentTransaction.payment.applicationUsername;
if (orderId == null || orderId.isEmpty) {
// 如果applicationUsername为空,执行凑单
orderId = await foundRecentOrder(purchaseDetails.productID);
}
if (orderId.isEmpty) {
// 凑单失败,找不到业务单号,结束
finalTransaction(purchaseDetails);
BlocProvider.of(Application.navigatorState.currentContext).add(IosPayFailureEvent(errorMessage: ‘支付出错啦,请稍后再试~’));
return;
}
// 业务代码略:支付成功,向外部发出通知
// 业务代码略:开始核验订单,核验结果由外部监听
);
}
}
});
},
onDone: () {
stopListen();
},
onError: (error) {
stopListen();
},
);
}

/// 检查sku是否有对应商品
Future checkProductBySku(String sku, {Function(String err) onError}) async {
if (!await isAvailable()) {
onError?.call(‘无法连接AppStore,请稍后再试’);
return false;
}
ProductDetailsResponse appStoreProducts = await purchaseConnection.queryProductDetails([sku].toSet());
if (appStoreProducts.productDetails.length == 0) {
onError?.call(‘没有找到相关产品,请联系管理员’);
return false;
}
return true;
}

/// 启动支付
void iosPay(String sku, String orderId, {Function(String err) onError}) async {
// 获取商品列表
ProductDetailsResponse appStoreProducts = await purchaseConnection.queryProductDetails([sku].toSet());
// 发起支付
purchaseConnection
.buyNonConsumable(
purchaseParam: PurchaseParam(
productDetails: appStoreProducts.productDetails.first,
applicationUserName: orderId,
),
)
.then((value) {
if (value) {
// 只要能发起,就写入
writeStorage(sku, orderId, ‘pending’);
}
}).catchError((err) {
onError?.call(‘当前商品您有未完成的交易,请等待iOS系统核验后再次发起购买。’);
print(err);
});
}

Flutter实现微信支付和iOS IAP支付,ndk开发入门相关推荐

  1. Flutter实现微信支付和iOS IAP支付

    Flutter支付 微信支付 iOS IAP应用内支付 测试IAP中断购买的测试 公司近期将收费的功能排期了,由于项目做的是线上教育,提供的服务属于虚拟物品.根据iOS官方的规定,虚拟物品交易只能使用 ...

  2. Flutter实现微信支付和iOS IAP支付,老师讲的真棒

    // ① 弹出支付框后使用系统返回键关闭: // ② 进入微信支付密码框后不输入使用系统导航切回app或者系统返回键返回: // ③ 进入微信后直接返回桌面再回到应用: // ④ 弹出支付框后锁屏再开 ...

  3. 微信小程序iOS虚拟支付问题汇总

    一.做了一款在线听课的小程序,是否涉及到小程序虚拟支付的问题? 是,影响最大的还是那些把IOS会员当做变现唯一手段的小程序.如果小程序只是服务老用户,拉新.裂变等较少,影响自然少一点. 二.什么是虚拟 ...

  4. Unity微信支付,IOS提示支付签名验证失败。安卓没问题

    最近手上有一个Unity项目,Java后台,原作者跑路,我们属于半路上车. 打包ipa的时候碰到了很多问题. 在原来项目的基础上引入了微信支付体系. 安卓支付流程没有出问题,但是到了IOS这里,千辛万 ...

  5. php支付宝ios12.1.4支付失败,IOS 支付宝支付问题

    // iOS调用 AlipaySDK 支付宝找不到头文件 首先将 openssl 和 Util 目录添加到Build setting->header search path 中 ($(PROJE ...

  6. iap java md5_苹果应用内支付(iOS IAP)的流程与常用攻击方式

    1. 客户端直接verify苹果的receipt 如果verify成功 自行发放商品 2. 客户端将receipt传给server,由server进行验证并发放商品 按照安全性原则, 客户端的所有信息 ...

  7. Flutter之微信支付实战模板

    ​ ​ 活动地址:CSDN21天学习挑战赛 安装flutter插件 fluwx: ^3.8.5 #微信 注册微信接口 _initfluwx() async {print('78987979798797 ...

  8. [汇文教育]iOS内支付(IAP)研究

    关于iOS内支付(简称IAP,全称In App Purchase)的demo可以从以下链接下载:点击打开链接 一.实现支付功能的前提条件: 1.有一个可以发布产品的开发者账号,有一个可以运行应用的开发 ...

  9. Cocos2dx使用ios内支付IAP详细流程-白白

    今天总结了一下cocos2d-x使用ios内支付iap的详细流程,封装好了调用接口,代码与详细说明在此 http://download.csdn.net/detail/u010229677/81566 ...

最新文章

  1. debian php mysql 包_Linux+Varnish+Apache+MySQL+PHP一键包For Ubuntu/Debian
  2. Round Robin 算法
  3. 网页截图工具CutyCapt
  4. plt保存图片_人工智能Keras CNN卷积神经网络的图片识别模型训练
  5. udhcp源码详解(五) 之DHCP包--options字段
  6. python3数据类型:Set(集合)
  7. Windows7+VS2012下OpenGL 4的环境配置
  8. iis运行原理 Asp.Net详解IIS内部运行原理
  9. Java异常处理throws/throw
  10. 2018怎么打开2019_2019 年,我还是没有摆脱 Micro USB
  11. js中的onscroll的用法
  12. Firefox选择哪个IE TAB
  13. 百度地图 - 自定义划分区域并获取区域内的坐标点
  14. linux禁调usb,Linux主机禁用USB接口
  15. 【智慧医疗】EMR vs EHR vs PHR 有何区别?
  16. 用lombok插件,驼峰属性第一个是一个字母的,属性没有接收到值,使用@JsonProperty解决(工作遇到的坑)
  17. Linux-alias设置命令别名
  18. RTL88x2bu网卡驱动Kali安装及部分问题解决
  19. 计算机英语中poke什么意思,poke是什么意思_poke在线翻译_英语_读音_用法_例句_海词词典...
  20. Shopro商城,多平台移动商城(微信公众号、微信小程序、H5网页、Android-App、IOS-App购物商城)

热门文章

  1. 分类数据cotegory介绍以及常用API的属性和方法
  2. CSDN发博客上传照片失败问题解决方案
  3. 生产进度管理系统为制造管理提供较完善的解决方案
  4. 夏令时和时区最佳实践
  5. xp下载的java8_windows xp下安装java8(jdk8) 看完就明白
  6. 易语言练习笔记-大叔篇(3)-加减计算器
  7. Java数据结构与算法(二)
  8. dot.tk+namecheap.com搭建免费顶级域名+快速动态域名+Dns解析
  9. Vue安装element ui踩坑
  10. 小闫陪你入门 Java (一)