184 lines
6.8 KiB
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)
|
|
}
|
|
}
|