14. Flutter 에서 원스토어 인앱결제 (SDK V21) 사용하기

개요

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

플러그인 설치

pubspec.yaml 파일에 플러그인 추가하는 법

dependencids:
  ..
  flutter_onestore_inapp: ^0.1.0
  ..

'pub get' 을 클릭하여 패키지를 다운로드 하거나 명령줄에서 'flutter pub get' 을 실행합니다.

build.gradle 종속성 추가하기

프로젝트의 build.gradle 에 maven 주소 추가하기

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

앱의 build.gradle 에 configuration 종속성 추가하기

dependencies {
	..
    implementation "com.onestorecorp.sdk:sdk-configuration-kr:1.0.0"
    ..
}

AndroidManifest.xml 에 <queries> 추가하기

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

로그인 요청하기

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

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

  final OneStoreAuthClient _authClient = OneStoreAuthClient();

  Future<void> launchSignInFlow() async {
    await _authClient.launchSignInFlow().then((signInResult) {
      if (signInResult.isSuccess()) {
        // success
      }
    });
  }

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

PurchasesUpdatedStream 응답 구현하기

이 객체는 앱이 시작될 때 직접 구매 업데이트를 들어야하므로 'lazy: false' 를 사용하여 이 객체에 대한 'Lazy loading' 을 비활성화 합니다.

ChangeNotifierProvider<PurchaseViewModel>(
    create: (context) => PurchaseViewModel(),
    lazy: false,
)

원스토어 개발자 센터에 앱을 등록 하면 라이선스 키가 생성됩니다.

PurchaseClientManager.initialize() 호출 통해 최초 한 번 객체 셋업을 합니다.

'purchasesUpdatedStream' 스트림에 'listen'하여 구매 완료 응답을 받을 준비를 합니다. _listenToPurchasesUpdated 로는 구매 성공으로 구매 데이터를 응답 받습니다. _updateStreamOnError 로는 구매 실패에 대한 응답을 전달 받습니다.

앱 종료시에 'PurchaseClientManager.dispose()' 를 호출 하여 메모리 해지 및 연결을 해지하고 청취하고 있던 _purchaseUpdatedStream 도 dispose()하여 연결을 끊어야 합니다.

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

class PurchaseViewModel extends ChangeNotifier {

  final OneStoreAuthClient _authClient = OneStoreAuthClient();
  final PurchaseClientManager _clientManager = PurchaseClientManager.instance;

  late StreamSubscription<List<PurchaseData>> _purchaseUpdatedStream;

  PurchaseViewModel() {
    _clientManager.initialize('input your license key');
    
    _purchaseUpdatedStream = _clientManager.purchasesUpdatedStream.listen(
      _listenToPurchasesUpdated,
      onError: _updateStreamOnError,
      onDone: () => _purchaseDataStream.cancel(),
    );
  }

  @override
  void dispose() {
    _purchaseDataStream.cancel();
    _clientManager.dispose();
    super.dispose();
  }

  void _listenToPurchasesUpdated(List<PurchaseData> purchasesList) {
    // Handle purchases here
  }

  void _updateStreamOnError(dynamic error) {
    // Handle error here
  }

  Future<IapResult> launchPurchaseFlow(ProductDetail productDetail,
      int? quantity, String? developerPayload) async {
    return await _clientManager.launchPurchaseFlow(
        productDetail: productDetail,
        quantity: quantity,
        developerPayload: developerPayload
    );
  }

  Future<SignInResult> launchSignInFlow() async {
     return await _authClient.launchSignInFlow();
  }
}

상품 상세 정보 조회하기

'await _clientManager.queryProductDetails(List<String>, ProductType)' 를 사용하여 원스토어 개발자센터에 등록된 인앱 상품의 상세 정보를 요청합니다.

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

  static const consumableIds = ['p500', 'p510'];
  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);
    }
  
    /// WARNING! 요청할 데이터가 많을 경우 한 번에 요청하게 되면 응답 지연이 발생할 수 있습니다.
    ///          그러나 요청할 데이터가 적다면 한 번에 요청하는 것이 효율적입니다.
    // var allResponse = await _clientManager.queryProductDetails(
    //     productIds: (consumableIds + subscriptionIds),
    //     productType: ProductType.all);
    // 
    // if (allResponse.iapResult.isSuccess()) {
    //   final List<ProductDetail> result = allResponse.productDetailsList;
    //   _products.clear();
    //   _products.addAll(result);
    //   notifyListeners();
    // } else {
    //   _handleError('fetchProductDetails', allResponse.iapResult);
    // }
  }

구매 요청하기

'await _clientManager.launchPurchaseFlow(ProductDetail)' 사용하여 상품 구매를 요청합니다.

Request

Parameter

Type

Description

productDetail

ProductDetail

구매를 할 상품의 상세 정보

quantity

Int

복수 구매 시 사용

기본 값 : 1

developerPayload

String

개발사 입력 데이터로 필요한 데이터를 구매 요청 때 같이 전송하면 구매 결과(PurchaseData.developerPayload )에도 포함되어 전송됨

제약 사항 : max 200byte

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

  Future<IapResult> launchPurchaseFlow(ProductDetail productDetail,
      int? quantity, String? developerPayload) async {
    return await _clientManager.launchPurchaseFlow(
        productDetail: productDetail,
        quantity: quantity,
        developerPayload: developerPayload);
  }

구매 후 처리

구매가 성공적으로 이루졌다면 '앱에서 구매 데이터 업데이트 및 오류 응답 청취하기'에서 등록한 '_listenToPurchasesUpdated' 통해 응답을 받을 수 있습니다. 물론 구매 실패에 대한 응답도 '_updateStreamOnError' 통해 받을 수 있습니다.

