Support entering card details natively.
This commit is contained in:
parent
5bc6e6533f
commit
5e4bc200c2
25 changed files with 859 additions and 123 deletions
|
@ -1863,20 +1863,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_payments_checkout_title" = "Checkout";
|
"lng_payments_checkout_title" = "Checkout";
|
||||||
"lng_payments_total_label" = "Total";
|
"lng_payments_total_label" = "Total";
|
||||||
"lng_payments_pay_amount" = "Pay {amount}";
|
"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" = "Shipping Information";
|
||||||
|
"lng_payments_shipping_address_ph" = "Enter your shipping information";
|
||||||
"lng_payments_shipping_method" = "Shipping Method";
|
"lng_payments_shipping_method" = "Shipping Method";
|
||||||
|
"lng_payments_shipping_method_ph" = "Choose your shipping method";
|
||||||
"lng_payments_info_name" = "Name";
|
"lng_payments_info_name" = "Name";
|
||||||
|
"lng_payments_info_name_ph" = "Enter your name";
|
||||||
"lng_payments_info_email" = "Email";
|
"lng_payments_info_email" = "Email";
|
||||||
|
"lng_payments_info_email_ph" = "Enter your email";
|
||||||
"lng_payments_info_phone" = "Phone";
|
"lng_payments_info_phone" = "Phone";
|
||||||
|
"lng_payments_info_phone_ph" = "Enter your phone number";
|
||||||
"lng_payments_shipping_address_title" = "Shipping Address";
|
"lng_payments_shipping_address_title" = "Shipping Address";
|
||||||
"lng_payments_save_shipping_about" = "You can save your shipping information for future use.";
|
"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_payment_card" = "Payment Card";
|
||||||
//"lng_payments_cardholder_title" = "Cardholder";
|
"lng_payments_cardholder_title" = "Cardholder";
|
||||||
//"lng_payments_cardholder_about" = "Cardholder Name";
|
"lng_payments_cardholder_about" = "Cardholder Name";
|
||||||
//"lng_payments_billing_address" = "Billing Address";
|
"lng_payments_billing_address" = "Billing Address";
|
||||||
//"lng_payments_zip_code" = "Zip Code";
|
"lng_payments_zip_code" = "Zip Code";
|
||||||
//"lng_payments_save_payment_about" = "You can save your payment information for future use.";
|
"lng_payments_save_payment_about" = "You can save your payment information for future use.";
|
||||||
"lng_payments_save_information" = "Save Information";
|
"lng_payments_save_information" = "Save Information";
|
||||||
|
|
||||||
"lng_call_status_incoming" = "is calling you...";
|
"lng_call_status_incoming" = "is calling you...";
|
||||||
|
|
|
@ -18,6 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "core/local_url_handlers.h" // TryConvertUrlToLocal.
|
#include "core/local_url_handlers.h" // TryConvertUrlToLocal.
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "stripe/stripe_api_client.h"
|
||||||
|
#include "stripe/stripe_error.h"
|
||||||
|
#include "stripe/stripe_token.h"
|
||||||
|
|
||||||
// #TODO payments errors
|
// #TODO payments errors
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
@ -54,6 +57,13 @@ base::flat_map<not_null<Main::Session*>, SessionProcesses> Processes;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QString CardTitle(const Stripe::Card &card) {
|
||||||
|
// Like server stores saved_credentials title.
|
||||||
|
return Stripe::CardBrandToString(card.brand()).toLower()
|
||||||
|
+ " *"
|
||||||
|
+ card.last4();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void CheckoutProcess::Start(not_null<const HistoryItem*> item) {
|
void CheckoutProcess::Start(not_null<const HistoryItem*> item) {
|
||||||
|
@ -167,22 +177,23 @@ void CheckoutProcess::handleError(const Error &error) {
|
||||||
showForm();
|
showForm();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
using Field = Ui::InformationField;
|
||||||
if (id == u"REQ_INFO_NAME_INVALID"_q) {
|
if (id == u"REQ_INFO_NAME_INVALID"_q) {
|
||||||
showEditError(Ui::EditField::Name);
|
showInformationError(Field::Name);
|
||||||
} else if (id == u"REQ_INFO_EMAIL_INVALID"_q) {
|
} 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) {
|
} 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) {
|
} else if (id == u"ADDRESS_STREET_LINE1_INVALID"_q) {
|
||||||
showEditError(Ui::EditField::ShippingStreet);
|
showInformationError(Field::ShippingStreet);
|
||||||
} else if (id == u"ADDRESS_CITY_INVALID"_q) {
|
} else if (id == u"ADDRESS_CITY_INVALID"_q) {
|
||||||
showEditError(Ui::EditField::ShippingCity);
|
showInformationError(Field::ShippingCity);
|
||||||
} else if (id == u"ADDRESS_STATE_INVALID"_q) {
|
} else if (id == u"ADDRESS_STATE_INVALID"_q) {
|
||||||
showEditError(Ui::EditField::ShippingState);
|
showInformationError(Field::ShippingState);
|
||||||
} else if (id == u"ADDRESS_COUNTRY_INVALID"_q) {
|
} else if (id == u"ADDRESS_COUNTRY_INVALID"_q) {
|
||||||
showEditError(Ui::EditField::ShippingCountry);
|
showInformationError(Field::ShippingCountry);
|
||||||
} else if (id == u"ADDRESS_POSTCODE_INVALID"_q) {
|
} else if (id == u"ADDRESS_POSTCODE_INVALID"_q) {
|
||||||
showEditError(Ui::EditField::ShippingPostcode);
|
showInformationError(Field::ShippingPostcode);
|
||||||
} else if (id == u"SHIPPING_BOT_TIMEOUT"_q) {
|
} else if (id == u"SHIPPING_BOT_TIMEOUT"_q) {
|
||||||
showToast({ "Error: Bot Timeout!" }); // #TODO payments errors message
|
showToast({ "Error: Bot Timeout!" }); // #TODO payments errors message
|
||||||
} else if (id == u"SHIPPING_NOT_AVAILABLE"_q) {
|
} else if (id == u"SHIPPING_NOT_AVAILABLE"_q) {
|
||||||
|
@ -238,6 +249,7 @@ void CheckoutProcess::panelSubmit() {
|
||||||
|| _submitState == SubmitState::Finishing) {
|
|| _submitState == SubmitState::Finishing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const auto &native = _form->nativePayment();
|
||||||
const auto &invoice = _form->invoice();
|
const auto &invoice = _form->invoice();
|
||||||
const auto &options = _form->shippingOptions();
|
const auto &options = _form->shippingOptions();
|
||||||
if (!options.list.empty() && options.selectedId.isEmpty()) {
|
if (!options.list.empty() && options.selectedId.isEmpty()) {
|
||||||
|
@ -252,14 +264,23 @@ void CheckoutProcess::panelSubmit() {
|
||||||
_submitState = SubmitState::Validation;
|
_submitState = SubmitState::Validation;
|
||||||
_form->validateInformation(_form->savedInformation());
|
_form->validateInformation(_form->savedInformation());
|
||||||
return;
|
return;
|
||||||
|
} else if (native
|
||||||
|
&& !native.newCredentials
|
||||||
|
&& !native.savedCredentials) {
|
||||||
|
editPaymentMethod();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
_submitState = SubmitState::Finishing;
|
_submitState = SubmitState::Finishing;
|
||||||
_webviewWindow = std::make_unique<Ui::WebviewWindow>(
|
if (!native) {
|
||||||
webviewDataPath(),
|
_webviewWindow = std::make_unique<Ui::WebviewWindow>(
|
||||||
_form->details().url,
|
webviewDataPath(),
|
||||||
panelDelegate());
|
_form->details().url,
|
||||||
if (!_webviewWindow->shown()) {
|
panelDelegate());
|
||||||
// #TODO payments errors
|
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;
|
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<Stripe::APIClient>(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() {
|
void CheckoutProcess::panelEditShippingInformation() {
|
||||||
showEditInformation(Ui::EditField::ShippingStreet);
|
showEditInformation(Ui::InformationField::ShippingStreet);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckoutProcess::panelEditName() {
|
void CheckoutProcess::panelEditName() {
|
||||||
showEditInformation(Ui::EditField::Name);
|
showEditInformation(Ui::InformationField::Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckoutProcess::panelEditEmail() {
|
void CheckoutProcess::panelEditEmail() {
|
||||||
showEditInformation(Ui::EditField::Email);
|
showEditInformation(Ui::InformationField::Email);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckoutProcess::panelEditPhone() {
|
void CheckoutProcess::panelEditPhone() {
|
||||||
showEditInformation(Ui::EditField::Phone);
|
showEditInformation(Ui::InformationField::Phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckoutProcess::showForm() {
|
void CheckoutProcess::showForm() {
|
||||||
_panel->showForm(
|
_panel->showForm(
|
||||||
_form->invoice(),
|
_form->invoice(),
|
||||||
_form->savedInformation(),
|
_form->savedInformation(),
|
||||||
|
_form->nativePayment().details,
|
||||||
_form->shippingOptions());
|
_form->shippingOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckoutProcess::showEditInformation(Ui::EditField field) {
|
void CheckoutProcess::showEditInformation(Ui::InformationField field) {
|
||||||
if (_submitState != SubmitState::None) {
|
if (_submitState != SubmitState::None) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -349,11 +422,11 @@ void CheckoutProcess::showEditInformation(Ui::EditField field) {
|
||||||
field);
|
field);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckoutProcess::showEditError(Ui::EditField field) {
|
void CheckoutProcess::showInformationError(Ui::InformationField field) {
|
||||||
if (_submitState != SubmitState::None) {
|
if (_submitState != SubmitState::None) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_panel->showEditError(
|
_panel->showInformationError(
|
||||||
_form->invoice(),
|
_form->invoice(),
|
||||||
_form->savedInformation(),
|
_form->savedInformation(),
|
||||||
field);
|
field);
|
||||||
|
@ -363,6 +436,10 @@ void CheckoutProcess::chooseShippingOption() {
|
||||||
_panel->chooseShippingOption(_form->shippingOptions());
|
_panel->chooseShippingOption(_form->shippingOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CheckoutProcess::editPaymentMethod() {
|
||||||
|
_panel->choosePaymentMethod(_form->nativePayment().details);
|
||||||
|
}
|
||||||
|
|
||||||
void CheckoutProcess::panelChooseShippingOption() {
|
void CheckoutProcess::panelChooseShippingOption() {
|
||||||
if (_submitState != SubmitState::None) {
|
if (_submitState != SubmitState::None) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
|
|
||||||
|
namespace Stripe {
|
||||||
|
class APIClient;
|
||||||
|
} // namespace Stripe
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
@ -19,7 +23,7 @@ class Session;
|
||||||
namespace Payments::Ui {
|
namespace Payments::Ui {
|
||||||
class Panel;
|
class Panel;
|
||||||
class WebviewWindow;
|
class WebviewWindow;
|
||||||
enum class EditField;
|
enum class InformationField;
|
||||||
} // namespace Payments::Ui
|
} // namespace Payments::Ui
|
||||||
|
|
||||||
namespace Payments {
|
namespace Payments {
|
||||||
|
@ -57,9 +61,10 @@ private:
|
||||||
void handleError(const Error &error);
|
void handleError(const Error &error);
|
||||||
|
|
||||||
void showForm();
|
void showForm();
|
||||||
void showEditInformation(Ui::EditField field);
|
void showEditInformation(Ui::InformationField field);
|
||||||
void showEditError(Ui::EditField field);
|
void showInformationError(Ui::InformationField field);
|
||||||
void chooseShippingOption();
|
void chooseShippingOption();
|
||||||
|
void editPaymentMethod();
|
||||||
|
|
||||||
void performInitialSilentValidation();
|
void performInitialSilentValidation();
|
||||||
[[nodiscard]] QString webviewDataPath() const;
|
[[nodiscard]] QString webviewDataPath() const;
|
||||||
|
@ -70,6 +75,7 @@ private:
|
||||||
void panelWebviewMessage(const QJsonDocument &message) override;
|
void panelWebviewMessage(const QJsonDocument &message) override;
|
||||||
bool panelWebviewNavigationAttempt(const QString &uri) override;
|
bool panelWebviewNavigationAttempt(const QString &uri) override;
|
||||||
|
|
||||||
|
void panelEditPaymentMethod() override;
|
||||||
void panelEditShippingInformation() override;
|
void panelEditShippingInformation() override;
|
||||||
void panelEditName() override;
|
void panelEditName() override;
|
||||||
void panelEditEmail() override;
|
void panelEditEmail() override;
|
||||||
|
@ -78,12 +84,14 @@ private:
|
||||||
void panelChangeShippingOption(const QString &id) override;
|
void panelChangeShippingOption(const QString &id) override;
|
||||||
|
|
||||||
void panelValidateInformation(Ui::RequestedInformation data) override;
|
void panelValidateInformation(Ui::RequestedInformation data) override;
|
||||||
|
void panelValidateCard(Ui::UncheckedCardDetails data) override;
|
||||||
void panelShowBox(object_ptr<Ui::BoxContent> box) override;
|
void panelShowBox(object_ptr<Ui::BoxContent> box) override;
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
const std::unique_ptr<Form> _form;
|
const std::unique_ptr<Form> _form;
|
||||||
const std::unique_ptr<Ui::Panel> _panel;
|
const std::unique_ptr<Ui::Panel> _panel;
|
||||||
std::unique_ptr<Ui::WebviewWindow> _webviewWindow;
|
std::unique_ptr<Ui::WebviewWindow> _webviewWindow;
|
||||||
|
std::unique_ptr<Stripe::APIClient> _stripe;
|
||||||
SubmitState _submitState = SubmitState::None;
|
SubmitState _submitState = SubmitState::None;
|
||||||
bool _initialSilentValidation = false;
|
bool _initialSilentValidation = false;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QJsonValue>
|
||||||
|
|
||||||
namespace Payments {
|
namespace Payments {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -101,7 +105,7 @@ void Form::processForm(const MTPDpayments_paymentForm &data) {
|
||||||
processSavedCredentials(data);
|
processSavedCredentials(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
fillNativePaymentInformation();
|
||||||
_updates.fire({ FormReady{} });
|
_updates.fire({ FormReady{} });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,10 +156,65 @@ void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) {
|
||||||
|
|
||||||
void Form::processSavedCredentials(
|
void Form::processSavedCredentials(
|
||||||
const MTPDpaymentSavedCredentialsCard &data) {
|
const MTPDpaymentSavedCredentialsCard &data) {
|
||||||
_savedCredentials = Ui::SavedCredentials{
|
// #TODO payments not yet supported
|
||||||
.id = qs(data.vid()),
|
//_nativePayment.savedCredentials = SavedCredentials{
|
||||||
.title = qs(data.vtitle()),
|
// .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) {
|
void Form::send(const QByteArray &serializedCredentials) {
|
||||||
|
@ -221,6 +280,13 @@ void Form::validateInformation(const Ui::RequestedInformation &information) {
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Form::setPaymentCredentials(const NewCredentials &credentials) {
|
||||||
|
Expects(!credentials.empty());
|
||||||
|
|
||||||
|
_nativePayment.newCredentials = credentials;
|
||||||
|
refreshNativePaymentDetails();
|
||||||
|
}
|
||||||
|
|
||||||
void Form::setShippingOption(const QString &id) {
|
void Form::setShippingOption(const QString &id) {
|
||||||
_shippingOptions.selectedId = id;
|
_shippingOptions.selectedId = id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 FormReady {};
|
||||||
struct ValidateFinished {};
|
struct ValidateFinished {};
|
||||||
struct Error {
|
struct Error {
|
||||||
|
@ -73,8 +117,8 @@ public:
|
||||||
[[nodiscard]] const Ui::RequestedInformation &savedInformation() const {
|
[[nodiscard]] const Ui::RequestedInformation &savedInformation() const {
|
||||||
return _savedInformation;
|
return _savedInformation;
|
||||||
}
|
}
|
||||||
[[nodiscard]] const Ui::SavedCredentials &savedCredentials() const {
|
[[nodiscard]] const NativePayment &nativePayment() const {
|
||||||
return _savedCredentials;
|
return _nativePayment;
|
||||||
}
|
}
|
||||||
[[nodiscard]] const Ui::ShippingOptions &shippingOptions() const {
|
[[nodiscard]] const Ui::ShippingOptions &shippingOptions() const {
|
||||||
return _shippingOptions;
|
return _shippingOptions;
|
||||||
|
@ -85,6 +129,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateInformation(const Ui::RequestedInformation &information);
|
void validateInformation(const Ui::RequestedInformation &information);
|
||||||
|
void setPaymentCredentials(const NewCredentials &credentials);
|
||||||
void setShippingOption(const QString &id);
|
void setShippingOption(const QString &id);
|
||||||
void send(const QByteArray &serializedCredentials);
|
void send(const QByteArray &serializedCredentials);
|
||||||
|
|
||||||
|
@ -97,6 +142,8 @@ private:
|
||||||
void processSavedCredentials(
|
void processSavedCredentials(
|
||||||
const MTPDpaymentSavedCredentialsCard &data);
|
const MTPDpaymentSavedCredentialsCard &data);
|
||||||
void processShippingOptions(const QVector<MTPShippingOption> &data);
|
void processShippingOptions(const QVector<MTPShippingOption> &data);
|
||||||
|
void fillNativePaymentInformation();
|
||||||
|
void refreshNativePaymentDetails();
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
|
@ -105,7 +152,7 @@ private:
|
||||||
Ui::Invoice _invoice;
|
Ui::Invoice _invoice;
|
||||||
FormDetails _details;
|
FormDetails _details;
|
||||||
Ui::RequestedInformation _savedInformation;
|
Ui::RequestedInformation _savedInformation;
|
||||||
Ui::SavedCredentials _savedCredentials;
|
NativePayment _nativePayment;
|
||||||
|
|
||||||
Ui::RequestedInformation _validatedInformation;
|
Ui::RequestedInformation _validatedInformation;
|
||||||
mtpRequestId _validateRequestId = 0;
|
mtpRequestId _validateRequestId = 0;
|
||||||
|
|
|
@ -65,7 +65,7 @@ void APIClient::createTokenWithCard(
|
||||||
CardParams card,
|
CardParams card,
|
||||||
TokenCompletionCallback completion) {
|
TokenCompletionCallback completion) {
|
||||||
createTokenWithData(
|
createTokenWithData(
|
||||||
FormEncoder::formEncodedDataForObject(card),
|
FormEncoder::formEncodedDataForObject(MakeEncodable(card)),
|
||||||
std::move(completion));
|
std::move(completion));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,25 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "stripe/stripe_decode.h"
|
#include "stripe/stripe_decode.h"
|
||||||
|
|
||||||
namespace Stripe {
|
namespace Stripe {
|
||||||
|
namespace {
|
||||||
|
|
||||||
Card::Card(
|
CardBrand BrandFromString(const QString &brand) {
|
||||||
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) {
|
|
||||||
if (brand == "visa") {
|
if (brand == "visa") {
|
||||||
return CardBrand::Visa;
|
return CardBrand::Visa;
|
||||||
} else if (brand == "american express") {
|
} 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") {
|
if (funding == "credit") {
|
||||||
return CardFundingType::Credit;
|
return CardFundingType::Credit;
|
||||||
} else if (funding == "debit") {
|
} 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) {
|
Card Card::DecodedObjectFromAPIResponse(QJsonObject object) {
|
||||||
if (!ContainsFields(object, {
|
if (!ContainsFields(object, {
|
||||||
u"id",
|
u"id",
|
||||||
|
@ -80,7 +83,7 @@ Card Card::DecodedObjectFromAPIResponse(QJsonObject object) {
|
||||||
auto result = Card(cardId, last4, brand, expMonth, expYear);
|
auto result = Card(cardId, last4, brand, expMonth, expYear);
|
||||||
result._name = string(u"name");
|
result._name = string(u"name");
|
||||||
result._dynamicLast4 = string(u"dynamic_last4");
|
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._fingerprint = string(u"fingerprint");
|
||||||
result._country = string(u"country");
|
result._country = string(u"country");
|
||||||
result._currency = string(u"currency");
|
result._currency = string(u"currency");
|
||||||
|
@ -97,6 +100,74 @@ Card Card::DecodedObjectFromAPIResponse(QJsonObject object) {
|
||||||
return result;
|
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 {
|
bool Card::empty() const {
|
||||||
return _cardId.isEmpty();
|
return _cardId.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,24 @@ public:
|
||||||
[[nodiscard]] static Card DecodedObjectFromAPIResponse(
|
[[nodiscard]] static Card DecodedObjectFromAPIResponse(
|
||||||
QJsonObject object);
|
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]] bool empty() const;
|
||||||
[[nodiscard]] explicit operator bool() const {
|
[[nodiscard]] explicit operator bool() const {
|
||||||
return !empty();
|
return !empty();
|
||||||
|
|
|
@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
namespace Stripe {
|
namespace Stripe {
|
||||||
|
|
||||||
QString CardParams::RootObjectName() const {
|
QString CardParams::rootObjectName() {
|
||||||
return "card";
|
return "card";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
namespace Stripe {
|
namespace Stripe {
|
||||||
|
|
||||||
class CardParams final : public FormEncodable {
|
struct CardParams {
|
||||||
public:
|
|
||||||
QString RootObjectName() const override;
|
|
||||||
std::map<QString, QString> formFieldValues() const override;
|
|
||||||
|
|
||||||
QString number;
|
QString number;
|
||||||
quint32 expMonth = 0;
|
quint32 expMonth = 0;
|
||||||
quint32 expYear = 0;
|
quint32 expYear = 0;
|
||||||
|
@ -28,6 +24,9 @@ public:
|
||||||
QString addressZip;
|
QString addressZip;
|
||||||
QString addressCountry;
|
QString addressCountry;
|
||||||
QString currency;
|
QString currency;
|
||||||
|
|
||||||
|
[[nodiscard]] static QString rootObjectName();
|
||||||
|
[[nodiscard]] std::map<QString, QString> formFieldValues() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Stripe
|
} // namespace Stripe
|
||||||
|
|
|
@ -14,11 +14,26 @@ namespace Stripe {
|
||||||
|
|
||||||
class FormEncodable {
|
class FormEncodable {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] virtual QString RootObjectName() const = 0;
|
[[nodiscard]] virtual QString rootObjectName() = 0;
|
||||||
|
[[nodiscard]] virtual std::map<QString, QString> formFieldValues() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct MakeEncodable final : FormEncodable {
|
||||||
|
public:
|
||||||
|
MakeEncodable(const T &value) : _value(value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
QString rootObjectName() override {
|
||||||
|
return _value.rootObjectName();
|
||||||
|
}
|
||||||
|
std::map<QString, QString> formFieldValues() override {
|
||||||
|
return _value.formFieldValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const T &_value;
|
||||||
|
|
||||||
// TODO incomplete, not used: nested complex structures not supported.
|
|
||||||
[[nodiscard]] virtual std::map<QString, QString> formFieldValues() const
|
|
||||||
= 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Stripe
|
} // namespace Stripe
|
||||||
|
|
|
@ -13,8 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Stripe {
|
namespace Stripe {
|
||||||
|
|
||||||
QByteArray FormEncoder::formEncodedDataForObject(
|
QByteArray FormEncoder::formEncodedDataForObject(
|
||||||
FormEncodable &object) {
|
FormEncodable &&object) {
|
||||||
const auto root = object.RootObjectName();
|
const auto root = object.rootObjectName();
|
||||||
const auto values = object.formFieldValues();
|
const auto values = object.formFieldValues();
|
||||||
auto result = QByteArray();
|
auto result = QByteArray();
|
||||||
auto keys = std::vector<QString>();
|
auto keys = std::vector<QString>();
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Stripe {
|
||||||
class FormEncoder {
|
class FormEncoder {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] static QByteArray formEncodedDataForObject(
|
[[nodiscard]] static QByteArray formEncodedDataForObject(
|
||||||
FormEncodable &object);
|
FormEncodable &&object);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,11 @@ namespace Stripe {
|
||||||
struct PaymentConfiguration {
|
struct PaymentConfiguration {
|
||||||
QString publishableKey;
|
QString publishableKey;
|
||||||
// PaymentMethodType additionalPaymentMethods; // Apply Pay
|
// PaymentMethodType additionalPaymentMethods; // Apply Pay
|
||||||
BillingAddressFields requiredBillingAddressFields
|
|
||||||
= BillingAddressFields::None;
|
// TODO incomplete, not used.
|
||||||
|
//BillingAddressFields requiredBillingAddressFields
|
||||||
|
// = BillingAddressFields::None;
|
||||||
|
|
||||||
QString companyName;
|
QString companyName;
|
||||||
// QString appleMerchantIdentifier; // Apple Pay
|
// QString appleMerchantIdentifier; // Apple Pay
|
||||||
// bool smsAutofillDisabled = true; // Mobile only
|
// bool smsAutofillDisabled = true; // Mobile only
|
||||||
|
|
245
Telegram/SourceFiles/payments/ui/payments_edit_card.cpp
Normal file
245
Telegram/SourceFiles/payments/ui/payments_edit_card.cpp
Normal file
|
@ -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<PanelDelegate*> 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<RpWidget*> EditCard::setupContent() {
|
||||||
|
const auto inner = _scroll->setOwnedWidget(
|
||||||
|
object_ptr<VerticalLayout>(this));
|
||||||
|
|
||||||
|
_scroll->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
inner->resizeToWidth(width);
|
||||||
|
}, inner->lifetime());
|
||||||
|
|
||||||
|
const auto showBox = [=](object_ptr<BoxContent> 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
|
73
Telegram/SourceFiles/payments/ui/payments_edit_card.h
Normal file
73
Telegram/SourceFiles/payments/ui/payments_edit_card.h
Normal file
|
@ -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<PanelDelegate*> 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<Ui::RpWidget*> setupContent();
|
||||||
|
void updateControlsGeometry();
|
||||||
|
[[nodiscard]] Row *controlForField(CardField field) const;
|
||||||
|
|
||||||
|
[[nodiscard]] UncheckedCardDetails collect() const;
|
||||||
|
|
||||||
|
const not_null<PanelDelegate*> _delegate;
|
||||||
|
NativePaymentDetails _native;
|
||||||
|
|
||||||
|
object_ptr<ScrollArea> _scroll;
|
||||||
|
object_ptr<FadeShadow> _topShadow;
|
||||||
|
object_ptr<FadeShadow> _bottomShadow;
|
||||||
|
object_ptr<RoundButton> _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
|
|
@ -33,7 +33,7 @@ EditInformation::EditInformation(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
const Invoice &invoice,
|
const Invoice &invoice,
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
EditField field,
|
InformationField field,
|
||||||
not_null<PanelDelegate*> delegate)
|
not_null<PanelDelegate*> delegate)
|
||||||
: _delegate(delegate)
|
: _delegate(delegate)
|
||||||
, _invoice(invoice)
|
, _invoice(invoice)
|
||||||
|
@ -48,7 +48,7 @@ EditInformation::EditInformation(
|
||||||
setupControls();
|
setupControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditInformation::setFocus(EditField field) {
|
void EditInformation::setFocus(InformationField field) {
|
||||||
_focusField = field;
|
_focusField = field;
|
||||||
if (const auto control = controlForField(field)) {
|
if (const auto control = controlForField(field)) {
|
||||||
_scroll->ensureWidgetVisible(control);
|
_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)) {
|
if (const auto control = controlForField(field)) {
|
||||||
_scroll->ensureWidgetVisible(control);
|
_scroll->ensureWidgetVisible(control);
|
||||||
control->showError(QString());
|
control->showError(QString());
|
||||||
|
@ -264,16 +264,16 @@ void EditInformation::updateControlsGeometry() {
|
||||||
_scroll->updateBars();
|
_scroll->updateBars();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto EditInformation::controlForField(EditField field) const -> Row* {
|
auto EditInformation::controlForField(InformationField field) const -> Row* {
|
||||||
switch (field) {
|
switch (field) {
|
||||||
case EditField::ShippingStreet: return _street1;
|
case InformationField::ShippingStreet: return _street1;
|
||||||
case EditField::ShippingCity: return _city;
|
case InformationField::ShippingCity: return _city;
|
||||||
case EditField::ShippingState: return _state;
|
case InformationField::ShippingState: return _state;
|
||||||
case EditField::ShippingCountry: return _country;
|
case InformationField::ShippingCountry: return _country;
|
||||||
case EditField::ShippingPostcode: return _postcode;
|
case InformationField::ShippingPostcode: return _postcode;
|
||||||
case EditField::Name: return _name;
|
case InformationField::Name: return _name;
|
||||||
case EditField::Email: return _email;
|
case InformationField::Email: return _email;
|
||||||
case EditField::Phone: return _phone;
|
case InformationField::Phone: return _phone;
|
||||||
}
|
}
|
||||||
Unexpected("Unknown field in EditInformation::controlForField.");
|
Unexpected("Unknown field in EditInformation::controlForField.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,11 +33,11 @@ public:
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
const Invoice &invoice,
|
const Invoice &invoice,
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
EditField field,
|
InformationField field,
|
||||||
not_null<PanelDelegate*> delegate);
|
not_null<PanelDelegate*> delegate);
|
||||||
|
|
||||||
void showError(EditField field);
|
void showError(InformationField field);
|
||||||
void setFocus(EditField field);
|
void setFocus(InformationField field);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Row = Passport::Ui::PanelDetailsRow;
|
using Row = Passport::Ui::PanelDetailsRow;
|
||||||
|
@ -48,7 +48,7 @@ private:
|
||||||
void setupControls();
|
void setupControls();
|
||||||
[[nodiscard]] not_null<Ui::RpWidget*> setupContent();
|
[[nodiscard]] not_null<Ui::RpWidget*> setupContent();
|
||||||
void updateControlsGeometry();
|
void updateControlsGeometry();
|
||||||
[[nodiscard]] Row *controlForField(EditField field) const;
|
[[nodiscard]] Row *controlForField(InformationField field) const;
|
||||||
|
|
||||||
[[nodiscard]] RequestedInformation collect() const;
|
[[nodiscard]] RequestedInformation collect() const;
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ private:
|
||||||
Row *_email = nullptr;
|
Row *_email = nullptr;
|
||||||
Row *_phone = nullptr;
|
Row *_phone = nullptr;
|
||||||
|
|
||||||
EditField _focusField = EditField::ShippingStreet;
|
InformationField _focusField = InformationField::ShippingStreet;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -30,10 +30,12 @@ FormSummary::FormSummary(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
const Invoice &invoice,
|
const Invoice &invoice,
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
|
const NativePaymentDetails &native,
|
||||||
const ShippingOptions &options,
|
const ShippingOptions &options,
|
||||||
not_null<PanelDelegate*> delegate)
|
not_null<PanelDelegate*> delegate)
|
||||||
: _delegate(delegate)
|
: _delegate(delegate)
|
||||||
, _invoice(invoice)
|
, _invoice(invoice)
|
||||||
|
, _native(native)
|
||||||
, _options(options)
|
, _options(options)
|
||||||
, _information(current)
|
, _information(current)
|
||||||
, _scroll(this, st::passportPanelScroll)
|
, _scroll(this, st::passportPanelScroll)
|
||||||
|
@ -134,6 +136,20 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
|
||||||
st::passportFormDividerHeight),
|
st::passportFormDividerHeight),
|
||||||
{ 0, 0, 0, st::passportFormHeaderPadding.top() });
|
{ 0, 0, 0, st::passportFormHeaderPadding.top() });
|
||||||
|
|
||||||
|
if (_native.supported) {
|
||||||
|
const auto method = inner->add(object_ptr<FormRow>(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) {
|
if (_invoice.isShippingAddressRequested) {
|
||||||
const auto info = inner->add(object_ptr<FormRow>(inner));
|
const auto info = inner->add(object_ptr<FormRow>(inner));
|
||||||
info->addClickHandler([=] {
|
info->addClickHandler([=] {
|
||||||
|
@ -153,7 +169,9 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
|
||||||
push(_information.shippingAddress.postcode);
|
push(_information.shippingAddress.postcode);
|
||||||
info->updateContent(
|
info->updateContent(
|
||||||
tr::lng_payments_shipping_address(tr::now),
|
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(),
|
!list.isEmpty(),
|
||||||
false,
|
false,
|
||||||
anim::type::instant);
|
anim::type::instant);
|
||||||
|
@ -167,7 +185,7 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
|
||||||
tr::lng_payments_shipping_method(tr::now),
|
tr::lng_payments_shipping_method(tr::now),
|
||||||
(selected != end(_options.list)
|
(selected != end(_options.list)
|
||||||
? selected->title
|
? selected->title
|
||||||
: "enter pls"),
|
: tr::lng_payments_shipping_method_ph(tr::now)),
|
||||||
(selected != end(_options.list)),
|
(selected != end(_options.list)),
|
||||||
false,
|
false,
|
||||||
anim::type::instant);
|
anim::type::instant);
|
||||||
|
@ -178,7 +196,7 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
|
||||||
name->updateContent(
|
name->updateContent(
|
||||||
tr::lng_payments_info_name(tr::now),
|
tr::lng_payments_info_name(tr::now),
|
||||||
(_information.name.isEmpty()
|
(_information.name.isEmpty()
|
||||||
? "enter pls"
|
? tr::lng_payments_info_name_ph(tr::now)
|
||||||
: _information.name),
|
: _information.name),
|
||||||
!_information.name.isEmpty(),
|
!_information.name.isEmpty(),
|
||||||
false,
|
false,
|
||||||
|
@ -190,7 +208,7 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
|
||||||
email->updateContent(
|
email->updateContent(
|
||||||
tr::lng_payments_info_email(tr::now),
|
tr::lng_payments_info_email(tr::now),
|
||||||
(_information.email.isEmpty()
|
(_information.email.isEmpty()
|
||||||
? "enter pls"
|
? tr::lng_payments_info_email_ph(tr::now)
|
||||||
: _information.email),
|
: _information.email),
|
||||||
!_information.email.isEmpty(),
|
!_information.email.isEmpty(),
|
||||||
false,
|
false,
|
||||||
|
@ -202,7 +220,7 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
|
||||||
phone->updateContent(
|
phone->updateContent(
|
||||||
tr::lng_payments_info_phone(tr::now),
|
tr::lng_payments_info_phone(tr::now),
|
||||||
(_information.phone.isEmpty()
|
(_information.phone.isEmpty()
|
||||||
? "enter pls"
|
? tr::lng_payments_info_phone_ph(tr::now)
|
||||||
: _information.phone),
|
: _information.phone),
|
||||||
!_information.phone.isEmpty(),
|
!_information.phone.isEmpty(),
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -29,6 +29,7 @@ public:
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
const Invoice &invoice,
|
const Invoice &invoice,
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
|
const NativePaymentDetails &native,
|
||||||
const ShippingOptions &options,
|
const ShippingOptions &options,
|
||||||
not_null<PanelDelegate*> delegate);
|
not_null<PanelDelegate*> delegate);
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ private:
|
||||||
|
|
||||||
const not_null<PanelDelegate*> _delegate;
|
const not_null<PanelDelegate*> _delegate;
|
||||||
Invoice _invoice;
|
Invoice _invoice;
|
||||||
|
NativePaymentDetails _native;
|
||||||
ShippingOptions _options;
|
ShippingOptions _options;
|
||||||
RequestedInformation _information;
|
RequestedInformation _information;
|
||||||
object_ptr<ScrollArea> _scroll;
|
object_ptr<ScrollArea> _scroll;
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "payments/ui/payments_form_summary.h"
|
#include "payments/ui/payments_form_summary.h"
|
||||||
#include "payments/ui/payments_edit_information.h"
|
#include "payments/ui/payments_edit_information.h"
|
||||||
|
#include "payments/ui/payments_edit_card.h"
|
||||||
#include "payments/ui/payments_panel_delegate.h"
|
#include "payments/ui/payments_panel_delegate.h"
|
||||||
#include "ui/widgets/separate_panel.h"
|
#include "ui/widgets/separate_panel.h"
|
||||||
#include "ui/boxes/single_choice_box.h"
|
#include "ui/boxes/single_choice_box.h"
|
||||||
|
@ -45,12 +46,14 @@ void Panel::requestActivate() {
|
||||||
void Panel::showForm(
|
void Panel::showForm(
|
||||||
const Invoice &invoice,
|
const Invoice &invoice,
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
|
const NativePaymentDetails &native,
|
||||||
const ShippingOptions &options) {
|
const ShippingOptions &options) {
|
||||||
_widget->showInner(
|
_widget->showInner(
|
||||||
base::make_unique_q<FormSummary>(
|
base::make_unique_q<FormSummary>(
|
||||||
_widget.get(),
|
_widget.get(),
|
||||||
invoice,
|
invoice,
|
||||||
current,
|
current,
|
||||||
|
native,
|
||||||
options,
|
options,
|
||||||
_delegate));
|
_delegate));
|
||||||
_widget->setBackAllowed(false);
|
_widget->setBackAllowed(false);
|
||||||
|
@ -59,29 +62,30 @@ void Panel::showForm(
|
||||||
void Panel::showEditInformation(
|
void Panel::showEditInformation(
|
||||||
const Invoice &invoice,
|
const Invoice &invoice,
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
EditField field) {
|
InformationField field) {
|
||||||
auto edit = base::make_unique_q<EditInformation>(
|
auto edit = base::make_unique_q<EditInformation>(
|
||||||
_widget.get(),
|
_widget.get(),
|
||||||
invoice,
|
invoice,
|
||||||
current,
|
current,
|
||||||
field,
|
field,
|
||||||
_delegate);
|
_delegate);
|
||||||
_weakEditWidget = edit.get();
|
_weakEditInformation = edit.get();
|
||||||
_widget->showInner(std::move(edit));
|
_widget->showInner(std::move(edit));
|
||||||
_widget->setBackAllowed(true);
|
_widget->setBackAllowed(true);
|
||||||
_weakEditWidget->setFocus(field);
|
_weakEditInformation->setFocus(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::showEditError(
|
void Panel::showInformationError(
|
||||||
const Invoice &invoice,
|
const Invoice &invoice,
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
EditField field) {
|
InformationField field) {
|
||||||
if (_weakEditWidget) {
|
if (_weakEditInformation) {
|
||||||
_weakEditWidget->showError(field);
|
_weakEditInformation->showError(field);
|
||||||
} else {
|
} else {
|
||||||
showEditInformation(invoice, current, field);
|
showEditInformation(invoice, current, field);
|
||||||
if (_weakEditWidget && field == EditField::ShippingCountry) {
|
if (_weakEditInformation
|
||||||
_weakEditWidget->showError(field);
|
&& 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<Ui::GenericBox*> 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<EditCard>(
|
||||||
|
_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 {
|
rpl::producer<> Panel::backRequests() const {
|
||||||
return _widget->backRequests();
|
return _widget->backRequests();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,11 @@ class PanelDelegate;
|
||||||
struct Invoice;
|
struct Invoice;
|
||||||
struct RequestedInformation;
|
struct RequestedInformation;
|
||||||
struct ShippingOptions;
|
struct ShippingOptions;
|
||||||
enum class EditField;
|
enum class InformationField;
|
||||||
|
enum class CardField;
|
||||||
class EditInformation;
|
class EditInformation;
|
||||||
|
class EditCard;
|
||||||
|
struct NativePaymentDetails;
|
||||||
|
|
||||||
class Panel final {
|
class Panel final {
|
||||||
public:
|
public:
|
||||||
|
@ -35,16 +38,24 @@ public:
|
||||||
void showForm(
|
void showForm(
|
||||||
const Invoice &invoice,
|
const Invoice &invoice,
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
|
const NativePaymentDetails &native,
|
||||||
const ShippingOptions &options);
|
const ShippingOptions &options);
|
||||||
void showEditInformation(
|
void showEditInformation(
|
||||||
const Invoice &invoice,
|
const Invoice &invoice,
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
EditField field);
|
InformationField field);
|
||||||
void showEditError(
|
void showInformationError(
|
||||||
const Invoice &invoice,
|
const Invoice &invoice,
|
||||||
const RequestedInformation ¤t,
|
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 chooseShippingOption(const ShippingOptions &options);
|
||||||
|
void choosePaymentMethod(const NativePaymentDetails &native);
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<> backRequests() const;
|
[[nodiscard]] rpl::producer<> backRequests() const;
|
||||||
|
|
||||||
|
@ -56,7 +67,8 @@ public:
|
||||||
private:
|
private:
|
||||||
const not_null<PanelDelegate*> _delegate;
|
const not_null<PanelDelegate*> _delegate;
|
||||||
std::unique_ptr<SeparatePanel> _widget;
|
std::unique_ptr<SeparatePanel> _widget;
|
||||||
QPointer<EditInformation> _weakEditWidget;
|
QPointer<EditInformation> _weakEditInformation;
|
||||||
|
QPointer<EditCard> _weakEditCard;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -104,19 +104,7 @@ struct RequestedInformation {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SavedCredentials {
|
enum class InformationField {
|
||||||
QString id;
|
|
||||||
QString title;
|
|
||||||
|
|
||||||
[[nodiscard]] bool valid() const {
|
|
||||||
return !id.isEmpty();
|
|
||||||
}
|
|
||||||
[[nodiscard]] explicit operator bool() const {
|
|
||||||
return valid();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class EditField {
|
|
||||||
ShippingStreet,
|
ShippingStreet,
|
||||||
ShippingCity,
|
ShippingCity,
|
||||||
ShippingState,
|
ShippingState,
|
||||||
|
@ -127,4 +115,32 @@ enum class EditField {
|
||||||
Phone,
|
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
|
} // namespace Payments::Ui
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace Payments::Ui {
|
||||||
using namespace ::Ui;
|
using namespace ::Ui;
|
||||||
|
|
||||||
struct RequestedInformation;
|
struct RequestedInformation;
|
||||||
|
struct UncheckedCardDetails;
|
||||||
|
|
||||||
class PanelDelegate {
|
class PanelDelegate {
|
||||||
public:
|
public:
|
||||||
|
@ -30,6 +31,7 @@ public:
|
||||||
virtual void panelWebviewMessage(const QJsonDocument &message) = 0;
|
virtual void panelWebviewMessage(const QJsonDocument &message) = 0;
|
||||||
virtual bool panelWebviewNavigationAttempt(const QString &uri) = 0;
|
virtual bool panelWebviewNavigationAttempt(const QString &uri) = 0;
|
||||||
|
|
||||||
|
virtual void panelEditPaymentMethod() = 0;
|
||||||
virtual void panelEditShippingInformation() = 0;
|
virtual void panelEditShippingInformation() = 0;
|
||||||
virtual void panelEditName() = 0;
|
virtual void panelEditName() = 0;
|
||||||
virtual void panelEditEmail() = 0;
|
virtual void panelEditEmail() = 0;
|
||||||
|
@ -38,6 +40,7 @@ public:
|
||||||
virtual void panelChangeShippingOption(const QString &id) = 0;
|
virtual void panelChangeShippingOption(const QString &id) = 0;
|
||||||
|
|
||||||
virtual void panelValidateInformation(RequestedInformation data) = 0;
|
virtual void panelValidateInformation(RequestedInformation data) = 0;
|
||||||
|
virtual void panelValidateCard(Ui::UncheckedCardDetails data) = 0;
|
||||||
virtual void panelShowBox(object_ptr<BoxContent> box) = 0;
|
virtual void panelShowBox(object_ptr<BoxContent> box) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,8 @@ PRIVATE
|
||||||
passport/ui/passport_form_row.cpp
|
passport/ui/passport_form_row.cpp
|
||||||
passport/ui/passport_form_row.h
|
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.cpp
|
||||||
payments/ui/payments_edit_information.h
|
payments/ui/payments_edit_information.h
|
||||||
payments/ui/payments_form_summary.cpp
|
payments/ui/payments_form_summary.cpp
|
||||||
|
|
Loading…
Reference in a new issue