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
pubspec.yaml
fileDownload 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>
onestore_00
South Korea
onestore_01
Singapore, Taiwan
onestore_02
United States
In version 21.01.00, the android:value could only be set to "global," and only the Singapore/Taiwan store apps could be specified.
Caution: Be sure to remove this option from the binary in the release version.
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);
}
}
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.
productIds
List<String>
Product ID list
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);
}
}
}
Requesting a purchase
Use the PurchaseClientManager.launchPurchaseFlow()
API to request a purchase.
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.
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.
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
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.
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.
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.
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.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:
If distributed via ONE store market signature
Checks whether the app was distributed using the ONE store market signature to see if it was installed from ONE store.
Determination based on installer package name
If not distributed via the ONE store market signature, it uses the PackageManager.getInstallerPackageName() API to check the store information used during app installation.
If the developer option (onestore:dev_option) is enabled
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