在Flutter中使用ONE store In-App Library(内部应用程序库)

概要

在 Flutter 环境中实现的应用程序中提供 One Store 支付库的最新功能。本指南说明如何在 Flutter 环境中应用 One Store 支付库的功能。

开发版本

Flutter

3.3.0

Java SDK (Java 11)

Purchase: v21.02.01

App License Checker: v2.2.1

安装插件

pubspec.yaml 文件中添加插件。

通过运行 flutter pub get 下载包。

dependencids:
  flutter_onestore_inapp: ^0.3.0

在 Android build.gradle 中添加依赖项

在顶级 build.gradle 文件中添加 Maven 仓库地址

allprojects {
    repositories {
        maven { url 'https://repo.onestore.net/repository/onestore-sdk-public' }
    }
}

在 AndroidManifest.xml 文件中添加 <queries>

应位于 <manifest> 标签的直属子级,并需要添加以下元素。

<manifest>

    <queries>
        <intent>
            <action android:name="com.onestore.ipc.iap.IapService.ACTION" />
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW" />

            <data android:scheme="onestore" />
        </intent>
    </queries>

    <application>

    </application>
</manifest>

为选择商店设置开发者选项

v21.02.00 更新 – 新增全球商店选择功能

从 IAP SDK 21.02.00 开始,可以通过如下设置 onestore:dev_option 的 android:value 值来指定与 SDK 关联的商店应用。 应位于 <application> 标签的直属子级,并应添加以下元素。

<manifest>
    <application>
        <activity>
        </activity>
            <meta-data android:name="onestore:dev_option" android:value="onestore_01" />
    </application>
</manifest>
값 (android:value)
适用国家/地区

onestore_00

South Korea

onestore_01

Singapore, Taiwan

onestore_02

United States

在应用中集成 ONE store 应用内支付库

日志级别设置

在开发阶段,可以通过设置日志级别,更详细地暴露 SDK 的数据流。该功能基于 android.util.Log 中定义的值运行。

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

class _HomePageState extends State<HomePage> {

  @override
  void initState() {
    super.initState();
    // 앱 개발 시 필요에 의해 SDK & Plugin의 로그 레벨을 변경하면 좀 더 자세한 정보를 얻을 수 있습니다.
    // WARNING! Release Build 시엔 로그 레벨 세팅을 제거 바랍니다. (default: Level.info)
    OneStoreLogger.setLogLevel(LogLevel.verbose);
  }
}
常量

VERBOSE

2

DEBUG

3

INFO (default)

4

WARN

5

ERROR

6

由于该选项在发布版本中可能存在安全漏洞,必须将其删除。

请求登录

ONE store 应用内支付是基于登录运行的服务。首次启动应用时,应在调用购买库 API 之前引导用户登录。在请求购买库时,这可以预先防止令牌过期等各种问题。

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

final OneStoreAuthClient _authClient = OneStoreAuthClient();

Future<void> launchSignInFlow() async {
  await _authClient.launchSignInFlow().then((signInResult) {
    if (signInResult.isSuccess()) {
      // success
    } else {
      // failure
    }
  });
}

初始化购买库

请求初始化 PurchaseClientManager 实例。此时需要的值是公钥许可证密钥。该许可证密钥可在你在 ONE store 开发者中心完成应用注册后获得。

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

final PurchaseClientManager _clientManager = PurchaseClientManager.instance;
_clientManager.initialize("your license key");

public license key 在 SDK 内用于校验购买响应的数据完整性。

监听购买数据更新及错误响应

通过 PurchaseClientManager.purchasesUpdatedStream,准备接收购买完成的响应。

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

class MyPurchaseManager {
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;
  late StreamSubscription<List<PurchaseData>> _purchaseDataStream;
  
  MyPurchaseManager() {
    _clientManager.initialize("your license key");
    
    // 구매 완료 후 Stream을 통해 데이터가 전달됩니다.
    _purchaseDataStream = _clientManager.purchasesUpdatedStream.listen(
        (List<PurchaseData> purchasesList) {
      _listenToPurchasesUpdated(purchasesList);
    }, onError: (error) {
      // 구매가 실패 되었거나 유저가 취소가 되었을 때 응답 됩니다.
      _logger.d('purchaseStream error: $error');
    }, onDone: () {
      _purchaseDataStream.cancel();
    });
  }
  

  void _listenToPurchasesUpdated(List<PurchaseData> purchasesList) {
    // do something
  }

}

查询商品详细信息

可以通过 PurchaseClientManager.queryProductDetails API 查询在 ONE store 开发者中心注册的应用内商品的详细信息。 商品详细信息会以包含 ProductDetail 对象的列表形式传递。

Parameter
Type
Description

productIds

