Skip to content

Instantly share code, notes, and snippets.

@fromkk
Last active August 4, 2022 06:23
Show Gist options
  • Save fromkk/c707651751df94ed01087bf36a15beef to your computer and use it in GitHub Desktop.
Save fromkk/c707651751df94ed01087bf36a15beef to your computer and use it in GitHub Desktop.
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