|
@@ -26,9 +26,9 @@ import StoreKit
|
|
|
|
|
|
public class SwiftyStoreKit {
|
|
|
|
|
|
- private let productsInfoController = ProductsInfoController()
|
|
|
+ private let productsInfoController: ProductsInfoController
|
|
|
|
|
|
- private let paymentQueueController = PaymentQueueController(paymentQueue: SKPaymentQueue.default())
|
|
|
+ private let paymentQueueController: PaymentQueueController
|
|
|
|
|
|
private var receiptRefreshRequest: InAppReceiptRefreshRequest?
|
|
|
|
|
@@ -36,36 +36,29 @@ public class SwiftyStoreKit {
|
|
|
case restoredPurchaseWhenPurchasing = 0
|
|
|
case purchasedWhenRestoringPurchase = 1
|
|
|
}
|
|
|
-
|
|
|
- // MARK: Singleton
|
|
|
- private static let sharedInstance = SwiftyStoreKit()
|
|
|
|
|
|
- public class var canMakePayments: Bool {
|
|
|
- return SKPaymentQueue.canMakePayments()
|
|
|
+ init(productsInfoController: ProductsInfoController = ProductsInfoController(),
|
|
|
+ paymentQueueController: PaymentQueueController = PaymentQueueController(paymentQueue: SKPaymentQueue.default())) {
|
|
|
+
|
|
|
+ self.productsInfoController = productsInfoController
|
|
|
+ self.paymentQueueController = paymentQueueController
|
|
|
}
|
|
|
|
|
|
- // MARK: Public methods - Purchases
|
|
|
- public class func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> ()) {
|
|
|
-
|
|
|
- return sharedInstance.productsInfoController.retrieveProductsInfo(productIds, completion: completion)
|
|
|
+ // MARK: Internal methods
|
|
|
+
|
|
|
+ func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> ()) {
|
|
|
+ return productsInfoController.retrieveProductsInfo(productIds, completion: completion)
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Purchase a product
|
|
|
- * - Parameter productId: productId as specified in iTunes Connect
|
|
|
- * - Parameter atomically: whether the product is purchased atomically (e.g. finishTransaction is called immediately)
|
|
|
- * - Parameter applicationUsername: an opaque identifier for the user’s account on your system
|
|
|
- * - Parameter completion: handler for result
|
|
|
- */
|
|
|
- public class func purchaseProduct(_ productId: String, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> ()) {
|
|
|
+ func purchaseProduct(_ productId: String, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> ()) {
|
|
|
|
|
|
- if let product = sharedInstance.productsInfoController.products[productId] {
|
|
|
- sharedInstance.purchase(product: product, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
|
|
|
+ if let product = productsInfoController.products[productId] {
|
|
|
+ purchase(product: product, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
|
|
|
}
|
|
|
else {
|
|
|
retrieveProductsInfo(Set([productId])) { result -> () in
|
|
|
if let product = result.retrievedProducts.first {
|
|
|
- sharedInstance.purchase(product: product, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
|
|
|
+ self.purchase(product: product, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
|
|
|
}
|
|
|
else if let error = result.error {
|
|
|
completion(.error(error: .failed(error: error)))
|
|
@@ -77,90 +70,30 @@ public class SwiftyStoreKit {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public class func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> ()) {
|
|
|
-
|
|
|
- sharedInstance.paymentQueueController.restorePurchases(RestorePurchases(atomically: atomically, applicationUsername: applicationUsername) { results in
|
|
|
+ func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> ()) {
|
|
|
|
|
|
- let results = sharedInstance.processRestoreResults(results)
|
|
|
+ paymentQueueController.restorePurchases(RestorePurchases(atomically: atomically, applicationUsername: applicationUsername) { results in
|
|
|
+
|
|
|
+ let results = self.processRestoreResults(results)
|
|
|
completion(results)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- public class func completeTransactions(atomically: Bool = true, completion: @escaping ([Product]) -> ()) {
|
|
|
+ func completeTransactions(atomically: Bool = true, completion: @escaping ([Product]) -> ()) {
|
|
|
|
|
|
- sharedInstance.paymentQueueController.completeTransactions(CompleteTransactions(atomically: atomically, callback: completion))
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- public class func finishTransaction(_ transaction: PaymentTransaction) {
|
|
|
-
|
|
|
- sharedInstance.paymentQueueController.finishTransaction(transaction)
|
|
|
- }
|
|
|
-
|
|
|
- // MARK: Public methods - Receipt verification
|
|
|
-
|
|
|
- /**
|
|
|
- * Return receipt data from the application bundle. This is read from Bundle.main.appStoreReceiptURL
|
|
|
- */
|
|
|
- public static var localReceiptData: Data? {
|
|
|
- return InAppReceipt.appStoreReceiptData
|
|
|
+ paymentQueueController.completeTransactions(CompleteTransactions(atomically: atomically, callback: completion))
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Verify application receipt
|
|
|
- * - Parameter password: Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string).
|
|
|
- * - Parameter session: the session used to make remote call.
|
|
|
- * - Parameter completion: handler for result
|
|
|
- */
|
|
|
- public class func verifyReceipt(
|
|
|
- using validator: ReceiptValidator,
|
|
|
- password: String? = nil,
|
|
|
- completion:@escaping (VerifyReceiptResult) -> ()) {
|
|
|
-
|
|
|
- InAppReceipt.verify(using: validator, password: password) { result in
|
|
|
-
|
|
|
- DispatchQueue.main.async {
|
|
|
- completion(result)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Verify the purchase of a Consumable or NonConsumable product in a receipt
|
|
|
- * - Parameter productId: the product id of the purchase to verify
|
|
|
- * - Parameter inReceipt: the receipt to use for looking up the purchase
|
|
|
- * - return: either NotPurchased or Purchased
|
|
|
- */
|
|
|
- public class func verifyPurchase(
|
|
|
- productId: String,
|
|
|
- inReceipt receipt: ReceiptInfo
|
|
|
- ) -> VerifyPurchaseResult {
|
|
|
- return InAppReceipt.verifyPurchase(productId: productId, inReceipt: receipt)
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Verify the purchase of a subscription (auto-renewable, free or non-renewing) in a receipt. This method extracts all transactions mathing the given productId and sorts them by date in descending order, then compares the first transaction expiry date against the validUntil value.
|
|
|
- * - Parameter productId: the product id of the purchase to verify
|
|
|
- * - Parameter inReceipt: the receipt to use for looking up the subscription
|
|
|
- * - Parameter validUntil: date to check against the expiry date of the subscription. If nil, no verification
|
|
|
- * - Parameter validDuration: the duration of the subscription. Only required for non-renewable subscription.
|
|
|
- * - return: either NotPurchased or Purchased / Expired with the expiry date found in the receipt
|
|
|
- */
|
|
|
- public class func verifySubscription(
|
|
|
- productId: String,
|
|
|
- inReceipt receipt: ReceiptInfo,
|
|
|
- validUntil date: Date = Date(),
|
|
|
- validDuration duration: TimeInterval? = nil
|
|
|
- ) -> VerifySubscriptionResult {
|
|
|
- return InAppReceipt.verifySubscription(productId: productId, inReceipt: receipt, validUntil: date, validDuration: duration)
|
|
|
+ func finishTransaction(_ transaction: PaymentTransaction) {
|
|
|
+
|
|
|
+ paymentQueueController.finishTransaction(transaction)
|
|
|
}
|
|
|
|
|
|
- // After verifying receive and have `ReceiptError.NoReceiptData`, refresh receipt using this method
|
|
|
- public class func refreshReceipt(_ receiptProperties: [String : Any]? = nil, completion: @escaping (RefreshReceiptResult) -> ()) {
|
|
|
- sharedInstance.receiptRefreshRequest = InAppReceiptRefreshRequest.refresh(receiptProperties) { result in
|
|
|
-
|
|
|
- sharedInstance.receiptRefreshRequest = nil
|
|
|
-
|
|
|
+ func refreshReceipt(_ receiptProperties: [String : Any]? = nil, completion: @escaping (RefreshReceiptResult) -> ()) {
|
|
|
+ receiptRefreshRequest = InAppReceiptRefreshRequest.refresh(receiptProperties) { result in
|
|
|
+
|
|
|
+ self.receiptRefreshRequest = nil
|
|
|
+
|
|
|
switch result {
|
|
|
case .success:
|
|
|
if let appStoreReceiptData = InAppReceipt.appStoreReceiptData {
|
|
@@ -175,6 +108,7 @@ public class SwiftyStoreKit {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
// MARK: private methods
|
|
|
private func purchase(product: SKProduct, atomically: Bool, applicationUsername: String = "", completion: @escaping (PurchaseResult) -> ()) {
|
|
|
guard SwiftyStoreKit.canMakePayments else {
|
|
@@ -219,3 +153,114 @@ public class SwiftyStoreKit {
|
|
|
return NSError(domain: "SwiftyStoreKit", code: code, userInfo: [ NSLocalizedDescriptionKey: description ])
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+extension SwiftyStoreKit {
|
|
|
+
|
|
|
+ // MARK: Singleton
|
|
|
+ private static let sharedInstance = SwiftyStoreKit()
|
|
|
+
|
|
|
+ // MARK: Public methods - Purchases
|
|
|
+ public class var canMakePayments: Bool {
|
|
|
+ return SKPaymentQueue.canMakePayments()
|
|
|
+ }
|
|
|
+
|
|
|
+ public class func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> ()) {
|
|
|
+
|
|
|
+ return sharedInstance.retrieveProductsInfo(productIds, completion: completion)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Purchase a product
|
|
|
+ * - Parameter productId: productId as specified in iTunes Connect
|
|
|
+ * - Parameter atomically: whether the product is purchased atomically (e.g. finishTransaction is called immediately)
|
|
|
+ * - Parameter applicationUsername: an opaque identifier for the user’s account on your system
|
|
|
+ * - Parameter completion: handler for result
|
|
|
+ */
|
|
|
+ public class func purchaseProduct(_ productId: String, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> ()) {
|
|
|
+
|
|
|
+ sharedInstance.purchaseProduct(productId, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
|
|
|
+ }
|
|
|
+
|
|
|
+ public class func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> ()) {
|
|
|
+
|
|
|
+ sharedInstance.restorePurchases(atomically: atomically, applicationUsername: applicationUsername, completion: completion)
|
|
|
+ }
|
|
|
+
|
|
|
+ public class func completeTransactions(atomically: Bool = true, completion: @escaping ([Product]) -> ()) {
|
|
|
+
|
|
|
+ sharedInstance.completeTransactions(atomically: atomically, completion: completion)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public class func finishTransaction(_ transaction: PaymentTransaction) {
|
|
|
+
|
|
|
+ sharedInstance.finishTransaction(transaction)
|
|
|
+ }
|
|
|
+
|
|
|
+ // After verifying receive and have `ReceiptError.NoReceiptData`, refresh receipt using this method
|
|
|
+ public class func refreshReceipt(_ receiptProperties: [String : Any]? = nil, completion: @escaping (RefreshReceiptResult) -> ()) {
|
|
|
+
|
|
|
+ sharedInstance.refreshReceipt(receiptProperties, completion: completion)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension SwiftyStoreKit {
|
|
|
+
|
|
|
+ // MARK: Public methods - Receipt verification
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Return receipt data from the application bundle. This is read from Bundle.main.appStoreReceiptURL
|
|
|
+ */
|
|
|
+ public static var localReceiptData: Data? {
|
|
|
+ return InAppReceipt.appStoreReceiptData
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Verify application receipt
|
|
|
+ * - Parameter password: Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string).
|
|
|
+ * - Parameter session: the session used to make remote call.
|
|
|
+ * - Parameter completion: handler for result
|
|
|
+ */
|
|
|
+ public class func verifyReceipt(
|
|
|
+ using validator: ReceiptValidator,
|
|
|
+ password: String? = nil,
|
|
|
+ completion:@escaping (VerifyReceiptResult) -> ()) {
|
|
|
+
|
|
|
+ InAppReceipt.verify(using: validator, password: password) { result in
|
|
|
+
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ completion(result)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Verify the purchase of a Consumable or NonConsumable product in a receipt
|
|
|
+ * - Parameter productId: the product id of the purchase to verify
|
|
|
+ * - Parameter inReceipt: the receipt to use for looking up the purchase
|
|
|
+ * - return: either NotPurchased or Purchased
|
|
|
+ */
|
|
|
+ public class func verifyPurchase(
|
|
|
+ productId: String,
|
|
|
+ inReceipt receipt: ReceiptInfo
|
|
|
+ ) -> VerifyPurchaseResult {
|
|
|
+ return InAppReceipt.verifyPurchase(productId: productId, inReceipt: receipt)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Verify the purchase of a subscription (auto-renewable, free or non-renewing) in a receipt. This method extracts all transactions mathing the given productId and sorts them by date in descending order, then compares the first transaction expiry date against the validUntil value.
|
|
|
+ * - Parameter productId: the product id of the purchase to verify
|
|
|
+ * - Parameter inReceipt: the receipt to use for looking up the subscription
|
|
|
+ * - Parameter validUntil: date to check against the expiry date of the subscription. If nil, no verification
|
|
|
+ * - Parameter validDuration: the duration of the subscription. Only required for non-renewable subscription.
|
|
|
+ * - return: either NotPurchased or Purchased / Expired with the expiry date found in the receipt
|
|
|
+ */
|
|
|
+ public class func verifySubscription(
|
|
|
+ productId: String,
|
|
|
+ inReceipt receipt: ReceiptInfo,
|
|
|
+ validUntil date: Date = Date(),
|
|
|
+ validDuration duration: TimeInterval? = nil
|
|
|
+ ) -> VerifySubscriptionResult {
|
|
|
+ return InAppReceipt.verifySubscription(productId: productId, inReceipt: receipt, validUntil: date, validDuration: duration)
|
|
|
+ }
|
|
|
+}
|