본문 바로가기
Dart/Flutter

[Flutter] 플러터 인앱(in_app_purchase) 결제 상품 구매 구현하기

by 검은냥냥이 2023. 3. 19.

플러터(Flutter)를 사용하여 앱 내에서 상품을 구매하고 소비하는 인앱 결제 기능을 구현하는 방법을 소개합니다. 인앱 결제는 사용자가 앱에서 콘텐츠, 기능, 서비스 등을 구매할 수 있도록 하는 강력한 기능입니다. 이 글에서는 플러터에서 인앱 결제를 구현하는 방법을 간단하게 설명합니다.

 

먼저, 진행하기 전에 가장 궁금하신 게 결제자를 어떻게 판별하고 관리하는지 궁금하실 거예요.

구글에서 전체적으로 관리를 제공해주지 않기 때문에 별도로 백엔드를 구현하셔서 주문 고객 등 관리를 별도로 해주셔야 합니다.

 

필요한 패키지 설치

플러터 인앱 결제를 구현하기 위해 필요한 패키지를 설치하려면, pubspec.yaml 파일에 아래와 같이 in_app_purchase 패키지를 추가하세요.

 

in_app_purchase | Flutter Package

A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.

pub.dev

dependencies:
  flutter:
    sdk: flutter
    
	in_app_purchase: ^3.1.5

 

권한 설정

android\app\src\main\AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wudy.app">
	
    <uses-permission android:name="com.android.vending.BILLING" />

 

android\build.gradle

classpath 'com.google.gms:google-services:4.3.14' 플러그인 추가

 

dependencies {
	classpath 'com.android.tools.build:gradle:7.2.0'
	classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
	classpath 'com.google.gms:google-services:4.3.14'
}

 

android\app\build.gradle

implementation 'com.android.billingclient:billing:5.1.0', implementation "com.android.billingclient:billing-ktx:5.1.0" 2개 의존성 추가

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'com.google.gms.google-services'

...

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.billingclient:billing:5.1.0'
    implementation "com.android.billingclient:billing-ktx:5.1.0"
}

 

 

인앱 상품 추가

Google Play Console에서 인앱 상품을 추가하기 위해 판매자 계정 설정을 진행합니다.

 

판매자 계정 설정

Google Play Console 판매자 계정 설정

인앱 상품 클릭 시 판매자 계정설정이 나옵니다. 클릭하여 이동해 주세요.

 

Google Play Console 결제 프로필 만들기

 

결제 프로필을 생성해 줍니다.

 

Google Play Console 결제 프로필 생성

항목을 체크하면 아래와 같이 진행됩니다.

 

Google Play Console 결제 프로필 정보 입력 제출

필요한 정보를 입력하여 제출을 통해 결제 프로필을 생성해 주세요.

 

Google Play Console 결제 프로필 생성 완료

결제 프로필 화면에서는 추가로 필요한 정보를 입력해 주세요.

 

Google Play Console 인앱 상품 화면

다시 인앱 상품 페이지로 돌아오면, 앱에 아직 인앱 상품이 없다고 나옵니다. 개발 중인 앱을 업로드해 주세요.

 

Google Play Console 인앱 상품 트랙 만들기

트랙 내용이 나오면 트랙 관리로 들어가서 비공개 형태로 앱을 업로드해줘야 합니다.

 

Google Play Console 인앱 상품 비공개 테스트 생성

새 버전 만들기를 통하여 기존 앱 등록과 동일하게 진행해 주시면 됩니다. 이동하기 전에 테스터 선택을 하여 테스트를 진행할 유저들을 추가해 주세요. 본인을 추가해도 됩니다. 비공개이기 때문에 테스터에 추가된 인원만 가능합니다.

 

Google Play Console 인앱 상품 비공개 테스트 앱 업로드

앱을 전체 만들어서 올릴 필요는 없습니다. 진행 중이던 앱을 업로드를 일단 강제로 진행하시고 업로드 시도가 발생되면, 인앱 상품에 상품을 추가할 수 있습니다.

 

Google Play Console 인앱 상품 만들기

상품 만들기를 클릭하여 상품을 추가해 주세요.

 

Google Play Console 인앱 상품 만들기

각 정보를 다 입력해 주시고, 가격 쪽에는 가격 테이블(템플릿)을 미리 만들어 놓을 수도 있고, 상품마다 개별로 가격 설정도 할 수 있습니다. 설정 후 저장을 눌러 상품을 추가해 주세요.

 

Google Play Console 인앱 상품 만들기

이제 인앱 상품이 생성되었습니다. 제품 ID를 통해서 실제 앱에서 데이터를 가져와 인앱 결제 할 수 있게 해 보겠습니다.

 

결제 구현

in_app_purchase_service.dart

productID 변수에 제품 ID를 넣어주면, 데이터를 가져와서 결제를 가능하게 도와줍니다.

// ignore_for_file: unnecessary_cast

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:in_app_purchase/in_app_purchase.dart';

/// 인앱 결제 서비스
class InAppPurchaseService extends GetxService {
  static InAppPurchaseService get to => Get.find();