List<String>

商品 ID 列表

productType

商品类型

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

class MyPurchaseManager {
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;
  
  static const consumableIds = ['product_1', 'product_2'];
  static const subscriptionIds = ['week', 'month', 'three_month'];
  
  final List<ProductDetail> _products = [];

  Future<void> fetchProductDetails() async {
    var responses = await Future.wait(<Future<ProductDetailsResponse>>[
      _clientManager.queryProductDetails(
        productIds: consumableIds,
        productType: ProductType.inapp
      ),
      _clientManager.queryProductDetails(
        productIds: subscriptionIds,
        productType: ProductType.subs
      )
    ]);
  
    if (responses.first.iapResult.isSuccess()) {
      final List<ProductDetail> result =
          responses.expand((element) => element.productDetailsList).toList();
      _products.clear();
      _products.addAll(result);
      notifyListeners();
    } else {
      _handleError('fetchProductDetails', responses.first.iapResult);
    }
  }
  
}

如果请求的商品列表较少,可以像上面的示例一样,使用 ProductType.all 类型一次性请求,而不是按商品类型分别请求。

class MyPurchaseManager {
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;
  
  static const consumableIds = ['product_1', 'product_2'];
  static const subscriptionIds = ['week', 'month', 'three_month'];
  
  final List<ProductDetail> _products = [];
  
  Future<void> fetchProductDetails() async {
    var productDetailsResponse = await _clientManager.queryProductDetails(
      productIds: (consumableIds + subscriptionIds),
      productType: ProductType.all
    );
     
    if (productDetailsResponse.iapResult.isSuccess()) {
      final List<ProductDetail> result = productDetailsResponse.productDetailsList;
      _products.clear();
      _products.addAll(result);

    } else {
      _handleError('fetchProductDetails', productDetailsResponse.iapResult);
    }
  }
  
}

如果注册的应用内商品数量较多,可能会出现响应延迟。此时建议以每 400 个为一批进行调用,以提高稳定性和速度。

ProductType.all 选项仅可用于商品详细信息查询,不能用于其他 API。

发起购买请求

通过 PurchaseClientManager.launchPurchaseFlow() API 发起购买请求。

Parameter
Type
Description

productDetail

通过商品详细信息查询 API 获得的对象

quantity

Int

数量(默认:1),批量购买时最大为 10

developerPayload

String

如果将所需数据作为开发者输入数据在购买请求时一起发送,购买结果(PurchaseData.developerPayload)中也会包含这些数据 限制条件:最大 200 字节

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

class MyPurchaseManager {
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;
    
  Future<IapResult> launchPurchaseFlow(ProductDetail productDetail,
      int? quantity, String? developerPayload) async {
    return await _clientManager.launchPurchaseFlow(
        productDetail: productDetail,
        quantity: quantity,
        developerPayload: developerPayload
    );
  }
  
}

定期订阅升级或降级

定期订阅会在取消前自动续订。定期订阅可以有以下状态:

  • 活跃:用户处于正常状态,可以正常使用订阅内容。

  • 已预约暂停:用户在使用订阅时可以选择暂停。

    • 周订阅:可以暂停 1~3 周。

    • 月订阅:可以暂停 1~3 个月。

    • 年订阅:不支持暂停。

  • 已预约取消:用户在使用订阅时选择取消,下一个结算日将不会扣费。

  • 宽限期、挂起:用户有支付问题时,下一个结算日不会扣费。无法预约取消,但可以立即“取消订阅”。

要更新定期订阅,必须应用 proration mode(比例分配模式)。以下是各 proration mode 的说明。

Mode
Value
Description

IMMEDIATE_WITH_TIME_PRORATION

1

定期订阅的更换会立即生效,剩余时间根据价格差额进行补偿或扣费。(这是默认操作。)

IMMEDIATE_AND_CHARGE_PRORATED_PRICE

2

定期订阅的更换会立即生效,账单周期保持不变。将会对剩余周期的价格进行扣费。(此选项仅适用于升级。)

IMMEDIATE_WITHOUT_PRORATION

3

定期订阅的更换会立即生效,下一个结算日会收取新的价格。账单周期保持不变。

DEFERRED

4

当前套餐到期后才会更换,同时收取新套餐的价格。

可以通过 PurchaseClientManager.launchUpdateSubscription() API 发起请求。

定期订阅的升级或降级可以使用与购买请求相同的 API 向用户提供。但在应用定期订阅的升级或降级时,必须提供现有定期订阅的购买令牌和 proration mode 值。

Parameter
Type
Description

productDetail

通过商品详细信息查询 API 获得的对象

oldPurchaseData

通过购买记录查询 API 获得的对象

prorationMode

proration mode(比例分配模式)

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

