From f7885da7dd8ab7e13059a0bcad8e247410279745 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 26 Jul 2022 17:01:08 +0300 Subject: [PATCH] Support additional saved payment methods. --- Telegram/Resources/langs/lang.strings | 1 - Telegram/Resources/tl/api.tl | 2 +- .../payments/payments_checkout_process.cpp | 15 ++- .../SourceFiles/payments/payments_form.cpp | 98 ++++++++++++++----- Telegram/SourceFiles/payments/payments_form.h | 7 +- .../payments/ui/payments_form_summary.cpp | 4 +- .../payments/ui/payments_panel.cpp | 79 ++++++++------- .../SourceFiles/payments/ui/payments_panel.h | 17 ++-- .../payments/ui/payments_panel_data.h | 9 +- 9 files changed, 155 insertions(+), 77 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7d0b3ce215..83d8700ac4 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2397,7 +2397,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_theme_editor_menu_show" = "Show palette file"; "lng_payments_not_supported" = "Sorry, Telegram Desktop doesn't support payments yet. Please use one of our mobile apps to do this."; -"lng_payments_webview_no_card" = "Unfortunately, you can't add a new card with current system configuration."; "lng_payments_webview_no_use" = "Unfortunately, you can't use payments with current system configuration."; "lng_payments_webview_install_edge" = "Please install {link}."; "lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkit2gtk-5.0/webkit2gtk-4.1/webkit2gtk-4.0) using your package manager."; diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index aa50bb4ea4..88b6bcad27 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -853,7 +853,7 @@ inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; -payments.paymentForm#4cc5563f flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON additional_methods:flags.6?Vector saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; +payments.paymentForm#a0058751 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON additional_methods:flags.6?Vector saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?Vector users:Vector = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index c64908ebd9..79f538bc66 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -234,6 +234,11 @@ CheckoutProcess::CheckoutProcess( handleFormUpdate(update); }, _lifetime); + _panel->savedMethodChosen( + ) | rpl::start_with_next([=](QString id) { + _form->chooseSavedMethod(id); + }, _panel->lifetime()); + _panel->backRequests( ) | rpl::start_with_next([=] { panelCancelEdit(); @@ -284,7 +289,7 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) { if (!_initialSilentValidation) { showForm(); } - if (_form->paymentMethod().savedCredentials) { + if (!_form->paymentMethod().savedCredentials.empty()) { _session->api().cloudPassword().reload(); } }, [&](const ThumbnailUpdated &data) { @@ -532,7 +537,8 @@ void CheckoutProcess::panelSubmit() { || invoice.isPhoneRequested)) { _submitState = SubmitState::Validating; _form->validateInformation(_form->information()); - } else if (!method.newCredentials && !method.savedCredentials) { + } else if (!method.newCredentials + && method.savedCredentialsIndex >= method.savedCredentials.size()) { editPaymentMethod(); } else if (invoice.isRecurring && !_form->details().termsAccepted) { _panel->requestTermsAcceptance( @@ -712,10 +718,13 @@ void CheckoutProcess::requestPassword() { getPasswordState([=](const Core::CloudPasswordState &state) { auto fields = PasscodeBox::CloudFields::From(state); fields.customTitle = tr::lng_payments_password_title(); + const auto &method = _form->paymentMethod(); + const auto &list = method.savedCredentials; + const auto index = method.savedCredentialsIndex; fields.customDescription = tr::lng_payments_password_description( tr::now, lt_card, - _form->paymentMethod().savedCredentials.title); + (index < list.size()) ? list[index].title : QString()); fields.customSubmitButton = tr::lng_payments_password_submit(); fields.customCheckCallback = [=]( const Core::CloudPasswordResult &result) { diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 61f350f081..33ebc7555f 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -314,10 +314,15 @@ void Form::processForm(const MTPDpayments_paymentForm &data) { processSavedInformation(data); }); } + _paymentMethod.savedCredentials.clear(); + _paymentMethod.savedCredentialsIndex = 0; if (const auto credentials = data.vsaved_credentials()) { - credentials->match([&](const auto &data) { - processSavedCredentials(data); - }); + _paymentMethod.savedCredentials.reserve(credentials->v.size()); + for (const auto &saved : credentials->v) { + saved.match([&](const auto &data) { + addSavedCredentials(data); + }); + } } if (const auto additional = data.vadditional_methods()) { processAdditionalPaymentMethods(additional->v); @@ -344,10 +349,11 @@ void Form::processReceipt(const MTPDpayments_paymentReceipt &data) { _shippingOptions.selectedId = _shippingOptions.list.front().id; } } - _paymentMethod.savedCredentials = SavedCredentials{ + _paymentMethod.savedCredentials = { { .id = "(used)", .title = qs(data.vcredentials_title()), - }; + } }; + _paymentMethod.savedCredentialsIndex = 0; fillPaymentMethodInformation(); _updates.fire(FormReady{}); } @@ -467,12 +473,12 @@ void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) { }; } -void Form::processSavedCredentials( +void Form::addSavedCredentials( const MTPDpaymentSavedCredentialsCard &data) { - _paymentMethod.savedCredentials = SavedCredentials{ + _paymentMethod.savedCredentials.push_back({ .id = qs(data.vid()), .title = qs(data.vtitle()), - }; + }); refreshPaymentMethodDetails(); } @@ -489,17 +495,33 @@ void Form::processAdditionalPaymentMethods( } void Form::refreshPaymentMethodDetails() { - const auto &saved = _paymentMethod.savedCredentials; - const auto &entered = _paymentMethod.newCredentials; - _paymentMethod.ui.title = entered ? entered.title : saved.title; + refreshSavedPaymentMethodDetails(); _paymentMethod.ui.provider = _invoice.provider; - _paymentMethod.ui.ready = entered || saved; _paymentMethod.ui.native.defaultCountry = defaultCountry(); _paymentMethod.ui.canSaveInformation = _paymentMethod.ui.native.canSaveInformation = _details.canSaveCredentials || _details.passwordMissing; } +void Form::refreshSavedPaymentMethodDetails() { + const auto &list = _paymentMethod.savedCredentials; + const auto index = _paymentMethod.savedCredentialsIndex; + const auto &entered = _paymentMethod.newCredentials; + _paymentMethod.ui.savedMethods.clear(); + if (entered) { + _paymentMethod.ui.savedMethods.push_back({ .title = entered.title }); + } + for (const auto &item : list) { + _paymentMethod.ui.savedMethods.push_back({ + .id = item.id, + .title = item.title, + }); + } + _paymentMethod.ui.savedMethodIndex = (index < list.size()) + ? (index + (entered ? 1 : 0)) + : 0; +} + QString Form::defaultPhone() const { return _session->user()->phone(); } @@ -588,12 +610,16 @@ void Form::fillSmartGlocalNativeMethod(QJsonObject object) { void Form::submit() { Expects(_paymentMethod.newCredentials - || _paymentMethod.savedCredentials); + || (_paymentMethod.savedCredentialsIndex + < _paymentMethod.savedCredentials.size())); - const auto password = _paymentMethod.newCredentials - ? QByteArray() - : _session->validTmpPassword(); - if (!_paymentMethod.newCredentials && password.isEmpty()) { + const auto index = _paymentMethod.savedCredentialsIndex; + const auto &list = _paymentMethod.savedCredentials; + + const auto password = (index < list.size()) + ? _session->validTmpPassword() + : QByteArray(); + if (index < list.size() && password.isEmpty()) { _updates.fire(TmpPasswordRequired{}); return; } else if (!_session->local().isBotTrustedPayment(_details.botId)) { @@ -618,16 +644,16 @@ void Form::submit() { inputInvoice(), MTP_string(_requestedInformationId), MTP_string(_shippingOptions.selectedId), - (_paymentMethod.newCredentials - ? MTP_inputPaymentCredentials( + (index < list.size() + ? MTP_inputPaymentCredentialsSaved( + MTP_string(list[index].id), + MTP_bytes(password)) + : MTP_inputPaymentCredentials( MTP_flags((_paymentMethod.newCredentials.saveOnServer && _details.canSaveCredentials) ? MTPDinputPaymentCredentials::Flag::f_save : MTPDinputPaymentCredentials::Flag(0)), - MTP_dataJSON(MTP_bytes(_paymentMethod.newCredentials.data))) - : MTP_inputPaymentCredentialsSaved( - MTP_string(_paymentMethod.savedCredentials.id), - MTP_bytes(password))), + MTP_dataJSON(MTP_bytes(_paymentMethod.newCredentials.data)))), MTP_long(_invoice.tipsSelected) )).done([=](const MTPpayments_PaymentResult &result) { hideProgress(); @@ -725,7 +751,9 @@ bool Form::hasChanges() const { return (information != _savedInformation) || (_stripe != nullptr) || (_smartglocal != nullptr) - || !_paymentMethod.newCredentials.empty(); + || (!_paymentMethod.newCredentials.empty() + && (_paymentMethod.savedCredentialsIndex + >= _paymentMethod.savedCredentials.size())); } bool Form::validateInformationLocal( @@ -940,10 +968,30 @@ void Form::setPaymentCredentials(const NewCredentials &credentials) { Expects(!credentials.empty()); _paymentMethod.newCredentials = credentials; + _paymentMethod.savedCredentialsIndex + = _paymentMethod.savedCredentials.size(); + refreshSavedPaymentMethodDetails(); const auto requestNewPassword = credentials.saveOnServer && !_details.canSaveCredentials && _details.passwordMissing; - refreshPaymentMethodDetails(); + _updates.fire(PaymentMethodUpdate{ requestNewPassword }); +} + +void Form::chooseSavedMethod(const QString &id) { + auto &index = _paymentMethod.savedCredentialsIndex; + const auto &list = _paymentMethod.savedCredentials; + if (id.isEmpty() && _paymentMethod.newCredentials) { + index = list.size(); + } else { + const auto i = ranges::find(list, id, &SavedCredentials::id); + index = (i != end(list)) ? (i - begin(list)) : 0; + } + refreshSavedPaymentMethodDetails(); + const auto requestNewPassword = (index == list.size()) + && _paymentMethod.newCredentials + && _paymentMethod.newCredentials.saveOnServer + && !_details.canSaveCredentials + && _details.passwordMissing; _updates.fire(PaymentMethodUpdate{ requestNewPassword }); } diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index e34a55c9a6..1811dc8e2b 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -113,7 +113,8 @@ struct NativePaymentMethod { struct PaymentMethod { NativePaymentMethod native; - SavedCredentials savedCredentials; + std::vector savedCredentials; + int savedCredentialsIndex = 0; NewCredentials newCredentials; Ui::PaymentMethodDetails ui; }; @@ -222,6 +223,7 @@ public: const Ui::UncheckedCardDetails &details, bool saveInformation); void setPaymentCredentials(const NewCredentials &credentials); + void chooseSavedMethod(const QString &id); void setHasPassword(bool has); void setShippingOption(const QString &id); void setTips(int64 value); @@ -254,7 +256,7 @@ private: void processDetails(const MTPDpayments_paymentForm &data); void processDetails(const MTPDpayments_paymentReceipt &data); void processSavedInformation(const MTPDpaymentRequestedInfo &data); - void processSavedCredentials( + void addSavedCredentials( const MTPDpaymentSavedCredentialsCard &data); void processAdditionalPaymentMethods( const QVector &list); @@ -263,6 +265,7 @@ private: void fillStripeNativeMethod(QJsonObject object); void fillSmartGlocalNativeMethod(QJsonObject object); void refreshPaymentMethodDetails(); + void refreshSavedPaymentMethodDetails(); [[nodiscard]] QString defaultPhone() const; [[nodiscard]] QString defaultCountry() const; diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp index ff6ee74809..3bc4a05aae 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp @@ -500,7 +500,9 @@ void FormSummary::setupSections(not_null layout) { }; add( tr::lng_payments_payment_method(), - _method.title, + (_method.savedMethods.empty() + ? QString() + : _method.savedMethods[_method.savedMethodIndex].title), &st::paymentsIconPaymentMethod, [=] { _delegate->panelEditPaymentMethod(); }); if (_invoice.isShippingAddressRequested) { diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp index ca774d4cf7..cb1fc5a2e3 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -227,7 +227,9 @@ void Panel::showForm( const RequestedInformation ¤t, const PaymentMethodDetails &method, const ShippingOptions &options) { - if (invoice && !method.ready && !method.native.supported) { + if (invoice + && method.savedMethods.empty() + && !method.native.supported) { const auto available = Webview::Availability(); if (available.error != Webview::Available::Error::None) { showWebviewError( @@ -410,25 +412,35 @@ void Panel::chooseTips(const Invoice &invoice) { } void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) { - auto bottomText = method.canSaveInformation - ? rpl::producer() - : tr::lng_payments_processed_by( - lt_provider, - rpl::single(method.provider)); setTitle(tr::lng_payments_card_title()); if (method.native.supported) { showEditCard(method.native, CardField::Number); - } else if (!showWebview(method.url, true, std::move(bottomText))) { + } else { + showEditCardByUrl( + method.url, + method.provider, + method.canSaveInformation); + } +} + +void Panel::showEditCardByUrl( + const QString &url, + const QString &provider, + bool canSaveInformation) { + auto bottomText = canSaveInformation + ? rpl::producer() + : tr::lng_payments_processed_by(lt_provider, rpl::single(provider)); + if (!showWebview(url, true, std::move(bottomText))) { const auto available = Webview::Availability(); if (available.error != Webview::Available::Error::None) { showWebviewError( - tr::lng_payments_webview_no_card(tr::now), + tr::lng_payments_webview_no_use(tr::now), available); } else { showCriticalError({ "Error: Could not initialize WebView." }); } _widget->setBackAllowed(true); - } else if (method.canSaveInformation) { + } else if (canSaveInformation) { const auto &padding = st::paymentsPanelPadding; _saveWebviewInformation = CreateChild( _webviewBottom.get(), @@ -444,23 +456,11 @@ void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) { } void Panel::showAdditionalMethod( + const PaymentMethodAdditional &method, const QString &provider, - const PaymentMethodAdditional &method) { - auto bottomText = tr::lng_payments_processed_by( - lt_provider, - rpl::single(provider)); + bool canSaveInformation) { setTitle(rpl::single(method.title)); - if (!showWebview(method.url, true, std::move(bottomText))) { - const auto available = Webview::Availability(); - if (available.error != Webview::Available::Error::None) { - showWebviewError( - tr::lng_payments_webview_no_use(tr::now), - available); - } else { - showCriticalError({ "Error: Could not initialize WebView." }); - } - _widget->setBackAllowed(true); - } + showEditCardByUrl(method.url, provider, canSaveInformation); } void Panel::showWebviewProgress() { @@ -592,29 +592,32 @@ postEvent: function(eventType, eventData) { } void Panel::choosePaymentMethod(const PaymentMethodDetails &method) { - const auto hasSaved = method.ready; - if (!hasSaved && method.additionalMethods.empty()) { + if (method.savedMethods.empty() && method.additionalMethods.empty()) { showEditPaymentMethod(method); return; } showBox(Box([=](not_null box) { const auto save = [=](int option) { - const auto basic = hasSaved ? 1 : 0; - if (option > basic) { - const auto index = option - basic - 1; + const auto saved = int(method.savedMethods.size()); + if (!option) { + showEditPaymentMethod(method); + } else if (option > saved) { + const auto index = option - saved - 1; Assert(index < method.additionalMethods.size()); showAdditionalMethod( + method.additionalMethods[index], method.provider, - method.additionalMethods[index]); - } else if (!option) { - showEditPaymentMethod(method); + method.canSaveInformation); + } else { + const auto index = option - 1; + _savedMethodChosen.fire_copy(method.savedMethods[index].id); } }; auto options = std::vector{ tr::lng_payments_new_card(tr::now), }; - if (hasSaved) { - options.push_back(method.title); + for (const auto &saved : method.savedMethods) { + options.push_back(saved.title); } for (const auto &additional : method.additionalMethods) { options.push_back(additional.title); @@ -622,7 +625,9 @@ void Panel::choosePaymentMethod(const PaymentMethodDetails &method) { SingleChoiceBox(box, { .title = tr::lng_payments_payment_method(), .options = std::move(options), - .initialSelection = hasSaved ? 1 : -1, + .initialSelection = (method.savedMethods.empty() + ? -1 + : (method.savedMethodIndex + 1)), .callback = save, }); })); @@ -823,6 +828,10 @@ rpl::producer<> Panel::backRequests() const { return _widget->backRequests(); } +rpl::producer Panel::savedMethodChosen() const { + return _savedMethodChosen.events(); +} + void Panel::showBox(object_ptr box) { if (const auto widget = _webview ? _webview->window.widget() : nullptr) { const auto hideNow = !widget->isHidden(); diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.h b/Telegram/SourceFiles/payments/ui/payments_panel.h index 6875a334a9..502eb5f073 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel.h @@ -63,14 +63,15 @@ public: InformationField field); void showEditPaymentMethod(const PaymentMethodDetails &method); void showAdditionalMethod( + const PaymentMethodAdditional &method, const QString &provider, - const PaymentMethodAdditional &method); - void showEditCard( - const NativeMethodDetails &native, - CardField field); - void showCardError( - const NativeMethodDetails &native, - CardField field); + bool canSaveInformation); + void showEditCard(const NativeMethodDetails &native, CardField field); + void showEditCardByUrl( + const QString &url, + const QString &provider, + bool canSaveInformation); + void showCardError(const NativeMethodDetails &native, CardField field); void chooseShippingOption(const ShippingOptions &options); void chooseTips(const Invoice &invoice); void choosePaymentMethod(const PaymentMethodDetails &method); @@ -86,6 +87,7 @@ public: void updateThemeParams(const Webview::ThemeParams ¶ms); [[nodiscard]] rpl::producer<> backRequests() const; + [[nodiscard]] rpl::producer savedMethodChosen() const; void showBox(object_ptr box); void showToast(const TextWithEntities &text); @@ -120,6 +122,7 @@ private: rpl::variable _formScrollTop; QPointer _weakEditInformation; QPointer _weakEditCard; + rpl::event_stream _savedMethodChosen; bool _webviewProgress = false; bool _testMode = false; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_data.h b/Telegram/SourceFiles/payments/ui/payments_panel_data.h index 8a6716f111..40d91f44ed 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_data.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_data.h @@ -167,13 +167,18 @@ struct PaymentMethodAdditional { QString url; }; -struct PaymentMethodDetails { +struct PaymentMethodSaved { + QString id; QString title; +}; + +struct PaymentMethodDetails { NativeMethodDetails native; + std::vector savedMethods; std::vector additionalMethods; QString url; QString provider; - bool ready = false; + int savedMethodIndex = 0; bool canSaveInformation = false; };