Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Subscriptions/Donations/DonationReceiptCredentialResultStore.swift
1 views
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

/// Stores the result of `ReceiptCredential`-related operations, for donations.
public struct DonationReceiptCredentialResultStore {
    public enum Mode: CaseIterable {
        /// Refers to a one-time boost.
        case oneTimeBoost
        /// Refers to a recurring subscription that was started for the first time.
        case recurringSubscriptionInitiation
        /// Refers to a recurring subscription that automatically renewed.
        case recurringSubscriptionRenewal
    }

    private enum StoreConstants {
        static let errorCollection = "SubRecCredReqErrorStore"
        static let successCollection = "SubRecCredReqSuccessStore"

        static let errorPresentationCollection = "SubRecCredReqErrorPresStore"
        static let successPresentationCollection = "SubRecCredReqSuccessPresStore"

        static let oneTimeBoostKey = "oneTimeBoost"
        static let recurringSubscriptionInitiationKey = "recurringSubscriptionInitiation"
        static let recurringSubscriptionRenewalKey = "recurringSubscriptionRenewal"
    }

    private let errorKVStore: KeyValueStore
    private let successKVStore: KeyValueStore

    private let errorPresentationKVStore: KeyValueStore
    private let successPresentationKVStore: KeyValueStore

    init() {
        errorKVStore = KeyValueStore(collection: StoreConstants.errorCollection)
        successKVStore = KeyValueStore(collection: StoreConstants.successCollection)

        errorPresentationKVStore = KeyValueStore(collection: StoreConstants.errorPresentationCollection)
        successPresentationKVStore = KeyValueStore(collection: StoreConstants.successPresentationCollection)
    }

    private func key(mode: Mode) -> String {
        switch mode {
        case .oneTimeBoost: return StoreConstants.oneTimeBoostKey
        case .recurringSubscriptionInitiation: return StoreConstants.recurringSubscriptionInitiationKey
        case .recurringSubscriptionRenewal: return StoreConstants.recurringSubscriptionRenewalKey
        }
    }

    // MARK: -

    public func getRequestErrorForAnyRecurringSubscription(
        tx: DBReadTransaction,
    ) -> DonationReceiptCredentialRequestError? {
        if
            let initiationError = getRequestError(
                errorMode: .recurringSubscriptionInitiation,
                tx: tx,
            )
        {
            return initiationError
        } else if
            let renewalError = getRequestError(
                errorMode: .recurringSubscriptionRenewal,
                tx: tx,
            )
        {
            return renewalError
        }

        return nil
    }

    public func getRequestError(
        errorMode: Mode,
        tx: DBReadTransaction,
    ) -> DonationReceiptCredentialRequestError? {
        return try? errorKVStore.getCodableValue(
            forKey: key(mode: errorMode),
            transaction: tx,
        )
    }

    // MARK: -

    public func setRequestError(
        error: DonationReceiptCredentialRequestError,
        errorMode: Mode,
        tx: DBWriteTransaction,
    ) {
        let modeKey = key(mode: errorMode)
        try? errorKVStore.setCodable(error, key: modeKey, transaction: tx)

        // Setting a new error means we haven't presented it, either.
        errorPresentationKVStore.removeValue(forKey: modeKey, transaction: tx)
    }

    public func clearRequestErrorForAnyRecurringSubscription(tx: DBWriteTransaction) {
        clearRequestError(errorMode: .recurringSubscriptionInitiation, tx: tx)
        clearRequestError(errorMode: .recurringSubscriptionRenewal, tx: tx)
    }

    public func clearRequestError(errorMode: Mode, tx: DBWriteTransaction) {
        let modeKey = key(mode: errorMode)
        errorKVStore.removeValue(forKey: modeKey, transaction: tx)

        // Clearing the error means we haven't presented it, either.
        errorPresentationKVStore.removeValue(forKey: modeKey, transaction: tx)
    }

    // MARK: -

    public func getRedemptionSuccessForAnyRecurringSubscription(
        tx: DBReadTransaction,
    ) -> DonationReceiptCredentialRedemptionSuccess? {
        if
            let initiationSuccess = getRedemptionSuccess(
                successMode: .recurringSubscriptionInitiation,
                tx: tx,
            )
        {
            return initiationSuccess
        } else if
            let renewalSuccess = getRedemptionSuccess(
                successMode: .recurringSubscriptionRenewal,
                tx: tx,
            )
        {
            return renewalSuccess
        }

        return nil
    }

    public func getRedemptionSuccess(
        successMode: Mode,
        tx: DBReadTransaction,
    ) -> DonationReceiptCredentialRedemptionSuccess? {
        return try? successKVStore.getCodableValue(
            forKey: key(mode: successMode),
            transaction: tx,
        )
    }

    // MARK: -

    public func setRedemptionSuccess(
        success: DonationReceiptCredentialRedemptionSuccess,
        successMode: Mode,
        tx: DBWriteTransaction,
    ) {
        let modeKey = key(mode: successMode)
        try? successKVStore.setCodable(
            success,
            key: modeKey,
            transaction: tx,
        )

        // Setting a new success means we haven't presented it, either.
        successPresentationKVStore.removeValue(forKey: modeKey, transaction: tx)
    }

    public func clearRedemptionSuccessForAnyRecurringSubscription(tx: DBWriteTransaction) {
        clearRedemptionSuccess(successMode: .recurringSubscriptionInitiation, tx: tx)
        clearRedemptionSuccess(successMode: .recurringSubscriptionRenewal, tx: tx)
    }

    public func clearRedemptionSuccess(
        successMode: Mode,
        tx: DBWriteTransaction,
    ) {
        let modeKey = key(mode: successMode)
        successKVStore.removeValue(
            forKey: modeKey,
            transaction: tx,
        )

        // Clearing the success means we haven't presented it, either.
        successPresentationKVStore.removeValue(forKey: modeKey, transaction: tx)
    }

    // MARK: -

    public func hasPresentedError(errorMode: Mode, tx: DBReadTransaction) -> Bool {
        return errorPresentationKVStore.getBool(
            key(mode: errorMode),
            defaultValue: false,
            transaction: tx,
        )
    }

    public func setHasPresentedError(errorMode: Mode, tx: DBWriteTransaction) {
        errorPresentationKVStore.setBool(
            true,
            key: key(mode: errorMode),
            transaction: tx,
        )
    }

    public func hasPresentedSuccess(successMode: Mode, tx: DBReadTransaction) -> Bool {
        return successPresentationKVStore.getBool(
            key(mode: successMode),
            defaultValue: false,
            transaction: tx,
        )
    }

    public func setHasPresentedSuccess(successMode: Mode, tx: DBWriteTransaction) {
        successPresentationKVStore.setBool(
            true,
            key: key(mode: successMode),
            transaction: tx,
        )
    }
}