본문 바로가기
728x90
반응형

저번 이야기에서는 인앱 결제를 다루어보았다면, 저의 2번째 업무이었던 정기 결제에 대해 리뷰해보록 하겠습니다. 

상품 등록은 이전 포스트에 있는 등록 방식에서 정기 결제 상품을 등록하면 됩니다. ~ 

정기 결제 상품 같은 경우 상품들에 대해 인앱 결제 상품과 달리 그룹 id를 통해 관리하니 그 부분만 알고 계시면 금방하실 수 있을 것입니다. 

인앱 결제 플로우

 

만약 인앱 상품 결제가 궁금하시다면 이전 포스트를 보고 오세요~  

2024.02.18 - [코딩/Flutter] - [flutter] 인앱 결제 iOS & Android

 

[flutter] 인앱 결제 iOS & Android

새로 들어간 회사에서 첫 업무는 인앱 결제 시스템을 어플리케이션 안에 도입하는 것이었다. 상품 등록을 하는 방법은 아래 링크를 따라하면 등록할 수 있고, 이 글에서는 등록된 상품 id들을 바

quddkflty.tistory.com

 

정기 결제를 구현 하기 위해서는 아래와 같은 기능이 필요합니다. 

  • 정기 결제 상품들 읽어오기
  • 결제 기능
  • 결제 후 서버에 구매 요청 기능
  • 완료시 로직

아래 코드를 통해 우린 확인할 수 있을 것이다. 

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

class SubscribePage extends StatefulWidget {
  const SubscribePage({Key? key}) : super(key: key);

  @override
  State<SubscribePage> createState() => _SubscribePageState();
}

class _SubscribePageState extends State<SubscribePage> {
  final InAppPurchase _iap = InAppPurchase.instance;
  List<ProductDetails> _products = [];
  String productId = "";

  @override
  void initState() {
    super.initState();
    _loadProducts(); // 상품 정보 출력
    _listenToPurchaseUpdated(); // 구매 업데이트 감지
  }

  // 상품 정보를 불러오는 함수
  void _loadProducts() async {
    const Set<String> kIds = {정기 상품 결제의 그룹 id};
    final ProductDetailsResponse response =
        await _iap.queryProductDetails(kIds);
    if (response.notFoundIDs.isEmpty) {
      setState(() {
        _products = response.productDetails;
      });
    }
  }

  // 상품 구매 함수
  void _buyProduct(ProductDetails product, String productID) {
    productId = productID;
    print("선택한 id : " + productId);
    final purchaseParam = PurchaseParam(productDetails: product);
    _iap.buyNonConsumable(purchaseParam: purchaseParam);
  }

  // 구매 업데이트 감지 함수
  void _listenToPurchaseUpdated() {
    final purchaseUpdated = _iap.purchaseStream;
    purchaseUpdated.listen((purchaseDetailsList) {
      purchaseDetailsList.forEach((purchaseDetails) async {
        if (purchaseDetails.status == PurchaseStatus.pending) {
          // 구매 진행중
          print("구독 구매 진행중");
        } else {
          if (purchaseDetails.status == PurchaseStatus.error) {
            // 구매 오류 발생시
            print("구독 구매 오류 발생");
          } else if (purchaseDetails.status == PurchaseStatus.purchased) {
            // 구매 성공
            if (purchaseDetails.pendingCompletePurchase) {
              bool isVerified = await _verifyPurchase(
                  productId, purchaseDetails);
              await _iap.completePurchase(purchaseDetails);
              print("구독 구매 완료");
            }
          }
        }
      });
    });
  }

  // 서버에 구매 검증 요청
  Future<bool> _verifyPurchase(
      String productId, PurchaseDetails purchaseDetails) async {
    // 플랫폼 확인
    String platform =
        Theme.of(context).platform == TargetPlatform.iOS ? 'apple' : 'google';

    // POST 데이터
    Map<String, dynamic> purchaseData = {
      'platform': platform,
    };

    // 플랫폼에 따라 필요한 데이터를 추가
    if (platform == 'apple') {
      purchaseData['encoded_receipt_data'] =
          purchaseDetails.verificationData.localVerificationData;
    } else if (platform == 'google') {
      purchaseData['product_id'] = productId;
      purchaseData['purchase_token'] = purchaseDetails.purchaseID;
    }

    // Django Post
    try {
      // 서버에 요청 로직을 추가하시면 됩니다. 
      // 구매 완료 Alert Dialog
      showPurchaseCompleteDialog(context, Intl.message('success'));
      return true;
    } catch (e) {
      showPurchaseCompleteDialog(context, Intl.message('fail'));
      return false;
    }
  }

  // 구매 완료 알림창
  void showPurchaseCompleteDialog(BuildContext context, String text) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text("${Intl.message('pay')}$text"),
          actions: <Widget>[
            ElevatedButton(
              child: Text(Intl.message('ok')),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('구독 결제 페이지'),
      ),
      body: _products.isEmpty
          ? const Center(child: CircularProgressIndicator())
          : ListView.builder(
              itemCount: _products.length,
              itemBuilder: (_, index) => ListTile(
                title: Text(_products[index].title),
                subtitle: Text(_products[index].price),
                trailing: ElevatedButton(
                  child: const Text('구매하기'),
                  onPressed: () =>
                      _buyProduct(_products[index], _products[index].id),
                ),
              ),
            ),
    );
  }
}

 

 

728x90
반응형