浏览代码

Add ReceiptItem to VerifyPurchaseResult, VerifySubscriptionResult

Andrea Bizzotto 8 年之前
父节点
当前提交
b59bf91497
共有 3 个文件被更改,包括 195 次插入145 次删除
  1. 88 30
      SwiftyStoreKit/InAppReceipt.swift
  2. 26 3
      SwiftyStoreKit/SwiftyStoreKit+Types.swift
  3. 81 112
      SwiftyStoreKitTests/InAppReceiptTests.swift

+ 88 - 30
SwiftyStoreKit/InAppReceipt.swift

@@ -25,6 +25,59 @@
 
 import Foundation
 
+extension Date {
+
+    init?(millisecondsSince1970: String) {
+        guard let millisecondsNumber = Double(millisecondsSince1970) else {
+            return nil
+        }
+        self = Date(timeIntervalSince1970: millisecondsNumber / 1000)
+    }
+}
+
+extension ReceiptItem {
+
+    public init?(receiptInfo: ReceiptInfo) {
+        guard
+            let productId = receiptInfo["product_id"] as? String,
+            let quantityString = receiptInfo["quantity"] as? String,
+            let quantity = Int(quantityString),
+            let transactionId = receiptInfo["transaction_id"] as? String,
+            let originalTransactionId = receiptInfo["original_transaction_id"] as? String,
+            let purchaseDate = ReceiptItem.parseDate(from: receiptInfo, key: "purchase_date_ms"),
+            let originalPurchaseDate = ReceiptItem.parseDate(from: receiptInfo, key: "original_purchase_date_ms"),
+            let webOrderLineItemId = receiptInfo["web_order_line_item_id"] as? String
+            else {
+                print("could not parse receipt item: \(receiptInfo). Skipping...")
+                return nil
+        }
+        self.productId = productId
+        self.quantity = quantity
+        self.transactionId = transactionId
+        self.originalTransactionId = originalTransactionId
+        self.purchaseDate = purchaseDate
+        self.originalPurchaseDate = originalPurchaseDate
+        self.webOrderLineItemId = webOrderLineItemId
+        self.subscriptionExpirationDate = ReceiptItem.parseDate(from: receiptInfo, key: "expires_date_ms")
+        self.cancellationDate = ReceiptItem.parseDate(from: receiptInfo, key: "cancellation_date_ms")
+        if let isTrialPeriod = receiptInfo["is_trial_period"] as? String {
+            self.isTrialPeriod = Bool(isTrialPeriod) ?? false
+        } else {
+            self.isTrialPeriod = false
+        }
+    }
+
+    private static func parseDate(from receiptInfo: ReceiptInfo, key: String) -> Date? {
+
+        guard
+            let requestDateString = receiptInfo[key] as? String,
+            let requestDateMs = Double(requestDateString) else {
+                return nil
+        }
+        return Date(timeIntervalSince1970: requestDateMs / 1000)
+    }
+}
+
 // MARK - receipt mangement
 internal class InAppReceipt {
 
@@ -81,8 +134,12 @@ internal class InAppReceipt {
         let receiptsInfo = filterReceiptsInfo(receipts: receipts, withProductId: productId)
         let nonCancelledReceiptsInfo = receiptsInfo.filter { receipt in receipt["cancellation_date"] == nil }
 
+        let receiptItems = nonCancelledReceiptsInfo.flatMap { ReceiptItem(receiptInfo: $0) }
         // Verify that at least one receipt has the right product id
-        return nonCancelledReceiptsInfo.count >= 1 ? .purchased : .notPurchased
+        if let firstItem = receiptItems.first {
+            return .purchased(item: firstItem)
+        }
+        return .notPurchased
     }
 
     /**
@@ -101,8 +158,6 @@ internal class InAppReceipt {
         validUntil date: Date = Date()
     ) -> VerifySubscriptionResult {
 
-        // Verify that at least one receipt has the right product id
-
         // 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)
@@ -113,37 +168,41 @@ internal class InAppReceipt {
 
         let receiptDate = getReceiptRequestDate(inReceipt: receipt) ?? date
 
-        // Return the expires dates sorted desc
-        let expiryDateValues = nonCancelledReceiptsInfo
-            .flatMap { (receipt) -> String? in
+        let receiptItems = nonCancelledReceiptsInfo.flatMap { ReceiptItem(receiptInfo: $0) }
 
-                let key: String = duration != nil ? "original_purchase_date_ms" : "expires_date_ms"
-                return receipt[key] as? String
-            }
-            .flatMap { (dateString) -> Date? in
-                guard let doubleValue = Double(dateString) else { return nil }
-                // If duration is set, create an "expires date" value calculated from the original purchase date
-                let addedDuration = duration ?? 0
-                let expiryDateDouble = (doubleValue / 1000 + addedDuration)
-                return Date(timeIntervalSince1970: expiryDateDouble)
-            }
-            .sorted { (a, b) -> Bool in
-                // Sort by descending date order
-                return a.compare(b) == .orderedDescending
-            }
+        if nonCancelledReceiptsInfo.count > receiptItems.count {
+            print("receipt has \(nonCancelledReceiptsInfo.count) items, but only \(receiptItems.count) were parsed")
+        }
+
+        let sortedExpiryDatesAndItems = expiryDatesAndItems(receiptItems: receiptItems, duration: duration).sorted { a, b in
+            return a.0 > b.0
+        }
 
-        guard let firstExpiryDate = expiryDateValues.first else {
+        guard let firstExpiryDateItemPair = sortedExpiryDatesAndItems.first else {
             return .notPurchased
         }
 
-        // Check if at least 1 receipt is valid
-        if firstExpiryDate.compare(receiptDate) == .orderedDescending {
+        if firstExpiryDateItemPair.0 > receiptDate {
+            return .purchased(expiryDate: firstExpiryDateItemPair.0, item: firstExpiryDateItemPair.1)
+        } else {
+            return .expired(expiryDate: firstExpiryDateItemPair.0, item: firstExpiryDateItemPair.1)
+        }
+    }
+
+    private class func expiryDatesAndItems(receiptItems: [ReceiptItem], duration: TimeInterval?) -> [(Date, ReceiptItem)] {
 
-            // The subscription is valid
-            return .purchased(expiryDate: firstExpiryDate)
+        if let duration = duration {
+            return receiptItems.map {
+                let expirationDate = Date(timeIntervalSince1970: $0.originalPurchaseDate.timeIntervalSince1970 + duration)
+                return (expirationDate, $0)
+            }
         } else {
-            // The subscription is expired
-            return .expired(expiryDate: firstExpiryDate)
+            return receiptItems.flatMap {
+                if let expirationDate = $0.subscriptionExpirationDate {
+                    return (expirationDate, $0)
+                }
+                return nil
+            }
         }
     }
 
@@ -159,11 +218,10 @@ internal class InAppReceipt {
     private class func getReceiptRequestDate(inReceipt receipt: ReceiptInfo) -> Date? {
 
         guard let receiptInfo = receipt["receipt"] as? ReceiptInfo,
-            let requestDateString = receiptInfo["request_date_ms"] as? String,
-            let requestDateMs = Double(requestDateString) else {
+            let requestDateString = receiptInfo["request_date_ms"] as? String else {
             return nil
         }
-        return Date(timeIntervalSince1970: requestDateMs / 1000)
+        return Date(millisecondsSince1970: requestDateString)
     }
 
     /**

+ 26 - 3
SwiftyStoreKit/SwiftyStoreKit+Types.swift

@@ -85,14 +85,14 @@ public enum VerifyReceiptResult {
 
 // Result for Consumable and NonConsumable
 public enum VerifyPurchaseResult {
-    case purchased
+    case purchased(item: ReceiptItem)
     case notPurchased
 }
 
 // Verify subscription result
 public enum VerifySubscriptionResult {
-    case purchased(expiryDate: Date)
-    case expired(expiryDate: Date)
+    case purchased(expiryDate: Date, item: ReceiptItem)
+    case expired(expiryDate: Date, item: ReceiptItem)
     case notPurchased
 }
 
@@ -101,6 +101,29 @@ public enum SubscriptionType {
     case nonRenewing(validDuration: TimeInterval)
 }
 
+public struct ReceiptItem {
+    // The product identifier of the item that was purchased. This value corresponds to the productIdentifier property of the SKPayment object stored in the transaction’s payment property.
+    public let productId: String
+    // The number of items purchased. This value corresponds to the quantity property of the SKPayment object stored in the transaction’s payment property.
+    public let quantity: Int
+    // The transaction identifier of the item that was purchased. This value corresponds to the transaction’s transactionIdentifier property.
+    public let transactionId: String
+    // For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier. This value corresponds to the original transaction’s transactionIdentifier property. All receipts in a chain of renewals for an auto-renewable subscription have the same value for this field.
+    public let originalTransactionId: String
+    // The date and time that the item was purchased. This value corresponds to the transaction’s transactionDate property.
+    public let purchaseDate: Date
+    // For a transaction that restores a previous transaction, the date of the original transaction. This value corresponds to the original transaction’s transactionDate property. In an auto-renewable subscription receipt, this indicates the beginning of the subscription period, even if the subscription has been renewed.
+    public let originalPurchaseDate: Date
+    // The primary key for identifying subscription purchases.
+    public let webOrderLineItemId: String
+    // The expiration date for the subscription, expressed as the number of milliseconds since January 1, 1970, 00:00:00 GMT. This key is only present for auto-renewable subscription receipts.
+    public let subscriptionExpirationDate: Date?
+    // For a transaction that was canceled by Apple customer support, the time and date of the cancellation. Treat a canceled receipt the same as if no purchase had ever been made.
+    public let cancellationDate: Date?
+
+    public let isTrialPeriod: Bool
+}
+
 // Error when managing receipt
 public enum ReceiptError: Swift.Error {
     // No receipt data

+ 81 - 112
SwiftyStoreKitTests/InAppReceiptTests.swift

@@ -32,66 +32,9 @@ private extension TimeInterval {
     }
 }
 
-public struct ReceiptItem {
-    // The product identifier of the item that was purchased. This value corresponds to the productIdentifier property of the SKPayment object stored in the transaction’s payment property.
-    public let productId: String
-    // The number of items purchased. This value corresponds to the quantity property of the SKPayment object stored in the transaction’s payment property.
-    public let quantity: Int
-    // The transaction identifier of the item that was purchased. This value corresponds to the transaction’s transactionIdentifier property.
-    public let transactionId: String
-    // For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier. This value corresponds to the original transaction’s transactionIdentifier property. All receipts in a chain of renewals for an auto-renewable subscription have the same value for this field.
-    public let originalTransactionId: String
-    // The date and time that the item was purchased. This value corresponds to the transaction’s transactionDate property.
-    public let purchaseDate: Date
-    // For a transaction that restores a previous transaction, the date of the original transaction. This value corresponds to the original transaction’s transactionDate property. In an auto-renewable subscription receipt, this indicates the beginning of the subscription period, even if the subscription has been renewed.
-    public let originalPurchaseDate: Date
-    // The primary key for identifying subscription purchases.
-    public let webOrderLineItemId: String
-    // The expiration date for the subscription, expressed as the number of milliseconds since January 1, 1970, 00:00:00 GMT. This key is only present for auto-renewable subscription receipts.
-    public let subscriptionExpirationDate: Date?
-    // For a transaction that was canceled by Apple customer support, the time and date of the cancellation. Treat a canceled receipt the same as if no purchase had ever been made.
-    public let cancellationDate: Date?
-    
-    public let isTrialPeriod: Bool?
-    
-    public init?(receiptInfo: ReceiptInfo) {
-        guard
-            let productId = receiptInfo["product_id"] as? String,
-            let quantity = receiptInfo["quantity"] as? Int,
-            let transactionId = receiptInfo["transaction_id"] as? String,
-            let originalTransactionId = receiptInfo["original_transaction_id"] as? String,
-            let purchaseDate = ReceiptItem.parseDate(from: receiptInfo, key: "purchase_date_ms"),
-            let originalPurchaseDate = ReceiptItem.parseDate(from: receiptInfo, key: "original_purchase_date_ms"),
-            let webOrderLineItemId = receiptInfo["web_order_line_item_id"] as? String
-            else {
-                return nil
-        }
-        self.productId = productId
-        self.quantity = quantity
-        self.transactionId = transactionId
-        self.originalTransactionId = originalTransactionId
-        self.purchaseDate = purchaseDate
-        self.originalPurchaseDate = originalPurchaseDate
-        self.webOrderLineItemId = webOrderLineItemId
-        self.subscriptionExpirationDate = ReceiptItem.parseDate(from: receiptInfo, key: "expires_date_ms")
-        self.cancellationDate = ReceiptItem.parseDate(from: receiptInfo, key: "cancellation_date_ms")
-        self.isTrialPeriod = receiptInfo["is_trial_period"] as? Bool
-    }
-    
-    private static func parseDate(from receiptInfo: ReceiptInfo, key: String) -> Date? {
-        
-        guard
-            let requestDateString = receiptInfo[key] as? String,
-            let requestDateMs = Double(requestDateString) else {
-                return nil
-        }
-        return Date(timeIntervalSince1970: requestDateMs / 1000)
-    }
-}
-
-extension ReceiptItem {
+extension ReceiptItem: Equatable {
 
-    init(productId: String, purchaseDate: Date, subscriptionExpirationDate: Date? = nil, cancellationDate: Date? = nil, isTrialPeriod: Bool? = nil) {
+    init(productId: String, purchaseDate: Date, subscriptionExpirationDate: Date? = nil, cancellationDate: Date? = nil, isTrialPeriod: Bool = false) {
         self.productId = productId
         self.quantity = 1
         self.purchaseDate = purchaseDate
@@ -109,7 +52,11 @@ extension ReceiptItem {
             "product_id": productId as NSString,
             "quantity": String(quantity) as NSString,
             "purchase_date_ms": purchaseDate.timeIntervalSince1970.millisecondsNSString,
-            "original_purchase_date_ms": originalPurchaseDate.timeIntervalSince1970.millisecondsNSString
+            "original_purchase_date_ms": originalPurchaseDate.timeIntervalSince1970.millisecondsNSString,
+            "is_trial_period": (isTrialPeriod ? "1" : "0") as NSString,
+            "transaction_id": transactionId as NSString,
+            "original_transaction_id": originalTransactionId as NSString,
+            "web_order_line_item_id": webOrderLineItemId as NSString
         ]
         if let subscriptionExpirationDate = subscriptionExpirationDate {
             result["expires_date_ms"] = subscriptionExpirationDate.timeIntervalSince1970.millisecondsNSString
@@ -118,11 +65,19 @@ extension ReceiptItem {
             result["cancellation_date_ms"] = cancellationDate.timeIntervalSince1970.millisecondsNSString
             result["cancellation_date"] = cancellationDate as NSDate
         }
-        if let isTrialPeriod = isTrialPeriod {
-            result["is_trial_period"] = NSNumber(value: isTrialPeriod)
-        }
         return NSDictionary(dictionary: result)
     }
+    
+    public static func == (lhs: ReceiptItem, rhs: ReceiptItem) -> Bool {
+        return
+            lhs.productId == rhs.productId &&
+            lhs.quantity == rhs.quantity &&
+            lhs.purchaseDate == rhs.purchaseDate &&
+            lhs.originalPurchaseDate == rhs.originalPurchaseDate &&
+            lhs.subscriptionExpirationDate == rhs.subscriptionExpirationDate &&
+            lhs.cancellationDate == rhs.cancellationDate &&
+            lhs.isTrialPeriod == rhs.isTrialPeriod
+    }
 }
 
 extension VerifySubscriptionResult: Equatable {
@@ -130,8 +85,22 @@ extension VerifySubscriptionResult: Equatable {
     public static func == (lhs: VerifySubscriptionResult, rhs: VerifySubscriptionResult) -> Bool {
         switch (lhs, rhs) {
         case (.notPurchased, .notPurchased): return true
-        case (.purchased(let lhsExpiryDate), .purchased(let rhsExpiryDate)): return lhsExpiryDate == rhsExpiryDate
-        case (.expired(let lhsExpiryDate), .expired(let rhsExpiryDate)): return lhsExpiryDate == rhsExpiryDate
+        case (.purchased(let lhsExpiryDate, let lhsReceiptItem), .purchased(let rhsExpiryDate, let rhsReceiptItem)):
+            return lhsExpiryDate == rhsExpiryDate && lhsReceiptItem == rhsReceiptItem
+        case (.expired(let lhsExpiryDate, let lhsReceiptItem), .expired(let rhsExpiryDate, let rhsReceiptItem)):
+            return lhsExpiryDate == rhsExpiryDate && lhsReceiptItem == rhsReceiptItem
+        default: return false
+        }
+    }
+}
+
+extension VerifyPurchaseResult: Equatable {
+    
+    public static func == (lhs: VerifyPurchaseResult, rhs: VerifyPurchaseResult) -> Bool {
+        switch (lhs, rhs) {
+        case (.notPurchased, .notPurchased): return true
+        case (.purchased(let lhsReceiptItem), .purchased(let rhsReceiptItem)):
+            return lhsReceiptItem == rhsReceiptItem
         default: return false
         }
     }
@@ -141,55 +110,55 @@ class InAppReceiptTests: XCTestCase {
 
     // MARK: Verify Purchase
     func testVerifyPurchase_when_noPurchases_then_resultIsNotPurchased() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
-        
+
         let verifyPurchaseResult = SwiftyStoreKit.verifyPurchase(productId: productId, inReceipt: receipt)
-        
+
         XCTAssertEqual(verifyPurchaseResult, .notPurchased)
     }
     func testVerifyPurchase_when_onePurchase_then_resultIsPurchased() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let item = ReceiptItem(productId: productId, purchaseDate: receiptRequestDate, subscriptionExpirationDate: nil, cancellationDate: nil, isTrialPeriod: false)
         let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
-        
+
         let verifyPurchaseResult = SwiftyStoreKit.verifyPurchase(productId: productId, inReceipt: receipt)
-        
-        XCTAssertEqual(verifyPurchaseResult, .purchased)
+
+        XCTAssertEqual(verifyPurchaseResult, .purchased(item: item))
     }
     func testVerifyPurchase_when_oneCancelledPurchase_then_resultIsNotPurchased() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let item = ReceiptItem(productId: productId, purchaseDate: receiptRequestDate, subscriptionExpirationDate: nil, cancellationDate: receiptRequestDate, isTrialPeriod: false)
         let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
-        
+
         let verifyPurchaseResult = SwiftyStoreKit.verifyPurchase(productId: productId, inReceipt: receipt)
-        
+
         XCTAssertEqual(verifyPurchaseResult, .notPurchased)
     }
-    
+
     // MARK: Verify Subscription, single receipt item tests
     // auto-renewable, not purchased
     func testVerifyAutoRenewableSubscription_when_noSubscriptions_then_resultIsNotPurchased() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
-        
+
         let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .autoRenewable, productId: productId, inReceipt: receipt)
-        
+
         let expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
 
     // auto-renewable, expired
     func testVerifyAutoRenewableSubscription_when_oneExpiredSubscription_then_resultIsExpired() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 15)
         let productId = "product1"
         let isTrialPeriod = false
@@ -197,10 +166,10 @@ class InAppReceiptTests: XCTestCase {
         let expirationDate = purchaseDate.addingTimeInterval(60 * 60)
         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 expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: expirationDate)
+
+        let expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: expirationDate, item: item)
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
 
@@ -217,13 +186,13 @@ class InAppReceiptTests: XCTestCase {
 
         let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .autoRenewable, productId: productId, inReceipt: receipt)
 
-        let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: expirationDate)
+        let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: expirationDate, item: item)
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
 
     // auto-renewable, cancelled
     func testVerifyAutoRenewableSubscription_when_oneCancelledSubscription_then_resultIsNotPurchased() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let isTrialPeriod = false
@@ -232,29 +201,29 @@ class InAppReceiptTests: XCTestCase {
         let cancelledDate = purchaseDate.addingTimeInterval(30 * 60)
         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 expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
-    
+
     // non-renewing, non purchased
     func testVerifyNonRenewingSubscription_when_noSubscriptions_then_resultIsNotPurchased() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
-        
+
         let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .nonRenewing(validDuration: 60 * 60), productId: productId, inReceipt: receipt)
-        
+
         let expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
-    
+
     // non-renewing, expired
     func testVerifyNonRenewingSubscription_when_oneExpiredSubscription_then_resultIsExpired() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 15)
         let productId = "product1"
         let isTrialPeriod = false
@@ -264,16 +233,16 @@ 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 expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: expirationDate)
+
+        let expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: expirationDate, item: item)
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
 
     // non-renewing, purchased
     func testVerifyNonRenewingSubscription_when_oneNonExpiredSubscription_then_resultIsPurchased() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let isTrialPeriod = false
@@ -283,16 +252,16 @@ 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 expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: expirationDate)
+
+        let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: expirationDate, item: item)
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
 
     // non-renewing, cancelled
     func testVerifyNonRenewingSubscription_when_oneCancelledSubscription_then_resultIsNotPurchased() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let isTrialPeriod = false
@@ -301,21 +270,21 @@ class InAppReceiptTests: XCTestCase {
         let cancelledDate = purchaseDate.addingTimeInterval(30 * 60)
         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 expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
 
     // MARK: Verify Subscription, multiple receipt item tests
     func verifyAutoRenewableSubscription_when_twoSubscriptions_sameProductId_mostRecentNonExpired_then_resultIsPurchased() {
-        
+
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
 
         let productId = "product1"
         let isTrialPeriod = false
-        
+
         let olderPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 12)
         let olderExpirationDate = olderPurchaseDate.addingTimeInterval(60 * 60)
         let olderItem = ReceiptItem(productId: productId,
@@ -323,7 +292,7 @@ class InAppReceiptTests: XCTestCase {
                                subscriptionExpirationDate: olderExpirationDate,
                                cancellationDate: nil,
                                isTrialPeriod: isTrialPeriod)
-        
+
         let newerPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let newerExpirationDate = olderPurchaseDate.addingTimeInterval(60 * 60)
         let newerItem = ReceiptItem(productId: productId,
@@ -331,12 +300,12 @@ class InAppReceiptTests: XCTestCase {
                                     subscriptionExpirationDate: newerExpirationDate,
                                     cancellationDate: nil,
                                     isTrialPeriod: isTrialPeriod)
-        
+
         let receipt = makeReceipt(items: [olderItem, newerItem], requestDate: receiptRequestDate)
-        
+
         let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(type: .autoRenewable, productId: productId, inReceipt: receipt)
-        
-        let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: newerExpirationDate)
+
+        let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: newerExpirationDate, item: newerItem)
         XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
     }
 
@@ -344,11 +313,11 @@ class InAppReceiptTests: XCTestCase {
     func makeReceipt(items: [ReceiptItem], requestDate: Date) -> [String: AnyObject] {
 
         let receiptInfos = items.map { $0.receiptInfo }
-        
+
         // Creating this with NSArray results in __NSSingleObjectArrayI which fails the cast to [String: AnyObject]
         let array = NSMutableArray()
         array.addObjects(from: receiptInfos)
-        
+
         return [
             //"latest_receipt": [:],
             "status": "200" as NSString,