본문 바로가기
SwiftUI로 1인 앱 개발 해보기

iOS앱 개발할 때 비소모품 인앱결제 구현해보기

by 유티끌 2025. 2. 2.

iOS에서 비소모품 항목에 대한 인앱결제를 구현했는데, 그 내용을 간략하게 정리해봅니다.

일단 전제조건으로는 개인사업자 혹은 법인이어야합니다. 1인개발자는 유료항목을 판매할 수 없습니다.

AppStore connect 등록 절차 및 세금신고 관련은 본 포스트에서는 생략합니다.


# 앱 내 구입 > 아이템 생성

인앱결제 항목을 추가하려면 무조건 신규 빌드에서 등록을 해야합니다. AppStore connect에서 등록하고자 하는 앱에 대해 신규빌드를 생성하시고, 좌측 메뉴에서 수익화 > 앱 내 구입 메뉴로 이동하세요.

요런 화면이 뜨는데 파란색 플러스 버튼을 눌러서 항목을 추가합니다.

앱 내 구매할 수 있는 항목은 하나씩 등록해주어야합니다.

여기서 유형, 식별정보, 제품ID를 입력해줍니다.

제품ID의 경우에는 코드상에서 StoreKit과 연동할때 필요한 항목입니다.

## 앱 내 구입 > 항목 메뉴들

등록하면 이런화면이 뜹니다. 가족 공유의 경우 켜기를 활성화 하면 Apple ID가 가족공유로 묶여져 있는 유저들 대상으로는 가족 구성원 중 한 명이 어떠한 아이템을 구매하면 다른 구성원들은 구매처리가 되도록하는 그런 설정입니다.

상태는 스크린샷에서는 "제출 준비 완료" 라고 뜨지만 이건 제가 이미 다 등록을 해놓아서 저렇게 뜨는겁니다.

사용 가능 여부는 판매가능한 국가들을 선택하는 거고, 가격은 말그대로 앱의 아이템에 대한 가격입니다.

기준 국가의 판매금액을 기준으로 해외 앱스토어 등록 시 환율 적용된 금액으로 자동 변환됩니다.

세금 관련은 생략하고, 현지화 부분은 실제로 앱 내에서 인앱결제 품목이 어떻게 노출될 것인지에 대한 메뉴입니다.

이 부분은 별도로 코드상에서 Localize할 수가 없어서 이 메뉴에서 하나하나 입력해주셔야합니다.

심사 정보란은 반드시 입력해주어야하고, 이미지는 말그대로 선택사항입니다.


# XCode Project Capabilities 설정

다음은 현재 개발중인 프로젝트와 AppStore Connect 상의 인앱결제 아이템들의 정보를 연동시켜주기 위한 설정입니다.

XCode의 프로젝트 파일에서 Signing & Capabilities > + Capability 를 누르시고 In-App Purchase 를 검색하고 등록해줍니다. 이렇게 하면 프로젝트에 등록하신 bundle id 기준으로 AppStore connect에 등록해놓은 정보와 연동하게합니다.


# 간단한 코드 구현

이제부터는 Storekit을 import 하시고 구현을 시작하시면 됩니다.

## 구매

일단 구매상품의 id를 준비합니다. 이것은 외부 서버를 통해 읽어오셔도 되고, 코드 상에 직접 하드코딩하셔도 됩니다. 인터넷을 찾아보니 info.plist에서 관리한다는 분들도 봤습니다.

private let productIdentifiers = [
        "01_remove_banner",           // 광고제거
        "02_remove_banner_and_donation",  // 광고제거 + 후원1
        "03_remove_banner_and_donation",  // 광고제거 + 후원2
        "04_remove_banner_and_donation"   // 광고제거 + 후원3
    ]

다음은 구매가능한 항목들을 불러옵니다.

private var products: [Product] = []

/// 구매 가능한 상품 목록을 로드합니다.
private func loadProducts() async {
    do {
        let loadedProducts = try await Product.products(for: productIdentifiers)
        // 가격 순으로 정렬
        products = loadedProducts.sorted { $0.price < $1.price }
    } catch {
        print("Failed to load products: \(error)")
    }
}

Storekit을 import해놓으셨다면 동작합니다. 아까 프로젝트 capability에 in-app purchase를 연동해놓아서, 알아서 불러와집니다. 

product값에는 유저가 사용할 수 있는 값들이 몇가지 있는데 price도 포함되어있어서, 정렬하기 좋습니다. 이 값에 대해서는 별도로 현지화하지않아도 알아서 적용됩니다.

다음은 구매입니다.

  /// 특정 상품의 구매를 시도합니다.
    /// - Parameter product: 구매하려는 Product 객체
    /// - Returns: 구매 성공 여부를 반환합니다.
    func purchase(_ product: Product) async throws -> Bool {
        // 구매 요청
        let result = try await product.purchase()
        
        switch result {
        case .success(let verificationResult):
            // 구매 검증
            switch verificationResult {
            case .verified(let transaction):
                // 구매 완료 처리
                await transaction.finish()
                return true
            case .unverified:
                throw PurchaseError.verificationFailed
            }
        case .userCancelled:
            throw PurchaseError.userCancelled
        case .pending:
            throw PurchaseError.pending
        @unknown default:
            throw PurchaseError.unknown
        }
    }

여기서 throw하는 것들은 제가 별도의 enum을 구현한 것들입니다. 생각보다 간단합니다.

다음은 구매복원입니다.

    /// 구매 이력을 복원합니다.
    func restorePurchases() async throws {
        // 앱스토어에서 이전 구매 내역을 확인
        var hasValidPurchase = false
        
        for await result in Transaction.all {
            switch result {
            case .verified(let transaction):
                if productIdentifiers.contains(transaction.productID) {
                    hasValidPurchase = true
                    break
                }
            case .unverified:
                continue
            }
        }
        
        // 구매 상태 업데이트
        await updatePurchaseStatus(hasValidPurchase)
        
        if !hasValidPurchase {
            throw PurchaseError.noPurchaseToRestore
        }
    }

저의 경우는 Transaction 을 이용해서 모든 거래이력을 불러와서 제가 가진 인앱결제 아이템들 ID와 비교하는 방식으로 했네요.


저도 Swift 고수가 아니라서.. 비소모성 아이템에 대한 인앱결제 구현은 이정도로 해봤습니다.

 

 

ref.

 

StoreKit | Apple Developer Documentation

Support In-App Purchases and interactions with the App Store.

developer.apple.com

 

반응형

댓글