  // Instance ▼ ============================================

  /// 구매를 위한 인스턴스를 가져옵니다.
  final Rx<InAppPurchase> iap = InAppPurchase.instance.obs;

  // 구매 세부 정보에 대한 업데이트 스트림을 수신하는 구독
  late Rx<StreamSubscription?> subscription = (null as StreamSubscription?).obs;

  // Data ▼ ================================================

  /// 구매를 위한 인스턴스를 가져옵니다.
  RxString productID = '여기에 제품 ID'.obs;
  // Playstore 또는 앱 스토어에서 쿼리한 제품 목록 유지
  RxList<ProductDetails> products = <ProductDetails>[].obs;
  // 과거 구매 사용자 목록
  RxList<PurchaseDetails> purchases = <PurchaseDetails>[].obs;

  // Function ▼ ============================================

  /// 상품 목록 조회 방법
  ///
  /// Future<void>
  Future<void> fetchUserProducts() async {
    // 구매를 위한 인스턴스를 가져옵니다.
    ProductDetailsResponse response = await iap.value.queryProductDetails({
      productID.value,
    });

    // 제품 목록 데이터 추가
    products.assignAll(response.productDetails);
  }

  /// 과거 구매 사용자를 검색하는 방법
  ///
  /// @return Future<void>
  Future<void> fetchPastPurchases() async {
    /// 모든 이전 구매를 복원합니다.
    /// `applicationUserName`은 초기 `PurchaseParam`에서 전송된 내용과 일치해야 합니다.
    /// 초기 `PurchaseParam`에 `applicationUserName`이 지정되지 않은 경우 null을 사용합니다.
    /// 복원된 구매는 [PurchaseStatus.restored] 상태로 [purchaseStream]을 통해 전달됩니다.
    /// 이러한 구매를 수신하고, 영수증을 확인하고, 콘텐츠를 전달하고, 각 구매에 대해 [finishPurchase] 메서드를 호출하여 구매 완료를 표시해야 합니다.
    /// 이것은 소비된 제품을 반환하지 않습니다.
    /// `사용하지 않는 소모품을 복원하려면 자체 서버에서 사용자에 대한 소모품 정보를 유지해야 합니다.`
    // await iap.value.restorePurchases();
  }

  /// 상품을 이미 구매했는지 여부를 확인하는 방법입니다.
  ///
  /// @return void
  void verifyPurchases() {
    try {
      PurchaseDetails purchase = purchases.firstWhere(
        (purchase) => purchase.productID == productID.value,
      );
      if (purchase.status == PurchaseStatus.purchased) {
        // 구매 완료
      }
    } catch (e) {
      // 구매 미완료
    }
  }

  /// 제품 구매 방법
  ///
  /// [prod] 구매할 제품
  ///
  /// @return 구매 성공 여부
  @required
  void purchaseProduct(ProductDetails prod) {
    final PurchaseParam purchaseParam = PurchaseParam(productDetails: prod);
    iap.value.buyConsumable(purchaseParam: purchaseParam, autoConsume: false);
  }

  /// 구매 세부 정보에 대한 업데이트 스트림을 수신하는 구독
  ///
  /// @return Future<void>
  Future<void> initialize() async {
    // 인앱 구매가 가능한지 체크
    if (await iap.value.isAvailable()) {
      // 구매 가능한 상품 목록 조회
      await fetchUserProducts();
      // 과거 구매 사용자 목록 조회
      // await fetchPastPurchases();
      // 상품을 이미 구매했는지 체크
      verifyPurchases();

      // 구매 세부 정보에 대한 업데이트 스트림을 수신하는 구독
      subscription.value = iap.value.purchaseStream.listen((data) {
        // 구매 세부 정보를 업데이트
        purchases.addAll(data);
        // 상품을 이미 구매했는지 체크
        verifyPurchases();
      });
    }
  }

  @override
  Future<void> onInit() async {
    await initialize();

    super.onInit();
  }

  @override
  void onReady() {
    super.onReady();
  }

  @override
  void onClose() {
    // 구독 해제
    subscription.value?.cancel();

    super.onClose();
  }
}

 

premium.dart

class PremiumView extends GetView<MyPageController> {
  const PremiumView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Column(
          children: [
            Center(
              child: ElevatedButton(
                onPressed: () {
                  // 버튼 클릭 시 수행할 작업
                  InAppPurchaseService.to.purchaseProduct(
                    InAppPurchaseService.to.products[0],
                  );
                },
                child: const Text('프리미엄 회원으로 업그레이드하기'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

위와 같이 적용해 주면 아래의 영상처럼 작동하게 됩니다.

 

Flutter Google Play 인앱 결제 예제
728x90
사업자 정보 표시
레플라 | 홍대기 | 경기도 부천시 부일로 519 화신오피스텔 1404호 | 사업자 등록번호 : 726-04-01977 | TEL : 070-8800-6071 | Mail : support@reafla.co.kr | 통신판매신고번호 : 호 | 사이버몰의 이용약관 바로가기