-
Notifications
You must be signed in to change notification settings - Fork 338
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [CustomerCenter] Introduce PurchaseHistory (#4686)
- Loading branch information
1 parent
3c5e07f
commit 475a72b
Showing
20 changed files
with
1,146 additions
and
45 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,8 @@ enum CustomerCenterConfigTestData { | |
// swiftlint:disable:next function_body_length | ||
static func customerCenterData( | ||
lastPublishedAppVersion: String?, | ||
shouldWarnCustomerToUpdate: Bool = false | ||
shouldWarnCustomerToUpdate: Bool = false, | ||
displayPurchaseHistoryLink: Bool = false | ||
) -> CustomerCenterConfigData { | ||
CustomerCenterConfigData( | ||
screens: [.management: | ||
|
@@ -116,7 +117,8 @@ enum CustomerCenterConfigTestData { | |
), | ||
support: .init( | ||
email: "[email protected]", | ||
shouldWarnCustomerToUpdate: shouldWarnCustomerToUpdate | ||
shouldWarnCustomerToUpdate: shouldWarnCustomerToUpdate, | ||
displayPurchaseHistoryLink: displayPurchaseHistoryLink | ||
), | ||
lastPublishedAppVersion: lastPublishedAppVersion, | ||
productId: 1 | ||
|
32 changes: 32 additions & 0 deletions
32
RevenueCatUI/CustomerCenter/Extensions/Store+Localization.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// | ||
// Copyright RevenueCat Inc. All Rights Reserved. | ||
// | ||
// Licensed under the MIT License (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// https://opensource.org/licenses/MIT | ||
// | ||
// Store+Localized.swift | ||
// | ||
// | ||
// Created by Facundo Menzella on 14/1/25. | ||
// | ||
import RevenueCat | ||
|
||
extension Store { | ||
|
||
var localizationKey: CCLocalizedString { | ||
switch self { | ||
case .appStore: return .storeAppStore | ||
case .macAppStore: return .storeMacAppStore | ||
case .playStore: return .storePlayStore | ||
case .stripe: return .storeStripe | ||
case .promotional: return .storePromotional | ||
case .amazon: return .storeAmazon | ||
case .rcBilling: return .storeRCBilling | ||
case .external: return .storeExternal | ||
case .unknownStore: return .storeUnknownStore | ||
} | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
RevenueCatUI/CustomerCenter/Utilities/CustomerCenterLocalizationStrings.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// | ||
// Copyright RevenueCat Inc. All Rights Reserved. | ||
// | ||
// Licensed under the MIT License (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// https://opensource.org/licenses/MIT | ||
// | ||
// CustomerCenterLocalizationStrings.swift | ||
// | ||
// Created by Facundo Menzella on 21/1/25. | ||
|
||
import RevenueCat | ||
|
||
/// **typealias** for **CustomerCenterConfigData.Localization.CommonLocalizedString** | ||
typealias CCLocalizedString = CustomerCenterConfigData.Localization.CommonLocalizedString |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
RevenueCatUI/CustomerCenter/ViewModels/PurchaseHistory/PurchaseDetailItem.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// | ||
// Copyright RevenueCat Inc. All Rights Reserved. | ||
// | ||
// Licensed under the MIT License (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// https://opensource.org/licenses/MIT | ||
// | ||
// PurchaseDetailItem.swift | ||
// | ||
// | ||
// Created by Facundo Menzella on 14/1/25. | ||
// | ||
|
||
import RevenueCat | ||
|
||
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) | ||
@available(macOS, unavailable) | ||
@available(tvOS, unavailable) | ||
@available(watchOS, unavailable) | ||
enum PurchaseDetailItem: Identifiable { | ||
case productName(String) | ||
case paidPrice(String?) | ||
case purchaseDate(String) | ||
case status(CCLocalizedString) | ||
|
||
case nextRenewalDate(String) | ||
case expiresDate(String) | ||
case unsubscribeDetectedAt(String) | ||
case billingIssuesDetectedAt(String) | ||
case gracePeriodExpiresDate(String) | ||
case periodType(CCLocalizedString) | ||
case refundedAtDate(String) | ||
|
||
// DEBUG only | ||
case store(CCLocalizedString) | ||
case productID(String) | ||
case sandbox(Bool) | ||
case transactionID(String) | ||
|
||
var label: CCLocalizedString { | ||
switch self { | ||
case .productName: return .productName | ||
case .paidPrice: return .paidPrice | ||
case .purchaseDate: return .originalDownloadDate | ||
case .status: return .status | ||
case .nextRenewalDate: return .nextRenewalDate | ||
case .expiresDate: return .expires | ||
case .unsubscribeDetectedAt: return .unsubscribedAt | ||
case .billingIssuesDetectedAt: return .billingIssueDetectedAt | ||
case .gracePeriodExpiresDate: return .gracePeriodExpiresAt | ||
case .periodType: return .periodType | ||
case .refundedAtDate: return .refundedAt | ||
case .store: return .store | ||
case .productID: return .productID | ||
case .sandbox: return .sandbox | ||
case .transactionID: return .transactionID | ||
} | ||
} | ||
|
||
var isDebugOnly: Bool { | ||
switch self { | ||
case .store, .productID, .sandbox, .transactionID: | ||
return true | ||
case .productName, | ||
.paidPrice, | ||
.purchaseDate, | ||
.status, | ||
.nextRenewalDate, | ||
.expiresDate, | ||
.unsubscribeDetectedAt, | ||
.billingIssuesDetectedAt, | ||
.gracePeriodExpiresDate, | ||
.periodType, | ||
.refundedAtDate: | ||
return false | ||
} | ||
} | ||
|
||
var id: String { | ||
label.rawValue | ||
} | ||
} |
83 changes: 83 additions & 0 deletions
83
RevenueCatUI/CustomerCenter/ViewModels/PurchaseHistory/PurchaseDetailViewModel.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// | ||
// Copyright RevenueCat Inc. All Rights Reserved. | ||
// | ||
// Licensed under the MIT License (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// https://opensource.org/licenses/MIT | ||
// | ||
// PurchaseHistoryViewModel.swift | ||
// | ||
// | ||
// Created by Facundo Menzella on 14/1/25. | ||
// | ||
|
||
import Foundation | ||
import SwiftUI | ||
|
||
import RevenueCat | ||
|
||
#if os(iOS) | ||
|
||
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) | ||
@available(macOS, unavailable) | ||
@available(tvOS, unavailable) | ||
@available(watchOS, unavailable) | ||
final class PurchaseDetailViewModel: ObservableObject { | ||
|
||
@Environment(\.localization) | ||
private var localization: CustomerCenterConfigData.Localization | ||
|
||
@Published var items: [PurchaseDetailItem] = [] | ||
|
||
var localizedOwnership: String? { | ||
switch purchaseInfo { | ||
case .subscription(let subscriptionInfo): | ||
subscriptionInfo.ownershipType == .familyShared | ||
? localization.commonLocalizedString(for: .sharedThroughFamilyMember) | ||
: nil | ||
case .nonSubscription: | ||
nil | ||
} | ||
} | ||
|
||
init(purchaseInfo: PurchaseInfo) { | ||
self.purchaseInfo = purchaseInfo | ||
} | ||
|
||
func didAppear() async { | ||
await fetchProduct() | ||
} | ||
|
||
// MARK: - Private | ||
|
||
private let purchaseInfo: PurchaseInfo | ||
} | ||
|
||
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) | ||
@available(macOS, unavailable) | ||
@available(tvOS, unavailable) | ||
@available(watchOS, unavailable) | ||
private extension PurchaseDetailViewModel { | ||
|
||
func fetchProduct() async { | ||
guard | ||
let product = await Purchases.shared.products([purchaseInfo.productIdentifier]).first | ||
else { | ||
return | ||
} | ||
|
||
await MainActor.run { | ||
var items: [PurchaseDetailItem] = [ | ||
.productName(product.localizedTitle) | ||
] | ||
|
||
items.append(contentsOf: purchaseInfo.purchaseDetailItems) | ||
|
||
self.items = items | ||
} | ||
} | ||
} | ||
|
||
#endif |
113 changes: 113 additions & 0 deletions
113
RevenueCatUI/CustomerCenter/ViewModels/PurchaseHistory/PurchaseHistoryViewModel.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// | ||
// Copyright RevenueCat Inc. All Rights Reserved. | ||
// | ||
// Licensed under the MIT License (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// https://opensource.org/licenses/MIT | ||
// | ||
// PurchaseHistoryViewModel.swift | ||
// | ||
// | ||
// Created by Facundo Menzella on 13/1/25. | ||
// | ||
|
||
import Foundation | ||
import SwiftUI | ||
|
||
import RevenueCat | ||
|
||
#if os(iOS) | ||
|
||
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) | ||
@available(macOS, unavailable) | ||
@available(tvOS, unavailable) | ||
@available(watchOS, unavailable) | ||
final class PurchaseHistoryViewModel: ObservableObject { | ||
|
||
@Published var selectedPurchase: PurchaseInfo? | ||
|
||
@Published var customerInfo: CustomerInfo? { | ||
didSet { | ||
isLoading = false | ||
updateActiveAndNonActiveSubscriptions() | ||
} | ||
} | ||
@Published var errorMessage: String? | ||
@Published var isLoading: Bool = true | ||
|
||
var activeSubscriptions: [PurchaseInfo] = [] | ||
var inactiveSubscriptions: [PurchaseInfo] = [] | ||
var nonSubscriptions: [PurchaseInfo] = [] | ||
|
||
init( | ||
selectedPurchase: PurchaseInfo? = nil, | ||
customerInfo: CustomerInfo? = nil, | ||
errorMessage: String? = nil, | ||
isLoading: Bool = true, | ||
activeSubscriptions: [PurchaseInfo] = [], | ||
inactiveSubscriptions: [PurchaseInfo] = [], | ||
nonSubscriptions: [PurchaseInfo] = [] | ||
) { | ||
self.selectedPurchase = selectedPurchase | ||
self.customerInfo = customerInfo | ||
self.errorMessage = errorMessage | ||
self.isLoading = isLoading | ||
self.activeSubscriptions = activeSubscriptions | ||
self.inactiveSubscriptions = inactiveSubscriptions | ||
self.nonSubscriptions = nonSubscriptions | ||
} | ||
|
||
func didAppear() async { | ||
await fetchCustomerInfo() | ||
} | ||
} | ||
|
||
@available(iOS 15.0, *) | ||
private extension PurchaseHistoryViewModel { | ||
func fetchCustomerInfo() async { | ||
do { | ||
let customerInfo = try await Purchases.shared.customerInfo() | ||
await MainActor.run { | ||
self.customerInfo = customerInfo | ||
} | ||
} catch { | ||
await MainActor.run { | ||
self.errorMessage = error.localizedDescription | ||
} | ||
} | ||
} | ||
|
||
func updateActiveAndNonActiveSubscriptions() { | ||
activeSubscriptions = customerInfo.map { | ||
$0.subscriptionsByProductIdentifier | ||
.filter { $0.value.isActive } | ||
.values | ||
.sorted(by: { sub1, sub2 in | ||
sub1.purchaseDate < sub2.purchaseDate | ||
}) | ||
.map { | ||
PurchaseInfo.subscription($0) | ||
} | ||
} ?? [] | ||
|
||
inactiveSubscriptions = customerInfo.map { | ||
$0.subscriptionsByProductIdentifier | ||
.filter { !$0.value.isActive } | ||
.values | ||
.sorted(by: { sub1, sub2 in | ||
sub1.purchaseDate < sub2.purchaseDate | ||
}) | ||
.map { | ||
PurchaseInfo.subscription($0) | ||
} | ||
} ?? [] | ||
|
||
nonSubscriptions = customerInfo?.nonSubscriptions.map { | ||
.nonSubscription($0) | ||
} ?? [] | ||
} | ||
} | ||
|
||
#endif |
Oops, something went wrong.