스토어에서 구매 완료 응답을 받았다면, 소비(consume) 또는 확인(acknowledge) 작업을 하는 것이 매우 중요합니다.

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

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

  List<ProductDetail> get consumableProducts;
  List<ProductDetail> get subscriptionProducts

  Future<void> _listenToPurchasesUpdated(List<PurchaseData> purchasesList) async {
    for (var element in purchasesList) {
      if (consumableProducts.any((p) => p.productId == element.productId)) {
        /// [ProductType.inapp] 상품은 [consumePurchase] 호출을 하여 소비 해야합니다.
        await consumePurchase(element);
      } else if (subscriptionProducts.any((p) => p.productId == element.productId)) {
        /// [ProductType.subs] 상품은 [acknowledgePurchase] 호출을 하여 확인 해야합니다.
        await acknowledgePurchase(element);
      }
    }
    notifyListeners();
  }

구매 소비하기 (consume)

'_clientManager.consumePurchase(PurchaseData)' 사용하여 관리형 상품의 소비를 요청합니다.

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

  Future<void> consumePurchase(PurchaseData purchaseData) async {
    await _clientManager
        .consumePurchase(purchaseData: purchaseData)
        .then((iapResult) {
      // iapResult 를 통해 해당 API의 성공 여부를 판단할 수 있습니다.
      if (iapResult.isSuccess()) {
        fetchPurchases([ProductType.inapp]);
      }
    });
  }

구매 인증하기 (acknowledge)

'_clientManager.acknowledgePurchase(PurchaseData)' 사용하여 관리형 상품 또는 구독 상품의 확인을 요청합니다.

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

  Future<void> acknowledgePurchase(PurchaseData purchaseData) async {
    await _clientManager
        .acknowledgePurchase(purchaseData: purchaseData)
        .then((iapResult) {
      // iapResult 를 통해 해당 API의 성공 여부를 판단할 수 있습니다.
      if (iapResult.isSuccess()) {
        fetchPurchases([ProductType.subs]);
      }
    });
  }

구매 내역 조회하기

'_clientManager.queryPurchases(ProductType)' 사용하여 소비되지 않은 구매 내역을 요청합니다.

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

  Future<void> fetchPurchases([List<ProductType>? types]) async {
    types ??= <ProductType>[ProductType.inapp, ProductType.subs];
    for (var element in types) {
      await _clientManager
          .queryPurchases(productType: element)
          .then((response) {
        if (response.iapResult.isSuccess()) {
          if (element == ProductType.inapp) {
            for (var purchaseData in response.purchasesList) {
              consumePurchase(purchaseData);
            }
          } else if (element == ProductType.subs) {
            for (var purchaseData in response.purchasesList) {
              if (!purchaseData.isAcknowledged) {
                acknowledgePurchase(purchaseData);
              }
            }
          }
          notifyListeners();
        } else {
          _handleError('fetchPurchases($element)', response.iapResult);
        }
      });
    }
  }

업그레이드 또는 다운그레이드 하기

'_clientManager.launchUpdateSubscription(ProductDetail, PurchaseData, ProrationMode)' 를 사용하여 구매한 정기결제 상품을 새로운 상품으로 변경 합니다. Upgrade or Downgrade 는 구매 요청 흐름과 동일하기 때문에 '구매 요청하기'와 응답 처리하는 방식이 같습니다. '_listenToPurchasesUpdated'로 응답을 받을 수 있습니다.

Request

Parameter

Type

Description

productDetail

ProductDetail

변경할 구독 상품의 정보

oldPurchseData

PurchaseData

변경대상이 될 구독 상품의 구매 정보

prorationMode

ProrationMode

기존 구독 구매 정보를 변경 할 때 적용 가능한 모드 (자세한 정보는 기존 가이드 참고)

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

  Future<IapResult> launchUpdateSubscription(ProductDetail productDetail,
      PurchaseData oldPurchaseData, ProrationMode prorationMode) async {
    return await _clientManager.launchUpdateSubscription(
        productDetail: productDetail,
        oldPurchaseData: oldPurchaseData,
        prorationMode: prorationMode);
  }

정기 결제 관리 화면을 열기

'_clientManager.launchManageSubscription(PurchaseData?)' 사용하여 구매한 구독 상품의 상세 페이지로 이동할 수 있습니다.

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

  • 결제 수단 변경

  • 구독 상태 변경 (해지 예약, 취소)

  • 기 구독한 상품의 가격 변경 동의

PurchaseData를 null로 요청할 경우 특정 구독 상품의 상세페이지가 아닌 정기 결제 리스트 화면으로 이동합니다.

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

  Future<void> launchManageSubscription(PurchaseData? purchaseData) async {
    await _clientManager.launchManageSubscription(purchaseData);
  }

원스토어 서비스 설치하기

원스토어 서비스가 설치되지 않았거나 v21 SDK 에서 바라는 최소 버전이 아닐경우 RESULT_NEED_UPDATE 에러가 발생합니다. 해당 에러를 전달 받았을 경우 '_clientManager.launchUpdateOrInstall()' 사용하여 원스토어 서비스 설치를 요청합니다.

import 'package:flutter_onestore_inapp/onestore_in_app_wrappers.dart';

  Future<void> launchUpdateOfInstall() async {
    await _clientManager.launchUpdateOrInstall().then((iapResult) {
      if (iapResult.isSuccess()) {
        fetchPurchases();
        fetchProductDetails();
      }
    });
  }

Last updated