【原创 附源码】Flutter集成Apple支付详细流程(附源码)

news2024/11/29 2:27:35

最近有时间,特意整理了一下之前使用过的Flutter平台的海外支付,附源码及demo可供参考

这篇文章只记录Apple支付的详细流程,其他相关Flutter文章链接如下:

【原创 附源码】Flutter集成谷歌支付详细流程(附源码)

【原创 附源码】Flutter安卓及iOS海外登录--Google登录最详细流程

【原创 附源码】Flutter安卓及iOS海外登录--Tiktok登录最详细流程

【原创 附源码】Flutter安卓及iOS海外登录--Facebook登录最详细流程

【原创 附源码】Flutter安卓及iOS海外登录--Apple登录最详细流程

让我们开始吧

一 Apple开发者平台添加内购商品

首先使用苹果开发者账户登录苹果开发者平台

Sign In - Apple

点击【App】 

 

添加新的苹果内购商品

 

添加的时候页面的指引很清晰,就不赘述了,苹果添加内购商品比较简单,加完就可以了。

然后去创建沙盒账户用来做苹果支付测试,回到首页,点击【用户和访问】

点击沙盒,然后添加一个苹果测试账户,这个账户可以是个假的邮箱,不需要是正式的Apple id,比如你可以设置为88888@qq.com类似之类的账户

 

添加完点击创建即可

二 flutter 代码集成

使用到的第三方插件:

in_app_purchase: ^3.1.5

插件官网地址:in_app_purchase | Flutter package

将插件添加至yaml文件,然后执行flutter pub get

执行完了记得去IOS和安卓端分别执行pod install 和 gradle sync同步一下第三方插件

然后在项目中新建dart文件,命名为:BuyEngine.dart

然后将以下代码放入:

import 'dart:async';
import 'dart:io';

import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';

class BuyEngin{

  StreamSubscription<List<PurchaseDetails>> _subscription;
  InAppPurchase _inAppPurchase;
  List<ProductDetails> _products; //内购的商品对象集合

  //初始化购买组件
  void initializeInAppPurchase() {

    // 初始化in_app_purchase插件
    _inAppPurchase = InAppPurchase.instance;

    //监听购买的事件
    final Stream<List<PurchaseDetails>> purchaseUpdated = _inAppPurchase.purchaseStream;
    _subscription = purchaseUpdated.listen((purchaseDetailsList) {
      _listenToPurchaseUpdated(purchaseDetailsList);
    }, onDone: () {
      _subscription.cancel();
    }, onError: (error) {
      error.printError();
      print("购买失败了");
    });
  }

  void resumePurchase(){
    _inAppPurchase.restorePurchases();
  }

  /// 加载全部的商品
  void buyProduct(String productId) async {

    print("请求商品id " + productId);

    List<String> _outProducts = [productId];

    final bool available = await _inAppPurchase.isAvailable();
    if (!available) {
      // ToastUtil.showToast("无法连接到商店");
      print("无法连接到商店");
      return;
    }

    //开始购买
    // ToastUtil.showToast("连接成功-开始查询全部商品");
    print("连接成功-开始查询全部商品");
    List<String> _kIds = _outProducts;

    final ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(_kIds.toSet());
    print("商品获取结果  " + response.productDetails.toString());
    if (response.notFoundIDs.isNotEmpty) {
      // ToastUtil.showToast("无法找到指定的商品");
      print("无法找到指定的商品");
      // ToastUtil.showToast("无法找到指定的商品 数量 " + response.productDetails.length.toString());

      return;
    }

    // 处理查询到的商品列表
    List<ProductDetails> products = response.productDetails;
    print("products ==== " + products.length.toString());
    if (products.isNotEmpty) {
      //赋值内购商品集合
      _products = products;
    }

    print("全部商品加载完成了,可以启动购买了,总共商品数量为:${products.length}");

    //先恢复可重复购买
    // await _inAppPurchase. ();

    startPurchase(productId);
  }


  // 调用此函数以启动购买过程
  void startPurchase(String productId) async {

    print("购买的商品id为" + productId);
    if (_products != null && _products.isNotEmpty) {
      // ToastUtil.showToast("准备开始启动购买流程");
      try {
        ProductDetails productDetails = _getProduct(productId);

        print("一切正常,开始购买,信息如下:title: ${productDetails.title}  desc:${productDetails.description} "
            "price:${productDetails.price}  currencyCode:${productDetails.currencyCode}  currencySymbol:${productDetails.currencySymbol}");
        _inAppPurchase.buyConsumable(purchaseParam: PurchaseParam(productDetails: productDetails));
      } catch (e) {
        e.printError();
        print("购买失败了");
      }
    } else {
      print("当前没有商品无法调用购买逻辑");
    }
  }

  // 根据产品ID获取产品信息
  ProductDetails _getProduct(String productId) {
    return _products.firstWhere((product) => product.id == productId);
  }

