New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better non-subscriptions support #281
Changes from all commits
6cc8db5
cfdbc62
46bb05b
06a864a
6b4af61
9d7111e
ac5c9ed
caddb51
6bdd356
8e6c19d
e0e5027
a35c6b2
40235a4
5266514
4d8609d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
github "AliSoftware/OHHTTPStubs" "9.0.0" | ||
github "Quick/Nimble" "v8.0.7" | ||
github "Quick/Nimble" "v8.1.1" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// | ||
// Transaction.swift | ||
// Purchases | ||
// | ||
// Created by RevenueCat. | ||
// Copyright © 2020 Purchases. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
@objc(RCTransaction) public class Transaction: NSObject { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all objects that inherit from required init?() { fatalError("init() has not been implemented") } There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. haven't tested that, though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to provide some context on the why: if revenuecatId != nil { the compiler will complain because the object isn't nullable. so you'll be in a bad place. Here's a blog post that goes into a bit more detail in case you're curious: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the explanation! Unfortunately it doesn't work, it says "Failable initializer 'init()' cannot override a non-failable initializer". Since it throws a
Should it be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I missed the override, but yeah, it should be there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah failable means There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, for a failable it'd be best practice to return nil if it fails. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe this is the way to go? |
||
|
||
@available(*, unavailable, message: "Use init(transactionId, productId, purchaseDate) instead") | ||
override init() { | ||
fatalError("init() has not been implemented") | ||
} | ||
Comment on lines
+13
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👌 |
||
|
||
let revenueCatId: String | ||
let productId: String | ||
let purchaseDate: Date | ||
|
||
init(transactionId: String, productId: String, purchaseDate: Date) { | ||
self.revenueCatId = transactionId | ||
self.productId = productId | ||
self.purchaseDate = purchaseDate | ||
super.init() | ||
} | ||
|
||
init(with serverResponse: [String: Any], productId: String, dateFormatter: DateFormatter) { | ||
guard let revenueCatId = serverResponse["id"] as? String, | ||
let dateString = serverResponse["purchase_date"] as? String, | ||
let purchaseDate = dateFormatter.date(from: dateString) else { | ||
fatalError(""" | ||
Couldn't initialize Transaction from dictionary. | ||
Reason: unexpected format. Dictionary: \(serverResponse). | ||
""") | ||
} | ||
|
||
self.revenueCatId = revenueCatId | ||
self.purchaseDate = purchaseDate | ||
self.productId = productId | ||
super.init() | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// | ||
// PurchaserInfoHelper.swift | ||
// Purchases | ||
// | ||
// Created by RevenueCat. | ||
// Copyright © 2020 Purchases. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
@objc(RCTransactionsFactory) public class TransactionsFactory: NSObject { | ||
|
||
@objc public func nonSubscriptionTransactions(with subscriptionsData: [String: [[String: Any]]], | ||
dateFormatter: DateFormatter) -> [Transaction] { | ||
subscriptionsData.flatMap { (productId: String, transactionData: [[String: Any]]) -> [Transaction] in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I usually don't specify the types in these since they can be inferred, but In this case I like it - it helps the compiler and the dev not get lost in the nested block. |
||
transactionData.map { | ||
Transaction(with: $0, productId: productId, dateFormatter: dateFormatter) | ||
} | ||
}.sorted { | ||
$0.purchaseDate < $1.purchaseDate | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// | ||
// PurchaserInfoHelperTests.swift | ||
// PurchasesTests | ||
// | ||
// Created by RevenueCat. | ||
// Copyright © 2020 Purchases. All rights reserved. | ||
// | ||
|
||
import Nimble | ||
import XCTest | ||
@testable import Purchases | ||
|
||
class TransactionsFactoryTests: XCTestCase { | ||
|
||
let dateFormatter = DateFormatter() | ||
let transactionsFactory = TransactionsFactory() | ||
|
||
let sampleTransactions = [ | ||
"100_coins": [ | ||
[ | ||
"id": "72c26cc69c", | ||
"is_sandbox": true, | ||
"original_purchase_date": "1990-08-30T02:40:36Z", | ||
"purchase_date": "2019-07-11T18:36:20Z", | ||
"store": "app_store" | ||
], [ | ||
"id": "6229b0bef1", | ||
"is_sandbox": true, | ||
"original_purchase_date": "2019-11-06T03:26:15Z", | ||
"purchase_date": "2019-11-06T03:26:15Z", | ||
"store": "play_store" | ||
]], | ||
"500_coins": [ | ||
[ | ||
"id": "d6c007ba74", | ||
"is_sandbox": true, | ||
"original_purchase_date": "2019-07-11T18:36:20Z", | ||
"purchase_date": "2019-07-11T18:36:20Z", | ||
"store": "play_store" | ||
], [ | ||
"id": "5b9ba226bc", | ||
"is_sandbox": true, | ||
"original_purchase_date": "2019-07-26T22:10:27Z", | ||
"purchase_date": "2019-07-26T22:10:27Z", | ||
"store": "app_store" | ||
]], | ||
"lifetime_access": [ | ||
[ | ||
"id": "d6c097ba74", | ||
"is_sandbox": true, | ||
"original_purchase_date": "2018-07-11T18:36:20Z", | ||
"purchase_date": "2018-07-11T18:36:20Z", | ||
"store": "app_store" | ||
]] | ||
] | ||
|
||
override func setUp() { | ||
dateFormatter.timeZone = TimeZone(abbreviation: "GMT") | ||
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" | ||
dateFormatter.locale = Locale(identifier: "en_US_POSIX") | ||
} | ||
|
||
func testNonSubscriptionsIsCorrectlyCreated() { | ||
let nonSubscriptionTransactions = transactionsFactory.nonSubscriptionTransactions(with: sampleTransactions, dateFormatter: dateFormatter) | ||
expect(nonSubscriptionTransactions.count) == 5 | ||
|
||
sampleTransactions.forEach { productId, transactionsData in | ||
let filteredTransactions = nonSubscriptionTransactions.filter { $0.productId == productId } | ||
expect(filteredTransactions.count) == transactionsData.count | ||
transactionsData.forEach { dictionary in | ||
guard let transactionId = dictionary["id"] as? String else { fatalError("incorrect dict format") } | ||
let containsTransaction = filteredTransactions.contains { $0.revenueCatId == transactionId } | ||
expect(containsTransaction) == true | ||
} | ||
} | ||
|
||
} | ||
|
||
func testNonSubscriptionsIsEmptyIfThereAreNoNonSubscriptions() { | ||
let list = transactionsFactory.nonSubscriptionTransactions(with: [:], dateFormatter: dateFormatter) | ||
expect(list).to(beEmpty()) | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so
non_subscriptions
filters out consumables as well?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes,
non_subscriptions
contains both consumables and non-consumables. We've named this (nonConsumablePurchases
) wrong since the beginningThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ohh, so
nonConsumablePurchases
should have been namednonSubscriptions
. Maybe we should add a warning? Seems like a gotchaThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in that case, disregard my comment about the sample purchases naming, since consumables would also work