Using the ONE Store In-App Library in Flutter

Overview

Provides the latest features of the One Store payment library in applications implemented in the Flutter environment. This guide explains how to apply the functions of the ONE store payment library in a Flutter environment.

Development version

Flutter

3.3.0

Java SDK (Java 11)

Purchase: v21.02.01

App License Checker: v2.2.1

Plugin Installation

Add a plugin to the pubspec.yaml file

Download the package using flutter pub get.

dependencids:
  flutter_onestore_inapp: ^0.3.0

Add dependencies to the Android build.gradle

Add the Maven repository address to the top-level build.gradle file

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

Add <queries> to the AndroidManifest.xml file

It should be positioned directly under the <manifest> tag, and the following elements need to be added.

<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>

Setting developer options for store selection

v21.02.00 Update – Added Global Store Selection Feature

Starting from IAP SDK 21.02.00, you can specify the store app linked with the SDK by setting the android:value of onestore:dev_option as shown below. It should be positioned directly under the <application> tag, and the following elements should be added.

<manifest>
    <application>
        <activity>
        </activity>
            <meta-data android:name="onestore:dev_option" android:value="onestore_01" />
    </application>
</manifest>
android:value
Applicable countries/regions

onestore_00

South Korea

onestore_01

Singapore, Taiwan

onestore_02

United States

Applying the ONE store in-app payment library in the app

Log level setting

In the development stage, you can set the log level to expose the SDK's data flow in more detail. It works based on the values defined in 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);
  }
}
Constant
Value

VERBOSE

2

DEBUG

3

INFO (default)

4

WARN

5

ERROR

6

Since this option may be vulnerable to security issues in the release build version, it should be removed.

Requesting Login

ONE store in-app payment is a service that operates based on login. When starting the app for the first time, prompt the user to log in before calling the purchase library's API. This can prevent issues such as token expiration and other potential problems in advance when making requests to the purchase library.

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
    }
  });
}

Initialize purchase library

Request initialization of the PurchaseClientManager instance. The required value at this time is the public license key. You can obtain this license key after registering your app in the ONE store Developer Center.

import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

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

The public license key is used within the SDK to verify the integrity of purchase responses.

Listening for purchase data updates and error responses

Prepare to receive purchase completion responses through 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
  }

}

Retrieving product details

You can query detailed information about in-app products registered in the ONE store Developer Center using the PurchaseClientManager.queryProductDetails API.

Product details are delivered as a list containing ProductDetail objects.

Parameter
Type
Description

productIds

List<String>

Product ID list

productType

Product type

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);
    }
  }
  
}

If the list of requested products is small, you can request with ProductType.all instead of making separate requests by product type, as shown in the example above.

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);
    }
  }
  
}

If there are many registered in-app products, response delays may occur. In such cases, it is recommended to make calls in batches of 400 for stability and speed.

The ProductType.all option can only be used when retrieving product details; it cannot be used with other APIs.

Requesting a purchase

Use the PurchaseClientManager.launchPurchaseFlow() API to request a purchase.

Parameter
Type
Description

productDetail

Object obtained through the product details query API

quantity

Int

Quantity (default: 1), up to a maximum of 10 for multiple purchases

developerPayload

String

If you send the necessary data as developer input data during the purchase request, it will also be included in the purchase result (PurchaseData.developerPayload)

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
    );
  }
  
}

Subscription Upgrade or Downgrade

A subscription is automatically renewed until it is canceled. A subscription can have the following statuses:

  • Active: The user is in good standing and has access to the subscription content without issues.

  • Pause Scheduled: The user can select this option to pause the subscription while using it.

    • Weekly subscription: Can be paused for 1–3 weeks.

    • Monthly subscription: Can be paused for 1–3 months.

    • Annual subscription: Pausing is not supported.

  • Cancel Scheduled: The user can select this option if they want to cancel the subscription while using it. The next payment will not be processed.

  • Grace Period, On Hold: If the user encounters a payment issue, the next payment will not be processed. Cancel scheduling is not available, but an immediate "unsubscribe" is possible.

To update a subscription, you must apply proration mode. Below are explanations for each proration mode.

Mode
Value
Description

IMMEDIATE_WITH_TIME_PRORATION

1

The subscription change takes effect immediately, and the remaining time is credited or charged based on the price difference. (This is the default behavior.)

IMMEDIATE_AND_CHARGE_PRORATED_PRICE

2

The subscription change takes effect immediately, and the billing cycle remains unchanged. The price for the remaining period is charged. (This option is only available for upgrades.)

IMMEDIATE_WITHOUT_PRORATION

3

The subscription change takes effect immediately, and the new price will be charged on the next billing date. The billing cycle remains the same.

DEFERRED

4

The replacement is applied when the existing plan expires, and the new price is charged at the same time.

You can make a request using the PurchaseClientManager.launchUpdateSubscription() API.

