浏览代码

Merge pull request #333 from bizz84/verify-subscriptions

Add verifySubscriptions method to check all subscriptions in a group at once
Andrea Bizzotto 7 年之前
父节点
当前提交
28285c7352

+ 3 - 0
CHANGELOG.md

@@ -2,7 +2,10 @@
 
 All notable changes to this project will be documented in this file.
 
+## [0.12.0](https://github.com/bizz84/SwiftyStoreKit/releases/tag/0.12.0) Add `verifySubscriptions` method for subscription groups 
 
+* Add `verifySubscriptions` method to check all subscriptions in a group at once ([#333](https://github.com/bizz84/SwiftyStoreKit/pull/333), related issue: [#194](https://github.com/bizz84/SwiftyStoreKit/issues/194))
+* Rename `verifySubscription(type:productId:inReceipt:validUntil:)` to `verifySubscription(ofType:productId:inReceipt:validUntil:)` ([#333](https://github.com/bizz84/SwiftyStoreKit/pull/333))
 * Add video tutorials section in README ([#328](https://github.com/bizz84/SwiftyStoreKit/pull/328), [#330](https://github.com/bizz84/SwiftyStoreKit/pull/330), see [#326](https://github.com/bizz84/SwiftyStoreKit/issues/326))
 * Update iOS Demo App ([#327](https://github.com/bizz84/SwiftyStoreKit/pull/327), see [#147](https://github.com/bizz84/SwiftyStoreKit/issues/147))
 

+ 34 - 4
README.md

@@ -394,7 +394,7 @@ SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
         let productId = "com.musevisions.SwiftyStoreKit.Subscription"
         // Verify the purchase of a Subscription
         let purchaseResult = SwiftyStoreKit.verifySubscription(
-            type: .autoRenewable, // or .nonRenewing (see below)
+            ofType: .autoRenewable, // or .nonRenewing (see below)
             productId: productId,
             inReceipt: receipt)
             
@@ -416,7 +416,7 @@ SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
 #### Auto-Renewable
 ```swift
 let purchaseResult = SwiftyStoreKit.verifySubscription(
-            type: .autoRenewable,
+            ofType: .autoRenewable,
             productId: "com.musevisions.SwiftyStoreKit.Subscription",
             inReceipt: receipt)
 ```
@@ -425,7 +425,7 @@ let purchaseResult = SwiftyStoreKit.verifySubscription(
 ```swift
 // validDuration: time interval in seconds
 let purchaseResult = SwiftyStoreKit.verifySubscription(
-            type: .nonRenewing(validDuration: 3600 * 24 * 30),
+            ofType: .nonRenewing(validDuration: 3600 * 24 * 30),
             productId: "com.musevisions.SwiftyStoreKit.Subscription",
             inReceipt: receipt)
 ```
@@ -454,7 +454,7 @@ SwiftyStoreKit.purchaseProduct(productId, atomically: true) { result in
             
             if case .success(let receipt) = result {
                 let purchaseResult = SwiftyStoreKit.verifySubscription(
-                    type: .autoRenewable,
+                    ofType: .autoRenewable,
                     productId: productId,
                     inReceipt: receipt)
                 
@@ -477,6 +477,36 @@ SwiftyStoreKit.purchaseProduct(productId, atomically: true) { result in
 }
 ```
 
+### Subscription Groups
+
+From [Apple Docs - Offering Subscriptions](https://developer.apple.com/app-store/subscriptions/):
+
+> A subscription group is a set of in-app purchases that you can create to provide users with a range of content offerings, service levels, or durations to best meet their needs. Users can only buy one subscription within a subscription group at a time. If users would want to buy more that one type of subscription — for example, to subscribe to more than one channel in a streaming app — you can put these in-app purchases in different subscription groups.
+
+You can verify all subscriptions within the same group with the `verifySubscriptions` method:
+
+```swift
+let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
+SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
+    switch result {
+    case .success(let receipt):
+        let productIds = Set([ "com.musevisions.SwiftyStoreKit.Weekly",
+                               "com.musevisions.SwiftyStoreKit.Monthly",
+                               "com.musevisions.SwiftyStoreKit.Yearly" ])
+        let purchaseResult = SwiftyStoreKit.verifySubscriptions(productIds: productIds, inReceipt: receipt)
+        switch purchaseResult {
+        case .purchased(let expiryDate, let items):
+            print("\(productIds) are valid until \(expiryDate)\n\(items)\n")
+        case .expired(let expiryDate, let items):
+            print("\(productIds) are expired since \(expiryDate)\n\(items)\n")
+        case .notPurchased:
+            print("The user has never purchased \(productIds)")
+        }
+    case .error(let error):
+        print("Receipt verification failed: \(error)")
+    }
+}
+```
 
 ## Notes
 The framework provides a simple block based API with robust error handling on top of the existing StoreKit framework. It does **NOT** persist in app purchases data locally. It is up to clients to do this with a storage solution of choice (i.e. NSUserDefaults, CoreData, Keychain).

+ 30 - 18
SwiftyStoreKit-iOS-Demo/ViewController.swift

@@ -113,7 +113,7 @@ class ViewController: UIViewController {
         purchase(autoRenewableSubscription, atomically: autoRenewableIsAtomic)
     }
     @IBAction func autoRenewableVerifyPurchase() {
-        verifyPurchase(autoRenewableSubscription)
+        verifySubscriptions([.autoRenewableWeekly, .autoRenewableMonthly, .autoRenewableYearly])
     }
 
     func getInfo(_ purchase: RegisteredPurchase) {
@@ -172,7 +172,7 @@ class ViewController: UIViewController {
         let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
         SwiftyStoreKit.verifyReceipt(using: appleValidator, completion: completion)
     }
-
+    
     func verifyPurchase(_ purchase: RegisteredPurchase) {
 
         NetworkActivityIndicatorManager.networkOperationStarted()
@@ -187,25 +187,20 @@ class ViewController: UIViewController {
                 switch purchase {
                 case .autoRenewableWeekly, .autoRenewableMonthly, .autoRenewableYearly:
                     let purchaseResult = SwiftyStoreKit.verifySubscription(
-                        type: .autoRenewable,
+                        ofType: .autoRenewable,
                         productId: productId,
-                        inReceipt: receipt,
-                        validUntil: Date()
-                    )
-                    self.showAlert(self.alertForVerifySubscription(purchaseResult, productId: productId))
+                        inReceipt: receipt)
+                    self.showAlert(self.alertForVerifySubscriptions(purchaseResult, productIds: [productId]))
                 case .nonRenewingPurchase:
                     let purchaseResult = SwiftyStoreKit.verifySubscription(
-                        type: .nonRenewing(validDuration: 60),
+                        ofType: .nonRenewing(validDuration: 60),
                         productId: productId,
-                        inReceipt: receipt,
-                        validUntil: Date()
-                    )
-                    self.showAlert(self.alertForVerifySubscription(purchaseResult, productId: productId))
+                        inReceipt: receipt)
+                    self.showAlert(self.alertForVerifySubscriptions(purchaseResult, productIds: [productId]))
                 default:
                     let purchaseResult = SwiftyStoreKit.verifyPurchase(
                         productId: productId,
-                        inReceipt: receipt
-                    )
+                        inReceipt: receipt)
                     self.showAlert(self.alertForVerifyPurchase(purchaseResult, productId: productId))
                 }
 
@@ -214,6 +209,23 @@ class ViewController: UIViewController {
             }
         }
     }
+    
+    func verifySubscriptions(_ purchases: Set<RegisteredPurchase>) {
+        
+        NetworkActivityIndicatorManager.networkOperationStarted()
+        verifyReceipt { result in
+            NetworkActivityIndicatorManager.networkOperationFinished()
+            
+            switch result {
+            case .success(let receipt):
+                let productIds = Set(purchases.map { self.appBundleId + "." + $0.rawValue })
+                let purchaseResult = SwiftyStoreKit.verifySubscriptions(productIds: productIds, inReceipt: receipt)
+                self.showAlert(self.alertForVerifySubscriptions(purchaseResult, productIds: productIds))
+            case .error:
+                self.showAlert(self.alertForVerifyReceipt(result))
+            }
+        }
+    }
 
 #if os(iOS)
     override var preferredStatusBarStyle: UIStatusBarStyle {
@@ -315,17 +327,17 @@ extension ViewController {
         }
     }
 
-    func alertForVerifySubscription(_ result: VerifySubscriptionResult, productId: String) -> UIAlertController {
+    func alertForVerifySubscriptions(_ result: VerifySubscriptionResult, productIds: Set<String>) -> UIAlertController {
 
         switch result {
         case .purchased(let expiryDate, let items):
-            print("\(productId) is valid until \(expiryDate)\n\(items)\n")
+            print("\(productIds) is valid until \(expiryDate)\n\(items)\n")
             return alertWithTitle("Product is purchased", message: "Product is valid until \(expiryDate)")
         case .expired(let expiryDate, let items):
-            print("\(productId) is expired since \(expiryDate)\n\(items)\n")
+            print("\(productIds) is expired since \(expiryDate)\n\(items)\n")
             return alertWithTitle("Product expired", message: "Product is expired since \(expiryDate)")
         case .notPurchased:
-            print("\(productId) has never been purchased")
+            print("\(productIds) has never been purchased")
             return alertWithTitle("Not purchased", message: "This product has never been purchased")
         }
     }

+ 5 - 8
SwiftyStoreKit-macOS-Demo/ViewController.swift

@@ -128,23 +128,20 @@ class ViewController: NSViewController {
                 switch purchase {
                 case .autoRenewablePurchase:
                     let purchaseResult = SwiftyStoreKit.verifySubscription(
-                        type: .autoRenewable,
+                        ofType: .autoRenewable,
                         productId: productId,
-                        inReceipt: receipt
-                    )
+                        inReceipt: receipt)
                     self.showAlert(self.alertForVerifySubscription(purchaseResult))
                 case .nonRenewingPurchase:
                     let purchaseResult = SwiftyStoreKit.verifySubscription(
-                        type: .nonRenewing(validDuration: 60),
+                        ofType: .nonRenewing(validDuration: 60),
                         productId: productId,
-                        inReceipt: receipt
-                    )
+                        inReceipt: receipt)
                     self.showAlert(self.alertForVerifySubscription(purchaseResult))
                 default:
                     let purchaseResult = SwiftyStoreKit.verifyPurchase(
                         productId: productId,
-                        inReceipt: receipt
-                    )
+                        inReceipt: receipt)
                     self.showAlert(self.alertForVerifyPurchase(purchaseResult))
                 }
 

+ 23 - 18
SwiftyStoreKit/InAppReceipt.swift

@@ -93,7 +93,7 @@ internal class InAppReceipt {
 
         // Get receipts info for the product
         let receipts = getInAppReceipts(receipt: receipt)
-        let filteredReceiptsInfo = filterReceiptsInfo(receipts: receipts, withProductId: productId)
+        let filteredReceiptsInfo = filterReceiptsInfo(receipts: receipts, withProductIds: [productId])
         let nonCancelledReceiptsInfo = filteredReceiptsInfo.filter { receipt in receipt["cancellation_date"] == nil }
 
         let receiptItems = nonCancelledReceiptsInfo.flatMap { ReceiptItem(receiptInfo: $0) }
@@ -105,24 +105,27 @@ internal class InAppReceipt {
     }
 
     /**
-     *  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 type: .autoRenewable or .nonRenewing(duration)
-     *  - 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
+     *  Verify the validity of a set of subscriptions in a receipt.
+     *
+     *  This method extracts all transactions matching the given productIds and sorts them by date in descending order. It then compares the first transaction expiry date against the receipt date, to determine its validity.
+     *  - Note: You can use this method to check the validity of (mutually exclusive) subscriptions in a subscription group.
+     *  - Remark: The type parameter determines how the expiration dates are calculated for all subscriptions. Make sure all productIds match the specified subscription type to avoid incorrect results.
+     *  - Parameter type: .autoRenewable or .nonRenewing.
+     *  - Parameter productIds: The product ids of the subscriptions to verify.
+     *  - Parameter receipt: The receipt to use for looking up the subscriptions
+     *  - Parameter validUntil: Date to check against the expiry date of the subscriptions. This is only used if a date is not found in the receipt.
+     *  - return: Either .notPurchased or .purchased / .expired with the expiry date found in the receipt.
      */
-    class func verifySubscription(
-        type: SubscriptionType,
-        productId: String,
+    class func verifySubscriptions(
+        ofType type: SubscriptionType,
+        productIds: Set<String>,
         inReceipt receipt: ReceiptInfo,
         validUntil date: Date = Date()
     ) -> VerifySubscriptionResult {
 
         // The values of the latest_receipt and latest_receipt_info keys are useful when checking whether an auto-renewable subscription is currently active. By providing any transaction receipt for the subscription and checking these values, you can get information about the currently-active subscription period. If the receipt being validated is for the latest renewal, the value for latest_receipt is the same as receipt-data (in the request) and the value for latest_receipt_info is the same as receipt.
         let (receipts, duration) = getReceiptsAndDuration(for: type, inReceipt: receipt)
-        let receiptsInfo = filterReceiptsInfo(receipts: receipts, withProductId: productId)
+        let receiptsInfo = filterReceiptsInfo(receipts: receipts, withProductIds: productIds)
         let nonCancelledReceiptsInfo = receiptsInfo.filter { receipt in receipt["cancellation_date"] == nil }
         if nonCancelledReceiptsInfo.count == 0 {
             return .notPurchased
@@ -198,19 +201,21 @@ internal class InAppReceipt {
      *  - Parameter receipts: the receipts array to grab info from
      *  - Parameter productId: the product id
      */
-    private class func filterReceiptsInfo(receipts: [ReceiptInfo]?, withProductId productId: String) -> [ReceiptInfo] {
+    private class func filterReceiptsInfo(receipts: [ReceiptInfo]?, withProductIds productIds: Set<String>) -> [ReceiptInfo] {
 
         guard let receipts = receipts else {
             return []
         }
 
-        // Filter receipts with matching product id
-        let receiptsMatchingProductId = receipts
+        // Filter receipts with matching product ids
+        let receiptsMatchingProductIds = receipts
             .filter { (receipt) -> Bool in
-                let product_id = receipt["product_id"] as? String
-                return product_id == productId
+                if let productId = receipt["product_id"] as? String {
+                    return productIds.contains(productId)
+                }
+                return false
             }
 
-        return receiptsMatchingProductId
+        return receiptsMatchingProductIds
     }
 }

+ 27 - 8
SwiftyStoreKit/SwiftyStoreKit.swift

@@ -262,15 +262,34 @@ extension SwiftyStoreKit {
     }
 
     /**
-     *  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 type: autoRenewable or nonRenewing
-     *  - 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
-     *  - return: either .notPurchased or .purchased / .expired with the expiry date found in the receipt
+     *  Verify the validity of a subscription (auto-renewable, free or non-renewing) in a receipt.
+     *
+     *  This method extracts all transactions matching the given productId and sorts them by date in descending order. It then compares the first transaction expiry date against the receipt date to determine its validity.
+     *  - Parameter type: .autoRenewable or .nonRenewing.
+     *  - Parameter productId: The product id of the subscription to verify.
+     *  - Parameter receipt: The receipt to use for looking up the subscription.
+     *  - Parameter validUntil: Date to check against the expiry date of the subscription. This is only used if a date is not found in the receipt.
+     *  - return: Either .notPurchased or .purchased / .expired with the expiry date found in the receipt.
+     */
+    public class func verifySubscription(ofType type: SubscriptionType, productId: String, inReceipt receipt: ReceiptInfo, validUntil date: Date = Date()) -> VerifySubscriptionResult {
+
+        return InAppReceipt.verifySubscriptions(ofType: type, productIds: [productId], inReceipt: receipt, validUntil: date)
+    }
+    
+    /**
+     *  Verify the validity of a set of subscriptions in a receipt.
+     *
+     *  This method extracts all transactions matching the given productIds and sorts them by date in descending order. It then compares the first transaction expiry date against the receipt date, to determine its validity.
+     *  - Note: You can use this method to check the validity of (mutually exclusive) subscriptions in a subscription group.
+     *  - Remark: The type parameter determines how the expiration dates are calculated for all subscriptions. Make sure all productIds match the specified subscription type to avoid incorrect results.
+     *  - Parameter type: .autoRenewable or .nonRenewing.
+     *  - Parameter productIds: The product ids of the subscriptions to verify.
+     *  - Parameter receipt: The receipt to use for looking up the subscriptions
+     *  - Parameter validUntil: Date to check against the expiry date of the subscriptions. This is only used if a date is not found in the receipt.
+     *  - return: Either .notPurchased or .purchased / .expired with the expiry date found in the receipt.
      */
-    public class func verifySubscription(type: SubscriptionType, productId: String, inReceipt receipt: ReceiptInfo, validUntil date: Date = Date()) -> VerifySubscriptionResult {
+    public class func verifySubscriptions(ofType type: SubscriptionType = .autoRenewable, productIds: Set<String>, inReceipt receipt: ReceiptInfo, validUntil date: Date = Date()) -> VerifySubscriptionResult {
 
-        return InAppReceipt.verifySubscription(type: type, productId: productId, inReceipt: receipt, validUntil: date)
+        return InAppReceipt.verifySubscriptions(ofType: type, productIds: productIds, inReceipt: receipt, validUntil: date)
     }
 }

+ 57 - 14
SwiftyStoreKitTests/InAppReceiptTests.swift

@@ -26,6 +26,7 @@
 import XCTest
 import SwiftyStoreKit
 
+// swiftlint:disable file_length
 private extension TimeInterval {
     var millisecondsNSString: NSString {
         return String(format: "%.0f", self * 1000) as NSString
@@ -149,7 +150,7 @@ class InAppReceiptTests: XCTestCase {
         let productId = "product1"
         let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
 
-        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .autoRenewable, productId: productId, inReceipt: receipt)
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
 
         let expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
@@ -166,7 +167,7 @@ class InAppReceiptTests: XCTestCase {
         let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: expirationDate, cancellationDate: nil, isTrialPeriod: isTrialPeriod)
         let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
 
-        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .autoRenewable, productId: productId, inReceipt: receipt)
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
 
         let expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: expirationDate, items: [item])
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
@@ -183,7 +184,7 @@ class InAppReceiptTests: XCTestCase {
         let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: expirationDate, cancellationDate: nil, isTrialPeriod: isTrialPeriod)
         let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
 
-        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .autoRenewable, productId: productId, inReceipt: receipt)
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
 
         let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: expirationDate, items: [item])
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
@@ -201,7 +202,7 @@ class InAppReceiptTests: XCTestCase {
         let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: expirationDate, cancellationDate: cancelledDate, isTrialPeriod: isTrialPeriod)
         let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
 
-        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .autoRenewable, productId: productId, inReceipt: receipt)
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
 
         let expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
@@ -214,7 +215,7 @@ class InAppReceiptTests: XCTestCase {
         let productId = "product1"
         let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
 
-        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .nonRenewing(validDuration: 60 * 60), productId: productId, inReceipt: receipt)
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .nonRenewing(validDuration: 60 * 60), productId: productId, inReceipt: receipt)
 
         let expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
@@ -233,7 +234,7 @@ class InAppReceiptTests: XCTestCase {
         let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: nil, cancellationDate: nil, isTrialPeriod: isTrialPeriod)
         let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
 
-        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .nonRenewing(validDuration: duration), productId: productId, inReceipt: receipt)
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .nonRenewing(validDuration: duration), productId: productId, inReceipt: receipt)
 
         let expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: expirationDate, items: [item])
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
@@ -252,7 +253,7 @@ class InAppReceiptTests: XCTestCase {
         let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: nil, cancellationDate: nil, isTrialPeriod: isTrialPeriod)
         let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
 
-        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .nonRenewing(validDuration: duration), productId: productId, inReceipt: receipt)
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .nonRenewing(validDuration: duration), productId: productId, inReceipt: receipt)
 
         let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: expirationDate, items: [item])
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
@@ -270,14 +271,14 @@ class InAppReceiptTests: XCTestCase {
         let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: nil, cancellationDate: cancelledDate, isTrialPeriod: isTrialPeriod)
         let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
 
-        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .nonRenewing(validDuration: duration), productId: productId, inReceipt: receipt)
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .nonRenewing(validDuration: duration), productId: productId, inReceipt: receipt)
 
         let expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
 
     // MARK: Verify Subscription, multiple receipt item tests
-    func verifyAutoRenewableSubscription_when_twoSubscriptions_sameProductId_mostRecentNonExpired_then_resultIsPurchased_itemsSorted() {
+    func testVerifyAutoRenewableSubscription_when_twoSubscriptions_sameProductId_mostRecentNonExpired_then_resultIsPurchased_itemsSorted() {
 
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
 
@@ -293,7 +294,7 @@ class InAppReceiptTests: XCTestCase {
                                isTrialPeriod: isTrialPeriod)
 
         let newerPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
-        let newerExpirationDate = olderPurchaseDate.addingTimeInterval(60 * 60)
+        let newerExpirationDate = newerPurchaseDate.addingTimeInterval(60 * 60)
         let newerItem = ReceiptItem(productId: productId,
                                     purchaseDate: newerPurchaseDate,
                                     subscriptionExpirationDate: newerExpirationDate,
@@ -302,13 +303,13 @@ class InAppReceiptTests: XCTestCase {
 
         let receipt = makeReceipt(items: [olderItem, newerItem], requestDate: receiptRequestDate)
 
-        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .autoRenewable, productId: productId, inReceipt: receipt)
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
 
         let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: newerExpirationDate, items: [newerItem, olderItem])
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
 
-    func verifyAutoRenewableSubscription_when_twoSubscriptions_sameProductId_bothExpired_then_resultIsExpired_itemsSorted() {
+    func testVerifyAutoRenewableSubscription_when_twoSubscriptions_sameProductId_bothExpired_then_resultIsExpired_itemsSorted() {
         
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         
@@ -324,7 +325,7 @@ class InAppReceiptTests: XCTestCase {
                                     isTrialPeriod: isTrialPeriod)
         
         let newerPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 13)
-        let newerExpirationDate = olderPurchaseDate.addingTimeInterval(60 * 60)
+        let newerExpirationDate = newerPurchaseDate.addingTimeInterval(60 * 60)
         let newerItem = ReceiptItem(productId: productId,
                                     purchaseDate: newerPurchaseDate,
                                     subscriptionExpirationDate: newerExpirationDate,
@@ -333,11 +334,53 @@ class InAppReceiptTests: XCTestCase {
         
         let receipt = makeReceipt(items: [olderItem, newerItem], requestDate: receiptRequestDate)
         
-        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .autoRenewable, productId: productId, inReceipt: receipt)
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
         
         let expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: newerExpirationDate, items: [newerItem, olderItem])
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
+    
+    // MARK: Verify Subscriptions, multiple receipt item tests
+    func testVerifyAutoRenewableSubscriptions_when_threeSubscriptions_twoMatchingProductIds_mostRecentNonExpired_then_resultIsPurchased_itemsSorted() {
+        
+        let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
+        
+        let productId1 = "product1"
+        let productId2 = "product2"
+        let productIds = Set([ productId1, productId2 ])
+        let isTrialPeriod = false
+        
+        let olderPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 12)
+        let olderExpirationDate = olderPurchaseDate.addingTimeInterval(60 * 60)
+        let olderItem = ReceiptItem(productId: productId1,
+                                    purchaseDate: olderPurchaseDate,
+                                    subscriptionExpirationDate: olderExpirationDate,
+                                    cancellationDate: nil,
+                                    isTrialPeriod: isTrialPeriod)
+        
+        let newerPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
+        let newerExpirationDate = newerPurchaseDate.addingTimeInterval(60 * 60)
+        let newerItem = ReceiptItem(productId: productId2,
+                                    purchaseDate: newerPurchaseDate,
+                                    subscriptionExpirationDate: newerExpirationDate,
+                                    cancellationDate: nil,
+                                    isTrialPeriod: isTrialPeriod)
+        
+        let otherPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 15)
+        let otherExpirationDate = otherPurchaseDate.addingTimeInterval(60 * 60)
+        let otherItem = ReceiptItem(productId: "otherProduct",
+                                    purchaseDate: otherPurchaseDate,
+                                    subscriptionExpirationDate: otherExpirationDate,
+                                    cancellationDate: nil,
+                                    isTrialPeriod: isTrialPeriod)
+        
+        let receipt = makeReceipt(items: [olderItem, newerItem, otherItem], requestDate: receiptRequestDate)
+        
+        let verifySubscriptionResult = SwiftyStoreKit.verifySubscriptions(ofType: .autoRenewable, productIds: productIds, inReceipt: receipt)
+        
+        let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: newerExpirationDate, items: [newerItem, olderItem])
+        XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
+    }
 
     // MARK: Helper methods
     func makeReceipt(items: [ReceiptItem], requestDate: Date) -> [String: AnyObject] {