Flutter还不会App支付?一篇文章带你吃透微信/支付宝/苹果内购

文章目录

image.png

微信支付

支付流程

  • 1.先下单,获取商品订单号orderId
  • 2.拿到orderId去请求微信支付参数
    • appId
      • 微信AppId
    • partnerId
      • 商户ID
    • prepayId
      • 预支付Id
    • nonceStr
      • 随机字符串
    • timestamp
      • 支付当前时间戳
    • packageValue
      • 签名包
    • sign
      • 签名
    • signType
      • 签名类型
  • 3.App端吊起微信支付并监听支付结果并展示
  • 4.拿到支付结果后跳转到订单详情或我的订单展示支付结果

支付配置

依赖配置

# 微信SDK Share:https://tanggongfanghos.com.cn/.well-known/apple-app-site-association
fluwx: ^3.6.1+4

源码

import 'package:fluwx/fluwx.dart' as fluwx;
/// 微信支付
static void wxPay() async{
  bool installed = await fluwx.isWeChatInstalled;
  if (!installed) {
    // 未安装微信,请前去下载
    String name = Platform.isIOS ? "AppStore":"应用商店";
    Toast.show("您未安装微信,请前往$name下载~");
    return;
  }

  try {
    WeChatPayModel weChatPayParams =
      WeChatPayModel(
      Config.WX_APPID,
      "partnerId",
      "prepayId",
      "nonceStr",
      DateUtil.currentTimeMillis(),
      "package",
      "sign",
      "signType",
    );

    if (weChatPayParams != null) {
      fluwx.payWithWeChat(
        appId: weChatPayParams.appId,
        partnerId: weChatPayParams.partnerId,
        prepayId: weChatPayParams.prepayId,
        packageValue: weChatPayParams.package,
        nonceStr: weChatPayParams.nonceStr,
        timeStamp: weChatPayParams.timestamp,
        sign: weChatPayParams.sign,
      );
      // 监听支付结果
      fluwx.weChatResponseEventHandler
        .listen((event) async {
          print(event.errCode);
          // 支付成功
          if (event.errCode == 0) {
            Toast.show('微信支付成功~');
          } else {
            Toast.show('微信支付失败~');
          }
          // 关闭弹窗
        });
    }
  } catch (e) {
    Toast.show("吊起微信失败~${e.toString()}");
  }
}

支付宝支付

准备

  • 1.申请支付宝企业账号
  • 2.创建App应用并添加App能力,绑定App
  • 3.设置接口加密方式(公钥/秘钥)
  • 4.开始审核,审核成功即可(1天之内)

支付流程

  • 1.先下单,传入商品id、规格、商品数量进行下单,拿到订单号orderId
  • 2.拿到orderId去请求支付宝支付参数及签名
    • app_id
      • 支付宝AppId
    • biz_content
      • 商品信息
        • 商品id
        • 商品数量
        • 交易订单号
          • out_trade_no
    • charset
      • utf-8
    • method
      • alipay.trade.app.pay
    • timestamp
      • 支付当前时间戳
    • version
      • 1.0
    • sign_type
      • 签名类型:RSA2
  • 3.App端吊起支付宝支付并监听支付结果并展示
  • 4.拿到支付结果后跳转到订单详情或我的订单展示支付结果

支付配置

依赖配置

# AliPay支付宝支付插件 https://github.com/RxReader/alipay_kit
#alipay_kit: ^2.0.0
  alipay_kit:
        path: ./alipay_kit/

源码

