반응형

결제를 위한 클래스 만들기

import UIKit
import StoreKit

//MARK: -type alias
public typealias ProductIdentifier = String
public typealias ProductsRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -> Void

extension Notification.Name {
    static let IAPHelperPurchaseNotification = Notification.Name("IAPHelperPurchaseNotification")
}
//MARK: -class
open class InAppPurchaseManager: NSObject  {
    let TAG = "[custom][yourTAG][InAppPurchaseManager]"
    static let subID: String = "com.your.custom.subscriptionID"
    //상품 비밀번호
    static let sharePS: String = "aaaaaaaaaaaaaaaaaaaaaaa"
    
    //불러올 상품의 아이디
    private var purchasedPID: Set<ProductIdentifier> = []
    //SKProductsRequestDelegate의 delegate를 갖기위함.
    private var productsRequest: SKProductsRequest?
    //제품을 불러온뒤 controller에 알려주기위한 callback
    private var productsRequestCompletionHandler: ProductsRequestCompletionHandler?
    //옵저버 등록후 상품리스트를 불러오면 바로 complete가 되서 결제됨을 막음....
    private var didTheListComeOver = false
    
    public init( productIdentifiers:Set<ProductIdentifier> ){
        self.purchasedPID = productIdentifiers
        super.init()
        //appStore 결제 모듈에 연결함..
        SKPaymentQueue.default().add(self)
    }
    //상품 리스트를 요청
    // productsRequestCompletionHandler로 메인으로 전달, ui update
    public func requestProducts(completionHandler: @escaping ProductsRequestCompletionHandler) {
      productsRequest?.cancel()
      productsRequestCompletionHandler = completionHandler

      productsRequest = SKProductsRequest(productIdentifiers: purchasedPID)
      productsRequest!.delegate = self
      productsRequest!.start()
    }
    
    //ui로 보여진뒤 구입을 누르면 구입 프로세스 진행
    public func buyProduct(_ product: SKProduct){
        let payment = SKPayment(product: product)
        SKPaymentQueue.default().add(payment)
    }
    //이미 구입했는지 여부 확인 (여기서 안씀..)
    public func isProductPurchased(_ productIdentifier: ProductIdentifier) -> Bool {
        return purchasedPID.contains(productIdentifier)
    }
    
    //결제가 가능한 폰,계정상태 리턴
    public class func canPayments() -> Bool {
        return SKPaymentQueue.canMakePayments()
    }
 
}
//MARK: - get product delegate
extension InAppPurchaseManager:SKProductsRequestDelegate{
    
    //success get products
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        logi("\(TAG) Loaded list of products...")
        let products = response.products
        productsRequestCompletionHandler?(true, products)//get product complete
        clearRequestAndHandler()
        didTheListComeOver = true
    }
    
    //get products error!
    public func request(_ request: SKRequest, didFailWithError error: Error) {
      loge("\(TAG) didFailWithError : Failed to load list of products.")
      loge("\(TAG) didFailWithError :  \(error.localizedDescription)")
      productsRequestCompletionHandler?(false, nil)
      clearRequestAndHandler()
    }

    private func clearRequestAndHandler() {
      productsRequest = nil
      productsRequestCompletionHandler = nil
    }
}

//MARK: - Payment Transction
extension InAppPurchaseManager: SKPaymentTransactionObserver {
 
  //앱스토어 결제 모듈에 연결된 부분.. queue가 계속 연결되어있음
  public func paymentQueue(_ queue: SKPaymentQueue,updatedTransactions transactions: [SKPaymentTransaction]) {
     loge("\(TAG) paymentQueue ----->>")
    if (didTheListComeOver){
        for transaction in transactions {
          switch transaction.transactionState {
          case .purchased:
            complete(transaction: transaction)
            break
          case .failed:
            fail(transaction: transaction)
            break
          case .restored:
            restore(transaction: transaction)
            break
          case .deferred:
            break
          case .purchasing:
            break
          }
        }
    }
  }
 //결제성공
  private func complete(transaction: SKPaymentTransaction) {
    logi("\(TAG) complete...")
    deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
    
    SKPaymentQueue.default().finishTransaction(transaction)
    didTheListComeOver = false
    NotificationCenter.default.post(name: .completePurchase, object: nil, userInfo: ["complete": "결제 성공"])
  }
 
  //이미 결제된 사항을 복원
  private func restore(transaction: SKPaymentTransaction) {
    guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
 
     logi("\(TAG) restore... \(productIdentifier)")
    deliverPurchaseNotificationFor(identifier: productIdentifier)
    SKPaymentQueue.default().finishTransaction(transaction)
    didTheListComeOver = false
    NotificationCenter.default.post(name: .completePurchase, object: nil, userInfo: ["complete": "결제 성공(재구입)"])
  }
 
  //실패
  private func fail(transaction: SKPaymentTransaction) {
     logi("\(TAG) SKPaymentTransaction  fail...")
    if let transactionError = transaction.error as NSError?,
      let localizedDescription = transaction.error?.localizedDescription,
        transactionError.code != SKError.paymentCancelled.rawValue {
        print("Transaction Error: \(localizedDescription)")
      }

    SKPaymentQueue.default().finishTransaction(transaction)
    didTheListComeOver = false
    NotificationCenter.default.post(name: .cancelPurchase, object: nil, userInfo: ["fail": "결제 실패"])
  }
 
  private func deliverPurchaseNotificationFor(identifier: String?) {
    guard let identifier = identifier else { return }
 
    purchasedPID.insert(identifier)
    UserDefaults.standard.set(true, forKey: identifier)
    NotificationCenter.default.post(name: .IAPHelperPurchaseNotification, object: identifier)
  }
}

 


