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

import AuthenticationServices
import Foundation
import SignalServiceKit

extension BadgeGiftingConfirmationViewController {
    typealias SendGiftError = DonationViewsUtil.Gifts.SendGiftError

    func startPaypal() {
        Logger.info("[Gifting] Starting PayPal...")

        // This variable doesn't actually do anything because `startJob` will
        // always throw a `SendGiftError` and there aren't any other throwable
        // things that come after it.
        var mightHaveBeenCharged = false

        let threadId = self.thread.uniqueId
        Task.detached(priority: .userInitiated) {
            do {
                try SSKEnvironment.shared.databaseStorageRef.read { transaction in
                    try DonationViewsUtil.Gifts.throwIfAlreadySendingGift(threadId: threadId, transaction: transaction)
                }
                let (approvalUrl, paymentId) = try await DonationViewsUtil.Paypal.createPaypalPaymentBehindActivityIndicator(
                    amount: self.price,
                    level: .giftBadge(.signalGift),
                    fromViewController: self,
                )
                let preparedPayment = try await self.presentPaypal(with: approvalUrl, paymentId: paymentId)
                guard await DonationViewsUtil.Gifts.showSafetyNumberConfirmationIfNecessary(for: self.thread) else {
                    throw DonationViewsUtil.Gifts.SendGiftError.userCanceledBeforeChargeCompleted
                }
                mightHaveBeenCharged = true
                try await DonationViewsUtil.wrapInProgressView(
                    from: self,
                    operation: {
                        try await DonationViewsUtil.Gifts.startJob(
                            amount: self.price,
                            preparedPayment: preparedPayment,
                            thread: self.thread,
                            messageText: self.messageText,
                            databaseStorage: SSKEnvironment.shared.databaseStorageRef,
                            blockingManager: SSKEnvironment.shared.blockingManagerRef,
                        )
                    },
                )

                Logger.info("[Gifting] Gifting PayPal donation finished")
                await self.didCompleteDonation()
            } catch let error as SendGiftError {
                DonationViewsUtil.Gifts.presentErrorSheetIfApplicable(for: error)
            } catch {
                owsFailDebug("[Gifting] Expected a SendGiftError but got \(error)")
                DonationViewsUtil.Gifts.presentErrorSheetIfApplicable(for: mightHaveBeenCharged ? .failedAndUserMaybeCharged : .failedAndUserNotCharged)
            }
        }
    }

    private func presentPaypal(with approvalUrl: URL, paymentId: String) async throws -> PreparedGiftPayment {
        Logger.info("[Gifting] Presenting PayPal web UI for user approval of gift donation")
        do {
            let approvalParams: Paypal.OneTimePaymentWebAuthApprovalParams = try await Paypal.presentExpectingApprovalParams(
                approvalUrl: approvalUrl,
                withPresentationContext: self,
            )
            return .forPaypal(approvalParams: approvalParams, paymentId: paymentId)
        } catch let paypalAuthError as Paypal.AuthError {
            switch paypalAuthError {
            case .userCanceled:
                Logger.info("[Gifting] User canceled PayPal")
                throw DonationViewsUtil.Gifts.SendGiftError.userCanceledBeforeChargeCompleted
            }
        } catch {
            owsFailDebug("[Gifting] PayPal failed with error \(error)")
            throw error
        }
    }
}

// MARK: - ASWebAuthenticationPresentationContextProviding

extension BadgeGiftingConfirmationViewController: ASWebAuthenticationPresentationContextProviding {
    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        view.window!
    }
}