From 51b262afacd2fa5643dc1284df98e34653b3ebc4 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 28 May 2024 13:06:02 +0400 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../Sources/InAppPurchaseManager.swift | 27 ++++++++++-- .../Sources/CreateGiveawayController.swift | 2 + .../PremiumUI/Sources/PremiumGiftScreen.swift | 2 + .../Sources/PremiumIntroScreen.swift | 2 + .../TelegramEngine/Payments/Stars.swift | 9 +++- .../Sources/StarsPurchaseScreen.swift | 2 + .../Sources/StarsTransactionScreen.swift | 44 ++++++++++++++++++- 8 files changed, 83 insertions(+), 7 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 2dcb26b0ca8..20d80e1fa41 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7799,6 +7799,7 @@ Sorry for the inconvenience."; "Premium.Purchase.ErrorNetwork" = "Please check your internet connection and try again."; "Premium.Purchase.ErrorNotAllowed" = "The device is not not allowed to make the payment."; "Premium.Purchase.ErrorCantMakePayments" = "In-app purchases are not allowed on this device."; +"Premium.Purchase.ErrorTryLater" = "An error occurred. Please try again."; "Premium.Restore.Success" = "Done"; "Premium.Restore.ErrorUnknown" = "An error occurred. Please try again."; @@ -12286,6 +12287,7 @@ Sorry for the inconvenience."; "Stars.Transaction.FragmentTopUp.Title" = "Stars Top-Up"; "Stars.Transaction.FragmentTopUp.Subtitle" = "Fragment"; "Stars.Transaction.Unsupported.Title" = "Unsupported"; +"Stars.Transaction.Refund" = "Refund"; "Stars.Transfer.Title" = "Confirm Your Purchase"; "Stars.Transfer.Info" = "Do you want to buy **%1$@** in **%2$@** for **%3$@**?"; diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index 543f756750c..01096c74592 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -184,6 +184,7 @@ public final class InAppPurchaseManager: NSObject { case notAllowed case cantMakePayments case assignFailed + case tryLater } public enum RestoreState { @@ -219,6 +220,8 @@ public final class InAppPurchaseManager: NSObject { private let stateQueue = Queue() private var paymentContexts: [String: PaymentTransactionContext] = [:] + + private var finishedSuccessfulTransactions = Set() private var onRestoreCompletion: ((RestoreState) -> Void)? @@ -315,6 +318,12 @@ public final class InAppPurchaseManager: NSObject { mappedError = .network case .paymentNotAllowed, .clientInvalid: mappedError = .notAllowed + case .unknown: + if let _ = error.userInfo["tryLater"] { + mappedError = .tryLater + } else { + mappedError = .generic + } default: mappedError = .generic } @@ -400,9 +409,15 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { let transactionState: TransactionState? switch transaction.transactionState { case .purchased: - Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") purchased") - transactionState = .purchased(transactionId: transaction.transactionIdentifier) - transactionsToAssign.append(transaction) + if transaction.payment.productIdentifier.contains(".topup."), let transactionIdentifier = transaction.transactionIdentifier, self.finishedSuccessfulTransactions.contains(transactionIdentifier) { + Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") seems to be already reported, ask to try later") + transactionState = .failed(error: SKError(SKError.Code.unknown, userInfo: ["tryLater": true])) + queue.finishTransaction(transaction) + } else { + Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") purchased") + transactionState = .purchased(transactionId: transaction.transactionIdentifier) + transactionsToAssign.append(transaction) + } case .restored: Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "") restroring") let transactionIdentifier = transaction.transactionIdentifier @@ -490,6 +505,12 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { self.debugSaveReceipt(receiptData: receiptData) #endif + for transaction in transactionsToAssign { + if let transactionIdentifier = transaction.transactionIdentifier { + self.finishedSuccessfulTransactions.insert(transactionIdentifier) + } + } + self.disposableSet.set( (purpose |> castError(AssignAppStoreTransactionError.self) diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index 42ba522cc8e..57f20ff4297 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -1163,6 +1163,8 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments case .assignFailed: errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .tryLater: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown case .cancelled: break } diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index e82f316816a..8e3668042e7 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -955,6 +955,8 @@ private final class PremiumGiftScreenComponent: CombinedComponent { errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments case .assignFailed: errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .tryLater: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown case .cancelled: break } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 7243125fe49..6c31bd1c3a6 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -3113,6 +3113,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent { errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments case .assignFailed: errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .tryLater: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown case .cancelled: break } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 868a392f63b..1c5b0a70748 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -231,7 +231,7 @@ private final class StarsContextImpl { private extension StarsContext.State.Transaction { init?(apiTransaction: Api.StarsTransaction, transaction: Transaction) { switch apiTransaction { - case let .starsTransaction(_, id, stars, date, transactionPeer, title, description, photo): + case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo): let parsedPeer: StarsContext.State.Transaction.Peer switch transactionPeer { case .starsTransactionPeerAppStore: @@ -250,7 +250,12 @@ private extension StarsContext.State.Transaction { } parsedPeer = .peer(EnginePeer(peer)) } - self.init(flags: [], id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init)) + + var flags: Flags = [] + if (apiFlags & (1 << 3)) != 0 { + flags.insert(.isRefund) + } + self.init(flags: flags, id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init)) } } } diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index c028bf2aa31..b73c62a6f40 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -663,6 +663,8 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments case .assignFailed: errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .tryLater: + errorText = presentationData.strings.Premium_Purchase_ErrorTryLater case .cancelled: break } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift index 3ba5605471b..e441c8e5956 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift @@ -125,6 +125,9 @@ private final class StarsTransactionSheetContent: CombinedComponent { let additional = Child(BalancedTextComponent.self) let button = Child(SolidRoundedButtonComponent.self) + let refundBackgound = Child(RoundedRectangle.self) + let refundText = Child(MultilineTextComponent.self) + return { context in let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let controller = environment.controller @@ -172,6 +175,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { let toPeer: EnginePeer? let transactionPeer: StarsContext.State.Transaction.Peer? let photo: TelegramMediaWebFile? + let isRefund: Bool var delayedCloseOnOpenPeer = true switch subject { @@ -208,6 +212,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { } transactionPeer = transaction.peer photo = transaction.photo + isRefund = transaction.flags.contains(.isRefund) case let .receipt(receipt): titleText = receipt.invoiceMedia.title descriptionText = receipt.invoiceMedia.description @@ -222,6 +227,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { } transactionPeer = nil photo = receipt.invoiceMedia.photo + isRefund = false delayedCloseOnOpenPeer = false } @@ -455,11 +461,45 @@ private final class StarsTransactionSheetContent: CombinedComponent { originY += description.size.height + 10.0 } + let amountSpacing: CGFloat = 3.0 + var totalAmountWidth: CGFloat = amount.size.width + amountSpacing + amountStar.size.width + var amountOriginX: CGFloat = floor(context.availableSize.width - totalAmountWidth) / 2.0 + if isRefund { + let refundText = refundText.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.Stars_Transaction_Refund, + font: Font.medium(14.0), + textColor: theme.list.itemDisclosureActions.constructive.fillColor + )) + ), + availableSize: context.availableSize, + transition: .immediate + ) + let refundBackground = refundBackgound.update( + component: RoundedRectangle( + color: theme.list.itemDisclosureActions.constructive.fillColor.withAlphaComponent(0.1), + cornerRadius: 6.0 + ), + availableSize: CGSize(width: refundText.size.width + 10.0, height: refundText.size.height + 4.0), + transition: .immediate + ) + totalAmountWidth += amountSpacing * 2.0 + refundBackground.size.width + amountOriginX = floor(context.availableSize.width - totalAmountWidth) / 2.0 + + context.add(refundBackground + .position(CGPoint(x: amountOriginX + amount.size.width + amountSpacing + amountStar.size.width + amountSpacing * 2.0 + refundBackground.size.width / 2.0, y: originY + refundBackground.size.height / 2.0)) + ) + context.add(refundText + .position(CGPoint(x: amountOriginX + amount.size.width + amountSpacing + amountStar.size.width + amountSpacing * 2.0 + refundBackground.size.width / 2.0, y: originY + refundBackground.size.height / 2.0)) + ) + } + context.add(amount - .position(CGPoint(x: context.availableSize.width / 2.0 - 10.0, y: originY + amount.size.height / 2.0)) + .position(CGPoint(x: amountOriginX + amount.size.width / 2.0, y: originY + amount.size.height / 2.0)) ) context.add(amountStar - .position(CGPoint(x: context.availableSize.width / 2.0 + amount.size.width / 2.0 + amountStar.size.width / 2.0 - 7.0, y: originY + amountStar.size.height / 2.0)) + .position(CGPoint(x: amountOriginX + amount.size.width + amountSpacing + amountStar.size.width / 2.0, y: originY + amountStar.size.height / 2.0)) ) originY += amount.size.height + 20.0