class MyPurchaseManager {
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;

  Future<IapResult> launchUpdateSubscription(ProductDetail productDetail,
      PurchaseData oldPurchaseData, ProrationMode prorationMode) async {
    return await _clientManager.launchUpdateSubscription(
        productDetail: productDetail,
        oldPurchaseData: oldPurchaseData,
        prorationMode: prorationMode
    );
  }
  
}

处理购买

  • PurchaseClientManager.launchPurchaseFlow()

  • PurchaseClientManager.launchUpdateSubscription()

通过这些 API 成功完成购买后,可以通过在“监听购买数据更新及错误响应”中注册的 _listenToPurchasesUpdated() 方法接收响应。

成功收到购买响应后,用户必须执行“消费(Consume)”或“确认(Acknowledge)”操作,这一点非常重要。

如果在3天内未进行确认(acknowledge)或消费(consume),系统会认为商品未交付给用户,并自动退款。

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

class MyPurchaseManager {
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;
  
  final List<ProductDetail> _products = [];
  
  // 상품 상세 정보에서 ProductType.inapp인 것만 필터링 된 데이터
  List<ProductDetail> get consumableProducts => _products
      .where((element) => element.productType == ProductType.inapp)
      .toList();

  // 상품 상세 정보에서 ProductType.subs인 것만 필터링 된 데이터
  List<ProductDetail> get subscriptionProducts => _products
      .where((element) => element.productType == ProductType.subs)
      .toList();

  void _listenToPurchasesUpdated(List<PurchaseData> purchasesList) {
    if (purchasesList.isNotEmpty) {
      for (var element in purchasesList) {
        if (consumableProducts.any((p) => p.productId == element.productId)) {
        /// [ProductType.inapp] 상품은 [consumePurchase] 호출하여 소비합니다.
        } else if (subscriptionProducts.any((p) => p.productId == element.productId)) {
        /// [ProductType.subs] 상품은 [acknowledgePurchase] 호출하여 확인합니다.
        }
      }
    }
  }
  
}

消费(Consume)

管理型商品(ProductType.inapp)通过 PurchaseClientManager.consumePurchase() API 进行消费。

管理型商品(ProductType.inapp)如未消费,将无法再次购买。

管理型商品(ProductType.inapp)根据 API 使用方式可以有两种用法:

  • 消耗型商品:购买请求 → 响应 → 发放道具 → consumePurchase

  • 定期商品:购买请求 → 响应 → 发放道具 → acknowledgePurchase → 一段时间后 → consumePurchase

Parameter
Type
Description

purchaseData

通过购买完成或购买记录查询获得的 PurchaseData

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

class MyPurchaseManager {
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;

  Future<void> consumePurchase(PurchaseData purchaseData) async {
    await _clientManager
        .consumePurchase(purchaseData: purchaseData)
        .then((iapResult) {
      // IapResult를 통해 해당 API의 성공 여부를 판단할 수 있습니다.
      if (iapResult.isSuccess()) {
        fetchPurchases([ProductType.inapp]);
      }
    });
  }
  
}

确认(Acknowledge)

通过 PurchaseClientManager.acknowledgePurchase() API 可对管理型商品(ProductType.inapp)或订阅型商品(ProductType.subs)进行确认。 订阅型商品(ProductType.subs)只能进行确认(acknowledge),无法消费(consume)。

Parameter
Type
Description

purchaseData

通过购买完成或购买记录查询获得的 PurchaseData

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

class MyPurchaseManager {
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;

  Future<void> acknowledgePurchase(PurchaseData purchaseData) async {
    await _clientManager
        .acknowledgePurchase(purchaseData: purchaseData)
        .then((iapResult) {
      // IapResult를 통해 해당 API의 성공 여부를 판단할 수 있습니다.
      if (iapResult.isSuccess()) {
        fetchPurchases([ProductType.subs]);
      }
    });
  }
  
}

确认完成后,对于订阅型商品(ProductType.subs),需通过查询购买记录更新 PurchaseData,才能确认 PurchaseData.isAcknowledged 值已更改。

查询购买记录

使用 PurchaseClientManager.queryPurchases() API 请求未消费的购买记录。 查询购买记录的响应数据与通过购买数据更新及错误响应监听接收的数据相同。 仅处理购买完成后的数据不足以确保应用处理了所有购买。应用可能无法识别用户购买的部分项目。可能导致遗漏的场景包括:

  • 购买时网络问题:用户成功完成购买并收到 ONE Store 确认,但设备在接收购买通知前断网。

  • 多设备:用户在一台设备购买商品后,切换设备时期望该商品可见。

为解决这些问题,需在以下情况下调用购买记录查询 API:

  • 应用首次启动时。

  • 应用从后台回到前台时。

  • 进入商店界面时。

