sodalive-ios/SodaLive/Sources/IAP/StoreManager.swift

184 lines
6.8 KiB
Swift

//
// StoreManager.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
// Ref.
// https://www.youtube.com/watch?v=qyKmpr9EjwU
// https://nicgoon.tistory.com/205
// https://charlie-choi.tistory.com/241
// https://eunjo-princess.tistory.com/20
// https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/validating_receipts_with_the_app_store#//apple_ref/doc/uid/TP40010573-CH104-SW2
// https://velog.io/@givepro91/%EC%9D%B8%EC%95%B1%EA%B2%B0%EC%A0%9C-%EC%84%9C%EB%B2%84-%EA%B0%9C%EB%B0%9C
// https://twih1203.medium.com/swift-%EC%9D%B8%EC%95%B1-%EA%B2%B0%EC%A0%9C-%EA%B5%AC%ED%98%84-ff4b2d20a260
//
import Foundation
import StoreKit
class StoreManager: NSObject, ObservableObject {
@Published var errorMessage = ""
@Published var isShowPopup = false
@Published var isLoading = false
@Published var products = [SKProduct]()
var onSuccessPayment: ((String, Int) -> Void)?
var chargeId: Int!
var request: SKProductsRequest!
func getProducts() {
isLoading = true
products.removeAll()
let request = SKProductsRequest(productIdentifiers: [
"\(Bundle.main.bundleIdentifier!).can_35",
"\(Bundle.main.bundleIdentifier!).can_55",
"\(Bundle.main.bundleIdentifier!).can_105",
"\(Bundle.main.bundleIdentifier!).can_350",
"\(Bundle.main.bundleIdentifier!).can_550",
"\(Bundle.main.bundleIdentifier!).can_1170",
"\(Bundle.main.bundleIdentifier!).can_3580",
"\(Bundle.main.bundleIdentifier!).can_5750",
])
request.delegate = self
request.start()
}
func payment(product: SKProduct, chargeId: Int) {
isLoading = true
self.chargeId = chargeId
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
}
}
extension StoreManager: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if !response.products.isEmpty {
let products = response.products.sorted { $0.price.compare($1.price) == .orderedAscending }
for product in products {
DispatchQueue.main.async {
self.products.append(product)
}
}
}
DispatchQueue.main.async { [unowned self] in
self.isLoading = false
}
self.request = nil
}
func request(_ request: SKRequest, didFailWithError error: Error) {
self.request = nil
DEBUG_LOG("상품불러오기 실패: \(error)")
DispatchQueue.main.async { [unowned self] in
self.isLoading = false
errorMessage = "상품을 불러오지 못했습니다.\n다시 시도해 주세요."
self.isShowPopup = true
}
}
}
extension StoreManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch(transaction.transactionState) {
case .purchasing:
DEBUG_LOG("결제 진행 중...")
break
case .purchased:
isLoading = false
DEBUG_LOG("결제 완료")
complete(transaction: transaction)
break
case .failed:
isLoading = false
DEBUG_LOG("결제 실패")
fail(transaction: transaction)
break
case .deferred:
isLoading = false
DEBUG_LOG("아이폰이 잠김 등의 이유로 결제를 진행하지 못했습니다.")
errorMessage = "아이폰이 잠김 등의 이유로 결제를 진행하지 못했습니다."
isShowPopup = true
SKPaymentQueue.default().finishTransaction(transaction)
SKPaymentQueue.default().remove(self)
break
case .restored:
isLoading = false
DEBUG_LOG("상품 검증을 하였습니다.")
errorMessage = "상품 검증을 하였습니다."
isShowPopup = true
SKPaymentQueue.default().finishTransaction(transaction)
SKPaymentQueue.default().remove(self)
break
@unknown default:
isLoading = false
DEBUG_LOG("알 수 없는 오류가 발생했습니다.")
errorMessage = "알 수 없는 오류가 발생했습니다."
isShowPopup = true
SKPaymentQueue.default().finishTransaction(transaction)
SKPaymentQueue.default().remove(self)
fatalError()
}
}
}
private func complete(transaction: SKPaymentTransaction) {
if let onSuccessPayment = onSuccessPayment {
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptString = receiptData.base64EncodedString(options: [])
onSuccessPayment(receiptString, chargeId)
SKPaymentQueue.default().finishTransaction(transaction)
SKPaymentQueue.default().remove(self)
}
catch {
DEBUG_LOG("영수증 데이터 읽기 실패: " + error.localizedDescription)
fail(transaction: transaction)
}
} else {
DEBUG_LOG("영수증 데이터 읽기 실패")
fail(transaction: transaction)
}
} else {
fail(transaction: transaction)
}
}
private func fail(transaction: SKPaymentTransaction) {
if let transactionError = transaction.error as NSError?,
let localizedDescription = transaction.error?.localizedDescription,
transactionError.code != SKError.paymentCancelled.rawValue {
DEBUG_LOG("Transaction Error: \(localizedDescription)")
}
DispatchQueue.main.async { [unowned self] in
errorMessage = "결제를 진행하지 못했습니다.\n다시 시도해 주세요."
isShowPopup = true
}
SKPaymentQueue.default().finishTransaction(transaction)
SKPaymentQueue.default().remove(self)
}
}