# Using the ONE Store In-App Library in Flutter

## Overview <a href="#overview" id="overview"></a>

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

<table data-view="cards"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td><strong>Flutter</strong></td><td>3.3.0</td><td></td></tr><tr><td><strong>Java SDK (Java 11)</strong></td><td><p>Purchase: v21.04.00</p><p>App License Checker: v2.2.1</p></td><td></td></tr></tbody></table>

## Plugin Installation <a href="#undefined-2" id="undefined-2"></a>

### Add a plugin to the `pubspec.yaml` file <a href="#pubspec.yaml" id="pubspec.yaml"></a>

Download the package using `flutter pub get`.

```
dependencies:
  flutter_onestore_inapp: ^0.4.2
```

### Add dependencies to the Android build.gradle <a href="#android-build.gradle" id="android-build.gradle"></a>

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 <a href="#androidmainifest.xml-less-than-queries-greater-than" id="androidmainifest.xml-less-than-queries-greater-than"></a>

* Reference : <https://developer.android.com/training/package-visibility>

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

```xml
<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 <a href="#undefined-3" id="undefined-3"></a>

#### **v21.04.00 Update - Added ONE Billing Lab Selection Feature**

you can designate the store app integrated with the SDK by setting the `android:value` of `onestore:dev_option` as follows:

```xml
<manifest>
    <application>
        <activity>
        </activity>
            <meta-data android:name="onestore:dev_option" android:value="onestore_01" />
    </application>
</manifest>
```

<table><thead><tr><th>android:value</th><th>Applicable countries/regions</th><th data-hidden></th></tr></thead><tbody><tr><td><code>onestore_00</code></td><td>South Korea</td><td></td></tr><tr><td><code>onestore_01</code></td><td>Singapore, Taiwan</td><td></td></tr><tr><td><code>onestore_02</code></td><td>United States</td><td></td></tr><tr><td><code>onestore_03</code></td><td>ONE Billing Lab</td><td></td></tr></tbody></table>

{% hint style="info" %}
**Version History**

* **v21.04.00** : Added ONE Billing Lab&#x20;
* **v21.02.00** : With `android:value`, you can set South Korea, Singapore/Taiwan, and the United States.
* **v21.01.00** : With `android:value`, only global can be set, and only Singapore/Taiwan store apps can be specified.
  {% endhint %}

{% hint style="danger" %}
Caution: Be sure to remove this option from the binary in the release version.
{% endhint %}

## Applying the ONE store in-app payment library in the app <a href="#undefined-4" id="undefined-4"></a>

### Log level setting <a href="#undefined-5" id="undefined-5"></a>

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.

```dart
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.

```dart
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.

```dart
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`.

```dart
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 | [`ProductType`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype) | Product type    |

```dart
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.&#x20;

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

{% hint style="info" %}
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.
{% endhint %}

### Requesting a purchase

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

| Parameter        | Type                                                                                                    | Description                                                                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| productDetail    | [`ProductDetail`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/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`) |

```dart
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   | [`ProductDetail`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/productdetail)                        | Object obtained through the product details query API  |
| oldPurchaseData | [`PurchaseData`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata)                          | Object obtained through the purchase history query API |
| prorationMode   | [`ProrationMode`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseflowparams.prorationmode) | prorationMode                                          |

```dart
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.

```dart
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`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata) | PurchaseData obtained through purchase completion or purchase history query |

```dart
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`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata) | PurchaseData obtained through purchase completion or purchase history query |

```dart
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 | [`ProductType`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype) | Product type |

```dart
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`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata) | PurchaseData obtained through purchase completion or purchase history query |

```dart
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.

<table><thead><tr><th width="235">StoreType</th><th width="81">value</th><th>description</th></tr></thead><tbody><tr><td><code>StoreType.UNKNOWN</code></td><td>0</td><td>Unable to determine the app installation store (direct APK installation, unknown sources, etc.)</td></tr><tr><td><code>StoreType.ONESTORE</code></td><td>1</td><td>Installed from ONE Store (or when developer options are enabled)</td></tr><tr><td>StoreType.VENDING</td><td>2</td><td>Installed from Google Play Store</td></tr><tr><td>StoreType.ETC</td><td>3</td><td>Installed from another store</td></tr></tbody></table>

#### How to Use the API

This API can be used by calling StoreEnvironment.getStoreType().

```dart
  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.

```dart
  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.

```dart
  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.

```dart
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();
    }
  });
}
```