Subscriptions can be upgraded or downgraded for users using the same API as for purchase requests. However, to apply a subscription upgrade or downgrade, both the existing subscription purchase token and the proration mode value are required.

Parameter
Type
Description

productDetail

Object obtained through the product details query API

oldPurchaseData

Object obtained through the purchase history query API

prorationMode

prorationMode

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
    );
  }
  
}

Processing purchases

  • PurchaseClientManager.launchPurchaseFlow()

  • PurchaseClientManager.launchUpdateSubscription()

If a purchase is successfully completed using these APIs, you can receive the response through the _listenToPurchasesUpdated() method registered in "Listening for purchase data updates and error responses."

After successfully receiving a purchase response, it is very important for the user to perform either a consume or acknowledge operation.

If the purchase is not acknowledged or consumed within 3 days, it will be considered that the product was not delivered to the user, resulting in an automatic refund.

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

Managed products (ProductType.inapp) are consumed using the PurchaseClientManager.consumePurchase() API.

Managed products (ProductType.inapp) cannot be purchased again unless they are consumed.

Managed products (ProductType.inapp) can be used in two ways depending on API usage:

  • Consumable product: Purchase request → Response → Item delivery → consumePurchase

  • Time-based product: Purchase request → Response → Item delivery → acknowledgePurchase → After a certain period → consumePurchase

Parameter
Type
Description

purchaseData

PurchaseData obtained through purchase completion or purchase history query

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

Use the PurchaseClientManager.acknowledgePurchase() API to acknowledge managed products (ProductType.inapp) or subscription products (ProductType.subs). For subscription products (ProductType.subs), consume is not possible; only acknowledge is allowed.

Parameter
Type
Description

purchaseData

PurchaseData obtained through purchase completion or purchase history query

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]);
      }
    });
  }
  
}

After completing acknowledgment, for subscription products (ProductType.subs), you must refresh the PurchaseData by querying the purchase history to see the updated PurchaseData.isAcknowledged value.

Query purchase history

Use the PurchaseClientManager.queryPurchases() API to request unconsumed purchase records. The response data from querying purchase history is identical to the data received through purchase updates and error response listeners. Processing post-purchase data alone is insufficient to guarantee that the app handles all purchases. The app might fail to recognize some purchased items. Scenarios where purchases could be missed include:

  • Network issues during purchase: The user completes a purchase and receives confirmation from ONE Store, but the device loses network connectivity before receiving the purchase notification.

  • Multiple devices: A user purchases an item on one device and expects it to appear when switching to another device.

To handle these scenarios, call the purchase history query API appropriately:

  • When the app launches for the first time.

  • When the app returns from the background to the foreground.

  • When entering the store interface.

Adjust these calls based on your app’s specific context.

Parameter
Type
Description

productType

Product type

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);
      }
    });
  }
  
}

Open subscription management screen

Use the PurchaseClientManager.launchManageSubscription() API to navigate to the details page of a subscription product. Changing subscription settings is the user's responsibility, and the following actions can be managed from the subscription management menu:

  • Change payment methods

  • Change subscription status (schedule cancellation, cancel)

  • Agree to price changes for subscribed products

If the PurchaseData value is null, you will be redirected to the general subscription list screen instead of a specific subscription product details page.

Parameter
Type
Description

purchaseData

PurchaseData obtained through purchase completion or purchase history query

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 Feature Addition

The StoreEnvironment.getStoreType() API provides the capability to determine whether the application with the SDK installed was installed via ONE store.

Store Type Definition

This API returns a StoreType, which can be one of the following four values.

StoreType
value
description

StoreType.UNKNOWN

0

Unable to determine the app installation store (direct APK installation, unknown sources, etc.)

StoreType.ONESTORE

1

Installed from ONE Store (or when developer options are enabled)

StoreType.VENDING

2

Installed from Google Play Store

StoreType.ETC

3

Installed from another store

How to Use the API

This API can be used by calling 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;
  }

Store Determination Criteria

This API determines the installed store using three methods:

  1. If distributed via ONE store market signature

    1. Checks whether the app was distributed using the ONE store market signature to see if it was installed from ONE store.

  2. Determination based on installer package name

    1. If not distributed via the ONE store market signature, it uses the PackageManager.getInstallerPackageName() API to check the store information used during app installation.

  3. If the developer option (onestore:dev_option) is enabled

    1. If onestore:dev_option is set, it always responds with StoreType.ONESTORE.

Usage Example

Applying UI differentiation by store

If the payment systems provided by ONE store and other app markets are different, you can configure the UI differently.

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

Blocking features by store

You can configure certain features to be available only on ONE store.

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

Installing ONE store Service

You can install the "ONE store Service app" by calling the PurchaseClientManager.launchUpdateOrInstall() API.

While using the PurchaseClientManager API, you may receive an error response with the code RESULT_NEED_UPDATE. This occurs when the ONE store Service app is not installed or its version is lower than what the In-app SDK requires.

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