반응형
결제를 위한 클래스 만들기
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
}
}
반응형
'IOS' 카테고리의 다른 글
(IOS)앨범,사진찍어서 이미지 가저오기 (2) | 2020.07.03 |
---|---|
(ios)에러 - content and frame layout guides before 11.0 (0) | 2020.03.31 |
(IOS)WKWebview 앱캐시 문제 (1) | 2020.02.26 |
(IOS)wkwebview 적용해보기 (0) | 2020.01.31 |
(IOS)sms로 인증문자 받아서 keyboard위에 띄우기 (0) | 2020.01.30 |