From 714a789cc5ed037d158eac8b4a4f857bb26b336b Mon Sep 17 00:00:00 2001 From: Sasha Weiss Date: Wed, 13 May 2026 16:15:30 -0700 Subject: [PATCH] Make `DonationSubscriptionManager` a singleton instance, not a static class --- Signal/AppLaunch/AppEnvironment.swift | 3 +- .../Components/CVComponentState.swift | 2 +- ...onversationViewController+GiftBadges.swift | 2 +- ...eteAccountConfirmationViewController.swift | 4 +- ...adgeGiftingChooseBadgeViewController.swift | 2 +- ...tionSettingsViewController+MySupport.swift | 4 +- .../DonationSettingsViewController.swift | 20 +- .../InternalSettingsViewController.swift | 2 +- .../BadgeConfigurationViewController.swift | 2 +- .../ProfileSettingsViewController.swift | 6 +- .../Donations/BadgeThanksSheet.swift | 2 +- ...ewController+MonthlyApplePayDonation.swift | 8 +- ...ViewController+MonthlyPaypalDonation.swift | 8 +- ...ewController+OneTimeApplePayDonation.swift | 2 +- ...ViewController+OneTimePaypalDonation.swift | 2 +- .../Donations/DonateViewController.swift | 14 +- ...etailsViewController+MonthlyDonation.swift | 4 +- .../Donations/DonationViewsUtil+IDEAL.swift | 2 +- .../Donations/DonationViewsUtil.swift | 6 +- .../ChatListFYISheetCoordinator.swift | 16 +- .../Chat List/ChatListViewController.swift | 2 +- .../Archiving/BackupArchive+Shims.swift | 22 +- SignalServiceKit/Environment/AppSetup.swift | 13 +- .../Environment/DependenciesBridge.swift | 3 + ...nReceiptCredentialRedemptionJobQueue.swift | 15 +- .../Profiles/OWSUserProfile.swift | 4 +- .../StorageServiceManager.swift | 1 + .../StorageServiceProto+Sync.swift | 31 +- .../Subscriptions/Donations/CachedBadge.swift | 2 +- .../DonationSubscriptionManager.swift | 355 +++++++++--------- 30 files changed, 298 insertions(+), 261 deletions(-) diff --git a/Signal/AppLaunch/AppEnvironment.swift b/Signal/AppLaunch/AppEnvironment.swift index 4a0e411a71..d4de3225c1 100644 --- a/Signal/AppLaunch/AppEnvironment.swift +++ b/Signal/AppLaunch/AppEnvironment.swift @@ -223,10 +223,11 @@ public class AppEnvironment: NSObject { }, ) + let donationSubscriptionManager = DependenciesBridge.shared.donationSubscriptionManager cron.scheduleFrequently( mustBeRegistered: true, mustBeConnected: true, - operation: { try await DonationSubscriptionManager.redeemSubscriptionIfNecessary() }, + operation: { try await donationSubscriptionManager.redeemSubscriptionIfNecessary() }, handleResult: { switch $0 { case .success, .failure(is CancellationError): diff --git a/Signal/ConversationView/Components/CVComponentState.swift b/Signal/ConversationView/Components/CVComponentState.swift index a9c7ec9fb9..b3f5fa6d9a 100644 --- a/Signal/ConversationView/Components/CVComponentState.swift +++ b/Signal/ConversationView/Components/CVComponentState.swift @@ -2006,7 +2006,7 @@ private extension CVComponentState.Builder { self.giftBadge = GiftBadge( messageUniqueId: messageUniqueId, otherUserShortName: threadViewModel.shortName ?? threadViewModel.name, - cachedBadge: DonationSubscriptionManager.getCachedBadge(level: .giftBadge(level)), + cachedBadge: DependenciesBridge.shared.donationSubscriptionManager.getCachedBadge(level: .giftBadge(level)), expirationDate: expirationDate, redemptionState: giftBadge.redemptionState, ) diff --git a/Signal/ConversationView/ConversationViewController+GiftBadges.swift b/Signal/ConversationView/ConversationViewController+GiftBadges.swift index 3204a07b59..1946b5c817 100644 --- a/Signal/ConversationView/ConversationViewController+GiftBadges.swift +++ b/Signal/ConversationView/ConversationViewController+GiftBadges.swift @@ -83,7 +83,7 @@ extension ConversationViewController { let mode: BadgeIssueSheetState.Mode if isRedeemed { let hasCurrentSubscription = SSKEnvironment.shared.databaseStorageRef.read { tx -> Bool in - return DonationSubscriptionManager.probablyHasCurrentSubscription(tx: tx) + return DependenciesBridge.shared.donationSubscriptionManager.probablyHasCurrentSubscription(tx: tx) } mode = .giftBadgeExpired(hasCurrentSubscription: hasCurrentSubscription) } else { diff --git a/Signal/src/ViewControllers/AppSettings/Account/DeleteAccountConfirmationViewController.swift b/Signal/src/ViewControllers/AppSettings/Account/DeleteAccountConfirmationViewController.swift index 7f72d846b2..a194bb1707 100644 --- a/Signal/src/ViewControllers/AppSettings/Account/DeleteAccountConfirmationViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Account/DeleteAccountConfirmationViewController.swift @@ -386,13 +386,13 @@ class DeleteAccountConfirmationViewController: OWSTableViewController2 { private func deleteDonationSubscriptionIfNecessary() async throws { let activeSubscriptionId = SSKEnvironment.shared.databaseStorageRef.read { - DonationSubscriptionManager.getSubscriberID(transaction: $0) + DependenciesBridge.shared.donationSubscriptionManager.getSubscriberID(tx: $0) } guard let activeSubscriptionId else { return } Logger.info("Found subscriber ID. Canceling subscription...") - return try await DonationSubscriptionManager.cancelSubscription(for: activeSubscriptionId) + return try await DependenciesBridge.shared.donationSubscriptionManager.cancelSubscription(for: activeSubscriptionId) } private func leaveGroups() async throws { diff --git a/Signal/src/ViewControllers/AppSettings/Donations/BadgeGiftingChooseBadgeViewController.swift b/Signal/src/ViewControllers/AppSettings/Donations/BadgeGiftingChooseBadgeViewController.swift index 8de8ac5586..25b721b2bb 100644 --- a/Signal/src/ViewControllers/AppSettings/Donations/BadgeGiftingChooseBadgeViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Donations/BadgeGiftingChooseBadgeViewController.swift @@ -74,7 +74,7 @@ public class BadgeGiftingChooseBadgeViewController: OWSTableViewController2 { private func loadData() async -> State { do { Logger.info("[Gifting] Fetching donation configuration...") - let donationConfiguration = try await DonationSubscriptionManager.fetchDonationConfiguration() + let donationConfiguration = try await DependenciesBridge.shared.donationSubscriptionManager.fetchDonationConfiguration() Logger.info("[Gifting] Populating badge assets...") let giftBadge = donationConfiguration.gift.badge try await SSKEnvironment.shared.profileManagerRef.badgeStore.populateAssetsOnBadge(giftBadge) diff --git a/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController+MySupport.swift b/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController+MySupport.swift index 99102a2f8c..4afb085da1 100644 --- a/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController+MySupport.swift +++ b/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController+MySupport.swift @@ -581,10 +581,10 @@ extension DonationSettingsViewController { return ActionSheetAction(title: title.localizedTitle) { _ in Task.detached { let subscriberId = SSKEnvironment.shared.databaseStorageRef.read { tx in - return DonationSubscriptionManager.getSubscriberID(transaction: tx) + return DependenciesBridge.shared.donationSubscriptionManager.getSubscriberID(tx: tx) } if let subscriberId { - try await DonationSubscriptionManager.cancelSubscription(for: subscriberId) + try await DependenciesBridge.shared.donationSubscriptionManager.cancelSubscription(for: subscriberId) } await self.loadAndUpdateState() await self.showDonateViewController(preferredDonateMode: .monthly) diff --git a/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController.swift b/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController.swift index 221c1bcf2e..7f834486d8 100644 --- a/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController.swift @@ -120,7 +120,7 @@ class DonationSettingsViewController: OWSTableViewController2 { @objc private func didLongPressAvatar(sender: UIGestureRecognizer) { - let subscriberID = SSKEnvironment.shared.databaseStorageRef.read { DonationSubscriptionManager.getSubscriberID(transaction: $0) } + let subscriberID = SSKEnvironment.shared.databaseStorageRef.read { DependenciesBridge.shared.donationSubscriptionManager.getSubscriberID(tx: $0) } guard let subscriberID else { return } UIPasteboard.general.string = subscriberID.asBase64Url @@ -163,7 +163,7 @@ class DonationSettingsViewController: OWSTableViewController2 { let resultStore = DependenciesBridge.shared.donationReceiptCredentialResultStore return ( - subscriberID: DonationSubscriptionManager.getSubscriberID(transaction: tx), + subscriberID: DependenciesBridge.shared.donationSubscriptionManager.getSubscriberID(tx: tx), hasEverRedeemedRecurringSubscriptionBadge: resultStore.getRedemptionSuccessForAnyRecurringSubscription(tx: tx) != nil, recurringSubscriptionReceiptCredentialRequestError: resultStore.getRequestErrorForAnyRecurringSubscription(tx: tx), oneTimeBoostReceiptCredentialRequestError: resultStore.getRequestError(errorMode: .oneTimeBoost, tx: tx), @@ -175,7 +175,7 @@ class DonationSettingsViewController: OWSTableViewController2 { } async let currentSubscription = DonationViewsUtil.loadCurrentSubscription(subscriberID: subscriberID) - async let donationConfiguration = DonationSubscriptionManager.fetchDonationConfiguration() + async let donationConfiguration = DependenciesBridge.shared.donationSubscriptionManager.fetchDonationConfiguration() do { let subscriptionStatus: State.SubscriptionStatus @@ -451,7 +451,7 @@ class DonationSettingsViewController: OWSTableViewController2 { static func shouldShowExpiredGiftBadgeSheetWithSneakyTransaction() -> Bool { let expiredGiftBadgeID = SSKEnvironment.shared.databaseStorageRef.read { transaction in - DonationSubscriptionManager.mostRecentlyExpiredGiftBadgeID(transaction: transaction) + DependenciesBridge.shared.donationSubscriptionManager.mostRecentlyExpiredGiftBadgeID(tx: transaction) } guard let expiredGiftBadgeID, GiftBadgeIds.contains(expiredGiftBadgeID) else { return false @@ -465,7 +465,7 @@ class DonationSettingsViewController: OWSTableViewController2 { } Logger.info("[Gifting] Preparing to show gift badge expiration sheet...") firstly { - DonationSubscriptionManager.getCachedBadge(level: .giftBadge(.signalGift)).fetchIfNeeded() + DependenciesBridge.shared.donationSubscriptionManager.getCachedBadge(level: .giftBadge(.signalGift)).fetchIfNeeded() }.done { [weak self] cachedValue in guard let self else { return } guard UIApplication.shared.frontmostViewController == self else { return } @@ -473,12 +473,12 @@ class DonationSettingsViewController: OWSTableViewController2 { // The server confirmed this badge doesn't exist. This shouldn't happen, // but clear the flag so that we don't keep trying. Logger.warn("[Gifting] Clearing expired badge ID because the server said it didn't exist") - DonationSubscriptionManager.clearMostRecentlyExpiredBadgeIDWithSneakyTransaction() + DependenciesBridge.shared.donationSubscriptionManager.clearMostRecentlyExpiredBadgeIDWithSneakyTransaction() return } let hasCurrentSubscription = SSKEnvironment.shared.databaseStorageRef.read { tx -> Bool in - return DonationSubscriptionManager.probablyHasCurrentSubscription(tx: tx) + return DependenciesBridge.shared.donationSubscriptionManager.probablyHasCurrentSubscription(tx: tx) } Logger.info("[Gifting] Showing badge gift expiration sheet (hasCurrentSubscription: \(hasCurrentSubscription))") let sheet = BadgeIssueSheet(badge: profileBadge, mode: .giftBadgeExpired(hasCurrentSubscription: hasCurrentSubscription)) @@ -486,7 +486,7 @@ class DonationSettingsViewController: OWSTableViewController2 { self.present(sheet, animated: true) // We've shown it, so don't show it again. - DonationSubscriptionManager.clearMostRecentlyExpiredGiftBadgeIDWithSneakyTransaction() + DependenciesBridge.shared.donationSubscriptionManager.clearMostRecentlyExpiredGiftBadgeIDWithSneakyTransaction() }.cauterize() } @@ -728,10 +728,10 @@ extension DonationSettingsViewController: BadgeConfigurationDelegate { } await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx in - DonationSubscriptionManager.setDisplayBadgesOnProfile( + DependenciesBridge.shared.donationSubscriptionManager.setDisplayBadgesOnProfile( displayBadgesOnProfile, updateStorageService: true, - transaction: tx, + tx: tx, ) } } catch { diff --git a/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift b/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift index 388e35d492..2f1f27bb75 100644 --- a/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Internal/InternalSettingsViewController.swift @@ -246,7 +246,7 @@ class InternalSettingsViewController: OWSTableViewController2 { TSThread.anyFetchAll(transaction: tx).filter { $0.isGroupThread }.count, TSInteraction.anyCount(transaction: tx), try? Attachment.Record.fetchCount(tx.database), - DonationSubscriptionManager.getSubscriberID(transaction: tx), + DependenciesBridge.shared.donationSubscriptionManager.getSubscriberID(tx: tx), SSKEnvironment.shared.storageServiceManagerRef.currentManifestVersion(tx: tx), DependenciesBridge.shared.tsAccountManager.getRegistrationId(for: .aci, tx: tx), DependenciesBridge.shared.tsAccountManager.getRegistrationId(for: .pni, tx: tx), diff --git a/Signal/src/ViewControllers/AppSettings/Profile/BadgeConfigurationViewController.swift b/Signal/src/ViewControllers/AppSettings/Profile/BadgeConfigurationViewController.swift index f30b6c6588..29b1cbfbd3 100644 --- a/Signal/src/ViewControllers/AppSettings/Profile/BadgeConfigurationViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Profile/BadgeConfigurationViewController.swift @@ -62,7 +62,7 @@ final class BadgeConfigurationViewController: OWSTableViewController2, BadgeColl static func load(delegate: BadgeConfigurationDelegate, tx: DBReadTransaction) -> Self { let badges = SSKEnvironment.shared.profileManagerRef.localUserProfile(tx: tx)?.badges ?? [] - let shouldDisplayOnProfile = DonationSubscriptionManager.displayBadgesOnProfile(transaction: tx) + let shouldDisplayOnProfile = DependenciesBridge.shared.donationSubscriptionManager.displayBadgesOnProfile(tx: tx) return Self(availableBadges: badges, shouldDisplayOnProfile: shouldDisplayOnProfile, delegate: delegate) } diff --git a/Signal/src/ViewControllers/AppSettings/Profile/ProfileSettingsViewController.swift b/Signal/src/ViewControllers/AppSettings/Profile/ProfileSettingsViewController.swift index 60e7f8872a..6e1289e91d 100644 --- a/Signal/src/ViewControllers/AppSettings/Profile/ProfileSettingsViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Profile/ProfileSettingsViewController.swift @@ -116,7 +116,7 @@ class ProfileSettingsViewController: OWSTableViewController2 { let localProfile: OWSUserProfile (localProfile, displayBadgesOnProfile) = databaseStorage.read { tx in ( profileManager.localUserProfile(tx: tx)!, - DonationSubscriptionManager.displayBadgesOnProfile(transaction: tx), + DependenciesBridge.shared.donationSubscriptionManager.displayBadgesOnProfile(tx: tx), ) } allBadges = localProfile.badges @@ -725,10 +725,10 @@ class ProfileSettingsViewController: OWSTableViewController2 { } try await updatePromise.awaitable() await databaseStorage.awaitableWrite { transaction in - DonationSubscriptionManager.setDisplayBadgesOnProfile( + DependenciesBridge.shared.donationSubscriptionManager.setDisplayBadgesOnProfile( displayBadgesOnProfile, updateStorageService: true, - transaction: transaction, + tx: transaction, ) } } catch { diff --git a/Signal/src/ViewControllers/Donations/BadgeThanksSheet.swift b/Signal/src/ViewControllers/Donations/BadgeThanksSheet.swift index 15ad0e23bb..632c7a416b 100644 --- a/Signal/src/ViewControllers/Donations/BadgeThanksSheet.swift +++ b/Signal/src/ViewControllers/Donations/BadgeThanksSheet.swift @@ -229,7 +229,7 @@ class BadgeThanksSheet: OWSTableSheetViewController { guard let giftBadge = incomingMessage.giftBadge else { throw OWSAssertionError("trying to redeem message without a badge") } - try await DonationSubscriptionManager.redeemReceiptCredentialPresentation( + try await DependenciesBridge.shared.donationSubscriptionManager.redeemReceiptCredentialPresentation( receiptCredentialPresentation: try giftBadge.getReceiptCredentialPresentation(), ) await Self.updateGiftBadge(incomingMessage: incomingMessage, state: .redeemed) diff --git a/Signal/src/ViewControllers/Donations/DonateViewController+MonthlyApplePayDonation.swift b/Signal/src/ViewControllers/Donations/DonateViewController+MonthlyApplePayDonation.swift index 9ad18843cd..83c5bd36ee 100644 --- a/Signal/src/ViewControllers/Donations/DonateViewController+MonthlyApplePayDonation.swift +++ b/Signal/src/ViewControllers/Donations/DonateViewController+MonthlyApplePayDonation.swift @@ -33,13 +33,13 @@ extension DonateViewController { do { if let existingSubscriberId = monthly.subscriberID { Logger.info("[Donations] Cancelling existing subscription") - try await DonationSubscriptionManager.cancelSubscription(for: existingSubscriberId) + try await DependenciesBridge.shared.donationSubscriptionManager.cancelSubscription(for: existingSubscriberId) } else { Logger.info("[Donations] No existing subscription to cancel") } Logger.info("[Donations] Preparing new monthly subscription with Apple Pay") - subscriberId = try await DonationSubscriptionManager.prepareNewSubscription( + subscriberId = try await DependenciesBridge.shared.donationSubscriptionManager.prepareNewSubscription( currencyCode: monthly.selectedCurrencyCode, ) @@ -54,7 +54,7 @@ extension DonateViewController { let paymentMethodId = confirmedIntent.paymentMethodId Logger.info("[Donations] Finalizing new subscription for Apple Pay donation") - _ = try await DonationSubscriptionManager.finalizeNewSubscription( + _ = try await DependenciesBridge.shared.donationSubscriptionManager.finalizeNewSubscription( forSubscriberId: subscriberId, paymentType: .applePay(paymentMethodId: paymentMethodId), subscription: selectedSubscriptionLevel, @@ -76,7 +76,7 @@ extension DonateViewController { from: self, operation: { try await DonationViewsUtil.waitForRedemption(paymentMethod: .applePay) { - try await DonationSubscriptionManager.requestAndRedeemReceipt( + try await DependenciesBridge.shared.donationSubscriptionManager.requestAndRedeemReceipt( subscriberId: subscriberId, subscriptionLevel: selectedSubscriptionLevel.level, priorSubscriptionLevel: monthly.currentSubscriptionLevel?.level, diff --git a/Signal/src/ViewControllers/Donations/DonateViewController+MonthlyPaypalDonation.swift b/Signal/src/ViewControllers/Donations/DonateViewController+MonthlyPaypalDonation.swift index 1085832f74..b4c766dd58 100644 --- a/Signal/src/ViewControllers/Donations/DonateViewController+MonthlyPaypalDonation.swift +++ b/Signal/src/ViewControllers/Donations/DonateViewController+MonthlyPaypalDonation.swift @@ -78,13 +78,13 @@ extension DonateViewController { wrappedAsyncBlock: { if let existingSubscriberId = monthly.subscriberID { Logger.info("[Donations] Cancelling existing subscription") - try await DonationSubscriptionManager.cancelSubscription(for: existingSubscriberId) + try await DependenciesBridge.shared.donationSubscriptionManager.cancelSubscription(for: existingSubscriberId) } else { Logger.info("[Donations] No existing subscription to cancel") } Logger.info("[Donations] Preparing new monthly subscription with PayPal") - let subscriberId = try await DonationSubscriptionManager.prepareNewSubscription( + let subscriberId = try await DependenciesBridge.shared.donationSubscriptionManager.prepareNewSubscription( currencyCode: monthly.selectedCurrencyCode, ) @@ -107,7 +107,7 @@ extension DonateViewController { from: self, operation: { Logger.info("[Donations] Finalizing new subscription for PayPal donation") - _ = try await DonationSubscriptionManager.finalizeNewSubscription( + _ = try await DependenciesBridge.shared.donationSubscriptionManager.finalizeNewSubscription( forSubscriberId: subscriberId, paymentType: .paypal(paymentMethodId: paymentMethodId), subscription: selectedSubscriptionLevel, @@ -116,7 +116,7 @@ extension DonateViewController { Logger.info("[Donations] Redeeming monthly receipt for PayPal donation") try await DonationViewsUtil.waitForRedemption(paymentMethod: .paypal) { - try await DonationSubscriptionManager.requestAndRedeemReceipt( + try await DependenciesBridge.shared.donationSubscriptionManager.requestAndRedeemReceipt( subscriberId: subscriberId, subscriptionLevel: selectedSubscriptionLevel.level, priorSubscriptionLevel: monthly.currentSubscriptionLevel?.level, diff --git a/Signal/src/ViewControllers/Donations/DonateViewController+OneTimeApplePayDonation.swift b/Signal/src/ViewControllers/Donations/DonateViewController+OneTimeApplePayDonation.swift index f5aaa316c9..d1d78543f1 100644 --- a/Signal/src/ViewControllers/Donations/DonateViewController+OneTimeApplePayDonation.swift +++ b/Signal/src/ViewControllers/Donations/DonateViewController+OneTimeApplePayDonation.swift @@ -47,7 +47,7 @@ extension DonateViewController { from: self, operation: { try await DonationViewsUtil.waitForRedemption(paymentMethod: .applePay) { - try await DonationSubscriptionManager.requestAndRedeemReceipt( + try await DependenciesBridge.shared.donationSubscriptionManager.requestAndRedeemReceipt( boostPaymentIntentId: confirmedIntent.paymentIntentId, amount: amount, paymentProcessor: .stripe, diff --git a/Signal/src/ViewControllers/Donations/DonateViewController+OneTimePaypalDonation.swift b/Signal/src/ViewControllers/Donations/DonateViewController+OneTimePaypalDonation.swift index ca741a6877..b3e927e61e 100644 --- a/Signal/src/ViewControllers/Donations/DonateViewController+OneTimePaypalDonation.swift +++ b/Signal/src/ViewControllers/Donations/DonateViewController+OneTimePaypalDonation.swift @@ -83,7 +83,7 @@ extension DonateViewController { ) try await DonationViewsUtil.waitForRedemption(paymentMethod: .paypal) { - try await DonationSubscriptionManager.requestAndRedeemReceipt( + try await DependenciesBridge.shared.donationSubscriptionManager.requestAndRedeemReceipt( boostPaymentIntentId: paymentIntentId, amount: amount, paymentProcessor: .braintree, diff --git a/Signal/src/ViewControllers/Donations/DonateViewController.swift b/Signal/src/ViewControllers/Donations/DonateViewController.swift index cb99e37312..4b69f945a7 100644 --- a/Signal/src/ViewControllers/Donations/DonateViewController.swift +++ b/Signal/src/ViewControllers/Donations/DonateViewController.swift @@ -528,7 +528,7 @@ class DonateViewController: OWSViewController, OWSNavigationChildController { try await DonationViewsUtil.wrapInProgressView( from: self, operation: { - let subscription = try await DonationSubscriptionManager.updateSubscriptionLevel( + let subscription = try await DependenciesBridge.shared.donationSubscriptionManager.updateSubscriptionLevel( for: subscriberID, to: selectedSubscriptionLevel, currencyCode: monthly.selectedCurrencyCode, @@ -540,7 +540,7 @@ class DonateViewController: OWSViewController, OWSNavigationChildController { try await DonationViewsUtil.waitForRedemption(paymentMethod: subscription.donationPaymentMethod) { // Treat updates like new subscriptions - try await DonationSubscriptionManager.requestAndRedeemReceipt( + try await DependenciesBridge.shared.donationSubscriptionManager.requestAndRedeemReceipt( subscriberId: subscriberID, subscriptionLevel: selectedSubscriptionLevel.level, priorSubscriptionLevel: subscription.level, @@ -657,7 +657,7 @@ class DonateViewController: OWSViewController, OWSNavigationChildController { canCancel: false, asyncBlock: { modal in do { - try await DonationSubscriptionManager.cancelSubscription(for: subscriberID) + try await DependenciesBridge.shared.donationSubscriptionManager.cancelSubscription(for: subscriberID) modal.dismiss { [weak self] in guard let self else { return } self.onFinished(.monthlySubscriptionCancelled( @@ -774,9 +774,9 @@ class DonateViewController: OWSViewController, OWSNavigationChildController { pendingIDEALSubscription, ) = SSKEnvironment.shared.databaseStorageRef.read { ( - DonationSubscriptionManager.getSubscriberID(transaction: $0), - DonationSubscriptionManager.getSubscriberCurrencyCode(transaction: $0), - DonationSubscriptionManager.getMostRecentSubscriptionPaymentMethod(transaction: $0), + DependenciesBridge.shared.donationSubscriptionManager.getSubscriberID(tx: $0), + DependenciesBridge.shared.donationSubscriptionManager.getSubscriberCurrencyCode(tx: $0), + DependenciesBridge.shared.donationSubscriptionManager.getMostRecentSubscriptionPaymentMethod(tx: $0), DependenciesBridge.shared.donationReceiptCredentialResultStore .getRequestError(errorMode: .oneTimeBoost, tx: $0), DependenciesBridge.shared.donationReceiptCredentialResultStore @@ -787,7 +787,7 @@ class DonateViewController: OWSViewController, OWSNavigationChildController { } let donationConfigurationClosure = { () async throws -> DonationConfiguration in - let donationConfiguration = try await DonationSubscriptionManager.fetchDonationConfiguration() + let donationConfiguration = try await DependenciesBridge.shared.donationSubscriptionManager.fetchDonationConfiguration() let boostBadge = donationConfiguration.boost.badge let subscriptionBadges = donationConfiguration.subscription.levels.map { $0.badge } diff --git a/Signal/src/ViewControllers/Donations/DonationPaymentDetailsViewController+MonthlyDonation.swift b/Signal/src/ViewControllers/Donations/DonationPaymentDetailsViewController+MonthlyDonation.swift index e1f0e548d4..8486b0bf71 100644 --- a/Signal/src/ViewControllers/Donations/DonationPaymentDetailsViewController+MonthlyDonation.swift +++ b/Signal/src/ViewControllers/Donations/DonationPaymentDetailsViewController+MonthlyDonation.swift @@ -28,13 +28,13 @@ extension DonationPaymentDetailsViewController { operation: { if let existingSubscriberId { Logger.info("[Donations] Cancelling existing subscription") - try await DonationSubscriptionManager.cancelSubscription(for: existingSubscriberId) + try await DependenciesBridge.shared.donationSubscriptionManager.cancelSubscription(for: existingSubscriberId) } else { Logger.info("[Donations] No existing subscription to cancel") } Logger.info("[Donations] Preparing new monthly subscription") - let subscriberId = try await DonationSubscriptionManager.prepareNewSubscription(currencyCode: currencyCode) + let subscriberId = try await DependenciesBridge.shared.donationSubscriptionManager.prepareNewSubscription(currencyCode: currencyCode) Logger.info("[Donations] Creating Signal payment method for new monthly subscription") let clientSecret = try await Stripe.createSignalPaymentMethodForSubscription(subscriberId: subscriberId) diff --git a/Signal/src/ViewControllers/Donations/DonationViewsUtil+IDEAL.swift b/Signal/src/ViewControllers/Donations/DonationViewsUtil+IDEAL.swift index b0270d66a7..559cf74e48 100644 --- a/Signal/src/ViewControllers/Donations/DonationViewsUtil+IDEAL.swift +++ b/Signal/src/ViewControllers/Donations/DonationViewsUtil+IDEAL.swift @@ -219,7 +219,7 @@ extension DonationViewsUtil { ) async throws -> ProfileBadge? { switch donationType { case .oneTime: - switch try await DonationSubscriptionManager.getCachedBadge(level: .boostBadge).fetchIfNeeded().awaitable() { + switch try await DependenciesBridge.shared.donationSubscriptionManager.getCachedBadge(level: .boostBadge).fetchIfNeeded().awaitable() { case .notFound: return nil case let .profileBadge(profileBadge): diff --git a/Signal/src/ViewControllers/Donations/DonationViewsUtil.swift b/Signal/src/ViewControllers/Donations/DonationViewsUtil.swift index c799dec053..b9992b9959 100644 --- a/Signal/src/ViewControllers/Donations/DonationViewsUtil.swift +++ b/Signal/src/ViewControllers/Donations/DonationViewsUtil.swift @@ -295,7 +295,7 @@ public enum DonationViewsUtil { paymentMethod: DonationPaymentMethod, ) async throws { return try await DonationViewsUtil.waitForRedemption(paymentMethod: paymentMethod) { - try await DonationSubscriptionManager.requestAndRedeemReceipt( + try await DependenciesBridge.shared.donationSubscriptionManager.requestAndRedeemReceipt( boostPaymentIntentId: paymentIntentId, amount: amount, paymentProcessor: .stripe, @@ -313,7 +313,7 @@ public enum DonationViewsUtil { ) async throws { Logger.info("[Donations] Finalizing new subscription") - _ = try await DonationSubscriptionManager.finalizeNewSubscription( + _ = try await DependenciesBridge.shared.donationSubscriptionManager.finalizeNewSubscription( forSubscriberId: subscriberId, paymentType: paymentType, subscription: newSubscriptionLevel, @@ -323,7 +323,7 @@ public enum DonationViewsUtil { Logger.info("[Donations] Redeeming monthly receipts") return try await DonationViewsUtil.waitForRedemption(paymentMethod: paymentType.paymentMethod) { - try await DonationSubscriptionManager.requestAndRedeemReceipt( + try await DependenciesBridge.shared.donationSubscriptionManager.requestAndRedeemReceipt( subscriberId: subscriberId, subscriptionLevel: newSubscriptionLevel.level, priorSubscriptionLevel: priorSubscriptionLevel?.level, diff --git a/Signal/src/ViewControllers/HomeView/Chat List/ChatListFYISheetCoordinator.swift b/Signal/src/ViewControllers/HomeView/Chat List/ChatListFYISheetCoordinator.swift index c765492422..18dcb022ef 100644 --- a/Signal/src/ViewControllers/HomeView/Chat List/ChatListFYISheetCoordinator.swift +++ b/Signal/src/ViewControllers/HomeView/Chat List/ChatListFYISheetCoordinator.swift @@ -51,7 +51,7 @@ class ChatListFYISheetCoordinator { private let backupExportJobRunner: BackupExportJobRunner private let backupSubscriptionIssueStore: BackupSubscriptionIssueStore private let donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore - private let donationSubscriptionManager: DonationSubscriptionManager.Type + private let donationSubscriptionManager: DonationSubscriptionManager private let db: DB private let keyTransparencyStore: KeyTransparencyStore private let networkManager: NetworkManager @@ -61,7 +61,7 @@ class ChatListFYISheetCoordinator { backupExportJobRunner: BackupExportJobRunner, backupSubscriptionIssueStore: BackupSubscriptionIssueStore, donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore, - donationSubscriptionManager: DonationSubscriptionManager.Type, + donationSubscriptionManager: DonationSubscriptionManager, db: DB, keyTransparencyStore: KeyTransparencyStore, networkManager: NetworkManager, @@ -104,13 +104,13 @@ class ChatListFYISheetCoordinator { } else if let sheet = shouldShowBadgeIssueSheet(errorMode: .recurringSubscriptionRenewal, tx: tx) { return sheet } else if - let expiredBadgeID = donationSubscriptionManager.mostRecentlyExpiredBadgeID(transaction: tx), - donationSubscriptionManager.showExpirySheetOnHomeScreenKey(transaction: tx) + let expiredBadgeID = donationSubscriptionManager.mostRecentlyExpiredBadgeID(tx: tx), + donationSubscriptionManager.showExpirySheetOnHomeScreenKey(tx: tx) { return .badgeExpiration(FYISheet.BadgeExpiration( expiredBadgeID: expiredBadgeID, - donationSubscriberID: donationSubscriptionManager.getSubscriberID(transaction: tx), - mostRecentSubscriptionPaymentMethod: donationSubscriptionManager.getMostRecentSubscriptionPaymentMethod(transaction: tx), + donationSubscriberID: donationSubscriptionManager.getSubscriberID(tx: tx), + mostRecentSubscriptionPaymentMethod: donationSubscriptionManager.getMostRecentSubscriptionPaymentMethod(tx: tx), probablyHasCurrentSubscription: donationSubscriptionManager.probablyHasCurrentSubscription(tx: tx), )) } else if backupSubscriptionIssueStore.shouldWarnIAPSubscriptionExpired(tx: tx) { @@ -335,7 +335,7 @@ class ChatListFYISheetCoordinator { await chatListViewController.awaitablePresent(badgeIssueSheet, animated: true) await db.awaitableWrite { tx in - donationSubscriptionManager.setShowExpirySheetOnHomeScreenKey(show: false, transaction: tx) + donationSubscriptionManager.setShowExpirySheetOnHomeScreenKey(show: false, tx: tx) } } else if SubscriptionBadgeIds.contains(expiredBadgeID) { /// We expect to show an error sheet when the subscription fails to @@ -390,7 +390,7 @@ class ChatListFYISheetCoordinator { } await db.awaitableWrite { tx in - donationSubscriptionManager.setShowExpirySheetOnHomeScreenKey(show: false, transaction: tx) + donationSubscriptionManager.setShowExpirySheetOnHomeScreenKey(show: false, tx: tx) } } } diff --git a/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController.swift b/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController.swift index f352419cee..2531d08812 100644 --- a/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController.swift +++ b/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController.swift @@ -376,7 +376,7 @@ public class ChatListViewController: OWSViewController, HomeTabViewController { backupExportJobRunner: DependenciesBridge.shared.backupExportJobRunner, backupSubscriptionIssueStore: BackupSubscriptionIssueStore(), donationReceiptCredentialResultStore: DependenciesBridge.shared.donationReceiptCredentialResultStore, - donationSubscriptionManager: DonationSubscriptionManager.self, + donationSubscriptionManager: DependenciesBridge.shared.donationSubscriptionManager, db: DependenciesBridge.shared.db, keyTransparencyStore: KeyTransparencyStore(), networkManager: SSKEnvironment.shared.networkManagerRef, diff --git a/SignalServiceKit/Backups/Archiving/BackupArchive+Shims.swift b/SignalServiceKit/Backups/Archiving/BackupArchive+Shims.swift index 3dbb94756d..ff01613637 100644 --- a/SignalServiceKit/Backups/Archiving/BackupArchive+Shims.swift +++ b/SignalServiceKit/Backups/Archiving/BackupArchive+Shims.swift @@ -125,36 +125,42 @@ public protocol _MessageBackup_DonationSubscriptionManagerShim { } public class _MessageBackup_DonationSubscriptionManagerWrapper: _MessageBackup_DonationSubscriptionManagerShim { + private let donationSubscriptionManager: DonationSubscriptionManager + + public init(_ donationSubscriptionManager: DonationSubscriptionManager) { + self.donationSubscriptionManager = donationSubscriptionManager + } + public func displayBadgesOnProfile(tx: DBReadTransaction) -> Bool { - DonationSubscriptionManager.displayBadgesOnProfile(transaction: tx) + donationSubscriptionManager.displayBadgesOnProfile(tx: tx) } public func setDisplayBadgesOnProfile(value: Bool, tx: DBWriteTransaction) { - DonationSubscriptionManager.setDisplayBadgesOnProfile(value, updateStorageService: false, transaction: tx) + donationSubscriptionManager.setDisplayBadgesOnProfile(value, updateStorageService: false, tx: tx) } public func getSubscriberID(tx: DBReadTransaction) -> Data? { - DonationSubscriptionManager.getSubscriberID(transaction: tx) + donationSubscriptionManager.getSubscriberID(tx: tx) } public func setSubscriberID(subscriberID: Data, tx: DBWriteTransaction) { - DonationSubscriptionManager.setSubscriberID(subscriberID, transaction: tx) + donationSubscriptionManager.setSubscriberID(subscriberID, tx: tx) } public func getSubscriberCurrencyCode(tx: DBReadTransaction) -> String? { - DonationSubscriptionManager.getSubscriberCurrencyCode(transaction: tx) + donationSubscriptionManager.getSubscriberCurrencyCode(tx: tx) } public func setSubscriberCurrencyCode(currencyCode: Currency.Code?, tx: DBWriteTransaction) { - DonationSubscriptionManager.setSubscriberCurrencyCode(currencyCode, transaction: tx) + donationSubscriptionManager.setSubscriberCurrencyCode(currencyCode, tx: tx) } public func userManuallyCancelledSubscription(tx: DBReadTransaction) -> Bool { - DonationSubscriptionManager.userManuallyCancelledSubscription(transaction: tx) + donationSubscriptionManager.userManuallyCancelledSubscription(tx: tx) } public func setUserManuallyCancelledSubscription(value: Bool, tx: DBWriteTransaction) { - DonationSubscriptionManager.setUserManuallyCancelledSubscription(value, updateStorageService: false, transaction: tx) + donationSubscriptionManager.setUserManuallyCancelledSubscription(value, updateStorageService: false, tx: tx) } } diff --git a/SignalServiceKit/Environment/AppSetup.swift b/SignalServiceKit/Environment/AppSetup.swift index bd733cc0c1..3051a5b5af 100644 --- a/SignalServiceKit/Environment/AppSetup.swift +++ b/SignalServiceKit/Environment/AppSetup.swift @@ -1232,6 +1232,15 @@ extension AppSetup.GlobalsContinuation { ) let donationReceiptCredentialResultStore = DonationReceiptCredentialResultStore() + let donationSubscriptionManager = DonationSubscriptionManager( + db: db, + donationReceiptCredentialResultStore: donationReceiptCredentialResultStore, + networkManager: networkManager, + profileManager: profileManager, + storageServiceManager: storageServiceManager, + subscriptionConfigManager: subscriptionConfigManager, + tsAccountManager: tsAccountManager, + ) let usernameApiClient = UsernameApiClientImpl( networkManager: networkManager, @@ -1452,7 +1461,7 @@ extension AppSetup.GlobalsContinuation { callServiceSettingsStore: CallServiceSettingsStore(), chatStyleArchiver: backupChatStyleArchiver, disappearingMessageConfigurationStore: disappearingMessagesConfigurationStore, - donationSubscriptionManager: BackupArchive.Wrappers.DonationSubscriptionManager(), + donationSubscriptionManager: BackupArchive.Wrappers.DonationSubscriptionManager(donationSubscriptionManager), imageQuality: BackupArchive.Wrappers.ImageQuality(), keyTransparencyManager: keyTransparencyManager, keyTransparencyStore: keyTransparencyStore, @@ -1743,6 +1752,7 @@ extension AppSetup.GlobalsContinuation { disappearingMessagesConfigurationStore: disappearingMessagesConfigurationStore, disappearingMessagesExpirationJob: disappearingMessagesExpirationJob, donationReceiptCredentialResultStore: donationReceiptCredentialResultStore, + donationSubscriptionManager: donationSubscriptionManager, editManager: editManager, editMessageStore: editMessageStore, externalPendingIDEALDonationStore: externalPendingIDEALDonationStore, @@ -1866,6 +1876,7 @@ extension AppSetup.GlobalsContinuation { dateProvider: dateProvider, db: db, donationReceiptCredentialResultStore: donationReceiptCredentialResultStore, + donationSubscriptionManager: donationSubscriptionManager, networkManager: networkManager, profileManager: profileManager, reachabilityManager: reachabilityManager, diff --git a/SignalServiceKit/Environment/DependenciesBridge.swift b/SignalServiceKit/Environment/DependenciesBridge.swift index 19f2fb75eb..c588a52e26 100644 --- a/SignalServiceKit/Environment/DependenciesBridge.swift +++ b/SignalServiceKit/Environment/DependenciesBridge.swift @@ -110,6 +110,7 @@ public class DependenciesBridge { public let disappearingMessagesConfigurationStore: DisappearingMessagesConfigurationStore public let disappearingMessagesExpirationJob: DisappearingMessagesExpirationJob public let donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore + public let donationSubscriptionManager: DonationSubscriptionManager public let editManager: EditManager public let editMessageStore: EditMessageStore public let externalPendingIDEALDonationStore: ExternalPendingIDEALDonationStore @@ -252,6 +253,7 @@ public class DependenciesBridge { disappearingMessagesConfigurationStore: DisappearingMessagesConfigurationStore, disappearingMessagesExpirationJob: DisappearingMessagesExpirationJob, donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore, + donationSubscriptionManager: DonationSubscriptionManager, editManager: EditManager, editMessageStore: EditMessageStore, externalPendingIDEALDonationStore: ExternalPendingIDEALDonationStore, @@ -393,6 +395,7 @@ public class DependenciesBridge { self.disappearingMessagesConfigurationStore = disappearingMessagesConfigurationStore self.disappearingMessagesExpirationJob = disappearingMessagesExpirationJob self.donationReceiptCredentialResultStore = donationReceiptCredentialResultStore + self.donationSubscriptionManager = donationSubscriptionManager self.editManager = editManager self.editMessageStore = editMessageStore self.externalPendingIDEALDonationStore = externalPendingIDEALDonationStore diff --git a/SignalServiceKit/Jobs/DonationReceiptCredentialRedemptionJobQueue.swift b/SignalServiceKit/Jobs/DonationReceiptCredentialRedemptionJobQueue.swift index 372c3425d7..6c2e088e10 100644 --- a/SignalServiceKit/Jobs/DonationReceiptCredentialRedemptionJobQueue.swift +++ b/SignalServiceKit/Jobs/DonationReceiptCredentialRedemptionJobQueue.swift @@ -51,6 +51,7 @@ public class DonationReceiptCredentialRedemptionJobQueue { dateProvider: @escaping DateProvider, db: any DB, donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore, + donationSubscriptionManager: DonationSubscriptionManager, networkManager: NetworkManager, profileManager: ProfileManager, reachabilityManager: SSKReachabilityManager, @@ -60,6 +61,7 @@ public class DonationReceiptCredentialRedemptionJobQueue { dateProvider: dateProvider, db: db, donationReceiptCredentialResultStore: donationReceiptCredentialResultStore, + donationSubscriptionManager: donationSubscriptionManager, logger: .donations, networkManager: networkManager, profileManager: profileManager, @@ -222,6 +224,7 @@ private class DonationReceiptCredentialRedemptionJobRunnerFactory: JobRunnerFact private let dateProvider: DateProvider private let db: DB private let donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore + private let donationSubscriptionManager: DonationSubscriptionManager private let logger: PrefixedLogger private let networkManager: NetworkManager private let profileManager: ProfileManager @@ -231,6 +234,7 @@ private class DonationReceiptCredentialRedemptionJobRunnerFactory: JobRunnerFact dateProvider: @escaping DateProvider, db: DB, donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore, + donationSubscriptionManager: DonationSubscriptionManager, logger: PrefixedLogger, networkManager: NetworkManager, profileManager: ProfileManager, @@ -239,6 +243,7 @@ private class DonationReceiptCredentialRedemptionJobRunnerFactory: JobRunnerFact self.dateProvider = dateProvider self.db = db self.donationReceiptCredentialResultStore = donationReceiptCredentialResultStore + self.donationSubscriptionManager = donationSubscriptionManager self.logger = logger self.networkManager = networkManager self.profileManager = profileManager @@ -253,6 +258,7 @@ private class DonationReceiptCredentialRedemptionJobRunnerFactory: JobRunnerFact dateProvider: dateProvider, db: db, donationReceiptCredentialResultStore: donationReceiptCredentialResultStore, + donationSubscriptionManager: donationSubscriptionManager, networkManager: networkManager, profileManager: profileManager, tsAccountManager: tsAccountManager, @@ -268,6 +274,7 @@ private class DonationReceiptCredentialRedemptionJobRunner: JobRunner { private let dateProvider: DateProvider private let db: DB private let donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore + private let donationSubscriptionManager: DonationSubscriptionManager private let networkManager: NetworkManager private let profileManager: ProfileManager private let receiptCredentialManager: ReceiptCredentialManager @@ -281,6 +288,7 @@ private class DonationReceiptCredentialRedemptionJobRunner: JobRunner { dateProvider: @escaping DateProvider, db: DB, donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore, + donationSubscriptionManager: DonationSubscriptionManager, networkManager: NetworkManager, profileManager: ProfileManager, tsAccountManager: TSAccountManager, @@ -290,6 +298,7 @@ private class DonationReceiptCredentialRedemptionJobRunner: JobRunner { self.dateProvider = dateProvider self.db = db self.donationReceiptCredentialResultStore = donationReceiptCredentialResultStore + self.donationSubscriptionManager = donationSubscriptionManager self.networkManager = networkManager self.profileManager = profileManager self.receiptCredentialManager = ReceiptCredentialManager( @@ -616,7 +625,7 @@ private class DonationReceiptCredentialRedemptionJobRunner: JobRunner { } } - try await DonationSubscriptionManager.redeemReceiptCredentialPresentation( + try await donationSubscriptionManager.redeemReceiptCredentialPresentation( receiptCredentialPresentation: receiptCredentialPresentation, ) @@ -671,9 +680,9 @@ private class DonationReceiptCredentialRedemptionJobRunner: JobRunner { private func loadBadge(paymentType: PaymentType) async throws -> ProfileBadge { switch paymentType { case .oneTimeBoost: - return try await DonationSubscriptionManager.getBoostBadge() + return try await donationSubscriptionManager.getBoostBadge() case let .recurringSubscription(_, targetSubscriptionLevel, _, _): - return try await DonationSubscriptionManager.getSubscriptionBadge(subscriptionLevel: targetSubscriptionLevel) + return try await donationSubscriptionManager.getSubscriptionBadge(subscriptionLevel: targetSubscriptionLevel) } } diff --git a/SignalServiceKit/Profiles/OWSUserProfile.swift b/SignalServiceKit/Profiles/OWSUserProfile.swift index e2b4a5b20b..c681ed7dd0 100644 --- a/SignalServiceKit/Profiles/OWSUserProfile.swift +++ b/SignalServiceKit/Profiles/OWSUserProfile.swift @@ -1168,9 +1168,9 @@ extension OWSUserProfile { } if case .localUser = internalAddress, case .setTo = changes.badges { - DonationSubscriptionManager.reconcileBadgeStates( + DependenciesBridge.shared.donationSubscriptionManager.reconcileBadgeStates( currentLocalUserProfile: newInstance, - transaction: tx, + tx: tx, ) } diff --git a/SignalServiceKit/StorageService/StorageServiceManager.swift b/SignalServiceKit/StorageService/StorageServiceManager.swift index 350572198d..f12276df06 100644 --- a/SignalServiceKit/StorageService/StorageServiceManager.swift +++ b/SignalServiceKit/StorageService/StorageServiceManager.swift @@ -2163,6 +2163,7 @@ class StorageServiceOperation { avatarDefaultColorManager: DependenciesBridge.shared.avatarDefaultColorManager, backupPlanManager: DependenciesBridge.shared.backupPlanManager, backupSubscriptionManager: DependenciesBridge.shared.backupSubscriptionManager, + donationSubscriptionManager: DependenciesBridge.shared.donationSubscriptionManager, dmConfigurationStore: DependenciesBridge.shared.disappearingMessagesConfigurationStore, linkPreviewSettingStore: DependenciesBridge.shared.linkPreviewSettingStore, localUsernameManager: DependenciesBridge.shared.localUsernameManager, diff --git a/SignalServiceKit/StorageService/StorageServiceProto+Sync.swift b/SignalServiceKit/StorageService/StorageServiceProto+Sync.swift index e8dab2ba64..557d632949 100644 --- a/SignalServiceKit/StorageService/StorageServiceProto+Sync.swift +++ b/SignalServiceKit/StorageService/StorageServiceProto+Sync.swift @@ -1194,6 +1194,7 @@ class StorageServiceAccountRecordUpdater: StorageServiceRecordUpdater { private let avatarDefaultColorManager: AvatarDefaultColorManager private let backupPlanManager: BackupPlanManager private let backupSubscriptionManager: BackupSubscriptionManager + private let donationSubscriptionManager: DonationSubscriptionManager private let dmConfigurationStore: DisappearingMessagesConfigurationStore private let linkPreviewSettingStore: LinkPreviewSettingStore private let localUsernameManager: LocalUsernameManager @@ -1221,6 +1222,7 @@ class StorageServiceAccountRecordUpdater: StorageServiceRecordUpdater { avatarDefaultColorManager: AvatarDefaultColorManager, backupPlanManager: BackupPlanManager, backupSubscriptionManager: BackupSubscriptionManager, + donationSubscriptionManager: DonationSubscriptionManager, dmConfigurationStore: DisappearingMessagesConfigurationStore, linkPreviewSettingStore: LinkPreviewSettingStore, localUsernameManager: LocalUsernameManager, @@ -1248,6 +1250,7 @@ class StorageServiceAccountRecordUpdater: StorageServiceRecordUpdater { self.avatarDefaultColorManager = avatarDefaultColorManager self.backupPlanManager = backupPlanManager self.backupSubscriptionManager = backupSubscriptionManager + self.donationSubscriptionManager = donationSubscriptionManager self.dmConfigurationStore = dmConfigurationStore self.linkPreviewSettingStore = linkPreviewSettingStore self.localUsernameManager = localUsernameManager @@ -1377,13 +1380,13 @@ class StorageServiceAccountRecordUpdater: StorageServiceRecordUpdater { } if - let donationSubscriberID = DonationSubscriptionManager.getSubscriberID(transaction: transaction), - let donationSubscriberCurrencyCode = DonationSubscriptionManager.getSubscriberCurrencyCode(transaction: transaction) + let donationSubscriberID = donationSubscriptionManager.getSubscriberID(tx: transaction), + let donationSubscriberCurrencyCode = donationSubscriptionManager.getSubscriberCurrencyCode(tx: transaction) { builder.setDonorSubscriberID(donationSubscriberID) builder.setDonorSubscriberCurrencyCode(donationSubscriberCurrencyCode) } - builder.setDonorSubscriptionManuallyCancelled(DonationSubscriptionManager.userManuallyCancelledSubscription(transaction: transaction)) + builder.setDonorSubscriptionManuallyCancelled(donationSubscriptionManager.userManuallyCancelledSubscription(tx: transaction)) if let backupSubscriberData = backupSubscriptionManager.getIAPSubscriberData(tx: transaction) { var subscriberDataBuilder = StorageServiceProtoAccountRecordIAPSubscriberData.builder() @@ -1404,7 +1407,7 @@ class StorageServiceAccountRecordUpdater: StorageServiceRecordUpdater { builder.setReadOnboardingStory(systemStoryManager.isOnboardingStoryRead(transaction: transaction)) builder.setViewedOnboardingStory(systemStoryManager.isOnboardingStoryViewed(transaction: transaction)) - builder.setDisplayBadgesOnProfile(DonationSubscriptionManager.displayBadgesOnProfile(transaction: transaction)) + builder.setDisplayBadgesOnProfile(donationSubscriptionManager.displayBadgesOnProfile(tx: transaction)) builder.setKeepMutedChatsArchived(SSKPreferences.shouldKeepMutedChatsArchived(transaction: transaction)) @@ -1686,21 +1689,21 @@ class StorageServiceAccountRecordUpdater: StorageServiceRecordUpdater { let donationSubscriberId = record.donorSubscriberID, let donationSubscriberCurrencyCode = record.donorSubscriberCurrencyCode { - if donationSubscriberId != DonationSubscriptionManager.getSubscriberID(transaction: transaction) { - DonationSubscriptionManager.setSubscriberID(donationSubscriberId, transaction: transaction) + if donationSubscriberId != donationSubscriptionManager.getSubscriberID(tx: transaction) { + donationSubscriptionManager.setSubscriberID(donationSubscriberId, tx: transaction) } - if donationSubscriberCurrencyCode != DonationSubscriptionManager.getSubscriberCurrencyCode(transaction: transaction) { - DonationSubscriptionManager.setSubscriberCurrencyCode(donationSubscriberCurrencyCode, transaction: transaction) + if donationSubscriberCurrencyCode != donationSubscriptionManager.getSubscriberCurrencyCode(tx: transaction) { + donationSubscriptionManager.setSubscriberCurrencyCode(donationSubscriberCurrencyCode, tx: transaction) } } - let localDonationSubscriptionManuallyCancelled = DonationSubscriptionManager.userManuallyCancelledSubscription(transaction: transaction) + let localDonationSubscriptionManuallyCancelled = donationSubscriptionManager.userManuallyCancelledSubscription(tx: transaction) if localDonationSubscriptionManuallyCancelled != record.donorSubscriptionManuallyCancelled { - DonationSubscriptionManager.setUserManuallyCancelledSubscription( + donationSubscriptionManager.setUserManuallyCancelledSubscription( record.donorSubscriptionManuallyCancelled, updateStorageService: false, - transaction: transaction, + tx: transaction, ) } @@ -1728,12 +1731,12 @@ class StorageServiceAccountRecordUpdater: StorageServiceRecordUpdater { ) } - let localDisplayBadgesOnProfile = DonationSubscriptionManager.displayBadgesOnProfile(transaction: transaction) + let localDisplayBadgesOnProfile = donationSubscriptionManager.displayBadgesOnProfile(tx: transaction) if localDisplayBadgesOnProfile != record.displayBadgesOnProfile { - DonationSubscriptionManager.setDisplayBadgesOnProfile( + donationSubscriptionManager.setDisplayBadgesOnProfile( record.displayBadgesOnProfile, updateStorageService: false, - transaction: transaction, + tx: transaction, ) } diff --git a/SignalServiceKit/Subscriptions/Donations/CachedBadge.swift b/SignalServiceKit/Subscriptions/Donations/CachedBadge.swift index 2a37a12910..d937128c5e 100644 --- a/SignalServiceKit/Subscriptions/Donations/CachedBadge.swift +++ b/SignalServiceKit/Subscriptions/Donations/CachedBadge.swift @@ -47,7 +47,7 @@ public final class CachedBadge: Equatable { } // Otherwise, kick off a new fetch. let fetchPromise: Promise = Promise.wrapAsync { - try await DonationSubscriptionManager.getOneTimeBadge(level: self.badgeLevel) + try await DependenciesBridge.shared.donationSubscriptionManager.getOneTimeBadge(level: self.badgeLevel) }.then { profileBadge -> Promise in switch profileBadge { case .none: diff --git a/SignalServiceKit/Subscriptions/Donations/DonationSubscriptionManager.swift b/SignalServiceKit/Subscriptions/Donations/DonationSubscriptionManager.swift index 4c7cd55cc5..ecdbd42eed 100644 --- a/SignalServiceKit/Subscriptions/Donations/DonationSubscriptionManager.swift +++ b/SignalServiceKit/Subscriptions/Donations/DonationSubscriptionManager.swift @@ -51,6 +51,8 @@ public extension Notification.Name { static let hasExpiredGiftBadgeDidChangeNotification = NSNotification.Name("hasExpiredGiftBadgeDidChangeNotification") } +// MARK: - + /// Responsible for one-time and recurring-subscription actions related to /// donation payments and their resulting profile badges. /// @@ -63,16 +65,12 @@ public extension Notification.Name { /// Not to be confused with ``BackupSubscriptionManager``, which does many /// similar things but designed around In-App Payments (StoreKit) and paid-tier /// Backups. -public enum DonationSubscriptionManager { - - private static var receiptCredentialRedemptionJobQueue: DonationReceiptCredentialRedemptionJobQueue { - SSKEnvironment.shared.donationReceiptCredentialRedemptionJobQueue - } +public class DonationSubscriptionManager { /// - Note /// This collection name is reused by other subscription-related stores. For /// example, see ``DonationReceiptCredentialResultStore``. - private static let subscriptionKVS = KeyValueStore(collection: "SubscriptionKeyValueStore") + private let subscriptionKVS = KeyValueStore(collection: "SubscriptionKeyValueStore") fileprivate static let subscriberIDKey = "subscriberID" fileprivate static let subscriberCurrencyCodeKey = "subscriberCurrencyCode" @@ -88,17 +86,48 @@ public enum DonationSubscriptionManager { fileprivate static let showExpirySheetOnHomeScreenKey = "showExpirySheetOnHomeScreenKey" fileprivate static let mostRecentSubscriptionPaymentMethodKey = "mostRecentSubscriptionPaymentMethod" + private let db: any DB + private let donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore + private let networkManager: NetworkManager + private let profileManager: ProfileManager + private let storageServiceManager: StorageServiceManager + private let subscriptionConfigManager: SubscriptionConfigManager + private let tsAccountManager: TSAccountManager + + /// Lazily accessed to avoid dealing with a circular dependency. + private var receiptCredentialRedemptionJobQueue: DonationReceiptCredentialRedemptionJobQueue { + SSKEnvironment.shared.donationReceiptCredentialRedemptionJobQueue + } + + init( + db: any DB, + donationReceiptCredentialResultStore: DonationReceiptCredentialResultStore, + networkManager: NetworkManager, + profileManager: ProfileManager, + storageServiceManager: StorageServiceManager, + subscriptionConfigManager: SubscriptionConfigManager, + tsAccountManager: TSAccountManager, + ) { + self.db = db + self.donationReceiptCredentialResultStore = donationReceiptCredentialResultStore + self.networkManager = networkManager + self.profileManager = profileManager + self.storageServiceManager = storageServiceManager + self.subscriptionConfigManager = subscriptionConfigManager + self.tsAccountManager = tsAccountManager + } + // MARK: - - public static func currentProfileSubscriptionBadges(tx: DBReadTransaction) -> [OWSUserProfileBadgeInfo] { - let localProfile = SSKEnvironment.shared.profileManagerRef.localUserProfile(tx: tx) + public func currentProfileSubscriptionBadges(tx: DBReadTransaction) -> [OWSUserProfileBadgeInfo] { + let localProfile = profileManager.localUserProfile(tx: tx) return (localProfile?.badges ?? []).filter { SubscriptionBadgeIds.contains($0.badgeId) } } /// A low-overhead, synchronous check for whether we *probably* have a /// current donation subscription. Callers who need to know precise details /// about our subscription should use ``SubscriptionFetcher``. - public static func probablyHasCurrentSubscription(tx: DBReadTransaction) -> Bool { + public func probablyHasCurrentSubscription(tx: DBReadTransaction) -> Bool { return !currentProfileSubscriptionBadges(tx: tx).isEmpty } @@ -108,27 +137,27 @@ public enum DonationSubscriptionManager { /// payment has been authorized. /// /// - Returns: The new subscriber ID. - public static func prepareNewSubscription(currencyCode: Currency.Code) async throws -> Data { + public func prepareNewSubscription(currencyCode: Currency.Code) async throws -> Data { Logger.info("[Donations] Setting up new subscription") let subscriberID = try await setupNewSubscriberID() Logger.info("[Donations] Caching params after setting up new subscription") - await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { transaction in - self.setUserManuallyCancelledSubscription(false, transaction: transaction) - self.setSubscriberID(subscriberID, transaction: transaction) - self.setSubscriberCurrencyCode(currencyCode, transaction: transaction) - self.setMostRecentlyExpiredBadgeID(badgeID: nil, transaction: transaction) - self.setShowExpirySheetOnHomeScreenKey(show: false, transaction: transaction) + await db.awaitableWrite { tx in + self.setUserManuallyCancelledSubscription(false, tx: tx) + self.setSubscriberID(subscriberID, tx: tx) + self.setSubscriberCurrencyCode(currencyCode, tx: tx) + self.setMostRecentlyExpiredBadgeID(badgeID: nil, tx: tx) + self.setShowExpirySheetOnHomeScreenKey(show: false, tx: tx) } - SSKEnvironment.shared.storageServiceManagerRef.recordPendingLocalAccountUpdates() + storageServiceManager.recordPendingLocalAccountUpdates() return subscriberID } /// Finalize a new subscription, after payment has been authorized with the /// given processor. - public static func finalizeNewSubscription( + public func finalizeNewSubscription( forSubscriberId subscriberId: Data, paymentType: RecurringSubscriptionPaymentType, subscription: DonationSubscriptionLevel, @@ -156,10 +185,10 @@ public enum DonationSubscriptionManager { Logger.info("[Donations] Selecting subscription level on service") - await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { transaction in - setMostRecentSubscriptionPaymentMethod( + await db.awaitableWrite { tx in + self.setMostRecentSubscriptionPaymentMethod( paymentMethod: paymentType.paymentMethod, - transaction: transaction, + tx: tx, ) } @@ -167,7 +196,7 @@ public enum DonationSubscriptionManager { } /// Update the subscription level for the given subscriber ID. - public static func updateSubscriptionLevel( + public func updateSubscriptionLevel( for subscriberID: Data, to subscription: DonationSubscriptionLevel, currencyCode: Currency.Code, @@ -182,12 +211,7 @@ public enum DonationSubscriptionManager { } /// Cancel a subscription for the given subscriber ID. - public static func cancelSubscription(for subscriberID: Data) async throws { - let databaseStorage = SSKEnvironment.shared.databaseStorageRef - let donationReceiptCredentialResultStore = DependenciesBridge.shared.donationReceiptCredentialResultStore - let networkManager = SSKEnvironment.shared.networkManagerRef - let storageServiceManager = SSKEnvironment.shared.storageServiceManagerRef - + public func cancelSubscription(for subscriberID: Data) async throws { Logger.info("[Donations] Cancelling subscription") let request = OWSRequestFactory.deleteSubscriberID(subscriberID) @@ -197,14 +221,14 @@ public enum DonationSubscriptionManager { } Logger.info("[Donations] Deleted remote subscription.") - await databaseStorage.awaitableWrite { transaction in - self.setSubscriberID(nil, transaction: transaction) - self.setSubscriberCurrencyCode(nil, transaction: transaction) - self.setMostRecentSubscriptionPaymentMethod(paymentMethod: nil, transaction: transaction) - self.setUserManuallyCancelledSubscription(true, transaction: transaction) + await db.awaitableWrite { tx in + self.setSubscriberID(nil, tx: tx) + self.setSubscriberCurrencyCode(nil, tx: tx) + self.setMostRecentSubscriptionPaymentMethod(paymentMethod: nil, tx: tx) + self.setUserManuallyCancelledSubscription(true, tx: tx) - donationReceiptCredentialResultStore.clearRedemptionSuccessForAnyRecurringSubscription(tx: transaction) - donationReceiptCredentialResultStore.clearRequestErrorForAnyRecurringSubscription(tx: transaction) + self.donationReceiptCredentialResultStore.clearRedemptionSuccessForAnyRecurringSubscription(tx: tx) + self.donationReceiptCredentialResultStore.clearRequestErrorForAnyRecurringSubscription(tx: tx) } storageServiceManager.recordPendingLocalAccountUpdates() @@ -214,13 +238,13 @@ public enum DonationSubscriptionManager { /// Generate and register an ID for a new subscriber. /// /// - Returns the new subscriber ID. - private static func setupNewSubscriberID() async throws -> Data { + private func setupNewSubscriberID() async throws -> Data { Logger.info("[Donations] Setting up new subscriber ID") let newSubscriberID = Randomness.generateRandomBytes(UInt(32)) let request = OWSRequestFactory.setSubscriberID(newSubscriberID) - let response = try await SSKEnvironment.shared.networkManagerRef + let response = try await networkManager .asyncRequest(request, retryPolicy: .hopefullyRecoverable) let statusCode = response.responseStatusCode @@ -231,7 +255,7 @@ public enum DonationSubscriptionManager { return newSubscriberID } - private static func setDefaultPaymentMethod( + private func setDefaultPaymentMethod( for subscriberId: Data, using processor: DonationPaymentProcessor, paymentMethodId: String, @@ -241,7 +265,7 @@ public enum DonationSubscriptionManager { processor: processor.rawValue, paymentMethodId: paymentMethodId, ) - let response = try await SSKEnvironment.shared.networkManagerRef + let response = try await networkManager .asyncRequest(request, retryPolicy: .hopefullyRecoverable) let statusCode = response.responseStatusCode if statusCode != 200 { @@ -249,7 +273,7 @@ public enum DonationSubscriptionManager { } } - private static func setDefaultIDEALPaymentMethod( + private func setDefaultIDEALPaymentMethod( for subscriberId: Data, setupIntentId: String, ) async throws { @@ -258,7 +282,7 @@ public enum DonationSubscriptionManager { setupIntentId: setupIntentId, ) - let response = try await SSKEnvironment.shared.networkManagerRef + let response = try await networkManager .asyncRequest(request, retryPolicy: .hopefullyRecoverable) let statusCode = response.responseStatusCode if statusCode != 200 { @@ -270,15 +294,11 @@ public enum DonationSubscriptionManager { /// /// - Returns /// The updated subscription. - private static func setSubscription( + private func setSubscription( for subscriberID: Data, subscription: DonationSubscriptionLevel, currencyCode: Currency.Code, ) async throws -> Subscription { - let databaseStorage = SSKEnvironment.shared.databaseStorageRef - let networkManager = SSKEnvironment.shared.networkManagerRef - let storageServiceManager = SSKEnvironment.shared.storageServiceManagerRef - let key = Randomness.generateRandomBytes(UInt(32)).asBase64Url let request = OWSRequestFactory.subscriptionSetSubscriptionLevelRequest( subscriberID: subscriberID, @@ -302,8 +322,8 @@ public enum DonationSubscriptionManager { throw OWSAssertionError("Failed to fetch valid subscription object after setSubscription") } - await databaseStorage.awaitableWrite { transaction in - setSubscriberCurrencyCode(currencyCode, transaction: transaction) + await db.awaitableWrite { tx in + self.setSubscriberCurrencyCode(currencyCode, tx: tx) } storageServiceManager.recordPendingLocalAccountUpdates() @@ -313,7 +333,7 @@ public enum DonationSubscriptionManager { // MARK: - - public static func requestAndRedeemReceipt( + public func requestAndRedeemReceipt( subscriberId: Data, subscriptionLevel: UInt, priorSubscriptionLevel: UInt?, @@ -321,15 +341,13 @@ public enum DonationSubscriptionManager { paymentMethod: DonationPaymentMethod?, isNewSubscription: Bool, ) async throws { - let db = DependenciesBridge.shared.db - let ( receiptCredentialRequestContext, receiptCredentialRequest, ) = ReceiptCredentialManager.generateReceiptRequest() let redemptionJobRecord = await db.awaitableWrite { tx in - return receiptCredentialRedemptionJobQueue.saveSubscriptionRedemptionJob( + return self.receiptCredentialRedemptionJobQueue.saveSubscriptionRedemptionJob( paymentProcessor: paymentProcessor, paymentMethod: paymentMethod, receiptCredentialRequestContext: receiptCredentialRequestContext, @@ -347,21 +365,19 @@ public enum DonationSubscriptionManager { ) } - public static func requestAndRedeemReceipt( + public func requestAndRedeemReceipt( boostPaymentIntentId: String, amount: FiatMoney, paymentProcessor: DonationPaymentProcessor, paymentMethod: DonationPaymentMethod, ) async throws { - let db = DependenciesBridge.shared.db - let ( receiptCredentialRequestContext, receiptCredentialRequest, ) = ReceiptCredentialManager.generateReceiptRequest() let redemptionJobRecord = await db.awaitableWrite { tx in - return receiptCredentialRedemptionJobQueue.saveBoostRedemptionJob( + return self.receiptCredentialRedemptionJobQueue.saveBoostRedemptionJob( amount: amount, paymentProcessor: paymentProcessor, paymentMethod: paymentMethod, @@ -377,7 +393,7 @@ public enum DonationSubscriptionManager { ) } - public static func redeemReceiptCredentialPresentation( + public func redeemReceiptCredentialPresentation( receiptCredentialPresentation: ReceiptCredentialPresentation, ) async throws { let expiresAtForLogging: String = { @@ -386,36 +402,35 @@ public enum DonationSubscriptionManager { }() Logger.info("[Donations] Redeeming receipt credential presentation. Expires at \(expiresAtForLogging)") - let databaseStorage = SSKEnvironment.shared.databaseStorageRef let receiptCredentialPresentationData = receiptCredentialPresentation.serialize() let request = OWSRequestFactory.subscriptionRedeemReceiptCredential( receiptCredentialPresentation: receiptCredentialPresentationData, - displayBadgesOnProfile: databaseStorage.read(block: displayBadgesOnProfile(transaction:)), + displayBadgesOnProfile: db.read { tx in displayBadgesOnProfile(tx: tx) }, ) - let response = try await SSKEnvironment.shared.networkManagerRef.asyncRequest(request) + let response = try await networkManager.asyncRequest(request) let statusCode = response.responseStatusCode if statusCode != 200 { throw OWSAssertionError("[Donations] Receipt credential presentation request failed with status code \(statusCode)") } - _ = try await SSKEnvironment.shared.profileManagerImplRef.fetchLocalUsersProfile(authedAccount: .implicit()) + _ = try await profileManager.fetchLocalUsersProfile(authedAccount: .implicit()) } // MARK: Heartbeat - public static func redeemSubscriptionIfNecessary() async throws { + public func redeemSubscriptionIfNecessary() async throws { struct CheckerStore: SubscriptionRedemptionNecessityCheckerStore { - let donationSubscriptionManager: DonationSubscriptionManager.Type + let donationSubscriptionManager: DonationSubscriptionManager func subscriberId(tx: DBReadTransaction) -> Data? { - return donationSubscriptionManager.getSubscriberID(transaction: tx) + return donationSubscriptionManager.getSubscriberID(tx: tx) } func getLastRedemptionNecessaryCheck(tx: DBReadTransaction) -> Date? { - return donationSubscriptionManager.subscriptionKVS.getDate(donationSubscriptionManager.lastSubscriptionHeartbeatKey, transaction: tx) + return donationSubscriptionManager.subscriptionKVS.getDate(DonationSubscriptionManager.lastSubscriptionHeartbeatKey, transaction: tx) } func setLastRedemptionNecessaryCheck(_ now: Date, tx: DBWriteTransaction) { - donationSubscriptionManager.subscriptionKVS.setDate(now, key: donationSubscriptionManager.lastSubscriptionHeartbeatKey, transaction: tx) + donationSubscriptionManager.subscriptionKVS.setDate(now, key: DonationSubscriptionManager.lastSubscriptionHeartbeatKey, transaction: tx) } } @@ -426,16 +441,16 @@ public enum DonationSubscriptionManager { >( checkerStore: CheckerStore(donationSubscriptionManager: self), dateProvider: { Date() }, - db: DependenciesBridge.shared.db, + db: db, logger: logger, - networkManager: SSKEnvironment.shared.networkManagerRef, - tsAccountManager: DependenciesBridge.shared.tsAccountManager, + networkManager: networkManager, + tsAccountManager: tsAccountManager, ) _ = try await subscriptionRedemptionNecessityChecker.redeemSubscriptionIfNecessary( fetchSubscriptionBlock: { db, subscriptionFetcher -> (subscriberID: Data, subscription: Subscription)? in if - let subscriberID = db.read(block: { getSubscriberID(transaction: $0) }), + let subscriberID = db.read(block: { self.getSubscriberID(tx: $0) }), let subscription = try await subscriptionFetcher.fetch(subscriberID: subscriberID) { return (subscriberID, subscription) @@ -460,7 +475,7 @@ public enum DonationSubscriptionManager { }, saveRedemptionJobBlock: { subscriberId, subscription, tx -> DonationReceiptCredentialRedemptionJobRecord? in if - receiptCredentialRedemptionJobQueue.subscriptionJobExists( + self.receiptCredentialRedemptionJobQueue.subscriptionJobExists( subscriberID: subscriberId, tx: tx, ) @@ -493,7 +508,7 @@ public enum DonationSubscriptionManager { receiptCredentialRequest, ) = ReceiptCredentialManager.generateReceiptRequest() - return receiptCredentialRedemptionJobQueue.saveSubscriptionRedemptionJob( + return self.receiptCredentialRedemptionJobQueue.saveSubscriptionRedemptionJob( paymentProcessor: donationPaymentProcessor, paymentMethod: subscription.donationPaymentMethod, receiptCredentialRequestContext: receiptCredentialRequestContext, @@ -506,22 +521,19 @@ public enum DonationSubscriptionManager { ) }, startRedemptionJobBlock: { jobRecord async throws in - try await receiptCredentialRedemptionJobQueue.runRedemptionJob(jobRecord: jobRecord) + try await self.receiptCredentialRedemptionJobQueue.runRedemptionJob(jobRecord: jobRecord) }, ) } -} -// MARK: - State management + // MARK: - State management -extension DonationSubscriptionManager { - - public static func getSubscriberID(transaction: DBReadTransaction) -> Data? { + public func getSubscriberID(tx: DBReadTransaction) -> Data? { guard let subscriberID = subscriptionKVS.getObject( - subscriberIDKey, + Self.subscriberIDKey, ofClass: NSData.self, - transaction: transaction, + transaction: tx, ) as Data? else { return nil @@ -529,19 +541,19 @@ extension DonationSubscriptionManager { return subscriberID } - public static func setSubscriberID(_ subscriberID: Data?, transaction: DBWriteTransaction) { + public func setSubscriberID(_ subscriberID: Data?, tx: DBWriteTransaction) { subscriptionKVS.setObject( subscriberID as NSData?, - key: subscriberIDKey, - transaction: transaction, + key: Self.subscriberIDKey, + transaction: tx, ) } - public static func getSubscriberCurrencyCode(transaction: DBReadTransaction) -> String? { + public func getSubscriberCurrencyCode(tx: DBReadTransaction) -> String? { guard let subscriberCurrencyCode = subscriptionKVS.getString( - subscriberCurrencyCodeKey, - transaction: transaction, + Self.subscriberCurrencyCodeKey, + transaction: tx, ) else { return nil @@ -549,127 +561,127 @@ extension DonationSubscriptionManager { return subscriberCurrencyCode } - public static func setSubscriberCurrencyCode( + public func setSubscriberCurrencyCode( _ currencyCode: Currency.Code?, - transaction: DBWriteTransaction, + tx: DBWriteTransaction, ) { subscriptionKVS.setString( currencyCode, - key: subscriberCurrencyCodeKey, - transaction: transaction, + key: Self.subscriberCurrencyCodeKey, + transaction: tx, ) } - public static func userManuallyCancelledSubscription(transaction: DBReadTransaction) -> Bool { - return subscriptionKVS.getBool(userManuallyCancelledSubscriptionKey, transaction: transaction) ?? false + public func userManuallyCancelledSubscription(tx: DBReadTransaction) -> Bool { + return subscriptionKVS.getBool(Self.userManuallyCancelledSubscriptionKey, transaction: tx) ?? false } - public static func setUserManuallyCancelledSubscription(_ value: Bool, updateStorageService: Bool = false, transaction: DBWriteTransaction) { - guard value != userManuallyCancelledSubscription(transaction: transaction) else { return } - subscriptionKVS.setBool(value, key: userManuallyCancelledSubscriptionKey, transaction: transaction) + public func setUserManuallyCancelledSubscription(_ value: Bool, updateStorageService: Bool = false, tx: DBWriteTransaction) { + guard value != userManuallyCancelledSubscription(tx: tx) else { return } + subscriptionKVS.setBool(value, key: Self.userManuallyCancelledSubscriptionKey, transaction: tx) if updateStorageService { - SSKEnvironment.shared.storageServiceManagerRef.recordPendingLocalAccountUpdates() + storageServiceManager.recordPendingLocalAccountUpdates() } } // MARK: - - public static func displayBadgesOnProfile(transaction: DBReadTransaction) -> Bool { - return subscriptionKVS.getBool(displayBadgesOnProfileKey, transaction: transaction) ?? false + public func displayBadgesOnProfile(tx: DBReadTransaction) -> Bool { + return subscriptionKVS.getBool(Self.displayBadgesOnProfileKey, transaction: tx) ?? false } - public static func setDisplayBadgesOnProfile(_ value: Bool, updateStorageService: Bool = false, transaction: DBWriteTransaction) { - guard value != displayBadgesOnProfile(transaction: transaction) else { return } - subscriptionKVS.setBool(value, key: displayBadgesOnProfileKey, transaction: transaction) + public func setDisplayBadgesOnProfile(_ value: Bool, updateStorageService: Bool = false, tx: DBWriteTransaction) { + guard value != displayBadgesOnProfile(tx: tx) else { return } + subscriptionKVS.setBool(value, key: Self.displayBadgesOnProfileKey, transaction: tx) if updateStorageService { - SSKEnvironment.shared.storageServiceManagerRef.recordPendingLocalAccountUpdates() + storageServiceManager.recordPendingLocalAccountUpdates() } } // MARK: - - fileprivate static func setKnownUserSubscriptionBadgeIDs(badgeIDs: [String], transaction: DBWriteTransaction) { - subscriptionKVS.setStringArray(badgeIDs, key: knownUserSubscriptionBadgeIDsKey, transaction: transaction) + private func setKnownUserSubscriptionBadgeIDs(badgeIDs: [String], tx: DBWriteTransaction) { + subscriptionKVS.setStringArray(badgeIDs, key: Self.knownUserSubscriptionBadgeIDsKey, transaction: tx) } - fileprivate static func knownUserSubscriptionBadgeIDs(transaction: DBReadTransaction) -> [String] { - return subscriptionKVS.getStringArray(knownUserSubscriptionBadgeIDsKey, transaction: transaction) ?? [] + private func knownUserSubscriptionBadgeIDs(tx: DBReadTransaction) -> [String] { + return subscriptionKVS.getStringArray(Self.knownUserSubscriptionBadgeIDsKey, transaction: tx) ?? [] } - fileprivate static func setKnownUserBoostBadgeIDs(badgeIDs: [String], transaction: DBWriteTransaction) { - subscriptionKVS.setStringArray(badgeIDs, key: knownUserBoostBadgeIDsKey, transaction: transaction) + private func setKnownUserBoostBadgeIDs(badgeIDs: [String], tx: DBWriteTransaction) { + subscriptionKVS.setStringArray(badgeIDs, key: Self.knownUserBoostBadgeIDsKey, transaction: tx) } - fileprivate static func knownUserBoostBadgeIDs(transaction: DBReadTransaction) -> [String] { - return subscriptionKVS.getStringArray(knownUserBoostBadgeIDsKey, transaction: transaction) ?? [] + private func knownUserBoostBadgeIDs(tx: DBReadTransaction) -> [String] { + return subscriptionKVS.getStringArray(Self.knownUserBoostBadgeIDsKey, transaction: tx) ?? [] } - fileprivate static func setKnownUserGiftBadgeIDs(badgeIDs: [String], transaction: DBWriteTransaction) { - subscriptionKVS.setStringArray(badgeIDs, key: knownUserGiftBadgeIDsKey, transaction: transaction) + private func setKnownUserGiftBadgeIDs(badgeIDs: [String], tx: DBWriteTransaction) { + subscriptionKVS.setStringArray(badgeIDs, key: Self.knownUserGiftBadgeIDsKey, transaction: tx) } - fileprivate static func knownUserGiftBadgeIDs(transaction: DBReadTransaction) -> [String] { - return subscriptionKVS.getStringArray(knownUserGiftBadgeIDsKey, transaction: transaction) ?? [] + private func knownUserGiftBadgeIDs(tx: DBReadTransaction) -> [String] { + return subscriptionKVS.getStringArray(Self.knownUserGiftBadgeIDsKey, transaction: tx) ?? [] } - fileprivate static func setMostRecentlyExpiredBadgeID(badgeID: String?, transaction: DBWriteTransaction) { + private func setMostRecentlyExpiredBadgeID(badgeID: String?, tx: DBWriteTransaction) { guard let badgeID else { - subscriptionKVS.removeValue(forKey: mostRecentlyExpiredBadgeIDKey, transaction: transaction) + subscriptionKVS.removeValue(forKey: Self.mostRecentlyExpiredBadgeIDKey, transaction: tx) return } - subscriptionKVS.setString(badgeID, key: mostRecentlyExpiredBadgeIDKey, transaction: transaction) + subscriptionKVS.setString(badgeID, key: Self.mostRecentlyExpiredBadgeIDKey, transaction: tx) } - public static func mostRecentlyExpiredBadgeID(transaction: DBReadTransaction) -> String? { - subscriptionKVS.getString(mostRecentlyExpiredBadgeIDKey, transaction: transaction) + public func mostRecentlyExpiredBadgeID(tx: DBReadTransaction) -> String? { + subscriptionKVS.getString(Self.mostRecentlyExpiredBadgeIDKey, transaction: tx) } - public static func clearMostRecentlyExpiredBadgeIDWithSneakyTransaction() { - SSKEnvironment.shared.databaseStorageRef.write { transaction in - self.setMostRecentlyExpiredBadgeID(badgeID: nil, transaction: transaction) + public func clearMostRecentlyExpiredBadgeIDWithSneakyTransaction() { + db.write { tx in + self.setMostRecentlyExpiredBadgeID(badgeID: nil, tx: tx) } } - fileprivate static func setMostRecentlyExpiredGiftBadgeID(badgeID: String?, transaction: DBWriteTransaction) { + private func setMostRecentlyExpiredGiftBadgeID(badgeID: String?, tx: DBWriteTransaction) { if let badgeID { - subscriptionKVS.setString(badgeID, key: mostRecentlyExpiredGiftBadgeIDKey, transaction: transaction) + subscriptionKVS.setString(badgeID, key: Self.mostRecentlyExpiredGiftBadgeIDKey, transaction: tx) } else { - subscriptionKVS.removeValue(forKey: mostRecentlyExpiredGiftBadgeIDKey, transaction: transaction) + subscriptionKVS.removeValue(forKey: Self.mostRecentlyExpiredGiftBadgeIDKey, transaction: tx) } - transaction.addSyncCompletion { + tx.addSyncCompletion { NotificationCenter.default.postOnMainThread(name: .hasExpiredGiftBadgeDidChangeNotification, object: nil) } } - public static func mostRecentlyExpiredGiftBadgeID(transaction: DBReadTransaction) -> String? { - subscriptionKVS.getString(mostRecentlyExpiredGiftBadgeIDKey, transaction: transaction) + public func mostRecentlyExpiredGiftBadgeID(tx: DBReadTransaction) -> String? { + subscriptionKVS.getString(Self.mostRecentlyExpiredGiftBadgeIDKey, transaction: tx) } - public static func clearMostRecentlyExpiredGiftBadgeIDWithSneakyTransaction() { - SSKEnvironment.shared.databaseStorageRef.write { transaction in - self.setMostRecentlyExpiredGiftBadgeID(badgeID: nil, transaction: transaction) + public func clearMostRecentlyExpiredGiftBadgeIDWithSneakyTransaction() { + db.write { tx in + self.setMostRecentlyExpiredGiftBadgeID(badgeID: nil, tx: tx) } } - public static func setShowExpirySheetOnHomeScreenKey(show: Bool, transaction: DBWriteTransaction) { - subscriptionKVS.setBool(show, key: showExpirySheetOnHomeScreenKey, transaction: transaction) + public func setShowExpirySheetOnHomeScreenKey(show: Bool, tx: DBWriteTransaction) { + subscriptionKVS.setBool(show, key: Self.showExpirySheetOnHomeScreenKey, transaction: tx) } - public static func showExpirySheetOnHomeScreenKey(transaction: DBReadTransaction) -> Bool { - return subscriptionKVS.getBool(showExpirySheetOnHomeScreenKey, transaction: transaction) ?? false + public func showExpirySheetOnHomeScreenKey(tx: DBReadTransaction) -> Bool { + return subscriptionKVS.getBool(Self.showExpirySheetOnHomeScreenKey, transaction: tx) ?? false } - public static func setMostRecentSubscriptionPaymentMethod( + public func setMostRecentSubscriptionPaymentMethod( paymentMethod: DonationPaymentMethod?, - transaction: DBWriteTransaction, + tx: DBWriteTransaction, ) { - subscriptionKVS.setString(paymentMethod?.rawValue, key: mostRecentSubscriptionPaymentMethodKey, transaction: transaction) + subscriptionKVS.setString(paymentMethod?.rawValue, key: Self.mostRecentSubscriptionPaymentMethodKey, transaction: tx) } - public static func getMostRecentSubscriptionPaymentMethod(transaction: DBReadTransaction) -> DonationPaymentMethod? { - guard let paymentMethodString = subscriptionKVS.getString(mostRecentSubscriptionPaymentMethodKey, transaction: transaction) else { + public func getMostRecentSubscriptionPaymentMethod(tx: DBReadTransaction) -> DonationPaymentMethod? { + guard let paymentMethodString = subscriptionKVS.getString(Self.mostRecentSubscriptionPaymentMethodKey, transaction: tx) else { return nil } @@ -680,16 +692,13 @@ extension DonationSubscriptionManager { return paymentMethod } -} -// MARK: - - -extension DonationSubscriptionManager { + // MARK: - private static let cachedBadges = AtomicValue<[OneTimeBadgeLevel: CachedBadge]>([:], lock: .init()) - public static func getCachedBadge(level: OneTimeBadgeLevel) -> CachedBadge { - return self.cachedBadges.update { + public func getCachedBadge(level: OneTimeBadgeLevel) -> CachedBadge { + return Self.cachedBadges.update { if let cachedBadge = $0[level] { return cachedBadge } @@ -699,7 +708,7 @@ extension DonationSubscriptionManager { } } - public static func getBoostBadge() async throws -> ProfileBadge { + public func getBoostBadge() async throws -> ProfileBadge { let profileBadge = try await getOneTimeBadge(level: .boostBadge) guard let profileBadge else { owsFail("No badge for this level was found") @@ -707,7 +716,7 @@ extension DonationSubscriptionManager { return profileBadge } - public static func getOneTimeBadge(level: OneTimeBadgeLevel) async throws -> ProfileBadge? { + public func getOneTimeBadge(level: OneTimeBadgeLevel) async throws -> ProfileBadge? { let donationConfiguration = try await fetchDonationConfiguration() switch level { case .boostBadge: @@ -722,7 +731,7 @@ extension DonationSubscriptionManager { } } - public static func getSubscriptionBadge(subscriptionLevel levelRawValue: UInt) async throws -> ProfileBadge { + public func getSubscriptionBadge(subscriptionLevel levelRawValue: UInt) async throws -> ProfileBadge { let donationConfiguration = try await fetchDonationConfiguration() guard let matchingLevel = donationConfiguration.subscription.levels.first(where: { @@ -735,18 +744,15 @@ extension DonationSubscriptionManager { return matchingLevel.badge } - public static func fetchDonationConfiguration() async throws -> DonationSubscriptionConfiguration { - let subscriptionConfigManager = DependenciesBridge.shared.subscriptionConfigManager + public func fetchDonationConfiguration() async throws -> DonationSubscriptionConfiguration { return try await subscriptionConfigManager.donationConfiguration() } -} -// MARK: - + // MARK: - -extension DonationSubscriptionManager { - public static func reconcileBadgeStates( + public func reconcileBadgeStates( currentLocalUserProfile: OWSUserProfile, - transaction: DBWriteTransaction, + tx: DBWriteTransaction, ) { let currentBadges = currentLocalUserProfile.badges @@ -766,14 +772,14 @@ extension DonationSubscriptionManager { } // Read existing values - let persistedSubscriberBadgeIDs = Self.knownUserSubscriptionBadgeIDs(transaction: transaction) - let persistedBoostBadgeIDs = Self.knownUserBoostBadgeIDs(transaction: transaction) - let persistedGiftBadgeIDs = Self.knownUserGiftBadgeIDs(transaction: transaction) - let oldExpiredGiftBadgeID = Self.mostRecentlyExpiredGiftBadgeID(transaction: transaction) - var expiringBadgeId = Self.mostRecentlyExpiredBadgeID(transaction: transaction) - var userManuallyCancelled = Self.userManuallyCancelledSubscription(transaction: transaction) - var showExpiryOnHomeScreen = Self.showExpirySheetOnHomeScreenKey(transaction: transaction) - var displayBadgesOnProfile = Self.displayBadgesOnProfile(transaction: transaction) + let persistedSubscriberBadgeIDs = self.knownUserSubscriptionBadgeIDs(tx: tx) + let persistedBoostBadgeIDs = self.knownUserBoostBadgeIDs(tx: tx) + let persistedGiftBadgeIDs = self.knownUserGiftBadgeIDs(tx: tx) + let oldExpiredGiftBadgeID = self.mostRecentlyExpiredGiftBadgeID(tx: tx) + var expiringBadgeId = self.mostRecentlyExpiredBadgeID(tx: tx) + var userManuallyCancelled = self.userManuallyCancelledSubscription(tx: tx) + var showExpiryOnHomeScreen = self.showExpirySheetOnHomeScreenKey(tx: tx) + var displayBadgesOnProfile = self.displayBadgesOnProfile(tx: tx) let isCurrentlyDisplayingBadgesOnProfile = currentBadges.allSatisfy { badge in badge.isVisible ?? { @@ -881,20 +887,17 @@ extension DonationSubscriptionManager { """) // Persist new values - Self.setKnownUserSubscriptionBadgeIDs(badgeIDs: currentSubscriberBadgeIDs, transaction: transaction) - Self.setKnownUserBoostBadgeIDs(badgeIDs: currentBoostBadgeIDs, transaction: transaction) - Self.setKnownUserGiftBadgeIDs(badgeIDs: currentGiftBadgeIDs, transaction: transaction) - Self.setMostRecentlyExpiredGiftBadgeID(badgeID: newExpiredGiftBadgeID, transaction: transaction) - Self.setMostRecentlyExpiredBadgeID(badgeID: expiringBadgeId, transaction: transaction) - Self.setShowExpirySheetOnHomeScreenKey(show: showExpiryOnHomeScreen, transaction: transaction) - Self.setUserManuallyCancelledSubscription(userManuallyCancelled, transaction: transaction) - Self.setDisplayBadgesOnProfile(displayBadgesOnProfile, transaction: transaction) + self.setKnownUserSubscriptionBadgeIDs(badgeIDs: currentSubscriberBadgeIDs, tx: tx) + self.setKnownUserBoostBadgeIDs(badgeIDs: currentBoostBadgeIDs, tx: tx) + self.setKnownUserGiftBadgeIDs(badgeIDs: currentGiftBadgeIDs, tx: tx) + self.setMostRecentlyExpiredGiftBadgeID(badgeID: newExpiredGiftBadgeID, tx: tx) + self.setMostRecentlyExpiredBadgeID(badgeID: expiringBadgeId, tx: tx) + self.setShowExpirySheetOnHomeScreenKey(show: showExpiryOnHomeScreen, tx: tx) + self.setUserManuallyCancelledSubscription(userManuallyCancelled, tx: tx) + self.setDisplayBadgesOnProfile(displayBadgesOnProfile, tx: tx) } -} -// MARK: - - -extension DonationSubscriptionManager { + // MARK: - public enum RecurringSubscriptionPaymentType { case applePay(paymentMethodId: String)