import 'package:alipay_kit/alipay_kit.dart';
static StreamSubscription<AlipayResp>? _paySubs;
/// 支付宝支付
  static void aliPay() async{
    bool isAliPayInstalled = await Alipay.instance.isInstalled();
    if (!isAliPayInstalled){
      String name = Platform.isIOS ? "AppStore":"应用商店";
      Toast.show("您未安装支付宝,请前往$name下载~");
      return;
    }
    //支付宝支付调试
    final Map<String, dynamic> bizContent = <String, dynamic>{
      'timeout_express': '30m',
      'product_code': 'QUICK_MSECURITY_PAY',
      'total_amount': '0.01',
      'subject': '1',
      'body': '我是测试数据',
      'out_trade_no': '123456789',
    };
    final Map<String, dynamic> orderInfo = <String, dynamic>{
      'app_id': '2021003109659746',
      'biz_content': json.encode(bizContent),
      'charset': 'utf-8',
      'method': 'alipay.trade.app.pay',
      'timestamp': DateUtil.currentTimeStamp(),
      'version': '1.0',
    };
    Log.d("AliPayInfo>>>>>${orderInfo.toString()}");
    final String? charset = orderInfo['charset'] as String?;
    final Encoding encoding = Encoding.getByName(charset) ?? utf8;
    final Map<String, dynamic> clone = <String, dynamic>{
      ...orderInfo,
      'sign_type': 'RSA2',
    };
    final String param = Utils.getParams(clone, encoding);
    final String sign =
        "ZoE2WQV6MXJUJrfdW7+sd2YQ6eeizsHr2i+qEGqseM7RvVpes/XglJ0vFhUlIXM0v8a6puM9uWYoQKLPJQ15182zezcmyMDFOPanofNZb4zIg7Axmv+KEyqqXwMM/ADajwzpHnoPy2cf+46ZBZZOMfvplA6dQ+CkdQz8oMveK7OQakshYu80oAkb9ICH8JTRCZmE3edYTxsUr5eD/T6SVnFBYKC4svXGmSctrkdPqQq9+Jrj+OPHjT8MAodUrBopMUXAPZYeu2ac4XyJI4PYvwdd3uic9YUfkkbZHh61rxT6xS+ygTJ7+N7tNeFtj+4x4rftynO9M08+tYcMdaqYEA==";
    Alipay.instance.pay(
        orderInfo:
            '$param&sign=${Uri.encodeQueryComponent(sign, encoding: encoding)}');
    _paySubs =  Alipay.instance.payResp().listen((AlipayResp resp) {
      final String content = 'pay: ${resp.resultStatus} - ${resp.result}';
      if (resp.resultStatus == 9000){
        Toast.show("支付宝支付成功~");
      }else {
        Toast.show("支付宝支付失败~");
      }
      Log.d('支付${content}');
    });
  }

苹果支付

支付流程

0.内购初始化
缺少产品ID列表,创建支付前先从苹果后台创建商品

  • 1.先下单,拿到订单Id
  • 2.IAP支付前通过订单Id获取支付参数
  • 3.调用Api吊起苹果支付
  • 4.拿到支付结果后通知服务端校验数据有效性(Noti)
    • 'receipt-data': productItem.transactionReceipt
    • 重试机制:5次,服务端还未收到则退出
    • 结束该笔交易,否则无法生成新的交易
  • 5.错误日志上报及漏单处理

配置内购环境

image.png

1.第一次注册需申请商户ID和证书

https://www.jianshu.com/p/39f5f87b0932

2.协议、税务和银行业务信息配置

按要求录入账户银行卡信息和相关用户信息


image.png

image.png

image.png

CNAPS代码获取:
https://e.czbank.com/CORPORBANK/QYUK

设置税务

https://www.jianshu.com/p/134b506a27e9
等待处理

image.png

审核完成

image.png

3.根据项目需求选择适合的内购类型

消耗型项目

只可使用一次的产品,使用之后即失效,必须再次购买。
示例:钓鱼 App 中的鱼食。

非消耗型项目

只需购买一次,不会过期或随着使用而减少的产品。
示例:游戏 App 的赛道。

自动续期订阅

允许用户在固定时间段内购买动态内容的产品。除非用户选择取消,否则此类订阅会自动续期。
示例:每月订阅提供流媒体服务的 App。

非续期订阅

允许用户购买有时限性服务的产品。此 App 内购买项目的内容可以是静态的。此类订阅不会自动续期。
示例:为期一年的已归档文章目录订阅。

4.添加内购商品信息录入相应的产品内购信息

如:名称、ID、价格、商品审核截图


image.png

