qs_ios_purchase

qs_ios_purchase 是一个基于 iOS StoreKit 2 的 Flutter 内购插件,封装了商品查询、购买、恢复购买、交易校验、历史交易判断,以及 VIP、取消免费试用、取消自动续订等事件监听能力。

当前插件仅支持 iOS,最低系统版本为 iOS 15.0。

功能特性

  • 获取 App Store Connect 中配置的内购商品信息
  • 发起消耗型、非消耗型、非续期订阅、自动续期订阅购买
  • 恢复购买
  • 校验当前交易状态
  • 判断当前账号是否存在历史交易
  • 监听 VIP 状态变化
  • 监听取消免费试用、取消自动续订事件
  • 支持取消事件处理失败后的补偿通知

安装

在项目的 pubspec.yaml 中添加依赖:

dependencies:
  qs_ios_purchase: ^1.0.6

如果需要本地调试,可以使用路径依赖:

dependencies:
  qs_ios_purchase:
    path: ../qs_ios_purchase

然后执行:

flutter pub get

iOS 配置

  1. 在 App Store Connect 中创建内购商品,并记录商品 ID。
  2. 确认 iOS 工程的 Deployment Target 不低于 15.0。
  3. 确认项目已经启用 In-App Purchase 能力。
  4. 使用真机、Sandbox 账号或 TestFlight 测试完整内购流程。

基础使用

1. 初始化监听

建议在应用启动后先调用 initialize。该方法会注册原生 StoreKit 相关监听,后续 VIP 状态和取消订阅相关事件会通过回调返回。

import 'package:qs_ios_purchase/qs_ios_purchase.dart';

Future<void> initPurchase() async {
  await QsIosPurchase.initialize(
    onVipChange: (isVip) {
      // VIP 状态变化
      print('isVip: $isVip');
    },
    onCancelFreeTrial: (transactionId) {
      // 用户取消免费试用
      print('cancel free trial: $transactionId');
    },
    onCancelAutoRenew: (transactionId) {
      // 用户取消自动续订
      print('cancel auto renew: $transactionId');
    },
    onCancelFreeTrialEveryTime: () {
      // 每次检测到取消免费试用时触发
      print('cancel free trial event');
    },
  );
}

2. 获取商品列表

final products = await QsIosPurchase.getProducts(
  productIds: [
    'your_product_id',
    'your_subscription_id',
  ],
);

for (final product in products) {
  print('${product.id}: ${product.currencyPrice}');
}

getProducts 成功时返回 List<QsProductDetail>,失败时会抛出 PlatformException。

3. 发起购买

final result = await QsIosPurchase.requestPurchase(
  productId: 'your_product_id',
);

switch (result.status) {
  case QsPurchaseStatus.success:
    print('购买成功: ${result.transactionID}');
    break;
  case QsPurchaseStatus.cancel:
    print('用户取消购买');
    break;
  case QsPurchaseStatus.error:
  default:
    print('购买失败: ${result.errorMessage}');
    break;
}

购买成功后,返回结果中会包含商品 ID、交易 ID、原始交易 ID、订阅时间、原始订阅时间和价格等信息。

4. 恢复购买

final result = await QsIosPurchase.restorePurchase();

if (result.status == QsPurchaseStatus.success) {
  print('恢复购买成功');
} else {
  print('恢复购买失败: ${result.errorMessage}');
}

5. 校验交易

final result = await QsIosPurchase.checkTransactions();

if (result?.status == QsPurchaseStatus.success) {
  print('存在有效交易');
} else {
  print('没有有效交易: ${result?.errorMessage}');
}

6. 判断是否存在历史交易

final hasHistory = await QsIosPurchase.hasHistoryTransactions();
print('是否存在历史交易: $hasHistory');

7. 取消事件处理失败后的补偿

如果业务侧处理取消自动续订或取消免费试用事件失败,可以调用对应方法通知原生侧重新处理。

await QsIosPurchase.handleCancelAutoRenewFailure(id: transactionId);
await QsIosPurchase.handleCancelFreeTrialFailure(id: transactionId);

数据模型

QsProductDetail

商品信息包含以下常用字段:

  • id:商品 ID
  • productType:商品类型
  • price:原始价格数值
  • currencyPrice:带货币符号的本地化价格
  • discountPrice:优惠价格数值
  • discountCurrencyPrice:带货币符号的本地化优惠价格
  • discountRate:折扣比例
  • trialPeriodValue:试用周期数值
  • trialPeriodUnit:试用周期单位
  • subscriptionPeriodValue:订阅周期数值
  • subscriptionPeriodUnit:订阅周期单位
  • languageCode:价格区域语言码
  • regionCode:价格区域地区码
  • weekAveragePrice:按周折算价格
  • paymentMode:优惠支付模式
  • isEligibleForIntroOffer:是否有资格享受订阅优惠
  • isFreeTrial:是否为可用的免费试用商品
  • isDiscount:是否为可用的折扣商品

QsPurchaseResult

购买、恢复购买和交易校验结果包含以下字段:

  • status:操作状态
  • errorMessage:错误信息
  • productID:商品 ID
  • transactionID:交易 ID
  • originalTransactionID:原始交易 ID
  • subscriptionDate:订阅时间
  • originalSubscriptionDate:原始订阅时间
  • price:交易价格

枚举说明

QsPurchaseStatus

  • success:操作成功
  • error:操作失败
  • cancel:用户取消

QsProductType

  • consumable:消耗型商品
  • nonConsumable:非消耗型商品
  • nonRenewable:非续期订阅
  • autoRenewable:自动续期订阅

QsPeriodUnit

  • day:天
  • week:周
  • month:月
  • year:年

QsPaymentMode

  • payAsYouGo:按周期支付优惠价
  • payUpFront:预付优惠价
  • freeTrial:免费试用

注意事项

  • 请先调用 initialize,再执行购买、恢复购买或交易校验相关逻辑。
  • 商品 ID 必须与 App Store Connect 中配置的商品 ID 完全一致。
  • StoreKit 2 需要 iOS 15.0 及以上系统。
  • 内购流程建议在真机、Sandbox 账号和 TestFlight 环境中完整验证。
  • 购买接口会先从已获取的商品中查找对应商品,建议购买前先调用 getProducts。
  • 业务侧收到取消免费试用或取消自动续订事件后,如果处理失败,请调用对应的补偿方法,便于后续重新处理。