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
'SwiftUI로 1인 앱 개발 해보기' 카테고리의 다른 글
SwiftData로 iCloud 대응할 때, 원하는 모델만 싱크하기 (0) | 2025.02.08 |
---|---|
XCode에서 실기기 테스트빌드를 무선으로 해보기 (0) | 2025.02.02 |
AppStore Connect 앱 내 구입 심사정보 스크린샷 첨부 오류 해결 (0) | 2025.02.01 |
iOS 앱 개발 시 인앱구매/인앱결제 구매항목 복원 테스트 방법 (1) | 2025.01.31 |
SwiftUI 커스텀 폰트 적용해보기 (0) | 2025.01.29 |
댓글