Make DonationSubscriptionManager a singleton instance, not a static class

This commit is contained in:
Sasha Weiss 2026-05-13 16:15:30 -07:00 committed by GitHub
parent 8a0e1a9a2c
commit 714a789cc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 298 additions and 261 deletions

View File

@ -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):

View File

@ -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,
)

View File

@ -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 {

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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 {

View File

@ -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),

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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 }

View File

@ -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)

View File

@ -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):

View File

@ -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,

View File

@ -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)
}
}
}

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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

View File

@ -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)
}
}

View File

@ -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,
)
}

View File

@ -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,

View File

@ -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,
)
}

View File

@ -47,7 +47,7 @@ public final class CachedBadge: Equatable {
}
// Otherwise, kick off a new fetch.
let fetchPromise: Promise<Value> = Promise.wrapAsync {
try await DonationSubscriptionManager.getOneTimeBadge(level: self.badgeLevel)
try await DependenciesBridge.shared.donationSubscriptionManager.getOneTimeBadge(level: self.badgeLevel)
}.then { profileBadge -> Promise<Value> in
switch profileBadge {
case .none:

View File

@ -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)