-
Notifications
You must be signed in to change notification settings - Fork 331
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
[Customer Center] Promotional Offers support #3968
Merged
vegaro
merged 24 commits into
integration/customer_support_workflow
from
06-17-removes_eligibility_from_reponse
Jul 19, 2024
Merged
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
6b61aa6
[Customer Center] Show the right subscription on the Manage Subscript…
JayShortway ae59add
Promotional Offers support
vegaro 0dfe3bd
change OS macro check
vegaro 3129190
refactor to remove viewmodel that depends on viewmodel
vegaro 94f17aa
customerInfo.earliestExpiringAppStoreEntitlement
vegaro 9ee7fbf
remove missing argument
vegaro 828544c
remove duplicated available annotation
vegaro 21fb54b
make locale and localizedStrings internal
vegaro 00d319c
remove unused promoOfferDetails
vegaro aa8eb3f
rename to onOptionSelected
vegaro 8a7ee83
rename to dismissButtonTitle
vegaro 2633c00
move defaults to CommonLocalizedString
vegaro 02bcf47
disable all buttons if loading
vegaro 6028e5d
cleanup viewModel
vegaro 140e20d
use localization env
vegaro 30fffa3
fix test
vegaro b0cc12f
fix ios 16
vegaro f2ba654
lint fix
vegaro 132ac95
made localization nullable
vegaro f322d6c
renames
vegaro 9eb0e49
use result to remove warning
vegaro a9882fc
Revert "made localization nullable"
vegaro 65b80b6
Generating new test snapshots for `06-17-removes_eligibility_from_rep…
RCGitBot 7ddb1c5
Generating new test snapshots for `06-17-removes_eligibility_from_rep…
RCGitBot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
32 changes: 32 additions & 0 deletions
32
RevenueCatUI/CustomerCenter/Abstractions/CustomerCenterPurchasesType.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 | ||
// | ||
// CustomerCenterPurchaseType.swift | ||
// | ||
// Created by Cesar de la Vega on 18/7/24. | ||
|
||
import Foundation | ||
import RevenueCat | ||
|
||
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) | ||
@available(macOS, unavailable) | ||
@available(tvOS, unavailable) | ||
@available(watchOS, unavailable) | ||
protocol CustomerCenterPurchasesType: Sendable { | ||
|
||
@Sendable | ||
func customerInfo() async throws -> CustomerInfo | ||
|
||
@Sendable | ||
func products(_ productIdentifiers: [String]) async -> [StoreProduct] | ||
|
||
func promotionalOffer(forProductDiscount discount: StoreProductDiscount, | ||
product: StoreProduct) async throws -> PromotionalOffer | ||
|
||
} |
File renamed without changes.
35 changes: 35 additions & 0 deletions
35
RevenueCatUI/CustomerCenter/CustomerInfo+CurrentEntitlement.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,35 @@ | ||
// | ||
// 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 | ||
// | ||
// CustomerInfo+CurrentEntitlement.swift | ||
// | ||
// Created by Cesar de la Vega on 17/7/24. | ||
|
||
import Foundation | ||
import RevenueCat | ||
|
||
extension CustomerInfo { | ||
|
||
/// Returns the earliest expiring iOS App Store entitlement. If this CustomerInfo contains multiple lifetime | ||
/// entitlements and no expiring entitlements, the returned entitlement is undefined. | ||
func earliestExpiringAppStoreEntitlement() -> EntitlementInfo? { | ||
return self.entitlements | ||
.active | ||
.values | ||
.lazy | ||
.filter { $0.store == .appStore } | ||
.sorted { lhs, rhs in | ||
let lhsDateSeconds = lhs.expirationDate?.timeIntervalSince1970 ?? TimeInterval.greatestFiniteMagnitude | ||
let rhsDateSeconds = rhs.expirationDate?.timeIntervalSince1970 ?? TimeInterval.greatestFiniteMagnitude | ||
return lhsDateSeconds < rhsDateSeconds | ||
} | ||
.first | ||
} | ||
|
||
} |
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
31 changes: 31 additions & 0 deletions
31
RevenueCatUI/CustomerCenter/Data/CustomerCenterEnvironment.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,31 @@ | ||
// | ||
// 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 | ||
// | ||
// CustomerCenterEnvironment.swift | ||
// | ||
// Created by Cesar de la Vega on 19/7/24. | ||
|
||
import Foundation | ||
import RevenueCat | ||
import SwiftUI | ||
|
||
struct LocalizationKey: EnvironmentKey { | ||
|
||
static let defaultValue: CustomerCenterConfigData.Localization? = nil | ||
|
||
} | ||
|
||
extension EnvironmentValues { | ||
|
||
var localization: CustomerCenterConfigData.Localization? { | ||
get { self[LocalizationKey.self] } | ||
set { self[LocalizationKey.self] = newValue } | ||
} | ||
|
||
} |
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
37 changes: 37 additions & 0 deletions
37
RevenueCatUI/CustomerCenter/Data/CustomerCenterPurchases.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,37 @@ | ||
// | ||
// 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 | ||
// | ||
// CustomerCenterPurchases.swift | ||
// | ||
// Created by Cesar de la Vega on 18/7/24. | ||
|
||
import Foundation | ||
import RevenueCat | ||
|
||
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) | ||
@available(macOS, unavailable) | ||
@available(tvOS, unavailable) | ||
@available(watchOS, unavailable) | ||
final class CustomerCenterPurchases: CustomerCenterPurchasesType { | ||
|
||
func customerInfo() async throws -> RevenueCat.CustomerInfo { | ||
try await Purchases.shared.customerInfo() | ||
} | ||
|
||
func products(_ productIdentifiers: [String]) async -> [StoreProduct] { | ||
await Purchases.shared.products(productIdentifiers) | ||
} | ||
|
||
func promotionalOffer(forProductDiscount discount: StoreProductDiscount, | ||
product: StoreProduct) async throws -> PromotionalOffer { | ||
try await Purchases.shared.promotionalOffer(forProductDiscount: discount, | ||
product: product) | ||
} | ||
|
||
} |
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
74 changes: 74 additions & 0 deletions
74
RevenueCatUI/CustomerCenter/Data/LoadPromotionalOfferUseCase.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,74 @@ | ||
// | ||
// 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 | ||
// | ||
// LoadPromotionalOfferUseCase.swift | ||
// | ||
// Created by Cesar de la Vega on 18/7/24. | ||
|
||
import Foundation | ||
import RevenueCat | ||
|
||
protocol LoadPromotionalOfferUseCaseType { | ||
|
||
func execute( | ||
promoOfferDetails: CustomerCenterConfigData.HelpPath.PromotionalOffer | ||
) async -> Result<PromotionalOfferData, Error> | ||
|
||
} | ||
|
||
#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) | ||
@available(visionOS, unavailable) | ||
@MainActor | ||
class LoadPromotionalOfferUseCase: LoadPromotionalOfferUseCaseType { | ||
|
||
private let purchasesProvider: CustomerCenterPurchasesType | ||
|
||
init(purchasesProvider: CustomerCenterPurchasesType = CustomerCenterPurchases()) { | ||
self.purchasesProvider = purchasesProvider | ||
} | ||
|
||
func execute( | ||
promoOfferDetails: CustomerCenterConfigData.HelpPath.PromotionalOffer | ||
) async -> Result<PromotionalOfferData, Error> { | ||
do { | ||
let customerInfo = try await self.purchasesProvider.customerInfo() | ||
|
||
guard let productIdentifier = customerInfo.earliestExpiringAppStoreEntitlement()?.productIdentifier, | ||
let subscribedProduct = await self.purchasesProvider.products([productIdentifier]).first else { | ||
Logger.warning(Strings.could_not_offer_for_active_subscriptions) | ||
return .failure(CustomerCenterError.couldNotFindSubscriptionInformation) | ||
} | ||
|
||
guard let discount = subscribedProduct.discounts.first(where: { | ||
$0.offerIdentifier == promoOfferDetails.iosOfferId | ||
}) else { | ||
Logger.warning(Strings.could_not_offer_for_active_subscriptions) | ||
return .failure(CustomerCenterError.couldNotFindSubscriptionInformation) | ||
} | ||
|
||
let promotionalOffer = try await self.purchasesProvider.promotionalOffer(forProductDiscount: discount, | ||
product: subscribedProduct) | ||
let promotionalOfferData = PromotionalOfferData(promotionalOffer: promotionalOffer, | ||
product: subscribedProduct, | ||
promoOfferDetails: promoOfferDetails) | ||
return .success(promotionalOfferData) | ||
} catch { | ||
Logger.warning(Strings.error_fetching_promotional_offer(error)) | ||
return .failure(CustomerCenterError.couldNotFindOfferForActiveProducts) | ||
} | ||
} | ||
|
||
} | ||
|
||
#endif |
24 changes: 24 additions & 0 deletions
24
RevenueCatUI/CustomerCenter/Data/PromotionalOfferData.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,24 @@ | ||
// | ||
// 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 | ||
// | ||
// PromotionalOfferData.swift | ||
// | ||
// Created by Cesar de la Vega on 17/7/24. | ||
|
||
import Foundation | ||
import RevenueCat | ||
|
||
struct PromotionalOfferData: Identifiable { | ||
|
||
let id = UUID() | ||
let promotionalOffer: PromotionalOffer | ||
let product: StoreProduct | ||
let promoOfferDetails: CustomerCenterConfigData.HelpPath.PromotionalOffer | ||
|
||
} |
74 changes: 74 additions & 0 deletions
74
RevenueCatUI/CustomerCenter/ViewModels/FeedbackSurveyViewModel.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,74 @@ | ||
// | ||
// 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 | ||
// | ||
// FeedbackSurveyViewModel.swift | ||
// | ||
// | ||
// Created by Cesar de la Vega on 17/6/24. | ||
// | ||
|
||
import Foundation | ||
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) | ||
@MainActor | ||
class FeedbackSurveyViewModel: ObservableObject { | ||
|
||
var feedbackSurveyData: FeedbackSurveyData | ||
|
||
@Published | ||
var loadingState: String? | ||
@Published | ||
var promotionalOfferData: PromotionalOfferData? | ||
|
||
private var purchasesProvider: CustomerCenterPurchasesType | ||
private let loadPromotionalOfferUseCase: LoadPromotionalOfferUseCaseType | ||
|
||
convenience init(feedbackSurveyData: FeedbackSurveyData) { | ||
self.init(feedbackSurveyData: feedbackSurveyData, | ||
purchasesProvider: CustomerCenterPurchases(), | ||
loadPromotionalOfferUseCase: LoadPromotionalOfferUseCase()) | ||
} | ||
|
||
init(feedbackSurveyData: FeedbackSurveyData, | ||
purchasesProvider: CustomerCenterPurchasesType, | ||
loadPromotionalOfferUseCase: LoadPromotionalOfferUseCaseType) { | ||
self.feedbackSurveyData = feedbackSurveyData | ||
self.purchasesProvider = purchasesProvider | ||
self.loadPromotionalOfferUseCase = loadPromotionalOfferUseCase | ||
} | ||
|
||
func handleAction(for option: CustomerCenterConfigData.HelpPath.FeedbackSurvey.Option) async { | ||
if let promotionalOffer = option.promotionalOffer { | ||
self.loadingState = option.id | ||
let result = await loadPromotionalOfferUseCase.execute(promoOfferDetails: promotionalOffer) | ||
switch result { | ||
case .success(let promotionalOfferData): | ||
self.promotionalOfferData = promotionalOfferData | ||
case .failure: | ||
self.feedbackSurveyData.onOptionSelected() | ||
} | ||
} else { | ||
self.feedbackSurveyData.onOptionSelected() | ||
} | ||
} | ||
|
||
func handleSheetDismiss() { | ||
self.feedbackSurveyData.onOptionSelected() | ||
self.loadingState = nil | ||
} | ||
|
||
} | ||
|
||
#endif |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
[not for this PR] I was thinking, if the user has a purchase in a different store, we could probably still get it and display whatever information is available in the "no_active" page I guess but nothing to do here right now.
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.
good point, will take notes of that