InAppReceiptTests.swift 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. //
  2. // InAppReceiptTests.swift
  3. // SwiftyStoreKit
  4. //
  5. // Created by Andrea Bizzotto on 08/05/2017.
  6. // Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com)
  7. //
  8. // Permission is hereby granted, free of charge, to any person obtaining a copy
  9. // of this software and associated documentation files (the "Software"), to deal
  10. // in the Software without restriction, including without limitation the rights
  11. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. // copies of the Software, and to permit persons to whom the Software is
  13. // furnished to do so, subject to the following conditions:
  14. //
  15. // The above copyright notice and this permission notice shall be included in
  16. // all copies or substantial portions of the Software.
  17. //
  18. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. // THE SOFTWARE.
  25. import XCTest
  26. import SwiftyStoreKit
  27. private extension TimeInterval {
  28. var millisecondsNSString: NSString {
  29. return String(format: "%.0f", self * 1000) as NSString
  30. }
  31. }
  32. extension ReceiptItem: Equatable {
  33. init(productId: String, purchaseDate: Date, subscriptionExpirationDate: Date? = nil, cancellationDate: Date? = nil, transactionId: String? = nil, isTrialPeriod: Bool = false, isInIntroOfferPeriod: Bool = false) {
  34. self.init(productId: productId, quantity: 1, transactionId: UUID().uuidString, originalTransactionId: UUID().uuidString, purchaseDate: purchaseDate, originalPurchaseDate: purchaseDate, webOrderLineItemId: UUID().uuidString, subscriptionExpirationDate: subscriptionExpirationDate, cancellationDate: cancellationDate, isTrialPeriod: isTrialPeriod, isInIntroOfferPeriod: isInIntroOfferPeriod)
  35. self.productId = productId
  36. self.quantity = 1
  37. self.purchaseDate = purchaseDate
  38. self.originalPurchaseDate = purchaseDate
  39. self.subscriptionExpirationDate = subscriptionExpirationDate
  40. self.cancellationDate = cancellationDate
  41. self.transactionId = transactionId ?? UUID().uuidString
  42. self.originalTransactionId = UUID().uuidString
  43. self.webOrderLineItemId = UUID().uuidString
  44. self.isTrialPeriod = isTrialPeriod
  45. self.isInIntroOfferPeriod = isInIntroOfferPeriod
  46. }
  47. var receiptInfo: NSDictionary {
  48. var result: [String: AnyObject] = [
  49. "product_id": productId as NSString,
  50. "quantity": String(quantity) as NSString,
  51. "purchase_date_ms": purchaseDate.timeIntervalSince1970.millisecondsNSString,
  52. "original_purchase_date_ms": originalPurchaseDate.timeIntervalSince1970.millisecondsNSString,
  53. "is_trial_period": (isTrialPeriod ? "1" : "0") as NSString,
  54. "transaction_id": transactionId as NSString,
  55. "original_transaction_id": originalTransactionId as NSString
  56. ]
  57. if let subscriptionExpirationDate = subscriptionExpirationDate {
  58. result["expires_date_ms"] = subscriptionExpirationDate.timeIntervalSince1970.millisecondsNSString
  59. }
  60. if let cancellationDate = cancellationDate {
  61. result["cancellation_date_ms"] = cancellationDate.timeIntervalSince1970.millisecondsNSString
  62. result["cancellation_date"] = cancellationDate as NSDate
  63. }
  64. return NSDictionary(dictionary: result)
  65. }
  66. public static func == (lhs: ReceiptItem, rhs: ReceiptItem) -> Bool {
  67. return
  68. lhs.productId == rhs.productId &&
  69. lhs.quantity == rhs.quantity &&
  70. lhs.purchaseDate == rhs.purchaseDate &&
  71. lhs.originalPurchaseDate == rhs.originalPurchaseDate &&
  72. lhs.subscriptionExpirationDate == rhs.subscriptionExpirationDate &&
  73. lhs.cancellationDate == rhs.cancellationDate &&
  74. lhs.isTrialPeriod == rhs.isTrialPeriod
  75. }
  76. }
  77. extension PendingRenewalInfo: Equatable {
  78. init(productId: String, expiryDate: Date, originalTransactionId: String) {
  79. self.init(autoRenewProductId: productId, autoRenewStatus: .willRenew, expirationIntent: nil, gracePeriodExpiresDate: nil, gracePeriodExpiresDateMS: expiryDate.timeIntervalSince1970.millisecondsNSString as String, gracePeriodExpiresDatePST: nil, isInBillingRetryPeriod: nil, offerCodeRefName: nil, originalTransactionId: originalTransactionId, priceConsentStatus: nil, productId: productId, promotionalOfferId: nil)
  80. }
  81. var receiptInfo: NSDictionary {
  82. var result: [String: AnyObject] = [
  83. "auto_renew_product_id": productId as NSString,
  84. "auto_renew_status": autoRenewStatus.rawValue as NSString,
  85. "product_id": productId as NSString,
  86. "original_transaction_id": originalTransactionId as NSString
  87. ]
  88. if let gracePeriodExpiresDateMS = gracePeriodExpiresDateMS {
  89. result["grace_period_expires_date_ms"] = gracePeriodExpiresDateMS as NSString
  90. }
  91. return NSDictionary(dictionary: result)
  92. }
  93. public static func == (lhs: PendingRenewalInfo, rhs: PendingRenewalInfo) -> Bool {
  94. return
  95. lhs.productId == rhs.productId &&
  96. lhs.autoRenewProductId == rhs.autoRenewProductId &&
  97. lhs.originalTransactionId == rhs.originalTransactionId &&
  98. lhs.gracePeriodExpiresDateMS == rhs.gracePeriodExpiresDateMS
  99. }
  100. }
  101. extension VerifySubscriptionResult: Equatable {
  102. public static func == (lhs: VerifySubscriptionResult, rhs: VerifySubscriptionResult) -> Bool {
  103. switch (lhs, rhs) {
  104. case (.notPurchased, .notPurchased): return true
  105. case (.purchased(let lhsExpiryDate, let lhsReceiptItem), .purchased(let rhsExpiryDate, let rhsReceiptItem)):
  106. return lhsExpiryDate == rhsExpiryDate && lhsReceiptItem == rhsReceiptItem
  107. case (.expired(let lhsExpiryDate, let lhsReceiptItem), .expired(let rhsExpiryDate, let rhsReceiptItem)):
  108. return lhsExpiryDate == rhsExpiryDate && lhsReceiptItem == rhsReceiptItem
  109. case (.inGracePeriod(let lhsEndDate, let lhsReceiptItem, let lhsRenewals), .inGracePeriod(let rhsEndDate, let rhsReceiptItem, let rhsRenewals)):
  110. return lhsEndDate == rhsEndDate && lhsReceiptItem == rhsReceiptItem && lhsRenewals == rhsRenewals
  111. default: return false
  112. }
  113. }
  114. }
  115. extension VerifyPurchaseResult: Equatable {
  116. public static func == (lhs: VerifyPurchaseResult, rhs: VerifyPurchaseResult) -> Bool {
  117. switch (lhs, rhs) {
  118. case (.notPurchased, .notPurchased): return true
  119. case (.purchased(let lhsReceiptItem), .purchased(let rhsReceiptItem)):
  120. return lhsReceiptItem == rhsReceiptItem
  121. default: return false
  122. }
  123. }
  124. }
  125. // swiftlint: disable file_length
  126. class InAppReceiptTests: XCTestCase {
  127. // MARK: Verify Purchase
  128. func testVerifyPurchase_when_noPurchases_then_resultIsNotPurchased() {
  129. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  130. let productId = "product1"
  131. let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
  132. let verifyPurchaseResult = SwiftyStoreKit.verifyPurchase(productId: productId, inReceipt: receipt)
  133. XCTAssertEqual(verifyPurchaseResult, .notPurchased)
  134. }
  135. func testVerifyPurchase_when_onePurchase_then_resultIsPurchased() {
  136. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  137. let productId = "product1"
  138. let item = ReceiptItem(productId: productId, purchaseDate: receiptRequestDate, subscriptionExpirationDate: nil, cancellationDate: nil, isTrialPeriod: false)
  139. let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
  140. let verifyPurchaseResult = SwiftyStoreKit.verifyPurchase(productId: productId, inReceipt: receipt)
  141. XCTAssertEqual(verifyPurchaseResult, .purchased(item: item))
  142. }
  143. func testVerifyPurchase_when_oneCancelledPurchase_then_resultIsNotPurchased() {
  144. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  145. let productId = "product1"
  146. let item = ReceiptItem(productId: productId, purchaseDate: receiptRequestDate, subscriptionExpirationDate: nil, cancellationDate: receiptRequestDate, isTrialPeriod: false)
  147. let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
  148. let verifyPurchaseResult = SwiftyStoreKit.verifyPurchase(productId: productId, inReceipt: receipt)
  149. XCTAssertEqual(verifyPurchaseResult, .notPurchased)
  150. }
  151. // MARK: Verify Subscription, single receipt item tests
  152. // auto-renewable, not purchased
  153. func testVerifyAutoRenewableSubscription_when_noSubscriptions_then_resultIsNotPurchased() {
  154. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  155. let productId = "product1"
  156. let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
  157. let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
  158. let expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
  159. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  160. }
  161. // auto-renewable, expired
  162. func testVerifyAutoRenewableSubscription_when_oneExpiredSubscription_then_resultIsExpired() {
  163. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 15)
  164. let productId = "product1"
  165. let isTrialPeriod = false
  166. let purchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  167. let expirationDate = purchaseDate.addingTimeInterval(60 * 60)
  168. let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: expirationDate, cancellationDate: nil, isTrialPeriod: isTrialPeriod)
  169. let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
  170. let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
  171. let expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: expirationDate, items: [item])
  172. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  173. }
  174. // auto-renewable, purchased
  175. func testVerifyAutoRenewableSubscription_when_oneNonExpiredSubscription_then_resultIsPurchased() {
  176. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  177. let productId = "product1"
  178. let isTrialPeriod = false
  179. let purchaseDate = receiptRequestDate
  180. let expirationDate = purchaseDate.addingTimeInterval(60 * 60)
  181. let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: expirationDate, cancellationDate: nil, isTrialPeriod: isTrialPeriod)
  182. let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
  183. let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
  184. let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: expirationDate, items: [item])
  185. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  186. }
  187. // auto-renewable, cancelled
  188. func testVerifyAutoRenewableSubscription_when_oneCancelledSubscription_then_resultIsNotPurchased() {
  189. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  190. let productId = "product1"
  191. let isTrialPeriod = false
  192. let purchaseDate = receiptRequestDate
  193. let expirationDate = purchaseDate.addingTimeInterval(60 * 60)
  194. let cancelledDate = purchaseDate.addingTimeInterval(30 * 60)
  195. let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: expirationDate, cancellationDate: cancelledDate, isTrialPeriod: isTrialPeriod)
  196. let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
  197. let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
  198. let expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
  199. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  200. }
  201. // auto-renewable, in grace period
  202. func testVerifyAutoRenewableSubscription_when_oneGracePeriodSubscription_then_resultIsPurchased() {
  203. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 15)
  204. let productId = "product1"
  205. let purchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  206. let expirationDate = purchaseDate.addingTimeInterval(60 * 60)
  207. let transactionId = UUID().uuidString
  208. let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: expirationDate, cancellationDate: nil, transactionId: transactionId, isTrialPeriod: false)
  209. let gracePeriodExpirationDate = makeDateAtMidnight(year: 2017, month: 5, day: 16)
  210. let pendingRenewal = PendingRenewalInfo(productId: productId, expiryDate: gracePeriodExpirationDate, originalTransactionId: transactionId)
  211. let receiptNormal = makeReceipt(items: [item], requestDate: receiptRequestDate)
  212. let verifySubscriptionResultNormal = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receiptNormal)
  213. let expectedSubscriptionResultNormal = VerifySubscriptionResult.expired(expiryDate: expirationDate, items: [item])
  214. //Sanity Check: Without the pending renewal info the receipt should have been expired.
  215. XCTAssertEqual(verifySubscriptionResultNormal, expectedSubscriptionResultNormal)
  216. let receiptWithPendingRenewal = makeReceipt(items: [item], requestDate: receiptRequestDate, pendingRenewals: [pendingRenewal])
  217. let verifySubscriptionResultWithPendingRenewal = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receiptWithPendingRenewal)
  218. let expectedSubscriptionResultWithPendingRenewal = VerifySubscriptionResult.inGracePeriod(endDate: gracePeriodExpirationDate, items: [item], pendingRenewals: [pendingRenewal])
  219. //With the pending renewal info, we're in a grace period
  220. XCTAssertEqual(verifySubscriptionResultWithPendingRenewal, expectedSubscriptionResultWithPendingRenewal)
  221. }
  222. // auto-renewable, in expired grace period
  223. func testVerifyAutoRenewableSubscription_when_oneExpiredGracePeriodSubscription_then_resultIsExpired() {
  224. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 20)
  225. let productId = "product1"
  226. let purchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  227. let expirationDate = purchaseDate.addingTimeInterval(60 * 60)
  228. let transactionId = UUID().uuidString
  229. let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: expirationDate, cancellationDate: nil, transactionId: transactionId, isTrialPeriod: false)
  230. let gracePeriodExpirationDate = makeDateAtMidnight(year: 2017, month: 5, day: 19)
  231. let pendingRenewal = PendingRenewalInfo(productId: productId, expiryDate: gracePeriodExpirationDate, originalTransactionId: transactionId)
  232. let receiptNormal = makeReceipt(items: [item], requestDate: receiptRequestDate)
  233. let verifySubscriptionResultNormal = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receiptNormal)
  234. let expectedSubscriptionResultNormal = VerifySubscriptionResult.expired(expiryDate: expirationDate, items: [item])
  235. //Sanity Check: Without the pending renewal info the receipt should have been expired.
  236. XCTAssertEqual(verifySubscriptionResultNormal, expectedSubscriptionResultNormal)
  237. let receiptWithPendingRenewal = makeReceipt(items: [item], requestDate: receiptRequestDate, pendingRenewals: [pendingRenewal])
  238. let verifySubscriptionResultWithPendingRenewal = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receiptWithPendingRenewal)
  239. let expectedSubscriptionResultWithPendingRenewal = VerifySubscriptionResult.expired(expiryDate: expirationDate, items: [item])
  240. //With the pending renewal info, we're still in the expired state as the pending renewal info has expired as well
  241. XCTAssertEqual(verifySubscriptionResultWithPendingRenewal, expectedSubscriptionResultWithPendingRenewal)
  242. }
  243. // non-renewing, non purchased
  244. func testVerifyNonRenewingSubscription_when_noSubscriptions_then_resultIsNotPurchased() {
  245. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  246. let productId = "product1"
  247. let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
  248. let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .nonRenewing(validDuration: 60 * 60), productId: productId, inReceipt: receipt)
  249. let expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
  250. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  251. }
  252. // non-renewing, expired
  253. func testVerifyNonRenewingSubscription_when_oneExpiredSubscription_then_resultIsExpired() {
  254. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 15)
  255. let productId = "product1"
  256. let isTrialPeriod = false
  257. let purchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  258. let duration: TimeInterval = 60 * 60
  259. let expirationDate = purchaseDate.addingTimeInterval(duration)
  260. let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: nil, cancellationDate: nil, isTrialPeriod: isTrialPeriod)
  261. let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
  262. let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .nonRenewing(validDuration: duration), productId: productId, inReceipt: receipt)
  263. let expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: expirationDate, items: [item])
  264. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  265. }
  266. // non-renewing, purchased
  267. func testVerifyNonRenewingSubscription_when_oneNonExpiredSubscription_then_resultIsPurchased() {
  268. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  269. let productId = "product1"
  270. let isTrialPeriod = false
  271. let purchaseDate = receiptRequestDate
  272. let duration: TimeInterval = 60 * 60
  273. let expirationDate = purchaseDate.addingTimeInterval(duration)
  274. let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: nil, cancellationDate: nil, isTrialPeriod: isTrialPeriod)
  275. let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
  276. let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .nonRenewing(validDuration: duration), productId: productId, inReceipt: receipt)
  277. let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: expirationDate, items: [item])
  278. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  279. }
  280. // non-renewing, cancelled
  281. func testVerifyNonRenewingSubscription_when_oneCancelledSubscription_then_resultIsNotPurchased() {
  282. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  283. let productId = "product1"
  284. let isTrialPeriod = false
  285. let purchaseDate = receiptRequestDate
  286. let duration: TimeInterval = 60 * 60
  287. let cancelledDate = purchaseDate.addingTimeInterval(30 * 60)
  288. let item = ReceiptItem(productId: productId, purchaseDate: purchaseDate, subscriptionExpirationDate: nil, cancellationDate: cancelledDate, isTrialPeriod: isTrialPeriod)
  289. let receipt = makeReceipt(items: [item], requestDate: receiptRequestDate)
  290. let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .nonRenewing(validDuration: duration), productId: productId, inReceipt: receipt)
  291. let expectedSubscriptionResult = VerifySubscriptionResult.notPurchased
  292. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  293. }
  294. // MARK: Verify Subscription, multiple receipt item tests
  295. func testVerifyAutoRenewableSubscription_when_twoSubscriptions_sameProductId_mostRecentNonExpired_then_resultIsPurchased_itemsSorted() {
  296. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  297. let productId = "product1"
  298. let isTrialPeriod = false
  299. let olderPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 12)
  300. let olderExpirationDate = olderPurchaseDate.addingTimeInterval(60 * 60)
  301. let olderItem = ReceiptItem(productId: productId,
  302. purchaseDate: olderPurchaseDate,
  303. subscriptionExpirationDate: olderExpirationDate,
  304. cancellationDate: nil,
  305. isTrialPeriod: isTrialPeriod)
  306. let newerPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  307. let newerExpirationDate = newerPurchaseDate.addingTimeInterval(60 * 60)
  308. let newerItem = ReceiptItem(productId: productId,
  309. purchaseDate: newerPurchaseDate,
  310. subscriptionExpirationDate: newerExpirationDate,
  311. cancellationDate: nil,
  312. isTrialPeriod: isTrialPeriod)
  313. let receipt = makeReceipt(items: [olderItem, newerItem], requestDate: receiptRequestDate)
  314. let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
  315. let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: newerExpirationDate, items: [newerItem, olderItem])
  316. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  317. }
  318. func testVerifyAutoRenewableSubscription_when_twoSubscriptions_sameProductId_bothExpired_then_resultIsExpired_itemsSorted() {
  319. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  320. let productId = "product1"
  321. let isTrialPeriod = false
  322. let olderPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 12)
  323. let olderExpirationDate = olderPurchaseDate.addingTimeInterval(60 * 60)
  324. let olderItem = ReceiptItem(productId: productId,
  325. purchaseDate: olderPurchaseDate,
  326. subscriptionExpirationDate: olderExpirationDate,
  327. cancellationDate: nil,
  328. isTrialPeriod: isTrialPeriod)
  329. let newerPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 13)
  330. let newerExpirationDate = newerPurchaseDate.addingTimeInterval(60 * 60)
  331. let newerItem = ReceiptItem(productId: productId,
  332. purchaseDate: newerPurchaseDate,
  333. subscriptionExpirationDate: newerExpirationDate,
  334. cancellationDate: nil,
  335. isTrialPeriod: isTrialPeriod)
  336. let receipt = makeReceipt(items: [olderItem, newerItem], requestDate: receiptRequestDate)
  337. let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
  338. let expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: newerExpirationDate, items: [newerItem, olderItem])
  339. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  340. }
  341. // MARK: Verify Subscriptions, multiple receipt item tests
  342. func testVerifyAutoRenewableSubscriptions_when_threeSubscriptions_twoMatchingProductIds_mostRecentNonExpired_then_resultIsPurchased_itemsSorted() {
  343. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  344. let productId1 = "product1"
  345. let productId2 = "product2"
  346. let productIds = Set([ productId1, productId2 ])
  347. let isTrialPeriod = false
  348. let olderPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 12)
  349. let olderExpirationDate = olderPurchaseDate.addingTimeInterval(60 * 60)
  350. let olderItem = ReceiptItem(productId: productId1,
  351. purchaseDate: olderPurchaseDate,
  352. subscriptionExpirationDate: olderExpirationDate,
  353. cancellationDate: nil,
  354. isTrialPeriod: isTrialPeriod)
  355. let newerPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  356. let newerExpirationDate = newerPurchaseDate.addingTimeInterval(60 * 60)
  357. let newerItem = ReceiptItem(productId: productId2,
  358. purchaseDate: newerPurchaseDate,
  359. subscriptionExpirationDate: newerExpirationDate,
  360. cancellationDate: nil,
  361. isTrialPeriod: isTrialPeriod)
  362. let otherPurchaseDate = makeDateAtMidnight(year: 2017, month: 5, day: 15)
  363. let otherExpirationDate = otherPurchaseDate.addingTimeInterval(60 * 60)
  364. let otherItem = ReceiptItem(productId: "otherProduct",
  365. purchaseDate: otherPurchaseDate,
  366. subscriptionExpirationDate: otherExpirationDate,
  367. cancellationDate: nil,
  368. isTrialPeriod: isTrialPeriod)
  369. let receipt = makeReceipt(items: [olderItem, newerItem, otherItem], requestDate: receiptRequestDate)
  370. let verifySubscriptionResult = SwiftyStoreKit.verifySubscriptions(ofType: .autoRenewable, productIds: productIds, inReceipt: receipt)
  371. let expectedSubscriptionResult = VerifySubscriptionResult.purchased(expiryDate: newerExpirationDate, items: [newerItem, olderItem])
  372. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  373. }
  374. func testVerifyAutoRenewableSubscriptions_when_threeSubscriptions_oneInGracePeriodExpired_twoInGracePeriod_then_resultIsPurchased_itemsSorted() {
  375. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 20)
  376. let productId1 = "product1"
  377. let productId2 = "product2"
  378. let productId3 = "product3"
  379. let productIds = Set([ productId1, productId2, productId3 ])
  380. let isTrialPeriod = false
  381. let id1 = UUID().uuidString
  382. let id2 = UUID().uuidString
  383. let id3 = UUID().uuidString
  384. let purchaseDate1 = makeDateAtMidnight(year: 2017, month: 5, day: 10)
  385. let expirationDate1 = purchaseDate1.addingTimeInterval(60 * 60)
  386. let item1 = ReceiptItem(productId: productId1,
  387. purchaseDate: purchaseDate1,
  388. subscriptionExpirationDate: expirationDate1,
  389. transactionId: id1,
  390. isTrialPeriod: isTrialPeriod)
  391. let purchaseDate2 = makeDateAtMidnight(year: 2017, month: 5, day: 11)
  392. let expirationDate2 = purchaseDate2.addingTimeInterval(60 * 60)
  393. let item2 = ReceiptItem(productId: productId2,
  394. purchaseDate: purchaseDate2,
  395. subscriptionExpirationDate: expirationDate2,
  396. transactionId: id2,
  397. isTrialPeriod: isTrialPeriod)
  398. let purchaseDate3 = makeDateAtMidnight(year: 2017, month: 5, day: 12)
  399. let expirationDate3 = purchaseDate3.addingTimeInterval(60 * 60)
  400. let item3 = ReceiptItem(productId: productId3,
  401. purchaseDate: purchaseDate3,
  402. subscriptionExpirationDate: expirationDate3,
  403. transactionId: id3,
  404. isTrialPeriod: isTrialPeriod)
  405. //Sanity Check: Without pending renewals the result should be expired, and items ordered descending
  406. let receipt = makeReceipt(items: [item1, item2, item3], requestDate: receiptRequestDate)
  407. let verifySubscriptionResult = SwiftyStoreKit.verifySubscriptions(ofType: .autoRenewable, productIds: productIds, inReceipt: receipt)
  408. let expectedSubscriptionResult = VerifySubscriptionResult.expired(expiryDate: expirationDate3, items: [item3, item2, item1])
  409. XCTAssertEqual(verifySubscriptionResult, expectedSubscriptionResult)
  410. let renewalDate1 = makeDateAtMidnight(year: 2017, month: 5, day: 19)
  411. let renewalDate2 = makeDateAtMidnight(year: 2017, month: 5, day: 21)
  412. let renewalDate3 = makeDateAtMidnight(year: 2017, month: 5, day: 22)
  413. let renewal1 = PendingRenewalInfo(productId: productId1, expiryDate: renewalDate1, originalTransactionId: id1)
  414. let renewal2 = PendingRenewalInfo(productId: productId2, expiryDate: renewalDate2, originalTransactionId: id2)
  415. let renewal3 = PendingRenewalInfo(productId: productId3, expiryDate: renewalDate3, originalTransactionId: id3)
  416. //With pending renewals here, renewal1 and thus item 2 should be expired and not returned.
  417. //But the result should be `.inGracePeriod` with the items/renewals in descending order.
  418. let receiptWithRenewables = makeReceipt(items: [item1, item2, item3], requestDate: receiptRequestDate, pendingRenewals: [renewal1, renewal2, renewal3])
  419. let verifySubscriptionResultRenewables = SwiftyStoreKit.verifySubscriptions(ofType: .autoRenewable, productIds: productIds, inReceipt: receiptWithRenewables)
  420. let expectedSubscriptionResultRenewables = VerifySubscriptionResult.inGracePeriod(endDate: renewalDate3, items: [item3, item2], pendingRenewals: [renewal3, renewal2])
  421. XCTAssertEqual(verifySubscriptionResultRenewables, expectedSubscriptionResultRenewables)
  422. }
  423. // MARK: Get Distinct Purchase Identifiers, empty receipt item tests
  424. func testGetDistinctPurchaseIds_when_noReceipt_then_resultIsNil() {
  425. let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
  426. let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
  427. let getdistinctProductIdsResult = SwiftyStoreKit.getDistinctPurchaseIds(ofType: .autoRenewable, inReceipt: receipt)
  428. XCTAssertNil(getdistinctProductIdsResult)
  429. }
  430. // MARK: Get Distinct Purchase Identifiers, multiple receipt item tests
  431. func testGetDistinctPurchaseIds_when_Receipt_then_resultIsNotNil() {
  432. let receiptRequestDateOne = makeDateAtMidnight(year: 2020, month: 2, day: 20)
  433. let purchaseDateOne = makeDateAtMidnight(year: 2020, month: 2, day: 1)
  434. let purchaseDateTwo = makeDateAtMidnight(year: 2020, month: 1, day: 1)
  435. let productId1 = "product1"
  436. let productId2 = "product2"
  437. let product1 = ReceiptItem(productId: productId1, purchaseDate: purchaseDateOne)
  438. let product2 = ReceiptItem(productId: productId2, purchaseDate: purchaseDateTwo)
  439. let receipt = makeReceipt(items: [product1, product2], requestDate: receiptRequestDateOne)
  440. let getdistinctProductIdsResult = SwiftyStoreKit.getDistinctPurchaseIds(ofType: .autoRenewable, inReceipt: receipt)
  441. XCTAssertNotNil(getdistinctProductIdsResult)
  442. }
  443. // MARK: Get Distinct Purchase Identifiers, multiple non unique product identifiers tests
  444. func testGetDistinctPurchaseIds_when_nonUniqueIdentifiers_then_resultIsUnique() {
  445. let receiptRequestDateOne = makeDateAtMidnight(year: 2020, month: 2, day: 20)
  446. let purchaseDateOne = makeDateAtMidnight(year: 2020, month: 2, day: 1)
  447. let purchaseDateTwo = makeDateAtMidnight(year: 2020, month: 2, day: 2)
  448. let purchaseDateThree = makeDateAtMidnight(year: 2020, month: 2, day: 3)
  449. let purchaseDateFour = makeDateAtMidnight(year: 2020, month: 2, day: 4)
  450. let productId1 = "product1"
  451. let productId2 = "product2"
  452. let productId3 = "product1"
  453. let productId4 = "product2"
  454. let product1 = ReceiptItem(productId: productId1, purchaseDate: purchaseDateOne)
  455. let product2 = ReceiptItem(productId: productId2, purchaseDate: purchaseDateTwo)
  456. let product3 = ReceiptItem(productId: productId3, purchaseDate: purchaseDateThree)
  457. let product4 = ReceiptItem(productId: productId4, purchaseDate: purchaseDateFour)
  458. let receipt = makeReceipt(items: [product1, product2, product3, product4], requestDate: receiptRequestDateOne)
  459. let getdistinctProductIdsResult = SwiftyStoreKit.getDistinctPurchaseIds(ofType: .autoRenewable, inReceipt: receipt)
  460. let expectedProductIdsResult = Set([productId1, productId2, productId3, productId4])
  461. XCTAssertEqual(getdistinctProductIdsResult, expectedProductIdsResult)
  462. }
  463. // MARK: Helper methods
  464. func makeReceipt(items: [ReceiptItem], requestDate: Date, pendingRenewals: [PendingRenewalInfo] = []) -> [String: AnyObject] {
  465. let receiptInfos = items.map { $0.receiptInfo }
  466. let renewalReceiptInfos = pendingRenewals.map { $0.receiptInfo }
  467. // Creating this with NSArray results in __NSSingleObjectArrayI which fails the cast to [String: AnyObject]
  468. let array = NSMutableArray()
  469. array.addObjects(from: receiptInfos)
  470. let arrayRenewables = NSMutableArray()
  471. arrayRenewables.addObjects(from: renewalReceiptInfos)
  472. return [
  473. "status": "200" as NSString,
  474. "environment": "Sandbox" as NSString,
  475. "receipt": NSDictionary(dictionary: [
  476. "request_date_ms": requestDate.timeIntervalSince1970.millisecondsNSString,
  477. "in_app": array // non renewing
  478. ]),
  479. "latest_receipt_info": array, // autoRenewable
  480. PendingRenewalInfo.KEY_IN_RESPONSE_BODY: arrayRenewables //autoRenewable in case of grace period active
  481. ]
  482. }
  483. func makeDateAtMidnight(year: Int, month: Int, day: Int) -> Date {
  484. var dateComponents = DateComponents()
  485. dateComponents.day = day
  486. dateComponents.month = month
  487. dateComponents.year = year
  488. let calendar = Calendar(identifier: .gregorian)
  489. return calendar.date(from: dateComponents)!
  490. }
  491. }