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)
 | 
						|
    }
 | 
						|
}
 |