在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. 基於 installer package name 判斷

    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