Last active
August 4, 2022 06:23
-
-
Save fromkk/c707651751df94ed01087bf36a15beef to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import StoreKit | |
// helper | |
struct IAPHelper { | |
static var canMakePayments: Bool { | |
SKPaymentQueue.canMakePayments() | |
} | |
static func formattedPrice(with product: SKProduct) -> String? { | |
let formatter = NumberFormatter() | |
formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4 | |
formatter.numberStyle = NumberFormatter.Style.currency | |
formatter.locale = product.priceLocale | |
return formatter.string(from: product.price) | |
} | |
private enum Period: String, Localizable { | |
case year = "formatted_year" // %d年 | |
case month = "formatted_month" // %dヶ月 | |
case week = "formatted_week" // %d週 | |
case day = "formatted_day" // %d日 | |
} | |
@available(iOS 11.2, macCatalyst 13.0, *) | |
static func formattedPeriod(with product: SKProduct) -> String? { | |
guard let period = product.subscriptionPeriod else { | |
return nil | |
} | |
switch period.unit { | |
case .day: | |
return String(format: Period.day.localize(), period.numberOfUnits) | |
case .week: | |
return String(format: Period.week.localize(), period.numberOfUnits) | |
case .month: | |
return String(format: Period.month.localize(), period.numberOfUnits) | |
case .year: | |
return String(format: Period.year.localize(), period.numberOfUnits) | |
@unknown default: | |
fatalError("unknown period unit \(period.unit)") | |
} | |
} | |
} | |
// products | |
final class ProductManager: NSObject { | |
static let shared = ProductManager() | |
override private init() { | |
super.init() | |
} | |
typealias Completion = ([SKProduct]) -> Void | |
fileprivate var completion: Completion? | |
fileprivate var request: SKProductsRequest? | |
var products: [SKProduct] = [] | |
func fetch(with productIdentifier: String, completion: @escaping Completion) { | |
guard products.count == 0 else { | |
completion(products) | |
return | |
} | |
self.completion = completion | |
request = SKProductsRequest(productIdentifiers: [productIdentifier]) | |
request?.delegate = self | |
request?.start() | |
} | |
static var subscriptionProduct: SKProduct? { | |
ProductManager.shared.products.filter { (product: SKProduct) -> Bool in product.productIdentifier == Constants.subscriptionProductionIdentifier }.first | |
} | |
} | |
extension ProductManager: SKProductsRequestDelegate { | |
func productsRequest(_: SKProductsRequest, didReceive response: SKProductsResponse) { | |
products = response.products | |
completion?(products) | |
} | |
} | |
// payments | |
final class PaymentManager: NSObject, SKPaymentTransactionObserver { | |
typealias ReceiveReceipt = (Data?, Error?) -> Void | |
fileprivate var _receiveReceipt: ReceiveReceipt? | |
lazy var receiptRequest: SKReceiptRefreshRequest = { () -> SKReceiptRefreshRequest in | |
let request = SKReceiptRefreshRequest() | |
request.delegate = self | |
return request | |
}() | |
static let shared = PaymentManager() | |
override private init() { | |
super.init() | |
} | |
// update transaction | |
typealias UpdateTransaction = (SKPaymentTransaction) -> Void | |
private var _updateTransactions: [String: UpdateTransaction] = [:] | |
func updateTransaction(with productionIdentifier: String, updateTransaction: @escaping UpdateTransaction) { | |
_updateTransactions[productionIdentifier] = updateTransaction | |
} | |
func paymentQueueRestoreCompletedTransactionsFinished(_: SKPaymentQueue) { | |
print(#function) | |
} | |
func paymentQueue(_: SKPaymentQueue, updatedDownloads _: [SKDownload]) { | |
print(#function) | |
} | |
func paymentQueue(_: SKPaymentQueue, removedTransactions _: [SKPaymentTransaction]) { | |
print(#function) | |
} | |
func paymentQueue(_: SKPaymentQueue, restoreCompletedTransactionsFailedWithError _: Error) { | |
print(#function) | |
} | |
func paymentQueue(_: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { | |
transactions.forEach { (transaction: SKPaymentTransaction) in | |
self._updateTransactions[transaction.payment.productIdentifier]?(transaction) | |
print(#function, transaction.transactionState.rawValue) | |
switch transaction.transactionState { | |
case .deferred: | |
break | |
case .purchasing: | |
break | |
case .failed: | |
SKPaymentQueue.default().finishTransaction(transaction) | |
case .purchased: | |
SKPaymentQueue.default().finishTransaction(transaction) | |
case .restored: | |
SKPaymentQueue.default().finishTransaction(transaction) | |
@unknown default: | |
fatalError("unknowned transactionState") | |
} | |
} | |
} | |
} | |
extension PaymentManager: SKRequestDelegate { | |
func requestReceipt(_ receiveReceipt: @escaping ReceiveReceipt) { | |
if let receiptURL: URL = Bundle.main.appStoreReceiptURL { | |
do { | |
let data: Data = try Data(contentsOf: receiptURL) | |
receiveReceipt(data, nil) | |
} catch { | |
receiveReceipt(nil, error) | |
} | |
} else { | |
_receiveReceipt = receiveReceipt | |
receiptRequest.start() | |
} | |
} | |
func requestDidFinish(_: SKRequest) { | |
guard let receiptURL: URL = Bundle.main.appStoreReceiptURL else { return } | |
do { | |
let data: Data = try Data(contentsOf: receiptURL) | |
_receiveReceipt?(data, nil) | |
} catch { | |
_receiveReceipt?(nil, error) | |
} | |
_receiveReceipt = nil | |
} | |
func request(_: SKRequest, didFailWithError error: Error) { | |
_receiveReceipt?(nil, error) | |
_receiveReceipt = nil | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment