이전 글
간단하게 기본 세팅을 완료하였다. 이제 코드를 작성해볼 시간!! +_+
1. StoreKitManager 파일 및 Class 추가
나는 StoreKitManager 라는 이름으로 파일을 만들었다. 이렇게 내가 만든 Product 를 타입으로 지정할 수 있다.
import StoreKit
class StoreKitManager: ObservableObject {
@Published var products: [Product] = []
}
2. requestProducts 함수 추가
우선 아래와 같이 products 들을 가져와야 한다. 그래야 뷰에 표시를 할 수 있으니까! @MainActor 를 통해 UI 업데이트와 같은 작업이 안전하게 수행될 수 있게 한다.
import StoreKit
class StoreKitManager: ObservableObject {
@Published var products: [Product] = []
private var productIDs = ["premium", "coffee"]
init() {
Task {
await requestProducts()
}
}
@MainActor
func requestProducts() async {
do {
products = try await Product.products(for: productIDs)
} catch {
print("Failed to retrieving products \(error)")
}
}
}
3. 뷰 만들기
아래와 같이 StoreKitManager 를 불러와서 products 를 가져와서 displayName, displayPrice 를 가져올 수 있다. 안타깝게도 시뮬레이터에서만 확인할 수 있다. 프리뷰는 안된다!
import SwiftUI
struct InAppPurchaseView: View {
@StateObject private var store = StoreKitManager()
var body: some View {
List {
ForEach(store.products, id: \.id) { product in
HStack {
Text(product.displayName)
Spacer()
Button(product.displayPrice) {
// purchasing action
}
.buttonStyle(.bordered)
}
}
}
}
}
4. Purchase 함수 구현
그렇다면 이제 Product 를 구입하는 함수를 만들어보자
@Published var purchasedProducts: [Product] = [] //변수 추가
@MainActor
func purchase(_ product: Product) async throws -> Transaction? {
let result = try await product.purchase()
switch result {
case .success(.verified(let transaction)):
purchasedProducts.append(product)
await transaction.finish()
return transaction
case .userCancelled, .pending:
return nil
default:
return nil
}
}
아래와 같이 뷰에 purchase 함수를 넣어주자
import SwiftUI
struct InAppPurchaseView: View {
@StateObject private var store = StoreKitManager()
var body: some View {
List {
ForEach(store.products, id: \.id) { product in
HStack {
Text(product.displayName)
Spacer()
Button(product.displayPrice) {
Task {
try await store.purchase(product)
}
}
.buttonStyle(.bordered)
}
}
}
}
}
그렇다면 아래와 결제가 진행되는 과정을 볼 수 있다. 물론 테스트용이라 실제 돈이 나가지 않는다
5. Listening transaction 하기
사용자가 구매를 하게 된다면 이 상황을 계속해서 감지해줄 필요가 있다. 이것을 listen 이라고 하는데, 이것이 없어서 앱을 나갔다 다시 들어오게 된다면 앱 내에서 구매내역이 사라지게 된다.
아래의 함수를 추가해 주자.
func listenForTransactions() -> Task<Void, Error> {
return Task.detached {
for await result in Transaction.updates {
switch result {
case let .verified(transaction):
guard let product = self.products.first(where: { $0.id == transaction.productID } ) else { continue }
self.purchasedProducts.append(product)
await transaction.finish()
default:
continue
}
}
}
}
하지만 이렇게 된다면 transaction이 검증되었다면 purchasedProducts 에 계속 product 를 추가하게 된다. 따라서 purchasedProducts를 Set<Product> 형식으로 만들어주고 self.purchasedProducts.append(product) 를 self.purchasedProducts.insert(product) 로 만들어 주자.
전체 코드는 아래와 같다. init() 에도 listener 를 포함시켜 주었다.
import StoreKit
class StoreKitManager: ObservableObject {
@Published var products: [Product] = []
@Published var purchasedProducts: Set<Product> = []
private var productIDs = ["premium", "coffee"]
var transacitonListener: Task<Void, Error>?
init() {
transacitonListener = listenForTransactions()
Task {
await requestProducts()
}
}
@MainActor
func requestProducts() async {
do {
products = try await Product.products(for: productIDs)
} catch {
print("Failed to retrieving products \(error)")
}
}
@MainActor
func purchase(_ product: Product) async throws -> Transaction? {
let result = try await product.purchase()
switch result {
case .success(.verified(let transaction)):
purchasedProducts.insert(product)
await transaction.finish()
return transaction
case .userCancelled, .pending:
return nil
default:
return nil
}
}
func listenForTransactions() -> Task<Void, Error> {
return Task.detached {
for await result in Transaction.updates {
switch result {
case let .verified(transaction):
guard let product = self.products.first(where: { $0.id == transaction.productID } ) else { continue }
self.purchasedProducts.insert(product)
await transaction.finish()
default:
continue
}
}
}
}
}
여기서 listenForTransactions() 를 아래와 같이 찢어준다.
func listenForTransactions() -> Task<Void, Error> {
return Task.detached {
for await result in Transaction.updates {
await self.handle(transactionVerification: result)
}
}
}
@MainActor
private func handle(transactionVerification result: VerificationResult <Transaction> ) async {
switch result {
case let.verified(transaction):
guard let product = self.products.first(where: { $0.id == transaction.productID } ) else { return }
self.purchasedProducts.insert(product)
await transaction.finish()
default:
return
}
}
6. 사용자가 구매한 상품 업데이트
현재 사용자가 구매한 상품을 업데이트해주는 함수도 추가해준다.
private func updateCurrentEntitlements() async {
for await result in Transaction.currentEntitlements {
await self.handle(transactionVerification: result)
}
}
시작할때 products 를 가져온 후 실행되게 init() requestProducts() 아래 부분에 추가해준다.
init() {
transacitonListener = listenForTransactions()
Task {
await requestProducts()
await updateCurrentEntitlements()
}
}
7. 만약 Consumable 한 상품을 구입했을때
배열로 저장하기 때문에 append 가 필요하다! 아래와 같이 addPurchased 를
private func addPurchased(_ product: Product) {
switch product.type {
case .consumable:
사용되는 구매항목.append(product)
case .nonConsumable:
지속되는 구매항목.insert(product)
default:
return
}
}
handle 함수의 아래 부분을 대체해주면 된다.
self.purchasedProducts.insert(product)
self.addPurchased(product)
코드는 거의 다 완성되었다! 다음은 App Store Connect 와 연결하고 간단한 작업만 하면 된다!
'→ StoreKit' 카테고리의 다른 글
[StoreKit] 인앱결제 구현하기 1 - 기본 세팅 (0) | 2024.02.13 |
---|