결제 사용

// 인앱결제 구조체
public struct InAppProducts {
    public static let product = "com.your.app.subscription_id"
    public static let productK: String = "aaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    public static let productIdentifiers: Set<ProductIdentifier> = [InAppProducts.product]
    public static let store2 = InAppPurchaseManager(productIdentifiers: InAppProducts.productIdentifiers)
}

 

    func purchaseShow(){
        if InAppPurchaseManager.canPayments() {
        //상품을 불러오는 부분
            InAppProducts.store2.requestProducts { (isCheck, products) in
                for p in products!{
                    if isCheck {
                       //상품 불러온후 결제 프로세스 진행
                        InAppProducts.store2.buyProduct(p)
                    }
                }
            }
        } else {
            //결제설정이 안되었거나 결제가 불가능한 기기입니다
        }
    }

결제체크용 클래스


import UIKit
import StoreKit

public typealias PurchaseCheckHandler = (_ purchased: Bool, _ msg:String?) -> Void

struct ReceiptParam {
    public static let data :String = "receipt-data"
    public static let password :String = "password"
    public static let excludeOld_trsc :String = "exclude-old-transactions"
}

open class InAppPurchaseChecker: NSObject {
    
    let TAG = "[custom][yourTAG][InAppPurchaseManager]"
    var productID:String
    var productKey:String
    
    //결제 상태를 controller에 돌려주기위한 handler
    private var checkHandler: PurchaseCheckHandler?
    
    
    public init(productID:String, productKey:String) {
        self.productID = productID
        self.productKey = productKey
        super.init()
    }
    
    func checkPurchase(purchaseCheckHandler: @escaping PurchaseCheckHandler ){
        self.checkHandler = purchaseCheckHandler
        refreshSubscriptionsStatus()
    }
    
    
//앱스토어에서 영수증 가저오는 부분
private func refreshSubscriptionsStatus(){
    logi("\(self.TAG) check refreshSubscriptionsStatus")
    guard let receiptUrl = Bundle.main.appStoreReceiptURL else {
            refreshReceipt()
            return
    }
    
    logi("\(TAG) \(receiptUrl)")
    
    let urlString = "https://sandbox.itunes.apple.com/verifyReceipt"
//      let urlString = "https://buy.itunes.apple.com/verifyReceipt"

    let receiptData = try? Data(contentsOf: receiptUrl).base64EncodedString()
    let requestData = [ReceiptParam.data : receiptData ?? "", ReceiptParam.password : productKey ,ReceiptParam.excludeOld_trsc : true] as [String : Any]
    var request = URLRequest(url: URL(string: urlString)!)
    request.httpMethod = "POST"
    request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
    let httpBody = try? JSONSerialization.data(withJSONObject: requestData, options: [])
    request.httpBody = httpBody
    URLSession.shared.dataTask(with: request)  { (data, response, error) in
        DispatchQueue.main.async {
            if data != nil {
                logi("\(self.TAG) come data")
                if let json = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments){
                    self.parseReceipt(json as! Dictionary<String, Any>)
                      logi("\(self.TAG) will do parseReceipt")
                    return
                }
            } else {
                loge("\(self.TAG)error validating receipt: \(error?.localizedDescription ?? "")")
            }
        }
    }.resume()
     
}
//못불러왔을경우 --> 영수증 리프래쉬
private func refreshReceipt(){
    let request = SKReceiptRefreshRequest(receiptProperties: nil)
    request.delegate = self
    request.start()
}
// json 으로 받아온 영수증 파싱해서 controller에 결과값 전송
private func parseReceipt(_ json : Dictionary<String, Any>) {
    //가저온 결과값중 가장 마지막 아이템 가저오기...
    guard let receipts_array = json["latest_receipt_info"] as? [Dictionary<String, Any>] else {
        checkHandler!(false,"애플 정기결제를 한적이 없습니다.")
        return
    }
    
    for receipt in receipts_array {
        let pid = receipt[IAPVO.product_id] as! String
        if pid == productID {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
            formatter.timeZone = NSTimeZone(name: "UTC") as TimeZone?
            if let date = formatter.date(from: receipt[IAPVO.expires_date] as! String) {
                logi("\(self.TAG) date: \(date), Date(): \(Date())" )
                //만료일자가 오늘보다 커야 결제한 상태이다.
                if date > Date() {
                    logi("\(self.TAG) date > Date()")
                    DispatchQueue.main.async {
                        self.checkHandler!(true,"결제한 상태")
                    }
                }
                //결제 취소상태
                else {
                    logi("\(self.TAG) date < Date()")
                    DispatchQueue.main.async {
                        self.checkHandler!(true,"결제한 취소한 상태")
                    }

                }
            }
        }
    }
}
    
    
    
    
}

extension InAppPurchaseChecker : SKRequestDelegate {
    public func request(_ request: SKRequest, didFailWithError error: Error) {
         loge("\(self.TAG) didFailWithError !!")
    }
    public func requestDidFinish(_ request: SKRequest) {
         logi("\(self.TAG) requestDidFinish !!")
    }
}

 


결제 체크 사용

let iapChecker = InAppPurchaseChecker(productID: InAppProducts.product, 
		productKey: InAppProducts.productK)
iapChecker.checkPurchase { (isPurchased, msg) in
	if isPurchased {
		// Purchased!! , update your ui
	} else {
		// not purchased!! ,update your ui
	}
}
반응형

+ Recent posts