  /// 内购的购买更新监听
  void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async {
    for (PurchaseDetails purchase in purchaseDetailsList) {
      if (purchase.status == PurchaseStatus.pending) {
        // 等待支付完成
        _handlePending();
      } else if (purchase.status == PurchaseStatus.canceled) {
        // 取消支付
        _handleCancel(purchase);
      } else if (purchase.status == PurchaseStatus.error) {
        // 购买失败
        _handleError(purchase.error);
      } else if (purchase.status == PurchaseStatus.purchased || purchase.status == PurchaseStatus.restored) {
        // ToastUtil.showToast(DataConfig.getShowName("Pay_Success_Tip"));
        //完成购买, 到服务器验证
        if (Platform.isAndroid) {
          var googleDetail = purchase as GooglePlayPurchaseDetails;
          checkAndroidPayInfo(googleDetail);
        } else if (Platform.isIOS) {
          var appstoreDetail = purchase as AppStorePurchaseDetails;
          checkApplePayInfo(appstoreDetail);
        }
      }
    }
  }

  /// 购买失败
  void _handleError(IAPError iapError) {
    // ToastUtil.showToast("${DataConfig.getShowName("Purchase_Failed")}:${iapError?.code} message${iapError?.message}");
  }

  /// 等待支付
  void _handlePending() {
    print("等待支付");
  }

  /// 取消支付
  void _handleCancel(PurchaseDetails purchase) {
    _inAppPurchase.completePurchase(purchase);
  }

  /// Android支付成功的校验
  void checkAndroidPayInfo(GooglePlayPurchaseDetails googleDetail) async {
    _inAppPurchase.completePurchase(googleDetail);
    print("安卓支付交易ID为" + googleDetail.purchaseID);
    print("安卓支付验证收据为" + googleDetail.verificationData.serverVerificationData);
  }

  /// Apple支付成功的校验
  void  checkApplePayInfo(AppStorePurchaseDetails appstoreDetail) async {
    _inAppPurchase.completePurchase(appstoreDetail);

    print("Apple支付交易ID为" + appstoreDetail.purchaseID);
    print("Apple支付验证收据为" + appstoreDetail.verificationData.serverVerificationData);
  }


  @override
  void onClose() {
    if (Platform.isIOS) {
      final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
      _inAppPurchase.getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
      iosPlatformAddition.setDelegate(null);
    }
    _subscription.cancel();
  }

}

至此集成完毕,开始测试苹果支付

三 支付测试 

在调用苹果支付的地方提前初始化购买插件:

BuyEngin _buyEngin = BuyEngin();
_buyEngin.initializeInAppPurchase();

然后调用即可:

_buyEngin.buyProduct("应用内商品ID");

应用内商品ID就是你在APP Store Connect配置的应用内购买商品的product ID

如果一切正常,则会正常唤醒苹果支付(记得是在科学上网的环境下测试)

源码地址:GitHub - TheRuningAnt/FGTALogin: 使用Flutter 去集成海外平台第三方登录,包含Google、Tiktok、Facebook、Apple登录 

(注:直接调用该demo的苹果支付无法支付成功,因为该demo使用的bundle ID是测试ID,并未正式上线,但是功能是经过使用真实上线的bundle ID支付验证过的,如需使用该demo进行苹果支付测试,可将IOS工程下的bundlle ID替换为你自己的包名然后进行测试)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1448091.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PR:熟悉PR工作环境

新建项目 设置自己的页面布局 首选项

【JavaEE】_JavaScript基础语法

目录 1. JavaScript概述 1.1 JavaScript简介 1.2 HTML、CSS、JavaScript的关系 1.3 JavaScrip的组成 2. JavaScript的书写形式 2.1 内嵌式 2.2 行内式 2.3 外部式 3. 输出 3.1 alert 3.2 console.log 4. 变量的使用 4.1 创建变量 4.1.1 使用var 4.1.2 使用let …

java中事务的使用

文章目录 前言一、同一张表1.业务代码2.测试代码3.测试结果 二、不同表1.业务代码2.测试代码3.测试结果 总结 前言 本文将介绍在springboot中使用Transactional注解来完成对数据库事务的操作&#xff0c;保证数据一致性。 一、同一张表 1.业务代码 Controller Controller p…

停止内耗,做有用的事

很多读者朋友跟我交流的时候&#xff0c;都以为我有存稿&#xff0c;于是听到我说每周四现写的时候都很惊讶。其实没什么好惊讶的&#xff0c;每周四我都会把自己关在书房里一整天&#xff0c;断掉一切电话、微信、邮件&#xff0c;从中午写到晚上&#xff0c;直到写完为止。 这…

算法学习——LeetCode力扣回溯篇1

算法学习——LeetCode力扣回溯篇1 77. 组合 77. 组合 - 力扣&#xff08;LeetCode&#xff09; 描述 任何顺序 返回答案。 示例 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 示例 2&#xff1a; 输…

springboot743二手交易平台

springboot743二手交易平台 获取源码——》公主号&#xff1a;计算机专业毕设大全

《Java 简易速速上手小册》第8章:Java 性能优化(2024 最新版)

