diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 287c713a3..11bb8041c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1864,6 +1864,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payments_total_label" = "Total"; "lng_payments_pay_amount" = "Pay {amount}"; "lng_payments_payment_method" = "Payment Method"; +"lng_payments_new_card" = "New Card..."; "lng_payments_payment_method_ph" = "Enter your card details"; "lng_payments_shipping_address" = "Shipping Information"; "lng_payments_shipping_address_ph" = "Enter your shipping information"; diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 4684896f6..d6ae27bb5 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/payments_form.h" #include "payments/ui/payments_panel.h" -#include "payments/ui/payments_webview.h" #include "main/main_session.h" #include "main/main_account.h" #include "main/main_domain.h" @@ -17,10 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history.h" #include "core/local_url_handlers.h" // TryConvertUrlToLocal. +#include "core/file_utilities.h" // File::OpenUrl. #include "apiwrap.h" -#include "stripe/stripe_api_client.h" -#include "stripe/stripe_error.h" -#include "stripe/stripe_token.h" // #TODO payments errors #include "mainwindow.h" @@ -57,13 +54,6 @@ base::flat_map, SessionProcesses> Processes; return result; } -[[nodiscard]] QString CardTitle(const Stripe::Card &card) { - // Like server stores saved_credentials title. - return Stripe::CardBrandToString(card.brand()).toLower() - + " *" - + card.last4(); -} - } // namespace void CheckoutProcess::Start(not_null item) { @@ -110,7 +100,7 @@ not_null CheckoutProcess::panelDelegate() { } void CheckoutProcess::handleFormUpdate(const FormUpdate &update) { - v::match(update.data, [&](const FormReady &) { + v::match(update, [&](const FormReady &) { performInitialSilentValidation(); if (!_initialSilentValidation) { showForm(); @@ -124,17 +114,12 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) { _submitState = SubmitState::Validated; panelSubmit(); } + }, [&](const PaymentMethodUpdate&) { + showForm(); }, [&](const VerificationNeeded &info) { - if (_webviewWindow) { - _webviewWindow->navigate(info.url); - } else { - _webviewWindow = std::make_unique( - webviewDataPath(), - info.url, - panelDelegate()); - if (!_webviewWindow->shown()) { - // #TODO payments errors - } + if (!_panel->showWebview(info.url, false)) { + File::OpenUrl(info.url); + panelCloseSure(); } }, [&](const PaymentFinished &result) { const auto weak = base::make_weak(this); @@ -249,7 +234,7 @@ void CheckoutProcess::panelSubmit() { || _submitState == SubmitState::Finishing) { return; } - const auto &native = _form->nativePayment(); + const auto &method = _form->paymentMethod(); const auto &invoice = _form->invoice(); const auto &options = _form->shippingOptions(); if (!options.list.empty() && options.selectedId.isEmpty()) { @@ -264,24 +249,12 @@ void CheckoutProcess::panelSubmit() { _submitState = SubmitState::Validation; _form->validateInformation(_form->savedInformation()); return; - } else if (native - && !native.newCredentials - && !native.savedCredentials) { + } else if (!method.newCredentials && !method.savedCredentials) { editPaymentMethod(); return; } _submitState = SubmitState::Finishing; - if (!native) { - _webviewWindow = std::make_unique( - webviewDataPath(), - _form->details().url, - panelDelegate()); - if (!_webviewWindow->shown()) { - // #TODO payments errors - } - } else if (native.newCredentials) { - _form->send(native.newCredentials.data); - } + _form->submit(); } void CheckoutProcess::panelWebviewMessage(const QJsonDocument &message) { @@ -321,19 +294,25 @@ void CheckoutProcess::panelWebviewMessage(const QJsonDocument &message) { "Not an object received in payment credentials.")); return; } - const auto serializedCredentials = QJsonDocument( - credentials.toObject() - ).toJson(QJsonDocument::Compact); - - _form->send(serializedCredentials); + crl::on_main(this, [=] { + _form->setPaymentCredentials(NewCredentials{ + .title = title, + .data = QJsonDocument( + credentials.toObject() + ).toJson(QJsonDocument::Compact), + .saveOnServer = false, // #TODO payments save + }); + }); } bool CheckoutProcess::panelWebviewNavigationAttempt(const QString &uri) { if (Core::TryConvertUrlToLocal(uri) == uri) { return true; } - panelCloseSure(); - App::wnd()->activate(); + crl::on_main(this, [=] { + panelCloseSure(); + App::wnd()->activate(); + }); return false; } @@ -346,46 +325,7 @@ void CheckoutProcess::panelEditPaymentMethod() { } void CheckoutProcess::panelValidateCard(Ui::UncheckedCardDetails data) { - Expects(_form->nativePayment().type == NativePayment::Type::Stripe); - Expects(!_form->nativePayment().stripePublishableKey.isEmpty()); - - if (_stripe) { - return; - } - auto configuration = Stripe::PaymentConfiguration{ - .publishableKey = _form->nativePayment().stripePublishableKey, - .companyName = "Telegram", - }; - _stripe = std::make_unique(std::move(configuration)); - auto card = Stripe::CardParams{ - .number = data.number, - .expMonth = data.expireMonth, - .expYear = data.expireYear, - .cvc = data.cvc, - .name = data.cardholderName, - .addressZip = data.addressZip, - .addressCountry = data.addressCountry, - }; - _stripe->createTokenWithCard(std::move(card), crl::guard(this, [=]( - Stripe::Token token, - Stripe::Error error) { - _stripe = nullptr; - - if (error) { - int a = 0; - // #TODO payment errors - } else { - _form->setPaymentCredentials({ - .title = CardTitle(token.card()), - .data = QJsonDocument(QJsonObject{ - { "type", "card" }, - { "id", token.tokenId() }, - }).toJson(QJsonDocument::Compact), - .saveOnServer = false, - }); - showForm(); - } - })); + _form->validateCard(data); } void CheckoutProcess::panelEditShippingInformation() { @@ -408,7 +348,7 @@ void CheckoutProcess::showForm() { _panel->showForm( _form->invoice(), _form->savedInformation(), - _form->nativePayment().details, + _form->paymentMethod().ui, _form->shippingOptions()); } @@ -437,7 +377,7 @@ void CheckoutProcess::chooseShippingOption() { } void CheckoutProcess::editPaymentMethod() { - _panel->choosePaymentMethod(_form->nativePayment().details); + _panel->choosePaymentMethod(_form->paymentMethod().ui); } void CheckoutProcess::panelChooseShippingOption() { @@ -474,7 +414,7 @@ void CheckoutProcess::performInitialSilentValidation() { _form->validateInformation(saved); } -QString CheckoutProcess::webviewDataPath() const { +QString CheckoutProcess::panelWebviewDataPath() { return _session->domain().local().webviewDataPath(); } diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 3613ff232..636915071 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -12,17 +12,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class HistoryItem; -namespace Stripe { -class APIClient; -} // namespace Stripe - namespace Main { class Session; } // namespace Main namespace Payments::Ui { class Panel; -class WebviewWindow; enum class InformationField; } // namespace Payments::Ui @@ -67,7 +62,6 @@ private: void editPaymentMethod(); void performInitialSilentValidation(); - [[nodiscard]] QString webviewDataPath() const; void panelRequestClose() override; void panelCloseSure() override; @@ -87,11 +81,11 @@ private: void panelValidateCard(Ui::UncheckedCardDetails data) override; void panelShowBox(object_ptr box) override; + QString panelWebviewDataPath() override; + const not_null _session; const std::unique_ptr
_form; const std::unique_ptr _panel; - std::unique_ptr _webviewWindow; - std::unique_ptr _stripe; SubmitState _submitState = SubmitState::None; bool _initialSilentValidation = false; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 62f9b59cb..b6ad2c95e 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -10,6 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "data/data_session.h" #include "apiwrap.h" +#include "stripe/stripe_api_client.h" +#include "stripe/stripe_error.h" +#include "stripe/stripe_token.h" #include #include @@ -67,6 +70,13 @@ namespace { MTP_string(information.shippingAddress.postcode))); } +[[nodiscard]] QString CardTitle(const Stripe::Card &card) { + // Like server stores saved_credentials title. + return Stripe::CardBrandToString(card.brand()).toLower() + + " *" + + card.last4(); +} + } // namespace Form::Form(not_null session, FullMsgId itemId) @@ -76,6 +86,8 @@ Form::Form(not_null session, FullMsgId itemId) requestForm(); } +Form::~Form() = default; + void Form::requestForm() { _api.request(MTPpayments_GetPaymentForm( MTP_int(_msgId) @@ -84,7 +96,7 @@ void Form::requestForm() { processForm(data); }); }).fail([=](const MTP::Error &error) { - _updates.fire({ Error{ Error::Type::Form, error.type() } }); + _updates.fire(Error{ Error::Type::Form, error.type() }); }).send(); } @@ -105,8 +117,8 @@ void Form::processForm(const MTPDpayments_paymentForm &data) { processSavedCredentials(data); }); } - fillNativePaymentInformation(); - _updates.fire({ FormReady{} }); + fillPaymentMethodInformation(); + _updates.fire(FormReady{}); } void Form::processInvoice(const MTPDinvoice &data) { @@ -156,30 +168,32 @@ void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) { void Form::processSavedCredentials( const MTPDpaymentSavedCredentialsCard &data) { - // #TODO payments not yet supported + // #TODO payments save //_nativePayment.savedCredentials = SavedCredentials{ // .id = qs(data.vid()), // .title = qs(data.vtitle()), //}; - refreshNativePaymentDetails(); + refreshPaymentMethodDetails(); } -void Form::refreshNativePaymentDetails() { - const auto &saved = _nativePayment.savedCredentials; - const auto &entered = _nativePayment.newCredentials; - _nativePayment.details.credentialsTitle = entered - ? entered.title - : saved.title; - _nativePayment.details.ready = entered || saved; +void Form::refreshPaymentMethodDetails() { + const auto &saved = _paymentMethod.savedCredentials; + const auto &entered = _paymentMethod.newCredentials; + _paymentMethod.ui.title = entered ? entered.title : saved.title; + _paymentMethod.ui.ready = entered || saved; } -void Form::fillNativePaymentInformation() { - auto saved = std::move(_nativePayment.savedCredentials); - auto entered = std::move(_nativePayment.newCredentials); - _nativePayment = NativePayment(); - if (_details.nativeProvider != "stripe") { - return; +void Form::fillPaymentMethodInformation() { + _paymentMethod.native = NativePaymentMethod(); + _paymentMethod.ui.native = Ui::NativeMethodDetails(); + _paymentMethod.ui.url = _details.url; + if (_details.nativeProvider == "stripe") { + fillStripeNativeMethod(); } + refreshPaymentMethodDetails(); +} + +void Form::fillStripeNativeMethod() { auto error = QJsonParseError(); auto document = QJsonDocument::fromJson( _details.nativeParamsJson, @@ -202,22 +216,22 @@ void Form::fillNativePaymentInformation() { LOG(("Payment Error: No publishable_key in native_params.")); return; } - _nativePayment = NativePayment{ - .type = NativePayment::Type::Stripe, - .stripePublishableKey = key, - .savedCredentials = std::move(saved), - .newCredentials = std::move(entered), - .details = Ui::NativePaymentDetails{ - .supported = true, - .needCountry = value(u"need_country").toBool(), - .needZip = value(u"need_zip").toBool(), - .needCardholderName = value(u"need_cardholder_name").toBool(), + _paymentMethod.native = NativePaymentMethod{ + .data = StripePaymentMethod{ + .publishableKey = key, }, }; - refreshNativePaymentDetails(); + _paymentMethod.ui.native = Ui::NativeMethodDetails{ + .supported = true, + .needCountry = value(u"need_country").toBool(), + .needZip = value(u"need_zip").toBool(), + .needCardholderName = value(u"need_cardholder_name").toBool(), + }; } -void Form::send(const QByteArray &serializedCredentials) { +void Form::submit() { + Expects(!_paymentMethod.newCredentials.data.isEmpty()); // #TODO payments save + using Flag = MTPpayments_SendPaymentForm::Flag; _api.request(MTPpayments_SendPaymentForm( MTP_flags((_requestedInformationId.isEmpty() @@ -231,15 +245,15 @@ void Form::send(const QByteArray &serializedCredentials) { MTP_string(_shippingOptions.selectedId), MTP_inputPaymentCredentials( MTP_flags(0), - MTP_dataJSON(MTP_bytes(serializedCredentials))) + MTP_dataJSON(MTP_bytes(_paymentMethod.newCredentials.data))) )).done([=](const MTPpayments_PaymentResult &result) { result.match([&](const MTPDpayments_paymentResult &data) { - _updates.fire({ PaymentFinished{ data.vupdates() } }); + _updates.fire(PaymentFinished{ data.vupdates() }); }, [&](const MTPDpayments_paymentVerificationNeeded &data) { - _updates.fire({ VerificationNeeded{ qs(data.vurl()) } }); + _updates.fire(VerificationNeeded{ qs(data.vurl()) }); }); }).fail([=](const MTP::Error &error) { - _updates.fire({ Error{ Error::Type::Send, error.type() } }); + _updates.fire(Error{ Error::Type::Send, error.type() }); }).send(); } @@ -273,18 +287,76 @@ void Form::validateInformation(const Ui::RequestedInformation &information) { _shippingOptions.selectedId = _shippingOptions.list.front().id; } _savedInformation = _validatedInformation; - _updates.fire({ ValidateFinished{} }); + _updates.fire(ValidateFinished{}); }).fail([=](const MTP::Error &error) { _validateRequestId = 0; - _updates.fire({ Error{ Error::Type::Validate, error.type() } }); + _updates.fire(Error{ Error::Type::Validate, error.type() }); }).send(); } +void Form::validateCard(const Ui::UncheckedCardDetails &details) { + Expects(!v::is_null(_paymentMethod.native.data)); + + const auto &native = _paymentMethod.native.data; + if (const auto stripe = std::get_if(&native)) { + validateCard(*stripe, details); + } else { + Unexpected("Native payment provider in Form::validateCard."); + } +} + +void Form::validateCard( + const StripePaymentMethod &method, + const Ui::UncheckedCardDetails &details) { + Expects(!method.publishableKey.isEmpty()); + + if (_stripe) { + return; + } + auto configuration = Stripe::PaymentConfiguration{ + .publishableKey = method.publishableKey, + .companyName = "Telegram", + }; + _stripe = std::make_unique(std::move(configuration)); + auto card = Stripe::CardParams{ + .number = details.number, + .expMonth = details.expireMonth, + .expYear = details.expireYear, + .cvc = details.cvc, + .name = details.cardholderName, + .addressZip = details.addressZip, + .addressCountry = details.addressCountry, + }; + _stripe->createTokenWithCard(std::move(card), crl::guard(this, [=]( + Stripe::Token token, + Stripe::Error error) { + _stripe = nullptr; + + if (error) { + LOG(("Stripe Error %1: %2 (%3)" + ).arg(int(error.code()) + ).arg(error.description() + ).arg(error.message())); + _updates.fire(Error{ Error::Type::Stripe, error.description() }); + } else { + setPaymentCredentials({ + .title = CardTitle(token.card()), + .data = QJsonDocument(QJsonObject{ + { "type", "card" }, + { "id", token.tokenId() }, + }).toJson(QJsonDocument::Compact), + .saveOnServer = false, // #TODO payments save + }); + } + })); +} + void Form::setPaymentCredentials(const NewCredentials &credentials) { Expects(!credentials.empty()); - _nativePayment.newCredentials = credentials; - refreshNativePaymentDetails(); + _paymentMethod.newCredentials = credentials; + refreshPaymentMethodDetails(); + _updates.fire(PaymentMethodUpdate{}); } void Form::setShippingOption(const QString &id) { diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index fbe4bd44f..7fbbe6c2a 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -8,8 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "payments/ui/payments_panel_data.h" +#include "base/weak_ptr.h" #include "mtproto/sender.h" +namespace Stripe { +class APIClient; +} // namespace Stripe + namespace Main { class Session; } // namespace Main @@ -58,55 +63,64 @@ struct NewCredentials { } }; -struct NativePayment { - enum class Type { - None, - Stripe, - }; - Type type = Type::None; - QString stripePublishableKey; - SavedCredentials savedCredentials; - NewCredentials newCredentials; - Ui::NativePaymentDetails details; +struct StripePaymentMethod { + QString publishableKey; +}; + +struct NativePaymentMethod { + std::variant< + v::null_t, + StripePaymentMethod> data; [[nodiscard]] bool valid() const { - return (type != Type::None); + return !v::is_null(data); } [[nodiscard]] explicit operator bool() const { return valid(); } }; +struct PaymentMethod { + NativePaymentMethod native; + SavedCredentials savedCredentials; + NewCredentials newCredentials; + Ui::PaymentMethodDetails ui; +}; + struct FormReady {}; struct ValidateFinished {}; -struct Error { - enum class Type { - Form, - Validate, - Send, - }; - Type type = Type::Form; - QString id; -}; +struct PaymentMethodUpdate {}; struct VerificationNeeded { QString url; }; struct PaymentFinished { MTPUpdates updates; }; - -struct FormUpdate { - std::variant< - FormReady, - VerificationNeeded, - ValidateFinished, - PaymentFinished, - Error> data; +struct Error { + enum class Type { + Form, + Validate, + Stripe, + Send, + }; + Type type = Type::Form; + QString id; }; -class Form final { +struct FormUpdate : std::variant< + FormReady, + ValidateFinished, + PaymentMethodUpdate, + VerificationNeeded, + PaymentFinished, + Error> { + using variant::variant; +}; + +class Form final : public base::has_weak_ptr { public: Form(not_null session, FullMsgId itemId); + ~Form(); [[nodiscard]] const Ui::Invoice &invoice() const { return _invoice; @@ -117,8 +131,8 @@ public: [[nodiscard]] const Ui::RequestedInformation &savedInformation() const { return _savedInformation; } - [[nodiscard]] const NativePayment &nativePayment() const { - return _nativePayment; + [[nodiscard]] const PaymentMethod &paymentMethod() const { + return _paymentMethod; } [[nodiscard]] const Ui::ShippingOptions &shippingOptions() const { return _shippingOptions; @@ -129,9 +143,10 @@ public: } void validateInformation(const Ui::RequestedInformation &information); + void validateCard(const Ui::UncheckedCardDetails &details); void setPaymentCredentials(const NewCredentials &credentials); void setShippingOption(const QString &id); - void send(const QByteArray &serializedCredentials); + void submit(); private: void requestForm(); @@ -142,8 +157,13 @@ private: void processSavedCredentials( const MTPDpaymentSavedCredentialsCard &data); void processShippingOptions(const QVector &data); - void fillNativePaymentInformation(); - void refreshNativePaymentDetails(); + void fillPaymentMethodInformation(); + void fillStripeNativeMethod(); + void refreshPaymentMethodDetails(); + + void validateCard( + const StripePaymentMethod &method, + const Ui::UncheckedCardDetails &details); const not_null _session; MTP::Sender _api; @@ -152,11 +172,13 @@ private: Ui::Invoice _invoice; FormDetails _details; Ui::RequestedInformation _savedInformation; - NativePayment _nativePayment; + PaymentMethod _paymentMethod; Ui::RequestedInformation _validatedInformation; mtpRequestId _validateRequestId = 0; + std::unique_ptr _stripe; + Ui::ShippingOptions _shippingOptions; QString _requestedInformationId; diff --git a/Telegram/SourceFiles/payments/stripe/stripe_error.cpp b/Telegram/SourceFiles/payments/stripe/stripe_error.cpp index 675d41d8f..72f843252 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_error.cpp +++ b/Telegram/SourceFiles/payments/stripe/stripe_error.cpp @@ -11,6 +11,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Stripe { +Error::Code Error::code() const { + return _code; +} + +QString Error::description() const { + return _description; +} + +QString Error::message() const { + return _message; +} + +QString Error::parameter() const { + return _parameter; +} + +Error Error::None() { + return Error(Code::None, {}, {}, {}); +} + Error Error::DecodedObjectFromResponse(QJsonObject object) { const auto entry = object.value("error"); if (!entry.isObject()) { @@ -80,4 +100,8 @@ Error Error::DecodedObjectFromResponse(QJsonObject object) { } } +bool Error::empty() const { + return (_code == Code::None); +} + } // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_error.h b/Telegram/SourceFiles/payments/stripe/stripe_error.h index 9a0a46ec3..36247d387 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_error.h +++ b/Telegram/SourceFiles/payments/stripe/stripe_error.h @@ -42,14 +42,15 @@ public: , _parameter(parameter) { } - [[nodiscard]] static Error None() { - return Error(Code::None, QString(), QString()); - } + [[nodiscard]] Code code() const; + [[nodiscard]] QString description() const; + [[nodiscard]] QString message() const; + [[nodiscard]] QString parameter() const; + + [[nodiscard]] static Error None(); [[nodiscard]] static Error DecodedObjectFromResponse(QJsonObject object); - [[nodiscard]] bool empty() const { - return (_code == Code::None); - } + [[nodiscard]] bool empty() const; [[nodiscard]] explicit operator bool() const { return !empty(); } diff --git a/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp b/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp index e9c497336..7d5918e9f 100644 --- a/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp @@ -35,7 +35,7 @@ constexpr auto kMaxPostcodeSize = 10; EditCard::EditCard( QWidget *parent, - const NativePaymentDetails &native, + const NativeMethodDetails &native, CardField field, not_null delegate) : _delegate(delegate) diff --git a/Telegram/SourceFiles/payments/ui/payments_edit_card.h b/Telegram/SourceFiles/payments/ui/payments_edit_card.h index fc77be6d6..157e13edc 100644 --- a/Telegram/SourceFiles/payments/ui/payments_edit_card.h +++ b/Telegram/SourceFiles/payments/ui/payments_edit_card.h @@ -31,7 +31,7 @@ class EditCard final : public RpWidget { public: EditCard( QWidget *parent, - const NativePaymentDetails &native, + const NativeMethodDetails &native, CardField field, not_null delegate); @@ -52,7 +52,7 @@ private: [[nodiscard]] UncheckedCardDetails collect() const; const not_null _delegate; - NativePaymentDetails _native; + NativeMethodDetails _native; object_ptr _scroll; object_ptr _topShadow; diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp index 0243f0acf..dfcee629e 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp @@ -30,12 +30,12 @@ FormSummary::FormSummary( QWidget *parent, const Invoice &invoice, const RequestedInformation ¤t, - const NativePaymentDetails &native, + const PaymentMethodDetails &method, const ShippingOptions &options, not_null delegate) : _delegate(delegate) , _invoice(invoice) -, _native(native) +, _method(method) , _options(options) , _information(current) , _scroll(this, st::passportPanelScroll) @@ -136,20 +136,18 @@ not_null FormSummary::setupContent() { st::passportFormDividerHeight), { 0, 0, 0, st::passportFormHeaderPadding.top() }); - if (_native.supported) { - const auto method = inner->add(object_ptr(inner)); - method->addClickHandler([=] { - _delegate->panelEditPaymentMethod(); - }); - method->updateContent( - tr::lng_payments_payment_method(tr::now), - (_native.ready - ? _native.credentialsTitle - : tr::lng_payments_payment_method_ph(tr::now)), - _native.ready, - false, - anim::type::instant); - } + const auto method = inner->add(object_ptr(inner)); + method->addClickHandler([=] { + _delegate->panelEditPaymentMethod(); + }); + method->updateContent( + tr::lng_payments_payment_method(tr::now), + (_method.ready + ? _method.title + : tr::lng_payments_payment_method_ph(tr::now)), + _method.ready, + false, + anim::type::instant); if (_invoice.isShippingAddressRequested) { const auto info = inner->add(object_ptr(inner)); info->addClickHandler([=] { diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.h b/Telegram/SourceFiles/payments/ui/payments_form_summary.h index 60c7a5538..38f30ff0f 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.h +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.h @@ -29,7 +29,7 @@ public: QWidget *parent, const Invoice &invoice, const RequestedInformation ¤t, - const NativePaymentDetails &native, + const PaymentMethodDetails &method, const ShippingOptions &options, not_null delegate); @@ -45,7 +45,7 @@ private: const not_null _delegate; Invoice _invoice; - NativePaymentDetails _native; + PaymentMethodDetails _method; ShippingOptions _options; RequestedInformation _information; object_ptr _scroll; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp index 6f98b2b82..97c652e12 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/separate_panel.h" #include "ui/boxes/single_choice_box.h" #include "lang/lang_keys.h" +#include "webview/webview_embed.h" #include "styles/style_payments.h" #include "styles/style_passport.h" @@ -37,7 +38,10 @@ Panel::Panel(not_null delegate) }, _widget->lifetime()); } -Panel::~Panel() = default; +Panel::~Panel() { + // Destroy _widget before _webview. + _widget = nullptr; +} void Panel::requestActivate() { _widget->showAndActivate(); @@ -46,14 +50,14 @@ void Panel::requestActivate() { void Panel::showForm( const Invoice &invoice, const RequestedInformation ¤t, - const NativePaymentDetails &native, + const PaymentMethodDetails &method, const ShippingOptions &options) { _widget->showInner( base::make_unique_q( _widget.get(), invoice, current, - native, + method, options, _delegate)); _widget->setBackAllowed(false); @@ -113,23 +117,84 @@ void Panel::chooseShippingOption(const ShippingOptions &options) { })); } -void Panel::choosePaymentMethod(const NativePaymentDetails &native) { - Expects(native.supported); +void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) { + if (method.native.supported) { + showEditCard(method.native, CardField::Number); + } else if (!showWebview(method.url, true)) { + // #TODO payments errors not supported + } +} - if (!native.ready) { - showEditCard(native, CardField::Number); +bool Panel::showWebview(const QString &url, bool allowBack) { + if (!_webview && !createWebview()) { + return false; + } + _webview->navigate(url); + _widget->setBackAllowed(allowBack); + return true; +} + +bool Panel::createWebview() { + auto container = base::make_unique_q(_widget.get()); + + container->setGeometry(_widget->innerGeometry()); + container->show(); + + _webview = std::make_unique( + container.get(), + Webview::WindowConfig{ + .userDataPath = _delegate->panelWebviewDataPath(), + }); + const auto raw = _webview.get(); + QObject::connect(container.get(), &QObject::destroyed, [=] { + if (_webview.get() == raw) { + _webview = nullptr; + } + }); + if (!raw->widget()) { + return false; + } + + container->geometryValue( + ) | rpl::start_with_next([=](QRect geometry) { + raw->widget()->setGeometry(geometry); + }, container->lifetime()); + + raw->setMessageHandler([=](const QJsonDocument &message) { + _delegate->panelWebviewMessage(message); + }); + + raw->setNavigationHandler([=](const QString &uri) { + return _delegate->panelWebviewNavigationAttempt(uri); + }); + + raw->init(R"( +window.TelegramWebviewProxy = { +postEvent: function(eventType, eventData) { + if (window.external && window.external.invoke) { + window.external.invoke(JSON.stringify([eventType, eventData])); + } +} +};)"); + + _widget->showInner(std::move(container)); + return true; +} + +void Panel::choosePaymentMethod(const PaymentMethodDetails &method) { + if (!method.ready) { + showEditPaymentMethod(method); return; } - const auto title = native.credentialsTitle; showBox(Box([=](not_null box) { const auto save = [=](int option) { if (option) { - showEditCard(native, CardField::Number); + showEditPaymentMethod(method); } }; SingleChoiceBox(box, { .title = tr::lng_payments_payment_method(), - .options = { native.credentialsTitle, "New Card..." }, // #TODO payments lang + .options = { method.title, tr::lng_payments_new_card(tr::now) }, .initialSelection = 0, .callback = save, }); @@ -137,8 +202,10 @@ void Panel::choosePaymentMethod(const NativePaymentDetails &native) { } void Panel::showEditCard( - const NativePaymentDetails &native, + const NativeMethodDetails &native, CardField field) { + Expects(native.supported); + auto edit = base::make_unique_q( _widget.get(), native, @@ -151,7 +218,7 @@ void Panel::showEditCard( } void Panel::showCardError( - const NativePaymentDetails &native, + const NativeMethodDetails &native, CardField field) { if (_weakEditCard) { _weakEditCard->showError(field); diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.h b/Telegram/SourceFiles/payments/ui/payments_panel.h index 6d7202591..a8a694a89 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel.h @@ -14,6 +14,10 @@ class SeparatePanel; class BoxContent; } // namespace Ui +namespace Webview { +class Window; +} // namespace Webview + namespace Payments::Ui { using namespace ::Ui; @@ -26,7 +30,8 @@ enum class InformationField; enum class CardField; class EditInformation; class EditCard; -struct NativePaymentDetails; +struct PaymentMethodDetails; +struct NativeMethodDetails; class Panel final { public: @@ -38,7 +43,7 @@ public: void showForm( const Invoice &invoice, const RequestedInformation ¤t, - const NativePaymentDetails &native, + const PaymentMethodDetails &method, const ShippingOptions &options); void showEditInformation( const Invoice &invoice, @@ -48,14 +53,17 @@ public: const Invoice &invoice, const RequestedInformation ¤t, InformationField field); + void showEditPaymentMethod(const PaymentMethodDetails &method); void showEditCard( - const NativePaymentDetails &native, + const NativeMethodDetails &native, CardField field); void showCardError( - const NativePaymentDetails &native, + const NativeMethodDetails &native, CardField field); void chooseShippingOption(const ShippingOptions &options); - void choosePaymentMethod(const NativePaymentDetails &native); + void choosePaymentMethod(const PaymentMethodDetails &method); + + bool showWebview(const QString &url, bool allowBack); [[nodiscard]] rpl::producer<> backRequests() const; @@ -65,8 +73,11 @@ public: [[nodiscard]] rpl::lifetime &lifetime(); private: + bool createWebview(); + const not_null _delegate; std::unique_ptr _widget; + std::unique_ptr _webview; QPointer _weakEditInformation; QPointer _weakEditCard; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_data.h b/Telegram/SourceFiles/payments/ui/payments_panel_data.h index fba8e2dc1..7c3964c29 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_data.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_data.h @@ -115,15 +115,20 @@ enum class InformationField { Phone, }; -struct NativePaymentDetails { - QString credentialsTitle; - bool ready = false; +struct NativeMethodDetails { bool supported = false; bool needCountry = false; bool needZip = false; bool needCardholderName = false; }; +struct PaymentMethodDetails { + QString title; + NativeMethodDetails native; + QString url; + bool ready = false; +}; + enum class CardField { Number, CVC, diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h index 9d50d481e..8b6124604 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h @@ -42,6 +42,8 @@ public: virtual void panelValidateInformation(RequestedInformation data) = 0; virtual void panelValidateCard(Ui::UncheckedCardDetails data) = 0; virtual void panelShowBox(object_ptr box) = 0; + + virtual QString panelWebviewDataPath() = 0; }; } // namespace Payments::Ui diff --git a/Telegram/SourceFiles/payments/ui/payments_webview.cpp b/Telegram/SourceFiles/payments/ui/payments_webview.cpp deleted file mode 100644 index 39ae11b6c..000000000 --- a/Telegram/SourceFiles/payments/ui/payments_webview.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include "payments/ui/payments_webview.h" - -#include "payments/ui/payments_panel_delegate.h" -#include "webview/webview_embed.h" -#include "webview/webview_interface.h" -#include "ui/widgets/window.h" -#include "ui/toast/toast.h" -#include "lang/lang_keys.h" - -namespace Payments::Ui { - -using namespace ::Ui; - -class PanelDelegate; - -WebviewWindow::WebviewWindow( - const QString &userDataPath, - const QString &url, - not_null delegate) { - if (!url.startsWith("https://", Qt::CaseInsensitive)) { - return; - } - - const auto window = &_window; - - window->setGeometry({ - style::ConvertScale(100), - style::ConvertScale(100), - style::ConvertScale(640), - style::ConvertScale(480) - }); - window->show(); - - window->events() | rpl::start_with_next([=](not_null e) { - if (e->type() == QEvent::Close) { - delegate->panelCloseSure(); - } - }, window->lifetime()); - - const auto body = window->body(); - body->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - QPainter(body).fillRect(clip, st::windowBg); - }, body->lifetime()); - - const auto path = - _webview = Ui::CreateChild( - window, - window, - Webview::WindowConfig{ .userDataPath = userDataPath }); - if (!_webview->widget()) { - return; - } - - body->geometryValue( - ) | rpl::start_with_next([=](QRect geometry) { - _webview->widget()->setGeometry(geometry); - }, body->lifetime()); - - _webview->setMessageHandler([=](const QJsonDocument &message) { - delegate->panelWebviewMessage(message); - }); - - _webview->setNavigationHandler([=](const QString &uri) { - return delegate->panelWebviewNavigationAttempt(uri); - }); - - _webview->init(R"( -window.TelegramWebviewProxy = { -postEvent: function(eventType, eventData) { - if (window.external && window.external.invoke) { - window.external.invoke(JSON.stringify([eventType, eventData])); - } -} -};)"); - - navigate(url); -} - -[[nodiscard]] bool WebviewWindow::shown() const { - return _webview && _webview->widget(); -} - -void WebviewWindow::navigate(const QString &url) { - if (shown()) { - _webview->navigate(url); - } -} - -} // namespace Payments::Ui diff --git a/Telegram/SourceFiles/payments/ui/payments_webview.h b/Telegram/SourceFiles/payments/ui/payments_webview.h deleted file mode 100644 index 823d11775..000000000 --- a/Telegram/SourceFiles/payments/ui/payments_webview.h +++ /dev/null @@ -1,38 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#pragma once - -#include "ui/widgets/window.h" - -namespace Webview { -class Window; -} // namespace Webview - -namespace Payments::Ui { - -using namespace ::Ui; - -class PanelDelegate; - -class WebviewWindow final { -public: - WebviewWindow( - const QString &userDataPath, - const QString &url, - not_null delegate); - - [[nodiscard]] bool shown() const; - void navigate(const QString &url); - -private: - Ui::Window _window; - Webview::Window *_webview = nullptr; - -}; - -} // namespace Payments::Ui diff --git a/Telegram/SourceFiles/ui/widgets/separate_panel.cpp b/Telegram/SourceFiles/ui/widgets/separate_panel.cpp index b05c98f93..798c825d5 100644 --- a/Telegram/SourceFiles/ui/widgets/separate_panel.cpp +++ b/Telegram/SourceFiles/ui/widgets/separate_panel.cpp @@ -344,6 +344,10 @@ void SeparatePanel::setInnerSize(QSize size) { } } +QRect SeparatePanel::innerGeometry() const { + return _body->geometry(); +} + void SeparatePanel::initGeometry(QSize size) { const auto active = QApplication::activeWindow(); const auto center = !active diff --git a/Telegram/SourceFiles/ui/widgets/separate_panel.h b/Telegram/SourceFiles/ui/widgets/separate_panel.h index 721674d29..8c185f98c 100644 --- a/Telegram/SourceFiles/ui/widgets/separate_panel.h +++ b/Telegram/SourceFiles/ui/widgets/separate_panel.h @@ -28,6 +28,7 @@ public: void setTitle(rpl::producer title); void setInnerSize(QSize size); + [[nodiscard]] QRect innerGeometry() const; void setHideOnDeactivate(bool hideOnDeactivate); void showAndActivate(); @@ -41,9 +42,9 @@ public: void showToast(const TextWithEntities &text); void destroyLayer(); - rpl::producer<> backRequests() const; - rpl::producer<> closeRequests() const; - rpl::producer<> closeEvents() const; + [[nodiscard]] rpl::producer<> backRequests() const; + [[nodiscard]] rpl::producer<> closeRequests() const; + [[nodiscard]] rpl::producer<> closeEvents() const; void setBackAllowed(bool allowed); protected: diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 6545b0f0e..fd8ada4b0 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -79,8 +79,6 @@ PRIVATE payments/ui/payments_panel.h payments/ui/payments_panel_data.h payments/ui/payments_panel_delegate.h - payments/ui/payments_webview.cpp - payments/ui/payments_webview.h platform/mac/file_bookmark_mac.h platform/mac/file_bookmark_mac.mm