async / await 도입  
  
  
  
  

import StoreKitSKProduct : 상품 정보 (price, name, ...)SKPayment : 지불 정보 (product id, request data)SKPaymentRequest : 결제 요청 시 사용 타입 (delegate로 성공/실패 알려줌)SKPaymentQueue.default() : 데이터 가져오기에 사용ProductRequestCompletionHandler// completion 정의. (외부에서 정의하고, 내부에서 실행)
typealias ProductRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -> Voidprotocol IAPServiceType외부에서 필요한 메서드 명시
getProducts : products 항목 (상품 정보) 가져오기buyProduct : product 구입isProductPurchased : 구입했는지 확인restorePurchased : 구입한 목록 확인// 프로토콜 -> 외부에서 필요한 메서드 명시
protocol IAPServiceType {
    var canMakePayments: Bool { get }
    func getProducts(completion: @escaping ProductRequestCompletionHandler)
    func buyProduct(_ product: SKProduct)
    func isProductPurchased(_ productID: String) -> Bool
    func restorePurchases()
}class IAPServiceNSObject 상속 (StoreKit 사용), IAPServiceType 채택class IAPService: NSObject, IAPServiceType {필요한 프로퍼티 선언 및 init
productIDs : 앱스토어 커넥트에 등록된 productIDpurchaseProductIDs : 구매한 productIDproductRequest : productID로 부가 정보 조회하기 위한 인스턴스productsCompletionHandler : 사용하는 쪽에서 성공/실패 했을 때 completion을 통해 값을 넘겨줄 수 있다.// 필요한 프로퍼티
private let productIdentifiers: Set<String>
private var purchasedProducts: Set<String> = []
private var productRequest: SKProductsRequest?
private var productsCompletionHandler: ProductsRequestCompletionHandler?
// 상품 정보 받아서 초기화 및 SKPaymentQueue 연결
init(productIdentifiers: Set<String>) {
    // "com.blogPost.App.~~"
    self.productIdentifiers = productIdentifiers
    self.purchasedProducts = productIdentifiers.filter {
        // UserDefaults에 저장해둔 구매 여부
    	UserDefaults.standard.bool(forKey: $0) == true
    }
    super.init()
    SKPaymentQueue.default().add(self)
    // App Store와 지불정보를 동기화하기 위한 Observer
    // -> SKPaymentTransactionObserver 프로토콜 채택
}canMakePayment : 사용자의 디바이스가 현재 결제가 가능한지 확인var canMakePayments: Bool {
    return SKPaymentQueue.canMakePayments()
}프로토콜 메서드 정의
// 상품 정보 조회
func getProducts(completion: @escaping ProductsRequestCompletionHandler) {
    self.productRequest?.cancel()
    self.productsCompletionHandler = completion
    self.productRequest = SKProductsRequest(productIdentifiers: self.productIdentifiers)
    self.productRequest?.delegate = self
    // -> SKProductsRequestDelegate 채택
    self.productRequest?.start()    // 인앱 상품 조회 시작
    // -> delegate 함수 실행 (SKProductsRequestDelegate)
}
// 상품 구입
func buyProduct(_ product: SKProduct) {
    let payment = SKPayment(product: product)
    SKPaymentQueue.default().add(payment)
    // -> paymentQueue() 함수 실행
}
// 구입 확인
func isProductPurchased(_ productID: String) -> Bool {
    return self.purchasedProducts.contains(productID)
}
// 구입 내역 복원
func restorePurchases() {
    SKPaymentQueue.default().restoreCompletedTransactions()
}extension IAPService: SKProductsRequestDelegategetProducts에서 completionHandler를 캡쳐해두고, 여기서 실행한다
extension IAPService: SKProductsRequestDelegate {
    // App Store Connect에서 상품 정보 조회
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        let products = response.products
        self.productsCompletionHandler?(true, products)
        self.clearRequestAndHandler()
        // 출력
        products.forEach { item in
            print("Found Product : \(item.productIdentifier) \(item.localizedTitle)")
        }
    }
	
	
    // fail
    func request(_ request: SKRequest, didFailWithError error: Error) {
        self.productsCompletionHandler?(false, nil)
        self.clearRequestAndHandler()
        // 출력
        print("Error : \(error.localizedDescription)")
    }
    // 초기화
    private func clearRequestAndHandler() {
        self.productRequest = nil
        self.productsCompletionHandler = nil
    }
}extension IAPService: SKPaymentTransactionObserverSKPaymentQueue에서 처리되는 일
purchased, failed, restored, deferred, purchasingextension IAPService: SKPaymentTransactionObserver {
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        print(#function)
        for transaction in transactions {
            let state = transaction.transactionState
            switch state {
            case .purchasing:
                print("구매하는 중")
            case .purchased:
                print("구매 완료")
                completePurchase(transaction: transaction)
            case .failed:
                print("구매 실패")
                failPurchase(transaction: transaction)
            case .restored:
                print("구매 복원")
                restorePurchase(transaction: transaction)
            case .deferred:
                print("deferred")
            @unknown default:
                fatalError()
            }
        }
    }
    // 구매 완료 (성공)
    private func completePurchase(transaction: SKPaymentTransaction) {
        print(#function)
        guard let id = transaction.original?.payment.productIdentifier else { return }
        deliverPurchaseNotificationFor(id: id)
        SKPaymentQueue.default().finishTransaction(transaction)
    }
    // 구매 실패
    private func failPurchase(transaction: SKPaymentTransaction) {
        print(#function)
        if let transactionError = transaction.error as NSError?,
           transactionError.code != SKError.paymentCancelled.rawValue {
            print("TransactionError : \(transactionError.localizedDescription)")
        }
        SKPaymentQueue.default().finishTransaction(transaction)
    }
    // 구매 복원 성공
    private func restorePurchase(transaction: SKPaymentTransaction) {
        print(#function)
        guard let id = transaction.original?.payment.productIdentifier else { return }
        deliverPurchaseNotificationFor(id: id)
        SKPaymentQueue.default().finishTransaction(transaction)
    }
    private func deliverPurchaseNotificationFor(id: String?) {
        guard let id else { return }
        self.purchasedProducts.insert(id)
        UserDefaults.standard.set(true, forKey: id)
        NotificationCenter.default.post(
            name: .iapServicePurchaseNotification,	// 곧 정의
            object: id
        )
    }
    func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
        print(#function)
    }
}extension Notification.Nameextension Notification.Name {
    static let iapServicePurchaseNotification = Notification.Name("IAPServicePurchaseNotification")
}enum MyProductsProduct ID를 가지고 있는 Wrapping 타입. (IAPService를 wrapping한다)
enum MyProducts {
    static let productID = "com.blogPost.s_sub.heart100"
    static let iapService: IAPServiceType = IAPService(productIdentifiers: Set<String>([productID]))
    static func getResourceProductName(_ id: String) -> String? {
        id.components(separatedBy: ".").last
    }
}class IAPTestViewControllerclass FinalIAPTestViewController: UIViewController {
    
    private let restoreButton = UIButton()
    private let buyButton = UIButton()
    private let productLabel = UILabel()
    
    private var products = [SKProduct]()
    
    func setting() {...}
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setting()
        getProducts()
    }
    
    // 상품 정보 조회
    func getProducts() {
        MyProducts.iapService.getProducts { [weak self] success, products in
            if success,
               let products = products {
                DispatchQueue.main.async {
                    self?.products = products
                    self?.productLabel.text = "\(products.first?.productIdentifier ?? "")\n \(products.first?.localizedTitle ?? "")"
                }
            }
        }
    }
    
    // 구매 버튼 클릭
    @objc func buyButtonClicked() {
        MyProducts.iapService.buyProduct(products.first!)
    }
    
    // 복구 버튼 클릭 (구입 목록 조회)
    @objc func restoreButtonClicked() {
        MyProducts.iapService.restorePurchases()
    }
    
    
    // 노티 세팅
    func setNotification() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handlePurchaseNoti(_:)),
            name: .iapServicePurchaseNotification,
            object: nil
        )
    }
    
    @objc private func handlePurchaseNoti(_ notification: Notification) {
        // 노티 핸들 (뷰 업데이트)
    }
}




SKPaymentQueue.default().finishTransaction(transaction)func getReceiptData(_ productIdentifier: String) -> String? {
    if let receiptURL = Bundle.main.appStoreReceiptURL,
       FileManager.default.fileExists(atPath: receiptFileURL.path) {
       
       do {
       		let receiptData = try Data(contensOf: receiptFileURL)
            let receiptString = receiptData.base64EncodedString(
                options: NSData.Base64EncodingOptions(rawValue: 0)
            )
            return receiptString
       
       } catch {
       		print("구매 이력 영수증 데이터 변환 과정에서 에러 : \(error.localizedDescription)")
            return nil
       }
       
    }
    
    print("구매 이력 영수증 URL 에러")
    return nil
}

현재 구매 복원 관련해서 심사 거부를 당해서 담당자에게 서버에서 결제 정보를 수신한다고 했는데 그럼에도 구매 복원 버튼이 필요하다고 하더라구요.
마지막 구매복원 부분에서 "서버가 존재해서 구매 복원에 대한 기능을 서버에서 자체적으로 해주고 있다면, 앱 내에서 구현하지 않아도 된다." 라는 내용은 어디서 찾으신 건지 알 수 있을까요? 해당 자료를 토대로 문의를 해보고싶습니다!