> For the complete documentation index, see [llms.txt](https://onestore-devs-organization.gitbook.io/guide-inapp-sdk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://onestore-devs-organization.gitbook.io/guide-inapp-sdk/sdk-purchase-v21/flutter-sdk-v21.md).

# Flutter에서 원스토어 인앱 결제 사용하기 (SDK v21)

## 개요

Flutter 환경에서 구현된 어플리케이션에서 원스토어 결제 라이브러리의 최신 기능 제공합니다.\
이 가이드는 원스토어 결제 라이브러리 기능을 Flutter 환경에서 적용하는 방법을 설명합니다.

## 개발 버전

<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>Purchase: v21.01.00</td><td>App License Checker: v2.1.0</td></tr></tbody></table>

## 플러그인 설정

### pubspec.yaml 파일에 플러그인 추가하기

`flutter pub get` 을 통하여 패키지를 다운로드합니다.

```yaml
dependencids:
  flutter_onestore_inapp: ^0.3.0
```

### Android build.gradle에 종속성 추가하기

Top-Level build.gradle 파일에 maven repository 주소 추가하기

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

### AndroidMainifest.xml 파일에 \<queries> 추가하기

* 원스토어 공지사항: [Target SDK 버전이 30(OS 11) 이상으로 설정한 경우](https://dev.onestore.co.kr/devpoc/support/news/noticeView.omp?pageNo=4\&noticeId=32968\&viewPageNo=\&searchValue=)&#x20;
* 참고: <https://developer.android.com/training/package-visibility>&#x20;

`<manifest>` 태그의 직속 하위에 위치하며, 아래와 같은 요소를 추가해야 합니다.

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

### 글로벌 스토어 배포를 위한 테스트 옵션 설정

원스토어 인앱 SDK를 임의로 글로벌 스토어로 연동하는 옵션\
이 기능은 **Java SDK v21.01.00 버전**부터 적용됩니다.

`<application>` 태그 직속 하위에 위치하며, 아래와 같은 요소를 추가합니다.

```xml
<manifest>
    <application>
        <activity>
        </activity>
        
        <!-- Options for in-app testing on your global store -->
        <meta-data android:name="onestore:dev_option" android:value="global" />
    </application>
</manifest>
```

{% hint style="danger" %}
배포 빌드 버전에서는 이 옵션을 **반드시 삭제**해야 합니다.
{% endhint %}

## 앱에서 원스토어 인앱 결제 라이브러리 적용하기

### 로그 레벨 설정

개발 단계에서 로그 레벨을 설정하여 SDK의 데이터 흐름을 좀 더 자세히 노출할 수 있습니다.\
[`android.util.Log`](https://developer.android.com/reference/android/util/Log#summary)에 정의된 값을 기반으로 동작합니다.

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

| 상수             | 값 |
| -------------- | - |
| VERBOSE        | 2 |
| DEBUG          | 3 |
| INFO (default) | 4 |
| WARN           | 5 |
| ERROR          | 6 |

{% hint style="danger" %}
배포 빌드 버전에서는 보안에 취약할 수 있으니 이 옵션을 **삭제**해야 합니다.
{% endhint %}

### 로그인 요청하기

원스토어 인앱 결제는 로그인 기반으로 구동되는 서비스입니다. 앱 최초 시작 시 구매 라이브러리의 API 호출하기 전에 로그인을 유도합니다. 구매 라이브러리 요청 시 토큰 만료나 다른 여러 가지 사항을 미연에 방지할 수 있습니다.

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

### 구매 라이브러리 초기화

`PurchaseClientManager` 인스턴스 초기화를 요청합니다. 이때 필요한 값은 public license key입니다.\
이 라이선스 키는 원스토어 개발자 센터에 앱 등록을 완료하면 발급받을 수 있습니다.

<figure><img src="https://4053804225-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvkeBBQAZkfKgt5OwYUGz%2Fuploads%2FGObQR8HoAsqSHCiFEssv%2Fimage.png?alt=media&amp;token=4f12b345-be5e-4fc5-93ed-0ee5649f4b74" alt=""><figcaption></figcaption></figure>

```dart
import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';

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

{% hint style="info" %}
public license key는 SDK 내에서 구매 응답에 대한 위변조 체크에 사용됩니다.
{% endhint %}

### 구매 데이터 업데이트 및 오류 응답 청취하기

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

}
```

### 상품 상세 정보 조회하기

`PurchaseClientManager.queryProductDetails` API를 통해 원스토어 개발자 센터에 등록된 인앱 상품의 상세 정보를 조회할 수 있습니다.

상품 상세 정보는 [`ProductDetail`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/productdetail) 객체를 담은 리스트로 전달됩니다.

<table><thead><tr><th width="153">Parameter</th><th width="167">Type</th><th>Description</th></tr></thead><tbody><tr><td>productIds</td><td>List&#x3C;String></td><td>상품 아이디 리스트</td></tr><tr><td>productType</td><td><a href="https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype"><code>ProductType</code></a></td><td>상품 타입</td></tr></tbody></table>

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

요청하는 상품 리스트가 적다면 위의 예제처럼 상품 타입에 따라 개별 요청이 아닌 [`ProductType.all`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype#id-a-purchaseclient.producttype-all) 타입으로 요청할 수 있습니다.

```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" %}
등록된 인앱상품의 개수가 많은 경우 응답 지연이 발생할 수 있습니다. \
이런 경우 안정성이나 속도 측면에서 400개 단위로 호출 하시는 것을 권장 드립니다.&#x20;
{% endhint %}

{% hint style="warning" %}
[`ProductType.all`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype#id-a-purchaseclient.producttype-all) 타입은 상품 상세 조회하기에서만 사용할 수 있는 옵션입니다.\
다른 API에서는 사용할 수 없습니다.
{% endhint %}

### 구매 요청하기

`PurchaseClientManager.launchPurchaseFlow()` API를 사용하여 구매 요청합니다.&#x20;

<table><thead><tr><th width="188">Parameter</th><th width="167">Type</th><th>Description</th></tr></thead><tbody><tr><td>productDetail</td><td><a href="https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/productdetail"><code>ProductDetail</code></a></td><td><a href="#undefined-9">상품 상세 정보 조회하기</a> API를 통해 얻는 객체</td></tr><tr><td>quantity</td><td>Int</td><td>수량 (default: 1) <br>복수 구매 시 max: 10</td></tr><tr><td>developerPayload</td><td>String</td><td><p>개발사 입력 데이터로 필요한 데이터를 구매 요청 때같이 전송하면 구매 결과(<a href="https://onestore-dev.gitbook.io/dev/v/eng/tools/tools/v21/references/en-classes/en-purchasedata#id-en-purchasedata-getdeveloperpayload"><code>PurchaseData.developerPayload</code></a> )에도 포함되어 전송됨</p><p>제약 사항 : max 200byte</p></td></tr></tbody></table>

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

### 정기 결제 업그레이드 또는 다운그레이드

정기 결제는 취소될 때까지 자동으로 갱신됩니다. 정기 결제는 다음 상태를 가질 수 있습니다.

* **활성**: 사용자가 콘텐츠 사용에 문제가 없는 양호한 상태이며 정기 결제에 접근할 수 있습니다.
* **일시 중지 예약**: 사용자가 정기 결제를 이용 중 일시 중지를 하고 싶을 때 선택할 수 있습니다.
  * 주간 정기 결제: 1\~3주 단위로 일시 중지할 수 있습니다.
  * 월간 정기 결제: 1\~3개월 단위로 일시 중지할 수 있습니다.
  * 연간 정기 결제: 일시 중지를 지원하지 않습니다.
* **해지 예약**: 사용자가 정기 결제를 이용 중이지만 취소하고 싶을 때 선택할 수 있습니다. 다음 결제일에 결제가 되지 않습니다.
* **유예, 보류**: 사용자에게 결제 문제가 발생하면 다음 결제일에 결제가 되지 않습니다. 취소 예약을 할 수 없으며 즉시 "구독 해지"를 할 수 있습니다.

정기 결제를 업데이트하기 위해서는 필수로 `비례 배분 모드`를 적용해야 합니다.\
아래는 `비례 배분 모드`별 설명입니다.

<table><thead><tr><th width="339">Mode</th><th width="78">Value</th><th>Description</th></tr></thead><tbody><tr><td>IMMEDIATE_WITH_TIME_PRORATION</td><td>1</td><td>정기 결제의 교체가 즉시 이루어지며, 남은 시간은 가격 차이를 기반으로 조정되어 입금되거나 청구됩니다. (이것은 기본 동작입니다.)</td></tr><tr><td>IMMEDIATE_AND_CHARGE_PRORATED_PRICE</td><td>2</td><td>정기 결제의 교체가 즉시 이루어지며, 청구 주기는 동일하게 유지됩니다. 나머지 기간에 대한 가격이 청구됩니다. (이 옵션은 업그레이드 에서만 사용할 수 있습니다.)</td></tr><tr><td>IMMEDIATE_WITHOUT_PRORATION</td><td>3</td><td>정기 결제의 교체가 즉시 이루어지며, 다음 결제일에 새로운 가격이 청구됩니다. 청구 주기는 동일하게 적용됩니다.</td></tr><tr><td>DEFERRED</td><td>4</td><td>기존 요금제가 만료되면 교체가 적용되며 새 요금이 동시에 청구됩니다.</td></tr></tbody></table>

`PurchaseClientManager.launchUpdateSubscription()` API를 통해 요청할 수 있습니다.

정기결제는 [구매 요청하기](#undefined-10)와 동일한 API를 사용하여 사용자에게 업그레이드 또는 다운그레이드를 제공할 수 있습니다.\
다만, 정기 결제의 업그레이드 다운그레이드를 적용하기 위해선 기존 정기 결제 구매 토큰과 비례 배분 모드 값이 필수로 필요합니다.

<table><thead><tr><th width="188">Parameter</th><th width="167">Type</th><th>Description</th></tr></thead><tbody><tr><td>productDetail</td><td><a href="https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/productdetail"><code>ProductDetail</code></a></td><td><a href="#undefined-9">상품 상세 정보 조회하기</a> API를 통해 얻는 객체</td></tr><tr><td>oldPurchaseData</td><td><a href="https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata"><code>PurchaseData</code></a></td><td><a href="#undefined-13">구매 내역 조회하기</a> API를 통해 얻는 객체</td></tr><tr><td>prorationMode</td><td><a href="https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseflowparams.prorationmode"><code>ProrationMode</code></a></td><td>비례 배분 모드</td></tr></tbody></table>

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

### 구매 후 처리

* `PurchaseClientManager.launchPurchaseFlow()`
* `PurchaseClientManager.launchUpdateSubscription()`

API를 사용하여 구매가 성공적으로 이루어졌다면 [구매 데이터 업데이트 및 오류 응답 청취하기](#undefined-8)서 등록한 `_listenToPurchasesUpdated()` 를 통해 응답을 받을 수 있습니다.

성공적으로 구매 완료되어 응답을 받았다면, 사용자는 소비(Consume) 또는 확인(Acknowledge) 작업을 하는 것이 매우 중요합니다.

{% hint style="warning" %}
3일 이내에 구매를 확인(*acknowledge*) 또는 소비(*consume*)를 하지 않으면 사용자에게 상품이 지급되지 않았다고 판단되어 자동으로 환불됩니다.
{% endhint %}

<pre class="language-dart"><code class="lang-dart"><strong>import 'package:flutter_onestore_inapp/flutter_onestore_inapp.dart';
</strong>
class MyPurchaseManager {
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;
  
  final List&#x3C;ProductDetail> _products = [];
  
  // 상품 상세 정보에서 ProductType.inapp인 것만 필터링 된 데이터
  List&#x3C;ProductDetail> get consumableProducts => _products
      .where((element) => element.productType == ProductType.inapp)
      .toList();

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

  void _listenToPurchasesUpdated(List&#x3C;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] 호출하여 확인합니다.
        }
<strong>      }
</strong>    }
  }
  
}
</code></pre>

#### 소비하기 (Consume)

`PurchaseClientManager.consumePurchase()` API를 사용하여 관리형 상품([`ProductType.inapp`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype#id-a-purchaseclient.producttype-inapp))을 소비합니다.

{% hint style="info" %}
관리형 상품([`ProductType.inapp`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype#id-a-purchaseclient.producttype-inapp))은 소비를 하지 않으면 재 구매가 불가합니다.
{% endhint %}

관리형 상품([`ProductType.inapp`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype#id-a-purchaseclient.producttype-inapp))은 API 사용에 따라 두 가지로 사용될 수 있습니다.

* 소모성 상품: 구매 요청 → 응답 → 아이템 지급 → `consumePurchase`
* 기간제 상품: 구매 요청 → 응답 → 아이템 지급 → `acknowledgePurchase` → 일정 기간이 지난 후 → `consumePurchase`

<table><thead><tr><th width="153">Parameter</th><th width="159">Type</th><th>Description</th></tr></thead><tbody><tr><td>purchaseData</td><td><a href="https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata"><code>PurchaseData</code></a></td><td><a href="#undefined-12">구매 완료</a> 또는 <a href="#undefined-13">구매 내역 조회하기</a>를 통해 얻는 PurchaseData</td></tr></tbody></table>

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

`PurchaseClientManager.acknowledgePurchase()` API를 사용하여 관리형 상품([`ProductType.inapp`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype#id-a-purchaseclient.producttype-inapp)) 또는 구독형 상품([`ProductType.subs`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype#id-a-purchaseclient.producttype-subs))의 확인을 요청합니다.

{% hint style="info" %}
구독형 상품([`ProductType.subs`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype#id-a-purchaseclient.producttype-subs))는 소비(Consume)는 할 수 없고 확인(Acknowledge)만 가능합니다.
{% endhint %}

<table><thead><tr><th width="153">Parameter</th><th width="159">Type</th><th>Description</th></tr></thead><tbody><tr><td>purchaseData</td><td><a href="https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata"><code>PurchaseData</code></a></td><td><a href="#undefined-12">구매 완료</a> 또는 <a href="#undefined-13">구매 내역 조회하기</a>를 통해 얻는 PurchaseData</td></tr></tbody></table>

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

확인 작업이 완료되었다면 구독형 상품([`ProductType.subs`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype#id-a-purchaseclient.producttype-subs))의 경우 [구매 내역 조회하기](#undefined-13)를 통해 [`PurchaseData`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata) 업데이트해야 [`PurchaseData.isAcknowledged`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata#id-c-purchasedata-isacknowledged) 값이 변경된 것을 확인할 수 있습니다.

### 구매 내역 조회하기

`PurchaseClientManager.queryPurchases()` API를 사용하여 소비되지 않은 구매 내역을 요청합니다.

구매 내역 조회하기의 응답 데이터는 [구매 데이터 업데이트 및 오류 응답 청취하기](#undefined-8) 통해 받는 데이터와 동일합니다.

구매 완료 후 데이터를 처리하는 것만으로는 앱이 모든 구매를 처리하는 것을 보장하기에 충분하지 않습니다. 앱에서 사용자가 구매한 모든 항목을 인식하지 못할 수 있습니다.\
앱에서 구매 추적을 놓치거나 구매를 인식하지 못할 수 있는 몇 가지 시나리오는 다음과 같습니다.

* **구매 중 네트워크 문제**: 사용자가 구매를 성공적으로 완료하고 원스토어에서 확인을 받았지만 기기가 구매 알림을 받기 전에 네트워크 연결이 끊어졌을 경우
* **여러 기기**: 사용자는 한 기기에서 항목을 구입한 후 기기를 전환할 때 이 항목이 표시되기를 기대합니다.

이러한 상황에 대처하려면 구매 내역 조회하기 API를 상황에 맞게 호출해야 합니다.&#x20;

* 어플리케이션 처음 구동시
* 어플리케이션이 백그라운드에서 포그라운드로 재 진입했을 경우
* 상점 진입 시

어플리케이션의 상황에 맞게 사용해 주세요.

<table><thead><tr><th width="153">Parameter</th><th width="159">Type</th><th>Description</th></tr></thead><tbody><tr><td>productType</td><td><a href="https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.producttype"><code>ProductType</code></a></td><td><a href="#undefined-12">구매 완료</a> 또는 <a href="#undefined-13">구매 내역 조회하기</a>를 통해 얻는 PurchaseData</td></tr></tbody></table>

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

### 정기 결제 관리 화면 열기

`PurchaseClientManager.launchManageSubscription()` API를 사용하여 구독 상품의 상세 페이지로 이동합니다.

구독 상품의 설정 변경은 유저의 몫으로 관리 메뉴에서 할 수 있는 것들은 아래와 같습니다.

* 결제 수단 변경
* 구독 상태 변경 (해지 예약, 취소)
* 기 구독한 상품의 가격 변경 동의

{% hint style="info" %}
[`PurchaseData`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata) 값이 null일 경우 특정 구독 상품의 상세 페이지가 아닌 정기 결제 리스트 화면으로 이동합니다.
{% endhint %}

<table><thead><tr><th width="153">Parameter</th><th width="174">Type</th><th>Description</th></tr></thead><tbody><tr><td>purchaseData</td><td><a href="https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/classes/purchasedata"><code>PurchaseData</code></a></td><td><a href="#undefined-12">구매 완료</a> 또는 <a href="#undefined-13">구매 내역 조회하기</a>를 통해 얻는 PurchaseData</td></tr></tbody></table>

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

## 원스토어 서비스 설치하기

`PurchaseClientManager.launchUpdateOrInstall()` API를 호출하여 '원스토어 서비스 앱'을 설치할 수 있습니다.

`PurchaseClientManager` API를 사용하는 중에 에러 응답 중 [`RESULT_NEED_UPDATE`](https://onestore-dev.gitbook.io/dev/tools/tools/v21/references/annotations/purchaseclient.responsecode#id-a-purchaseclient.responsecode-result_need_update) 코드가 발생했을 경우가 있습니다. 이는 원스토어 서비스 앱이 설치되지 않았거나 In-app SDK에서 요구하는 버전보다 낮을 경우 발생합니다.

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