Skip to content
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

Paywalls: don't display "Purchases restored successfully" if nothings was restored #3233

Merged
merged 4 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions RevenueCatUI/Data/TestData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ internal enum TestData {
#endif

static let customerInfo: CustomerInfo = {
let json = """
return .decode(
"""
{
"schema_version": "4",
"request_date": "2022-03-08T17:42:58Z",
Expand All @@ -463,13 +464,7 @@ internal enum TestData {
}
}
"""

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601

// swiftlint:disable:next force_try
return try! decoder.decode(CustomerInfo.self, from: Data(json.utf8))
)
}()

static let localization1: PaywallData.LocalizedConfiguration = .init(
Expand Down Expand Up @@ -540,4 +535,17 @@ extension PackageType {

}

extension CustomerInfo {

static func decode(_ json: String) -> Self {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601

// swiftlint:disable:next force_try
return try! decoder.decode(Self.self, from: Data(json.utf8))
}

}

#endif
6 changes: 3 additions & 3 deletions RevenueCatUI/Purchasing/PurchaseHandler+TestData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ import RevenueCat
@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)
extension PurchaseHandler {

static func mock() -> Self {
static func mock(_ customerInfo: CustomerInfo = TestData.customerInfo) -> Self {
return self.init(
purchases: MockPurchases { _ in
return (
transaction: nil,
customerInfo: TestData.customerInfo,
customerInfo: customerInfo,
userCancelled: false
)
} restorePurchases: {
return TestData.customerInfo
return customerInfo
} trackEvent: { event in
Logger.debug("Tracking event: \(event)")
}
Expand Down
15 changes: 13 additions & 2 deletions RevenueCatUI/Purchasing/PurchaseHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,10 @@ extension PurchaseHandler {
return result
}

/// - Returns: `success` is `true` only when the resulting `CustomerInfo`
/// had any transactions
@MainActor
func restorePurchases() async throws -> CustomerInfo {
func restorePurchases() async throws -> (info: CustomerInfo, success: Bool) {
self.actionInProgress = true
defer { self.actionInProgress = false }

Expand All @@ -103,7 +105,8 @@ extension PurchaseHandler {
self.restoredCustomerInfo = customerInfo
}

return customerInfo
return (info: customerInfo,
success: customerInfo.hasActiveSubscriptionsOrNonSubscriptions)
}

func trackPaywallImpression(_ eventData: PaywallEvent.Data) {
Expand Down Expand Up @@ -220,3 +223,11 @@ private extension PaywallEvent.Data {
}

}

private extension CustomerInfo {

var hasActiveSubscriptionsOrNonSubscriptions: Bool {
return !self.activeSubscriptions.isEmpty || !self.nonSubscriptions.isEmpty
}

}
6 changes: 4 additions & 2 deletions RevenueCatUI/Views/FooterView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,10 @@ private struct RestorePurchasesButton: View {

var body: some View {
AsyncButton {
_ = try await self.purchaseHandler.restorePurchases()
self.displayRestoredAlert = true
let success = try await self.purchaseHandler.restorePurchases().success
if success {
self.displayRestoredAlert = true
}
Comment on lines +170 to +173
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks good to me as a stopgap, but I think we should try to get copy for saying something about "no purchases found" and localize it.
No-op is kind of an awkward experience to me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed a Linear for this.

} label: {
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) {
ViewThatFits {
Expand Down
21 changes: 21 additions & 0 deletions Tests/RevenueCatUITests/PurchaseCompletedHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,27 @@ class PurchaseCompletedHandlerTests: TestCase {
expect(customerInfo).toEventually(be(TestData.customerInfo))
}

func testOnRestoreCompleted() throws {
var customerInfo: CustomerInfo?

try PaywallView(
offering: Self.offering.withLocalImages,
customerInfo: TestData.customerInfo,
introEligibility: .producing(eligibility: .eligible),
purchaseHandler: Self.purchaseHandler
)
.onRestoreCompleted {
customerInfo = $0
}
.addToHierarchy()

Task {
_ = try await Self.purchaseHandler.restorePurchases()
}

expect(customerInfo).toEventually(be(TestData.customerInfo))
}

private static let purchaseHandler: PurchaseHandler = .mock()
private static let offering = TestData.offeringWithNoIntroOffer
private static let package = TestData.annualPackage
Expand Down
103 changes: 102 additions & 1 deletion Tests/RevenueCatUITests/Purchasing/PurchaseHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,114 @@ class PurchaseHandlerTests: TestCase {
func testRestorePurchases() async throws {
let handler: PurchaseHandler = .mock()

_ = try await handler.restorePurchases()
let result = try await handler.restorePurchases()

expect(result.info) === TestData.customerInfo
expect(result.success) == false
expect(handler.restored) == true
expect(handler.restoredCustomerInfo) === TestData.customerInfo
expect(handler.purchasedCustomerInfo).to(beNil())
expect(handler.actionInProgress) == false
}

func testRestorePurchasesWithActiveSubscriptions() async throws {
let handler: PurchaseHandler = .mock(Self.customerInfoWithSubscriptions)

let result = try await handler.restorePurchases()
expect(result.info) === Self.customerInfoWithSubscriptions
expect(result.success) == true
}

func testRestorePurchasesWithNonSubscriptions() async throws {
let handler: PurchaseHandler = .mock(Self.customerInfoWithNonSubscriptions)

let result = try await handler.restorePurchases()
expect(result.info) === Self.customerInfoWithNonSubscriptions
expect(result.success) == true
}

}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
private extension PurchaseHandlerTests {

static let customerInfoWithSubscriptions: CustomerInfo = {
return .decode(
"""
{
"schema_version": "4",
"request_date": "2022-03-08T17:42:58Z",
"request_date_ms": 1646761378845,
"subscriber": {
"first_seen": "2022-03-08T17:42:58Z",
"last_seen": "2022-03-08T17:42:58Z",
"management_url": "https://apps.apple.com/account/subscriptions",
"non_subscriptions": {
},
"original_app_user_id": "$RCAnonymousID:5b6fdbac3a0c4f879e43d269ecdf9ba1",
"original_application_version": "1.0",
"original_purchase_date": "2022-04-12T00:03:24Z",
"other_purchases": {
},
"subscriptions": {
"com.revenuecat.product": {
"billing_issues_detected_at": null,
"expires_date": "2062-04-12T00:03:35Z",
"grace_period_expires_date": null,
"is_sandbox": true,
"original_purchase_date": "2022-04-12T00:03:28Z",
"period_type": "intro",
"purchase_date": "2022-04-12T00:03:28Z",
"store": "app_store",
"unsubscribe_detected_at": null
},
},
"entitlements": {
}
}
}
"""
)
}()

static let customerInfoWithNonSubscriptions: CustomerInfo = {
return .decode(
"""
{
"schema_version": "4",
"request_date": "2022-03-08T17:42:58Z",
"request_date_ms": 1646761378845,
"subscriber": {
"first_seen": "2022-03-08T17:42:58Z",
"last_seen": "2022-03-08T17:42:58Z",
"management_url": "https://apps.apple.com/account/subscriptions",
"non_subscriptions": {
"com.revenuecat.product.tip": [
{
"purchase_date": "2022-02-11T00:03:28Z",
"original_purchase_date": "2022-03-10T00:04:28Z",
"id": "17459f5ff7",
"store_transaction_id": "340001090153249",
"store": "app_store",
"is_sandbox": false
}
]
},
"original_app_user_id": "$RCAnonymousID:5b6fdbac3a0c4f879e43d269ecdf9ba1",
"original_application_version": "1.0",
"original_purchase_date": "2022-04-12T00:03:24Z",
"other_purchases": {
},
"subscriptions": {
},
"entitlements": {
}
}
}
"""
)
}()

}

#endif