Skip to content

Commit

Permalink
Fix google play purchases missing purchase date (#2703)
Browse files Browse the repository at this point in the history
### Description 
Reported in RevenueCat/purchases-flutter#738. 

After #2654, we were
using the new Google Play identifiers that include the plan id in
expiration dates, but we didn't change the system that parses purchase
dates. Later on, when we map purchases, we iterate over the expiration
dates map, and since that id wasn't found in the purchase dates, it was
null, causing the issue in flutter.

This makes the purchase date calculation use the same algorithm that we
use for expiration dates to get the new google play product identifiers.
  • Loading branch information
tonidero authored Jun 26, 2023
1 parent d80bcc9 commit 48127ff
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 12 deletions.
33 changes: 22 additions & 11 deletions Sources/Identity/CustomerInfo+ActiveDates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,24 @@ extension CustomerInfo {
.subscriptions
.lazy
.map { productID, subscription in
let key: String
let key = Self.productID(productID: productID, purchase: subscription)
let value = subscription.expiresDate

// Products purchased from Google Play will have a product plan identifier (base plan)
// These products get mapped as "productId:productPlanIdentifier" in the Android SDK
// so the same mapping needs to be handled here for cross platform purchases
if let productPlanIdentfier = subscription.productPlanIdentifier {
key = "\(productID):\(productPlanIdentfier)"
} else {
key = productID
}
return (key, value)
}
)
}

static func extractPurchaseDates(_ subscriber: CustomerInfoResponse.Subscriber) -> [String: Date?] {
return subscriber.allTransactionsByProductId.mapValues { $0.purchaseDate }
return Dictionary(
uniqueKeysWithValues: subscriber
.allPurchasesByProductId
.lazy
.map { productID, purchase in
let key = Self.productID(productID: productID, purchase: purchase)
let value = purchase.purchaseDate
return (key, value)
}
)
}

}
Expand All @@ -79,6 +79,17 @@ extension CustomerInfo {

private extension CustomerInfo {

static func productID(productID: String, purchase: CustomerInfoResponse.Subscription) -> String {
// Products purchased from Google Play will have a product plan identifier (base plan)
// These products get mapped as "productId:productPlanIdentifier" in the Android SDK
// so the same mapping needs to be handled here for cross platform purchases
if let productPlanIdentfier = purchase.productPlanIdentifier {
return "\(productID):\(productPlanIdentfier)"
} else {
return productID
}
}

static func referenceDate(for requestDate: Date) -> (Date, inGracePeriod: Bool) {
if Date().timeIntervalSince(requestDate) <= Self.requestDateGracePeriod.seconds {
return (requestDate, true)
Expand Down
1 change: 1 addition & 0 deletions Sources/Networking/Responses/CustomerInfoResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ extension CustomerInfoResponse.Subscriber {
return self.allPurchasesByProductId.mapValues { $0.asTransaction }
}

// This returns objects of type `Subscription` but also includes non-subscriptions
var allPurchasesByProductId: [String: CustomerInfoResponse.Subscription] {
let subscriptions = self.subscriptions
let latestNonSubscriptionTransactionsByProductId = self.nonSubscriptions
Expand Down
8 changes: 7 additions & 1 deletion Tests/UnitTests/Purchasing/CustomerInfoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ class BasicCustomerInfoTests: TestCase {
"expires_date": "2100-07-30T02:40:36Z",
"period_type": "normal",
"is_sandbox": false,
"product_plan_identifier": "monthly"
"product_plan_identifier": "monthly",
"purchase_date": "2018-05-20T06:24:50Z"
],
"onemonth": [
"expires_date": BasicCustomerInfoTests.expiredSubscriptionDate,
Expand Down Expand Up @@ -447,6 +448,11 @@ class BasicCustomerInfoTests: TestCase {
expect(purchaseDate) == Date(timeIntervalSince1970: 1526797490)
}

func testPurchaseDateForGooglePlayProductIdentifier() throws {
let purchaseDate = try XCTUnwrap(self.customerInfo.purchaseDate(forProductIdentifier: "gold:monthly"))
expect(purchaseDate) == Date(timeIntervalSince1970: 1526797490)
}

func testPurchaseDateEmpty() throws {
let response = [
"request_date": "2019-08-16T10:30:42Z",
Expand Down

0 comments on commit 48127ff

Please sign in to comment.