diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0a2a9c6ae..287c713a3 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1863,20 +1863,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payments_checkout_title" = "Checkout"; "lng_payments_total_label" = "Total"; "lng_payments_pay_amount" = "Pay {amount}"; -//"lng_payments_payment_method" = "Payment Method"; // #TODO payments native +"lng_payments_payment_method" = "Payment Method"; +"lng_payments_payment_method_ph" = "Enter your card details"; "lng_payments_shipping_address" = "Shipping Information"; +"lng_payments_shipping_address_ph" = "Enter your shipping information"; "lng_payments_shipping_method" = "Shipping Method"; +"lng_payments_shipping_method_ph" = "Choose your shipping method"; "lng_payments_info_name" = "Name"; +"lng_payments_info_name_ph" = "Enter your name"; "lng_payments_info_email" = "Email"; +"lng_payments_info_email_ph" = "Enter your email"; "lng_payments_info_phone" = "Phone"; +"lng_payments_info_phone_ph" = "Enter your phone number"; "lng_payments_shipping_address_title" = "Shipping Address"; "lng_payments_save_shipping_about" = "You can save your shipping information for future use."; -//"lng_payments_payment_card" = "Payment Card"; // #TODO payments native -//"lng_payments_cardholder_title" = "Cardholder"; -//"lng_payments_cardholder_about" = "Cardholder Name"; -//"lng_payments_billing_address" = "Billing Address"; -//"lng_payments_zip_code" = "Zip Code"; -//"lng_payments_save_payment_about" = "You can save your payment information for future use."; +"lng_payments_payment_card" = "Payment Card"; +"lng_payments_cardholder_title" = "Cardholder"; +"lng_payments_cardholder_about" = "Cardholder Name"; +"lng_payments_billing_address" = "Billing Address"; +"lng_payments_zip_code" = "Zip Code"; +"lng_payments_save_payment_about" = "You can save your payment information for future use."; "lng_payments_save_information" = "Save Information"; "lng_call_status_incoming" = "is calling you..."; diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 030803b83..4684896f6 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -18,6 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "core/local_url_handlers.h" // TryConvertUrlToLocal. #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" @@ -54,6 +57,13 @@ 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) { @@ -167,22 +177,23 @@ void CheckoutProcess::handleError(const Error &error) { showForm(); return; } + using Field = Ui::InformationField; if (id == u"REQ_INFO_NAME_INVALID"_q) { - showEditError(Ui::EditField::Name); + showInformationError(Field::Name); } else if (id == u"REQ_INFO_EMAIL_INVALID"_q) { - showEditError(Ui::EditField::Email); + showInformationError(Field::Email); } else if (id == u"REQ_INFO_PHONE_INVALID"_q) { - showEditError(Ui::EditField::Phone); + showInformationError(Field::Phone); } else if (id == u"ADDRESS_STREET_LINE1_INVALID"_q) { - showEditError(Ui::EditField::ShippingStreet); + showInformationError(Field::ShippingStreet); } else if (id == u"ADDRESS_CITY_INVALID"_q) { - showEditError(Ui::EditField::ShippingCity); + showInformationError(Field::ShippingCity); } else if (id == u"ADDRESS_STATE_INVALID"_q) { - showEditError(Ui::EditField::ShippingState); + showInformationError(Field::ShippingState); } else if (id == u"ADDRESS_COUNTRY_INVALID"_q) { - showEditError(Ui::EditField::ShippingCountry); + showInformationError(Field::ShippingCountry); } else if (id == u"ADDRESS_POSTCODE_INVALID"_q) { - showEditError(Ui::EditField::ShippingPostcode); + showInformationError(Field::ShippingPostcode); } else if (id == u"SHIPPING_BOT_TIMEOUT"_q) { showToast({ "Error: Bot Timeout!" }); // #TODO payments errors message } else if (id == u"SHIPPING_NOT_AVAILABLE"_q) { @@ -238,6 +249,7 @@ void CheckoutProcess::panelSubmit() { || _submitState == SubmitState::Finishing) { return; } + const auto &native = _form->nativePayment(); const auto &invoice = _form->invoice(); const auto &options = _form->shippingOptions(); if (!options.list.empty() && options.selectedId.isEmpty()) { @@ -252,14 +264,23 @@ void CheckoutProcess::panelSubmit() { _submitState = SubmitState::Validation; _form->validateInformation(_form->savedInformation()); return; + } else if (native + && !native.newCredentials + && !native.savedCredentials) { + editPaymentMethod(); + return; } _submitState = SubmitState::Finishing; - _webviewWindow = std::make_unique( - webviewDataPath(), - _form->details().url, - panelDelegate()); - if (!_webviewWindow->shown()) { - // #TODO payments errors + 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); } } @@ -316,30 +337,82 @@ bool CheckoutProcess::panelWebviewNavigationAttempt(const QString &uri) { return false; } +void CheckoutProcess::panelEditPaymentMethod() { + if (_submitState != SubmitState::None + && _submitState != SubmitState::Validated) { + return; + } + editPaymentMethod(); +} + +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(); + } + })); +} + void CheckoutProcess::panelEditShippingInformation() { - showEditInformation(Ui::EditField::ShippingStreet); + showEditInformation(Ui::InformationField::ShippingStreet); } void CheckoutProcess::panelEditName() { - showEditInformation(Ui::EditField::Name); + showEditInformation(Ui::InformationField::Name); } void CheckoutProcess::panelEditEmail() { - showEditInformation(Ui::EditField::Email); + showEditInformation(Ui::InformationField::Email); } void CheckoutProcess::panelEditPhone() { - showEditInformation(Ui::EditField::Phone); + showEditInformation(Ui::InformationField::Phone); } void CheckoutProcess::showForm() { _panel->showForm( _form->invoice(), _form->savedInformation(), + _form->nativePayment().details, _form->shippingOptions()); } -void CheckoutProcess::showEditInformation(Ui::EditField field) { +void CheckoutProcess::showEditInformation(Ui::InformationField field) { if (_submitState != SubmitState::None) { return; } @@ -349,11 +422,11 @@ void CheckoutProcess::showEditInformation(Ui::EditField field) { field); } -void CheckoutProcess::showEditError(Ui::EditField field) { +void CheckoutProcess::showInformationError(Ui::InformationField field) { if (_submitState != SubmitState::None) { return; } - _panel->showEditError( + _panel->showInformationError( _form->invoice(), _form->savedInformation(), field); @@ -363,6 +436,10 @@ void CheckoutProcess::chooseShippingOption() { _panel->chooseShippingOption(_form->shippingOptions()); } +void CheckoutProcess::editPaymentMethod() { + _panel->choosePaymentMethod(_form->nativePayment().details); +} + void CheckoutProcess::panelChooseShippingOption() { if (_submitState != SubmitState::None) { return; diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index bd3210d14..3613ff232 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class HistoryItem; +namespace Stripe { +class APIClient; +} // namespace Stripe + namespace Main { class Session; } // namespace Main @@ -19,7 +23,7 @@ class Session; namespace Payments::Ui { class Panel; class WebviewWindow; -enum class EditField; +enum class InformationField; } // namespace Payments::Ui namespace Payments { @@ -57,9 +61,10 @@ private: void handleError(const Error &error); void showForm(); - void showEditInformation(Ui::EditField field); - void showEditError(Ui::EditField field); + void showEditInformation(Ui::InformationField field); + void showInformationError(Ui::InformationField field); void chooseShippingOption(); + void editPaymentMethod(); void performInitialSilentValidation(); [[nodiscard]] QString webviewDataPath() const; @@ -70,6 +75,7 @@ private: void panelWebviewMessage(const QJsonDocument &message) override; bool panelWebviewNavigationAttempt(const QString &uri) override; + void panelEditPaymentMethod() override; void panelEditShippingInformation() override; void panelEditName() override; void panelEditEmail() override; @@ -78,12 +84,14 @@ private: void panelChangeShippingOption(const QString &id) override; void panelValidateInformation(Ui::RequestedInformation data) override; + void panelValidateCard(Ui::UncheckedCardDetails data) override; void panelShowBox(object_ptr box) 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 3e1a06869..62f9b59cb 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "apiwrap.h" +#include +#include +#include + namespace Payments { namespace { @@ -101,7 +105,7 @@ void Form::processForm(const MTPDpayments_paymentForm &data) { processSavedCredentials(data); }); } - + fillNativePaymentInformation(); _updates.fire({ FormReady{} }); } @@ -152,10 +156,65 @@ void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) { void Form::processSavedCredentials( const MTPDpaymentSavedCredentialsCard &data) { - _savedCredentials = Ui::SavedCredentials{ - .id = qs(data.vid()), - .title = qs(data.vtitle()), + // #TODO payments not yet supported + //_nativePayment.savedCredentials = SavedCredentials{ + // .id = qs(data.vid()), + // .title = qs(data.vtitle()), + //}; + refreshNativePaymentDetails(); +} + +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::fillNativePaymentInformation() { + auto saved = std::move(_nativePayment.savedCredentials); + auto entered = std::move(_nativePayment.newCredentials); + _nativePayment = NativePayment(); + if (_details.nativeProvider != "stripe") { + return; + } + auto error = QJsonParseError(); + auto document = QJsonDocument::fromJson( + _details.nativeParamsJson, + &error); + if (error.error != QJsonParseError::NoError) { + LOG(("Payment Error: Could not decode native_params, error %1: %2" + ).arg(error.error + ).arg(error.errorString())); + return; + } else if (!document.isObject()) { + LOG(("Payment Error: Not an object in native_params.")); + return; + } + const auto object = document.object(); + const auto value = [&](QStringView key) { + return object.value(key); }; + const auto key = value(u"publishable_key").toString(); + if (key.isEmpty()) { + 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(), + }, + }; + refreshNativePaymentDetails(); } void Form::send(const QByteArray &serializedCredentials) { @@ -221,6 +280,13 @@ void Form::validateInformation(const Ui::RequestedInformation &information) { }).send(); } +void Form::setPaymentCredentials(const NewCredentials &credentials) { + Expects(!credentials.empty()); + + _nativePayment.newCredentials = credentials; + refreshNativePaymentDetails(); +} + void Form::setShippingOption(const QString &id) { _shippingOptions.selectedId = id; } diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 565340654..fbe4bd44f 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -33,6 +33,50 @@ struct FormDetails { } }; +struct SavedCredentials { + QString id; + QString title; + + [[nodiscard]] bool valid() const { + return !id.isEmpty(); + } + [[nodiscard]] explicit operator bool() const { + return valid(); + } +}; + +struct NewCredentials { + QString title; + QByteArray data; + bool saveOnServer = false; + + [[nodiscard]] bool empty() const { + return data.isEmpty(); + } + [[nodiscard]] explicit operator bool() const { + return !empty(); + } +}; + +struct NativePayment { + enum class Type { + None, + Stripe, + }; + Type type = Type::None; + QString stripePublishableKey; + SavedCredentials savedCredentials; + NewCredentials newCredentials; + Ui::NativePaymentDetails details; + + [[nodiscard]] bool valid() const { + return (type != Type::None); + } + [[nodiscard]] explicit operator bool() const { + return valid(); + } +}; + struct FormReady {}; struct ValidateFinished {}; struct Error { @@ -73,8 +117,8 @@ public: [[nodiscard]] const Ui::RequestedInformation &savedInformation() const { return _savedInformation; } - [[nodiscard]] const Ui::SavedCredentials &savedCredentials() const { - return _savedCredentials; + [[nodiscard]] const NativePayment &nativePayment() const { + return _nativePayment; } [[nodiscard]] const Ui::ShippingOptions &shippingOptions() const { return _shippingOptions; @@ -85,6 +129,7 @@ public: } void validateInformation(const Ui::RequestedInformation &information); + void setPaymentCredentials(const NewCredentials &credentials); void setShippingOption(const QString &id); void send(const QByteArray &serializedCredentials); @@ -97,6 +142,8 @@ private: void processSavedCredentials( const MTPDpaymentSavedCredentialsCard &data); void processShippingOptions(const QVector &data); + void fillNativePaymentInformation(); + void refreshNativePaymentDetails(); const not_null _session; MTP::Sender _api; @@ -105,7 +152,7 @@ private: Ui::Invoice _invoice; FormDetails _details; Ui::RequestedInformation _savedInformation; - Ui::SavedCredentials _savedCredentials; + NativePayment _nativePayment; Ui::RequestedInformation _validatedInformation; mtpRequestId _validateRequestId = 0; diff --git a/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp b/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp index 5189fa506..798741c96 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp +++ b/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp @@ -65,7 +65,7 @@ void APIClient::createTokenWithCard( CardParams card, TokenCompletionCallback completion) { createTokenWithData( - FormEncoder::formEncodedDataForObject(card), + FormEncoder::formEncodedDataForObject(MakeEncodable(card)), std::move(completion)); } diff --git a/Telegram/SourceFiles/payments/stripe/stripe_card.cpp b/Telegram/SourceFiles/payments/stripe/stripe_card.cpp index af37edb9c..ca2c864a9 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_card.cpp +++ b/Telegram/SourceFiles/payments/stripe/stripe_card.cpp @@ -10,25 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "stripe/stripe_decode.h" namespace Stripe { +namespace { -Card::Card( - QString id, - QString last4, - CardBrand brand, - quint32 expMonth, - quint32 expYear) -: _cardId(id) -, _last4(last4) -, _brand(brand) -, _expMonth(expMonth) -, _expYear(expYear) { -} - -Card Card::Empty() { - return Card(QString(), QString(), CardBrand::Unknown, 0, 0); -} - -[[nodiscard]] CardBrand BrandFromString(const QString &brand) { +CardBrand BrandFromString(const QString &brand) { if (brand == "visa") { return CardBrand::Visa; } else if (brand == "american express") { @@ -46,7 +30,7 @@ Card Card::Empty() { } } -[[nodiscard]] CardFundingType FundingFromString(const QString &funding) { +CardFundingType FundingFromString(const QString &funding) { if (funding == "credit") { return CardFundingType::Credit; } else if (funding == "debit") { @@ -58,6 +42,25 @@ Card Card::Empty() { } } +} // namespace + +Card::Card( + QString id, + QString last4, + CardBrand brand, + quint32 expMonth, + quint32 expYear) +: _cardId(id) +, _last4(last4) +, _brand(brand) +, _expMonth(expMonth) +, _expYear(expYear) { +} + +Card Card::Empty() { + return Card(QString(), QString(), CardBrand::Unknown, 0, 0); +} + Card Card::DecodedObjectFromAPIResponse(QJsonObject object) { if (!ContainsFields(object, { u"id", @@ -80,7 +83,7 @@ Card Card::DecodedObjectFromAPIResponse(QJsonObject object) { auto result = Card(cardId, last4, brand, expMonth, expYear); result._name = string(u"name"); result._dynamicLast4 = string(u"dynamic_last4"); - result._funding = FundingFromString(string(u"funding")); + result._funding = FundingFromString(string(u"funding").toLower()); result._fingerprint = string(u"fingerprint"); result._country = string(u"country"); result._currency = string(u"currency"); @@ -97,6 +100,74 @@ Card Card::DecodedObjectFromAPIResponse(QJsonObject object) { return result; } +QString Card::cardId() const { + return _cardId; +} + +QString Card::name() const { + return _name; +} + +QString Card::last4() const { + return _last4; +} + +QString Card::dynamicLast4() const { + return _dynamicLast4; +} + +CardBrand Card::brand() const { + return _brand; +} + +CardFundingType Card::funding() const { + return _funding; +} + +QString Card::fingerprint() const { + return _fingerprint; +} + +QString Card::country() const { + return _country; +} + +QString Card::currency() const { + return _currency; +} + +quint32 Card::expMonth() const { + return _expMonth; +} + +quint32 Card::expYear() const { + return _expYear; +} + +QString Card::addressLine1() const { + return _addressLine1; +} + +QString Card::addressLine2() const { + return _addressLine2; +} + +QString Card::addressCity() const { + return _addressCity; +} + +QString Card::addressState() const { + return _addressState; +} + +QString Card::addressZip() const { + return _addressZip; +} + +QString Card::addressCountry() const { + return _addressCountry; +} + bool Card::empty() const { return _cardId.isEmpty(); } diff --git a/Telegram/SourceFiles/payments/stripe/stripe_card.h b/Telegram/SourceFiles/payments/stripe/stripe_card.h index d7cb63627..0b9059262 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_card.h +++ b/Telegram/SourceFiles/payments/stripe/stripe_card.h @@ -42,6 +42,24 @@ public: [[nodiscard]] static Card DecodedObjectFromAPIResponse( QJsonObject object); + [[nodiscard]] QString cardId() const; + [[nodiscard]] QString name() const; + [[nodiscard]] QString last4() const; + [[nodiscard]] QString dynamicLast4() const; + [[nodiscard]] CardBrand brand() const; + [[nodiscard]] CardFundingType funding() const; + [[nodiscard]] QString fingerprint() const; + [[nodiscard]] QString country() const; + [[nodiscard]] QString currency() const; + [[nodiscard]] quint32 expMonth() const; + [[nodiscard]] quint32 expYear() const; + [[nodiscard]] QString addressLine1() const; + [[nodiscard]] QString addressLine2() const; + [[nodiscard]] QString addressCity() const; + [[nodiscard]] QString addressState() const; + [[nodiscard]] QString addressZip() const; + [[nodiscard]] QString addressCountry() const; + [[nodiscard]] bool empty() const; [[nodiscard]] explicit operator bool() const { return !empty(); diff --git a/Telegram/SourceFiles/payments/stripe/stripe_card_params.cpp b/Telegram/SourceFiles/payments/stripe/stripe_card_params.cpp index 06894fe87..81b72c4e0 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_card_params.cpp +++ b/Telegram/SourceFiles/payments/stripe/stripe_card_params.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Stripe { -QString CardParams::RootObjectName() const { +QString CardParams::rootObjectName() { return "card"; } diff --git a/Telegram/SourceFiles/payments/stripe/stripe_card_params.h b/Telegram/SourceFiles/payments/stripe/stripe_card_params.h index 811ab67a8..d107dc57f 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_card_params.h +++ b/Telegram/SourceFiles/payments/stripe/stripe_card_params.h @@ -11,11 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Stripe { -class CardParams final : public FormEncodable { -public: - QString RootObjectName() const override; - std::map formFieldValues() const override; - +struct CardParams { QString number; quint32 expMonth = 0; quint32 expYear = 0; @@ -28,6 +24,9 @@ public: QString addressZip; QString addressCountry; QString currency; + + [[nodiscard]] static QString rootObjectName(); + [[nodiscard]] std::map formFieldValues() const; }; } // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_form_encodable.h b/Telegram/SourceFiles/payments/stripe/stripe_form_encodable.h index 1a09d69b1..7cae5e250 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_form_encodable.h +++ b/Telegram/SourceFiles/payments/stripe/stripe_form_encodable.h @@ -14,11 +14,26 @@ namespace Stripe { class FormEncodable { public: - [[nodiscard]] virtual QString RootObjectName() const = 0; + [[nodiscard]] virtual QString rootObjectName() = 0; + [[nodiscard]] virtual std::map formFieldValues() = 0; +}; + +template +struct MakeEncodable final : FormEncodable { +public: + MakeEncodable(const T &value) : _value(value) { + } + + QString rootObjectName() override { + return _value.rootObjectName(); + } + std::map formFieldValues() override { + return _value.formFieldValues(); + } + +private: + const T &_value; - // TODO incomplete, not used: nested complex structures not supported. - [[nodiscard]] virtual std::map formFieldValues() const - = 0; }; } // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.cpp b/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.cpp index 8ce752108..a60425076 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.cpp +++ b/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.cpp @@ -13,8 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Stripe { QByteArray FormEncoder::formEncodedDataForObject( - FormEncodable &object) { - const auto root = object.RootObjectName(); + FormEncodable &&object) { + const auto root = object.rootObjectName(); const auto values = object.formFieldValues(); auto result = QByteArray(); auto keys = std::vector(); diff --git a/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.h b/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.h index 798de928c..79e28471d 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.h +++ b/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.h @@ -14,7 +14,7 @@ namespace Stripe { class FormEncoder { public: [[nodiscard]] static QByteArray formEncodedDataForObject( - FormEncodable &object); + FormEncodable &&object); }; diff --git a/Telegram/SourceFiles/payments/stripe/stripe_payment_configuration.h b/Telegram/SourceFiles/payments/stripe/stripe_payment_configuration.h index f2b2867ae..a42e7921a 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_payment_configuration.h +++ b/Telegram/SourceFiles/payments/stripe/stripe_payment_configuration.h @@ -16,8 +16,11 @@ namespace Stripe { struct PaymentConfiguration { QString publishableKey; // PaymentMethodType additionalPaymentMethods; // Apply Pay - BillingAddressFields requiredBillingAddressFields - = BillingAddressFields::None; + + // TODO incomplete, not used. + //BillingAddressFields requiredBillingAddressFields + // = BillingAddressFields::None; + QString companyName; // QString appleMerchantIdentifier; // Apple Pay // bool smsAutofillDisabled = true; // Mobile only diff --git a/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp b/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp new file mode 100644 index 000000000..e9c497336 --- /dev/null +++ b/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp @@ -0,0 +1,245 @@ +/* +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_edit_card.h" + +#include "payments/ui/payments_panel_delegate.h" +#include "passport/ui/passport_details_row.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/wrap/fade_wrap.h" +#include "lang/lang_keys.h" +#include "styles/style_payments.h" +#include "styles/style_passport.h" + +namespace Payments::Ui { +namespace { + +constexpr auto kMaxPostcodeSize = 10; + +[[nodiscard]] uint32 ExtractYear(const QString &value) { + return value.split('/').value(1).toInt() + 2000; +} + +[[nodiscard]] uint32 ExtractMonth(const QString &value) { + return value.split('/').value(0).toInt(); +} + +} // namespace + +EditCard::EditCard( + QWidget *parent, + const NativePaymentDetails &native, + CardField field, + not_null delegate) +: _delegate(delegate) +, _native(native) +, _scroll(this, st::passportPanelScroll) +, _topShadow(this) +, _bottomShadow(this) +, _done( + this, + tr::lng_about_done(), + st::passportPanelSaveValue) { + setupControls(); +} + +void EditCard::setFocus(CardField field) { + _focusField = field; + if (const auto control = controlForField(field)) { + _scroll->ensureWidgetVisible(control); + control->setFocusFast(); + } +} + +void EditCard::showError(CardField field) { + if (const auto control = controlForField(field)) { + _scroll->ensureWidgetVisible(control); + control->showError(QString()); + } +} + +void EditCard::setupControls() { + const auto inner = setupContent(); + + _done->addClickHandler([=] { + _delegate->panelValidateCard(collect()); + }); + + using namespace rpl::mappers; + + _topShadow->toggleOn( + _scroll->scrollTopValue() | rpl::map(_1 > 0)); + _bottomShadow->toggleOn(rpl::combine( + _scroll->scrollTopValue(), + _scroll->heightValue(), + inner->heightValue(), + _1 + _2 < _3)); +} + +not_null EditCard::setupContent() { + const auto inner = _scroll->setOwnedWidget( + object_ptr(this)); + + _scroll->widthValue( + ) | rpl::start_with_next([=](int width) { + inner->resizeToWidth(width); + }, inner->lifetime()); + + const auto showBox = [=](object_ptr box) { + _delegate->panelShowBox(std::move(box)); + }; + using Type = Passport::Ui::PanelDetailsType; + auto maxLabelWidth = 0; + accumulate_max( + maxLabelWidth, + Row::LabelWidth("Card Number")); + accumulate_max( + maxLabelWidth, + Row::LabelWidth("CVC")); + accumulate_max( + maxLabelWidth, + Row::LabelWidth("MM/YY")); + if (_native.needCardholderName) { + accumulate_max( + maxLabelWidth, + Row::LabelWidth("Cardholder Name")); + } + if (_native.needCountry) { + accumulate_max( + maxLabelWidth, + Row::LabelWidth("Billing Country")); + } + if (_native.needZip) { + accumulate_max( + maxLabelWidth, + Row::LabelWidth("Billing Zip")); + } + _number = inner->add( + Row::Create( + inner, + showBox, + QString(), + Type::Text, + "Card Number", + maxLabelWidth, + QString(), + QString(), + 1024)); + _cvc = inner->add( + Row::Create( + inner, + showBox, + QString(), + Type::Text, + "CVC", + maxLabelWidth, + QString(), + QString(), + 1024)); + _expire = inner->add( + Row::Create( + inner, + showBox, + QString(), + Type::Text, + "MM/YY", + maxLabelWidth, + QString(), + QString(), + 1024)); + if (_native.needCardholderName) { + _name = inner->add( + Row::Create( + inner, + showBox, + QString(), + Type::Text, + "Cardholder Name", + maxLabelWidth, + QString(), + QString(), + 1024)); + } + if (_native.needCountry) { + _country = inner->add( + Row::Create( + inner, + showBox, + QString(), + Type::Country, + "Billing Country", + maxLabelWidth, + QString(), + QString())); + } + if (_native.needZip) { + _zip = inner->add( + Row::Create( + inner, + showBox, + QString(), + Type::Postcode, + "Billing Zip Code", + maxLabelWidth, + QString(), + QString(), + kMaxPostcodeSize)); + } + return inner; +} + +void EditCard::resizeEvent(QResizeEvent *e) { + updateControlsGeometry(); +} + +void EditCard::focusInEvent(QFocusEvent *e) { + if (const auto control = controlForField(_focusField)) { + control->setFocusFast(); + } +} + +void EditCard::updateControlsGeometry() { + const auto submitTop = height() - _done->height(); + _scroll->setGeometry(0, 0, width(), submitTop); + _topShadow->resizeToWidth(width()); + _topShadow->moveToLeft(0, 0); + _bottomShadow->resizeToWidth(width()); + _bottomShadow->moveToLeft(0, submitTop - st::lineWidth); + _done->setFullWidth(width()); + _done->moveToLeft(0, submitTop); + + _scroll->updateBars(); +} + +auto EditCard::controlForField(CardField field) const -> Row* { + switch (field) { + case CardField::Number: return _number; + case CardField::CVC: return _cvc; + case CardField::ExpireDate: return _expire; + case CardField::Name: return _name; + case CardField::AddressCountry: return _country; + case CardField::AddressZip: return _zip; + } + Unexpected("Unknown field in EditCard::controlForField."); +} + +UncheckedCardDetails EditCard::collect() const { + return { + .number = _number ? _number->valueCurrent() : QString(), + .cvc = _cvc ? _cvc->valueCurrent() : QString(), + .expireYear = _expire ? ExtractYear(_expire->valueCurrent()) : 0, + .expireMonth = _expire ? ExtractMonth(_expire->valueCurrent()) : 0, + .cardholderName = _name ? _name->valueCurrent() : QString(), + .addressCountry = _country ? _country->valueCurrent() : QString(), + .addressZip = _zip ? _zip->valueCurrent() : QString(), + }; +} + +} // namespace Payments::Ui diff --git a/Telegram/SourceFiles/payments/ui/payments_edit_card.h b/Telegram/SourceFiles/payments/ui/payments_edit_card.h new file mode 100644 index 000000000..fc77be6d6 --- /dev/null +++ b/Telegram/SourceFiles/payments/ui/payments_edit_card.h @@ -0,0 +1,73 @@ +/* +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/rp_widget.h" +#include "payments/ui/payments_panel_data.h" +#include "base/object_ptr.h" + +namespace Ui { +class ScrollArea; +class FadeShadow; +class RoundButton; +} // namespace Ui + +namespace Passport::Ui { +class PanelDetailsRow; +} // namespace Passport::Ui + +namespace Payments::Ui { + +using namespace ::Ui; + +class PanelDelegate; + +class EditCard final : public RpWidget { +public: + EditCard( + QWidget *parent, + const NativePaymentDetails &native, + CardField field, + not_null delegate); + + void showError(CardField field); + void setFocus(CardField field); + +private: + using Row = Passport::Ui::PanelDetailsRow; + + void resizeEvent(QResizeEvent *e) override; + void focusInEvent(QFocusEvent *e) override; + + void setupControls(); + [[nodiscard]] not_null setupContent(); + void updateControlsGeometry(); + [[nodiscard]] Row *controlForField(CardField field) const; + + [[nodiscard]] UncheckedCardDetails collect() const; + + const not_null _delegate; + NativePaymentDetails _native; + + object_ptr _scroll; + object_ptr _topShadow; + object_ptr _bottomShadow; + object_ptr _done; + + Row *_number = nullptr; + Row *_cvc = nullptr; + Row *_expire = nullptr; + Row *_name = nullptr; + Row *_country = nullptr; + Row *_zip = nullptr; + + CardField _focusField = CardField::Number; + +}; + +} // namespace Payments::Ui diff --git a/Telegram/SourceFiles/payments/ui/payments_edit_information.cpp b/Telegram/SourceFiles/payments/ui/payments_edit_information.cpp index 100a873d0..eff78b29b 100644 --- a/Telegram/SourceFiles/payments/ui/payments_edit_information.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_edit_information.cpp @@ -33,7 +33,7 @@ EditInformation::EditInformation( QWidget *parent, const Invoice &invoice, const RequestedInformation ¤t, - EditField field, + InformationField field, not_null delegate) : _delegate(delegate) , _invoice(invoice) @@ -48,7 +48,7 @@ EditInformation::EditInformation( setupControls(); } -void EditInformation::setFocus(EditField field) { +void EditInformation::setFocus(InformationField field) { _focusField = field; if (const auto control = controlForField(field)) { _scroll->ensureWidgetVisible(control); @@ -56,7 +56,7 @@ void EditInformation::setFocus(EditField field) { } } -void EditInformation::showError(EditField field) { +void EditInformation::showError(InformationField field) { if (const auto control = controlForField(field)) { _scroll->ensureWidgetVisible(control); control->showError(QString()); @@ -264,16 +264,16 @@ void EditInformation::updateControlsGeometry() { _scroll->updateBars(); } -auto EditInformation::controlForField(EditField field) const -> Row* { +auto EditInformation::controlForField(InformationField field) const -> Row* { switch (field) { - case EditField::ShippingStreet: return _street1; - case EditField::ShippingCity: return _city; - case EditField::ShippingState: return _state; - case EditField::ShippingCountry: return _country; - case EditField::ShippingPostcode: return _postcode; - case EditField::Name: return _name; - case EditField::Email: return _email; - case EditField::Phone: return _phone; + case InformationField::ShippingStreet: return _street1; + case InformationField::ShippingCity: return _city; + case InformationField::ShippingState: return _state; + case InformationField::ShippingCountry: return _country; + case InformationField::ShippingPostcode: return _postcode; + case InformationField::Name: return _name; + case InformationField::Email: return _email; + case InformationField::Phone: return _phone; } Unexpected("Unknown field in EditInformation::controlForField."); } diff --git a/Telegram/SourceFiles/payments/ui/payments_edit_information.h b/Telegram/SourceFiles/payments/ui/payments_edit_information.h index 8fb434074..d4f8ee78e 100644 --- a/Telegram/SourceFiles/payments/ui/payments_edit_information.h +++ b/Telegram/SourceFiles/payments/ui/payments_edit_information.h @@ -33,11 +33,11 @@ public: QWidget *parent, const Invoice &invoice, const RequestedInformation ¤t, - EditField field, + InformationField field, not_null delegate); - void showError(EditField field); - void setFocus(EditField field); + void showError(InformationField field); + void setFocus(InformationField field); private: using Row = Passport::Ui::PanelDetailsRow; @@ -48,7 +48,7 @@ private: void setupControls(); [[nodiscard]] not_null setupContent(); void updateControlsGeometry(); - [[nodiscard]] Row *controlForField(EditField field) const; + [[nodiscard]] Row *controlForField(InformationField field) const; [[nodiscard]] RequestedInformation collect() const; @@ -71,7 +71,7 @@ private: Row *_email = nullptr; Row *_phone = nullptr; - EditField _focusField = EditField::ShippingStreet; + InformationField _focusField = InformationField::ShippingStreet; }; diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp index 41d44b4ee..0243f0acf 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp @@ -30,10 +30,12 @@ FormSummary::FormSummary( QWidget *parent, const Invoice &invoice, const RequestedInformation ¤t, + const NativePaymentDetails &native, const ShippingOptions &options, not_null delegate) : _delegate(delegate) , _invoice(invoice) +, _native(native) , _options(options) , _information(current) , _scroll(this, st::passportPanelScroll) @@ -134,6 +136,20 @@ 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); + } if (_invoice.isShippingAddressRequested) { const auto info = inner->add(object_ptr(inner)); info->addClickHandler([=] { @@ -153,7 +169,9 @@ not_null FormSummary::setupContent() { push(_information.shippingAddress.postcode); info->updateContent( tr::lng_payments_shipping_address(tr::now), - (list.isEmpty() ? "enter pls" : list.join(", ")), + (list.isEmpty() + ? tr::lng_payments_shipping_address_ph(tr::now) + : list.join(", ")), !list.isEmpty(), false, anim::type::instant); @@ -167,7 +185,7 @@ not_null FormSummary::setupContent() { tr::lng_payments_shipping_method(tr::now), (selected != end(_options.list) ? selected->title - : "enter pls"), + : tr::lng_payments_shipping_method_ph(tr::now)), (selected != end(_options.list)), false, anim::type::instant); @@ -178,7 +196,7 @@ not_null FormSummary::setupContent() { name->updateContent( tr::lng_payments_info_name(tr::now), (_information.name.isEmpty() - ? "enter pls" + ? tr::lng_payments_info_name_ph(tr::now) : _information.name), !_information.name.isEmpty(), false, @@ -190,7 +208,7 @@ not_null FormSummary::setupContent() { email->updateContent( tr::lng_payments_info_email(tr::now), (_information.email.isEmpty() - ? "enter pls" + ? tr::lng_payments_info_email_ph(tr::now) : _information.email), !_information.email.isEmpty(), false, @@ -202,7 +220,7 @@ not_null FormSummary::setupContent() { phone->updateContent( tr::lng_payments_info_phone(tr::now), (_information.phone.isEmpty() - ? "enter pls" + ? tr::lng_payments_info_phone_ph(tr::now) : _information.phone), !_information.phone.isEmpty(), false, diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.h b/Telegram/SourceFiles/payments/ui/payments_form_summary.h index 32dd89bc7..60c7a5538 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.h +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.h @@ -29,6 +29,7 @@ public: QWidget *parent, const Invoice &invoice, const RequestedInformation ¤t, + const NativePaymentDetails &native, const ShippingOptions &options, not_null delegate); @@ -44,6 +45,7 @@ private: const not_null _delegate; Invoice _invoice; + NativePaymentDetails _native; 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 63d9ba40b..6f98b2b82 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/ui/payments_form_summary.h" #include "payments/ui/payments_edit_information.h" +#include "payments/ui/payments_edit_card.h" #include "payments/ui/payments_panel_delegate.h" #include "ui/widgets/separate_panel.h" #include "ui/boxes/single_choice_box.h" @@ -45,12 +46,14 @@ void Panel::requestActivate() { void Panel::showForm( const Invoice &invoice, const RequestedInformation ¤t, + const NativePaymentDetails &native, const ShippingOptions &options) { _widget->showInner( base::make_unique_q( _widget.get(), invoice, current, + native, options, _delegate)); _widget->setBackAllowed(false); @@ -59,29 +62,30 @@ void Panel::showForm( void Panel::showEditInformation( const Invoice &invoice, const RequestedInformation ¤t, - EditField field) { + InformationField field) { auto edit = base::make_unique_q( _widget.get(), invoice, current, field, _delegate); - _weakEditWidget = edit.get(); + _weakEditInformation = edit.get(); _widget->showInner(std::move(edit)); _widget->setBackAllowed(true); - _weakEditWidget->setFocus(field); + _weakEditInformation->setFocus(field); } -void Panel::showEditError( +void Panel::showInformationError( const Invoice &invoice, const RequestedInformation ¤t, - EditField field) { - if (_weakEditWidget) { - _weakEditWidget->showError(field); + InformationField field) { + if (_weakEditInformation) { + _weakEditInformation->showError(field); } else { showEditInformation(invoice, current, field); - if (_weakEditWidget && field == EditField::ShippingCountry) { - _weakEditWidget->showError(field); + if (_weakEditInformation + && field == InformationField::ShippingCountry) { + _weakEditInformation->showError(field); } } } @@ -109,6 +113,57 @@ void Panel::chooseShippingOption(const ShippingOptions &options) { })); } +void Panel::choosePaymentMethod(const NativePaymentDetails &native) { + Expects(native.supported); + + if (!native.ready) { + showEditCard(native, CardField::Number); + return; + } + const auto title = native.credentialsTitle; + showBox(Box([=](not_null box) { + const auto save = [=](int option) { + if (option) { + showEditCard(native, CardField::Number); + } + }; + SingleChoiceBox(box, { + .title = tr::lng_payments_payment_method(), + .options = { native.credentialsTitle, "New Card..." }, // #TODO payments lang + .initialSelection = 0, + .callback = save, + }); + })); +} + +void Panel::showEditCard( + const NativePaymentDetails &native, + CardField field) { + auto edit = base::make_unique_q( + _widget.get(), + native, + field, + _delegate); + _weakEditCard = edit.get(); + _widget->showInner(std::move(edit)); + _widget->setBackAllowed(true); + _weakEditCard->setFocus(field); +} + +void Panel::showCardError( + const NativePaymentDetails &native, + CardField field) { + if (_weakEditCard) { + _weakEditCard->showError(field); + } else { + showEditCard(native, field); + if (_weakEditCard + && field == CardField::AddressCountry) { + _weakEditCard->showError(field); + } + } +} + rpl::producer<> Panel::backRequests() const { return _widget->backRequests(); } diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.h b/Telegram/SourceFiles/payments/ui/payments_panel.h index 9cd852731..6d7202591 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel.h @@ -22,8 +22,11 @@ class PanelDelegate; struct Invoice; struct RequestedInformation; struct ShippingOptions; -enum class EditField; +enum class InformationField; +enum class CardField; class EditInformation; +class EditCard; +struct NativePaymentDetails; class Panel final { public: @@ -35,16 +38,24 @@ public: void showForm( const Invoice &invoice, const RequestedInformation ¤t, + const NativePaymentDetails &native, const ShippingOptions &options); void showEditInformation( const Invoice &invoice, const RequestedInformation ¤t, - EditField field); - void showEditError( + InformationField field); + void showInformationError( const Invoice &invoice, const RequestedInformation ¤t, - EditField field); + InformationField field); + void showEditCard( + const NativePaymentDetails &native, + CardField field); + void showCardError( + const NativePaymentDetails &native, + CardField field); void chooseShippingOption(const ShippingOptions &options); + void choosePaymentMethod(const NativePaymentDetails &native); [[nodiscard]] rpl::producer<> backRequests() const; @@ -56,7 +67,8 @@ public: private: const not_null _delegate; std::unique_ptr _widget; - QPointer _weakEditWidget; + 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 0ab3c0d23..fba8e2dc1 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_data.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_data.h @@ -104,19 +104,7 @@ struct RequestedInformation { } }; -struct SavedCredentials { - QString id; - QString title; - - [[nodiscard]] bool valid() const { - return !id.isEmpty(); - } - [[nodiscard]] explicit operator bool() const { - return valid(); - } -}; - -enum class EditField { +enum class InformationField { ShippingStreet, ShippingCity, ShippingState, @@ -127,4 +115,32 @@ enum class EditField { Phone, }; +struct NativePaymentDetails { + QString credentialsTitle; + bool ready = false; + bool supported = false; + bool needCountry = false; + bool needZip = false; + bool needCardholderName = false; +}; + +enum class CardField { + Number, + CVC, + ExpireDate, + Name, + AddressCountry, + AddressZip, +}; + +struct UncheckedCardDetails { + QString number; + QString cvc; + uint32 expireYear = 0; + uint32 expireMonth = 0; + QString cardholderName; + QString addressCountry; + QString addressZip; +}; + } // namespace Payments::Ui diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h index f737dc2b4..9d50d481e 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h @@ -21,6 +21,7 @@ namespace Payments::Ui { using namespace ::Ui; struct RequestedInformation; +struct UncheckedCardDetails; class PanelDelegate { public: @@ -30,6 +31,7 @@ public: virtual void panelWebviewMessage(const QJsonDocument &message) = 0; virtual bool panelWebviewNavigationAttempt(const QString &uri) = 0; + virtual void panelEditPaymentMethod() = 0; virtual void panelEditShippingInformation() = 0; virtual void panelEditName() = 0; virtual void panelEditEmail() = 0; @@ -38,6 +40,7 @@ public: virtual void panelChangeShippingOption(const QString &id) = 0; virtual void panelValidateInformation(RequestedInformation data) = 0; + virtual void panelValidateCard(Ui::UncheckedCardDetails data) = 0; virtual void panelShowBox(object_ptr box) = 0; }; diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index de3044487..6545b0f0e 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -69,6 +69,8 @@ PRIVATE passport/ui/passport_form_row.cpp passport/ui/passport_form_row.h + payments/ui/payments_edit_card.cpp + payments/ui/payments_edit_card.h payments/ui/payments_edit_information.cpp payments/ui/payments_edit_information.h payments/ui/payments_form_summary.cpp