请根据应用的具体场景调整调用逻辑。

Parameter
Type
Description

productType

商品类型

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

class MyPurchaseManager {
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;
  
  Future<void> fetchPurchases(ProductType type) async {
    await _clientManager
        .queryPurchases(productType: type)
        .then((response) {
      if (response.iapResult.isSuccess()) {
        if (type == ProductType.inapp) {
          for (var purchaseData in response.purchasesList) {
            consumePurchase(purchaseData);
          }
        } else if (type == ProductType.subs) {
          for (var purchaseData in response.purchasesList) {
            if (!purchaseData.isAcknowledged) {
              acknowledgePurchase(purchaseData);
            }
          }
        }
      } else {
        _handleError('fetchPurchases($type)', response.iapResult);
      }
    });
  }
  
}

打开定期订阅管理界面

使用 PurchaseClientManager.launchManageSubscription() API 可跳转到订阅商品的详细页面。 订阅商品的设置更改由用户自行管理,管理菜单中可执行如下操作:

  • 更改支付方式

  • 更改订阅状态(预约取消、取消)

  • 同意已订阅商品的价格变更

如果 PurchaseData 为空,则会跳转到定期订阅列表界面,而不是某个订阅商品的详细页面。

Parameter
Type
Description

purchaseData

通过购买完成或购买记录查询获得的 PurchaseData

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

final PurchaseClientManager _clientManager = PurchaseClientManager.instance;
  
Future<void> launchManageSubscription(PurchaseData? purchaseData) async {
  await _clientManager.launchManageSubscription(purchaseData);
}

StoreEnvironment API 功能新增

StoreEnvironment.getStoreType() API 提供判断集成了 SDK 的应用是否是通过 ONE store 安装的功能。

Store Type 定义

该 API 返回 StoreType,并且可能是以下四个值之一。

StoreType
value
description

StoreType.UNKNOWN

0

无法确定应用的安装商店(直接安装 APK、来源不明等)

StoreType.ONESTORE

1

通过 ONE Store 安装(或启用开发者选项时)

StoreType.VENDING

2

通过 Google Play Store 安装

StoreType.ETC

3

通过其他商店安装

API 使用方法

该 API 可通过调用 StoreEnvironment.getStoreType() 来使用。

  StoreType storeType = await OneStoreEnvironment.getStoreType();

  switch (storeType) {
    case StoreType.unknown:
      print("스토어 정보를 알 수 없습니다.");
      break;
    case StoreType.oneStore:
      print("ONE Store에서 설치된 앱입니다.");
      break;
    case StoreType.vending:
      print("Google Play Store에서 설치된 앱입니다.");
      break;
    case StoreType.etc:
      print("기타 스토어에서 설치된 앱입니다.");
      break;
  }

商店判断标准

该 API 通过三种方式判断应用的安装商店:

  1. 通过 ONE store 市场签名分发的情况

    1. 检查应用是否通过 ONE store 的市场签名分发,以确认是否从 ONE store 安装。

  2. 基于安装程序包名判断

    1. 若不是通过 ONE store 市场签名分发,则使用 PackageManager.getInstallerPackageName() API 检查安装应用所用的商店信息。

  3. 开发者选项(onestore:dev_option)已启用的情况

    1. 如果设置了 onestore:dev_option,无条件返回 StoreType.ONESTORE。

使用示例

按商店区分 UI

如果 ONE store 与其他应用商店提供的支付系统不同,可以针对不同商店配置不同的 UI。

  if (await OneStoreEnvironment.getStoreType() == StoreType.oneStore) {
    showOneStorePaymentUI()
  } else {
    showDefaultPaymentUI()
  }

按商店屏蔽功能

可以设置某些功能仅在 ONE store 上可用。

  if (await OneStoreEnvironment.getStoreType() != StoreType.oneStore) {
    print("이 기능은 ONE Store에서만 사용할 수 있습니다.");
  }
  enableOneStoreExclusiveFeature()

安装 ONE store 服务

可以通过调用 PurchaseClientManager.launchUpdateOrInstall() API 安装“ONE store 服务应用”。

在使用 PurchaseClientManager API 过程中,可能会遇到 RESULT_NEED_UPDATE 错误响应码。这通常是因为未安装 ONE store 服务应用,或当前版本低于 In-app SDK 要求。

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

final PurchaseClientManager _clientManager = PurchaseClientManager.instance;
  
Future<void> launchUpdateOfInstall() async {
  await _clientManager.launchUpdateOrInstall().then((iapResult) {
    if (iapResult.isSuccess()) {
      fetchPurchases();
      fetchProductDetails();
    }
  });
}

Last updated