文章目录 8.1 性能评估工具 - 你的性能探测仪8.1.1 基础知识8.1.2 重点案例&#xff1a;使用 VisualVM 监控应用性能8.1.3 拓展案例 1&#xff1a;使用 JProfiler 分析内存泄漏8.1.4 拓展案例 2&#xff1a;使用 Gatling 进行 Web 应用压力测试 8.2 JVM 调优 - 魔法引擎的调校8…

第四篇【传奇开心果微博系列】Python微项目技术点案例示例:美女颜值判官

传奇开心果微博系列 系列微博目录Python微项目技术点案例示例系列 微博目录一、微项目目标二、雏形示例代码三、扩展思路四、添加不同类型的美女示例代码五、增加难度等级示例代码六、添加特殊道具示例代码七、设计关卡系统示例代码八、添加音效和背景音乐示例代码九、多人游戏…

【解决】idea控制台不输出trace/debug日志

idea控制台不输出trace日志 问题原因解决 问题 idea控制台不输出trace日志。 pom文件&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></dependency>输出lo…

23种计模式之Python/Go实现

目录 设计模式what?why?设计模式&#xff1a;设计模式也衍生出了很多的新的种类&#xff0c;不局限于这23种创建类设计模式&#xff08;5种&#xff09;结构类设计模式&#xff08;7种&#xff09;行为类设计模式&#xff08;11种&#xff09; 六大设计原则开闭原则里氏替换原…

P3612 [USACO17JAN] Secret Cow Code S题解

题目 奶牛正在试验秘密代码&#xff0c;并设计了一种方法来创建一个无限长的字符串作为其代码的一部分使用。 给定一个字符串&#xff0c;让后面的字符旋转一次&#xff08;每一次正确的旋转&#xff0c;最后一个字符都会成为新的第一个字符&#xff09;。也就是说&#xff0…

localStorage、sessionStorage、cookie区别

localStorage: localStorage 的生命周期是永久的&#xff0c;关闭页面或浏览器之后 localStorage 中的数据也不会消失。localStorage 除非主动删除数据&#xff0c;否则数据永远不会消失 sessionStorage: sessionStorage 的生命周期是仅在当前会话下有效。sessionStorage 引入…

【STM32 CubeMX】STM32中断体系结构

文章目录 前言一、中断体系的比喻二、中断的内部结构2.1 EXTI触发方式 2.2 NVIC2.3 cpu与中断2.4 外部中断控制器框图上升沿触发选择寄存器屏蔽/使能寄存器等待处理寄存器 2.5 中断优先级 总结 前言 一、中断体系的比喻 STM32中断体系如下图所示&#xff1a; 一座大型建筑物…

(二)【Jmeter】专栏实战项目靶场drupal部署

该专栏后续实战示例&#xff0c;都以该篇部署的项目展开操作。 前置条件 参考“&#xff08;一&#xff09;【Jmeter】JDK及Jmeter的安装部署及简单配置” 安装部署Jmeter&#xff0c;从文章最后下载“Postman、Rancher.ova、VirtualBox-7.0.12-159484-Win.exe、Xshell-7.0.01…

VUE学习——表单的输入绑定

使用【v-model】。 输入框 <template><h1>表单输入绑定</h1><input type"text" v-model"message"><p>输入的值&#xff1a;{{ message }}</p> </template> <script>export default{data(){return{messa…

java 数据结构ArrayList类

目录 什么是List 线性表 顺序表 ArrayList类 ArrayList无参方法 ArrayList有参方法 &#xff1f;通配符 ArrayList 的remove方法 ArrayList 的subList方法 Iterator&#xff1a;迭代器 使用ArrayList完成杨辉三角 什么是List 在集合框架中&#xff0c;List是一个接…

FileZilla Server 1.8.1内网搭建

配置环境服务器服务器下载服务器配置服务器配置 Server - ConfigureServer Listeners - Port 协议设置 Protocols settingsFTP and FTP over TLS(FTPS) Rights management(权利管理)Users(用户) 客户端建立连接 配置环境 服务器处于局域网内: 客户端 < -访问- > 公网 &l…

猫头虎分享已解决Bug || Invariant Violation in React: Element Type is Invalid ‍

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

HarmonyOS鸿蒙学习基础篇 - 自定义组件(一)

前言 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为系统组件&#xff0c;由开发者定义的称为自定义组件。在进行 UI 界面开发时&#xff0c;通常不是简单的将系统组件进行组合使用&#xff0c;而是需要考虑代码可复用性、业务逻辑与UI分离&#…

987. 二叉树的垂序遍历 - 力扣(LeetCode)

题目描述 给你二叉树的根结点 root &#xff0c;请你设计算法计算二叉树的 垂序遍历 序列。 对位于 (row, col) 的每个结点而言&#xff0c;其左右子结点分别位于 (row 1, col - 1) 和 (row 1, col 1) 。树的根结点位于 (0, 0) 。 二叉树的 垂序遍历 从最左边的列开始直到…