App 内购买项目的截屏,即所售项目的示意图。例如,如果 App 内购买项目是一本图书,您可以提交图书的截屏。您也可以提交购买页的截屏。该截屏仅用于 Apple 审核,不会在 App Store 中显示。
截屏要求如下:
iOS 至少需要 640 x 920 像素
App 审核图像上传后,可以替换,但无法移除。当您的 App 内购买项目处于审核中时,您无法更新截屏。

  • 点按“存储”或“提交以供审核”。提交审核前需要上传购买界面截图,供苹果审核

注意:若出现无效的商品ID

则需要提交新的版本,最好选择手动发布,因为本次提交只是为了让创建的内购项目ID生效,项目中可以没有关于内购的逻辑代码

5.沙盒账号创建及使用注意事项

沙盒账号创建

https://appstoreconnect.apple.com/access/testers

image.png

注意📢

  • 电子邮件不能是别人已经注册过AppleID的邮箱
  • App Store 地区不要乱选。弹出的购买提示框会根据当前AppleID(沙盒账号)的地区显示语言。

沙箱账号怎么登录不成功

沙箱账号是不能直接在App Store进行登录的,只能在点击了购买商品之后,在弹出的登录框进行登录
验证是否已登录沙箱测试账号:
设置--iTunes Store与App Store,页面拉到最底部,会看到沙箱账户项会列出你已登录的沙箱测试账号!

沙盒账号使用流程

必须真机测试!!!

  • 1.在iPhone上安装测试包(必须是adhoc签名证书或者develop签名证书打的包,不能是从App Store上下载的)
  • 2.退出iPhone的App Store账号(因为我们需要使用沙盒账号登录)

操作方法一:打开App Store应用首页滑到最下方--选中AppleID--注销
操作方法二:设置--iTunes Store与App Store--选中AppleID--注销

6.Xcode Capabilities里面打开In-App Purchase功能

image.png

接入项目

依赖配置

# 支付代码测试
iap_pay:
    git:
        url:  https://gitee.com/Steven_Hu/iap_pay.git
        ref: master

伪代码展示

//TODO:IAP第一步 下单
await PairDao.enrollPay(stateId, couponId: 0, paymentChannel: 1);
//TODO:IAP第二步 获取支付参数
var r = await IapDao.getPayParams(orderId: orderId);
//TODO:IAP第三步 IAP支付
int pId = r.data['params']['apple_product_id'];
await InAppPurchase().requestPurchase(productId: '${pId}', orderId: orderId);
//TODO:IAP第四步: 发送给服务端验证数据有效性
IapPay.purchaseUpdated!.listen((PurchasedItem productItem) async {
  this.saveReceipt(
    transactionReceipt: productItem.transactionReceipt ?? "",
  );

  this.notiServer(
    transactionReceipt: productItem.transactionReceipt ?? "",
    complete: () {
      this.count = 0;
      this.time = 0;
      this.removeReceipt();
      finishTransition(productItem);
      if (success != null) {
        success(productItem);
        this.end();
      }
    },
  );
}
//TODO:IAP第五步:错误日志上报
IapDao.reportPayException(
   orderId: '${this.orderId}',
   errCode: purchaseError.code ?? "",
   message: mesage,
   status: purchaseError.responseCode ?? 0,
);

项目使用

/// 苹果内购
  static void iapPay(BuildContext context) async {
    // 查看是否能进行苹果内购
    var result = await IapPay.instance.check;
    print('CanInAppPurchase============: $result');
    if (result == "true") {
      await IapDao.instance.config(context);
      try {
        List<IAPItem> item = await IapPay.instance.getProducts(["10001"]);
        Log.d("IAPItem>>>>>>>>>>>${item.first.toJson()}");
        Future.delayed(Duration(seconds: 3)).then((_) async {
          if (item.length > 0) {
            IapDao.instance.iapPay(context: context);
          }
        });
      } catch (e) {
        Log.d("IAP-ERROR>>>>>>>>>>>${e.toString()}");
      }
    } else {
      IapPay.showLoading("CanPay:${result}");
    }
  }

吊起支付效果图

image.png

彩蛋~

苹果内购Flutter 2.0空安全正在调试中,上线后会通知大家~请大家多多支持

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容