Allow saving and using saved credentials.

This commit is contained in:
John Preston 2021-03-31 21:15:49 +04:00
parent 2e58993181
commit 663db64688
16 changed files with 350 additions and 55 deletions

View file

@ -1880,15 +1880,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_payments_info_email" = "Email"; "lng_payments_info_email" = "Email";
"lng_payments_info_phone" = "Phone"; "lng_payments_info_phone" = "Phone";
"lng_payments_shipping_address_title" = "Shipping Information"; "lng_payments_shipping_address_title" = "Shipping Information";
"lng_payments_save_shipping_about" = "You can save your shipping information for future use.";
"lng_payments_card_title" = "New Card"; "lng_payments_card_title" = "New Card";
"lng_payments_card_number" = "Card Number"; "lng_payments_card_number" = "Card Number";
"lng_payments_card_holder" = "Cardholder name"; "lng_payments_card_holder" = "Cardholder name";
"lng_payments_billing_address" = "Billing Information"; "lng_payments_billing_address" = "Billing Information";
"lng_payments_billing_country" = "Country"; "lng_payments_billing_country" = "Country";
"lng_payments_billing_zip_code" = "Zip Code"; "lng_payments_billing_zip_code" = "Zip Code";
"lng_payments_save_payment_about" = "You can save your payment information for future use.";
"lng_payments_save_information" = "Save Information for future use"; "lng_payments_save_information" = "Save Information for future use";
"lng_payments_need_password" = "You can save your payment information for future use. Please turn on Two-Step Verification to enable this.";
"lng_payments_password_title" = "Payment Confirmation";
"lng_payments_password_description" = "Your card {card} is on file. To pay with this card, please enter your 2-Step-Verification password.";
"lng_payments_password_submit" = "Pay";
"lng_payments_tips_label" = "Tips"; "lng_payments_tips_label" = "Tips";
"lng_payments_tips_title" = "Tips"; "lng_payments_tips_title" = "Tips";
"lng_payments_tips_enter" = "Enter tips amount"; "lng_payments_tips_enter" = "Enter tips amount";

View file

@ -361,7 +361,11 @@ void PasscodeBox::closeReplacedBy() {
} }
void PasscodeBox::setPasswordFail(const MTP::Error &error) { void PasscodeBox::setPasswordFail(const MTP::Error &error) {
if (MTP::IsFloodError(error)) { setPasswordFail(error.type());
}
void PasscodeBox::setPasswordFail(const QString &type) {
if (MTP::IsFloodError(type)) {
closeReplacedBy(); closeReplacedBy();
_setRequest = 0; _setRequest = 0;
@ -378,20 +382,19 @@ void PasscodeBox::setPasswordFail(const MTP::Error &error) {
closeReplacedBy(); closeReplacedBy();
_setRequest = 0; _setRequest = 0;
const auto &err = error.type(); if (type == qstr("PASSWORD_HASH_INVALID")
if (err == qstr("PASSWORD_HASH_INVALID") || type == qstr("SRP_PASSWORD_CHANGED")) {
|| err == qstr("SRP_PASSWORD_CHANGED")) {
if (_oldPasscode->isHidden()) { if (_oldPasscode->isHidden()) {
_passwordReloadNeeded.fire({}); _passwordReloadNeeded.fire({});
closeBox(); closeBox();
} else { } else {
badOldPasscode(); badOldPasscode();
} }
} else if (err == qstr("SRP_ID_INVALID")) { } else if (type == qstr("SRP_ID_INVALID")) {
handleSrpIdInvalid(); handleSrpIdInvalid();
//} else if (err == qstr("NEW_PASSWORD_BAD")) { //} else if (type == qstr("NEW_PASSWORD_BAD")) {
//} else if (err == qstr("NEW_SALT_INVALID")) { //} else if (type == qstr("NEW_SALT_INVALID")) {
} else if (err == qstr("EMAIL_INVALID")) { } else if (type == qstr("EMAIL_INVALID")) {
_emailError = tr::lng_cloud_password_bad_email(tr::now); _emailError = tr::lng_cloud_password_bad_email(tr::now);
_recoverEmail->setFocus(); _recoverEmail->setFocus();
_recoverEmail->showError(); _recoverEmail->showError();
@ -682,12 +685,15 @@ void PasscodeBox::serverError() {
} }
bool PasscodeBox::handleCustomCheckError(const MTP::Error &error) { bool PasscodeBox::handleCustomCheckError(const MTP::Error &error) {
const auto &type = error.type(); return handleCustomCheckError(error.type());
if (MTP::IsFloodError(error) }
bool PasscodeBox::handleCustomCheckError(const QString &type) {
if (MTP::IsFloodError(type)
|| type == qstr("PASSWORD_HASH_INVALID") || type == qstr("PASSWORD_HASH_INVALID")
|| type == qstr("SRP_PASSWORD_CHANGED") || type == qstr("SRP_PASSWORD_CHANGED")
|| type == qstr("SRP_ID_INVALID")) { || type == qstr("SRP_ID_INVALID")) {
setPasswordFail(error); setPasswordFail(type);
return true; return true;
} }
return false; return false;

View file

@ -56,6 +56,7 @@ public:
rpl::producer<> clearUnconfirmedPassword() const; rpl::producer<> clearUnconfirmedPassword() const;
bool handleCustomCheckError(const MTP::Error &error); bool handleCustomCheckError(const MTP::Error &error);
bool handleCustomCheckError(const QString &type);
protected: protected:
void prepare() override; void prepare() override;
@ -82,6 +83,7 @@ private:
void setPasswordDone(const QByteArray &newPasswordBytes); void setPasswordDone(const QByteArray &newPasswordBytes);
void setPasswordFail(const MTP::Error &error); void setPasswordFail(const MTP::Error &error);
void setPasswordFail(const QString &type);
void setPasswordFail( void setPasswordFail(
const QByteArray &newPasswordBytes, const QByteArray &newPasswordBytes,
const QString &email, const QString &email,

View file

@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_lock_widgets.h" #include "window/window_lock_widgets.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
//#include "platform/platform_specific.h" //#include "platform/platform_specific.h"
#include "base/unixtime.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "support/support_helper.h" #include "support/support_helper.h"
#include "facades.h" #include "facades.h"
@ -43,6 +44,7 @@ namespace Main {
namespace { namespace {
constexpr auto kLegacyCallsPeerToPeerNobody = 4; constexpr auto kLegacyCallsPeerToPeerNobody = 4;
constexpr auto kTmpPasswordReserveTime = TimeId(10);
[[nodiscard]] QString ValidatedInternalLinksDomain( [[nodiscard]] QString ValidatedInternalLinksDomain(
not_null<const Session*> session) { not_null<const Session*> session) {
@ -155,6 +157,20 @@ Session::Session(
_api->requestNotifySettings(MTP_inputNotifyBroadcasts()); _api->requestNotifySettings(MTP_inputNotifyBroadcasts());
} }
void Session::setTmpPassword(const QByteArray &password, TimeId validUntil) {
if (_tmpPassword.isEmpty() || validUntil > _tmpPasswordValidUntil) {
_tmpPassword = password;
_tmpPasswordValidUntil = validUntil;
}
}
QByteArray Session::validTmpPassword() const {
return (_tmpPasswordValidUntil
>= base::unixtime::now() + kTmpPasswordReserveTime)
? _tmpPassword
: QByteArray();
}
// Can be called only right before ~Session. // Can be called only right before ~Session.
void Session::finishLogout() { void Session::finishLogout() {
updates().updateOnline(); updates().updateOnline();

View file

@ -145,6 +145,9 @@ public:
[[nodiscard]] QString createInternalLink(const QString &query) const; [[nodiscard]] QString createInternalLink(const QString &query) const;
[[nodiscard]] QString createInternalLinkFull(const QString &query) const; [[nodiscard]] QString createInternalLinkFull(const QString &query) const;
void setTmpPassword(const QByteArray &password, TimeId validUntil);
[[nodiscard]] QByteArray validTmpPassword() const;
// Can be called only right before ~Session. // Can be called only right before ~Session.
void finishLogout(); void finishLogout();
@ -190,6 +193,9 @@ private:
base::flat_set<not_null<Window::SessionController*>> _windows; base::flat_set<not_null<Window::SessionController*>> _windows;
base::Timer _saveSettingsTimer; base::Timer _saveSettingsTimer;
QByteArray _tmpPassword;
TimeId _tmpPasswordValidUntil = 0;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };

View file

@ -38,8 +38,12 @@ private:
}; };
inline bool IsFloodError(const QString &type) {
return type.startsWith(qstr("FLOOD_WAIT_"));
}
inline bool IsFloodError(const Error &error) { inline bool IsFloodError(const Error &error) {
return error.type().startsWith(qstr("FLOOD_WAIT_")); return IsFloodError(error.type());
} }
inline bool IsTemporaryError(const Error &error) { inline bool IsTemporaryError(const Error &error) {

View file

@ -16,8 +16,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history.h" #include "history/history.h"
#include "data/data_user.h" // UserData::isBot. #include "data/data_user.h" // UserData::isBot.
#include "boxes/passcode_box.h"
#include "core/local_url_handlers.h" // TryConvertUrlToLocal. #include "core/local_url_handlers.h" // TryConvertUrlToLocal.
#include "core/file_utilities.h" // File::OpenUrl. #include "core/file_utilities.h" // File::OpenUrl.
#include "core/core_cloud_password.h" // Core::CloudPasswordState
#include "lang/lang_keys.h"
#include "apiwrap.h" #include "apiwrap.h"
#include <QJsonDocument> #include <QJsonDocument>
@ -104,11 +107,19 @@ CheckoutProcess::CheckoutProcess(
) | rpl::start_with_next([=](const FormUpdate &update) { ) | rpl::start_with_next([=](const FormUpdate &update) {
handleFormUpdate(update); handleFormUpdate(update);
}, _lifetime); }, _lifetime);
_panel->backRequests( _panel->backRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
panelCancelEdit(); panelCancelEdit();
}, _panel->lifetime()); }, _panel->lifetime());
showForm(); showForm();
if (mode == Mode::Payment) {
_session->api().passwordState(
) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
_form->setHasPassword(!!state.request);
}, _lifetime);
}
} }
CheckoutProcess::~CheckoutProcess() { CheckoutProcess::~CheckoutProcess() {
@ -132,6 +143,9 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
if (!_initialSilentValidation) { if (!_initialSilentValidation) {
showForm(); showForm();
} }
if (_form->paymentMethod().savedCredentials) {
_session->api().reloadPasswordState();
}
}, [&](const ThumbnailUpdated &data) { }, [&](const ThumbnailUpdated &data) {
_panel->updateFormThumbnail(data.thumbnail); _panel->updateFormThumbnail(data.thumbnail);
}, [&](const ValidateFinished &) { }, [&](const ValidateFinished &) {
@ -139,12 +153,19 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
_initialSilentValidation = false; _initialSilentValidation = false;
} }
showForm(); showForm();
if (_submitState == SubmitState::Validation) { const auto submitted = (_submitState == SubmitState::Validating);
_submitState = SubmitState::Validated; _submitState = SubmitState::Validated;
if (submitted) {
panelSubmit(); panelSubmit();
} }
}, [&](const PaymentMethodUpdate &) { }, [&](const PaymentMethodUpdate &data) {
showForm(); showForm();
if (data.requestNewPassword) {
requestSetPassword();
}
}, [&](const TmpPasswordRequired &) {
_submitState = SubmitState::Validated;
requestPassword();
}, [&](const VerificationNeeded &data) { }, [&](const VerificationNeeded &data) {
if (!_panel->showWebview(data.url, false)) { if (!_panel->showWebview(data.url, false)) {
File::OpenUrl(data.url); File::OpenUrl(data.url);
@ -176,7 +197,7 @@ void CheckoutProcess::handleError(const Error &error) {
} }
break; break;
case Error::Type::Validate: { case Error::Type::Validate: {
if (_submitState == SubmitState::Validation if (_submitState == SubmitState::Validating
|| _submitState == SubmitState::Validated) { || _submitState == SubmitState::Validated) {
_submitState = SubmitState::None; _submitState = SubmitState::None;
} }
@ -243,9 +264,19 @@ void CheckoutProcess::handleError(const Error &error) {
showToast({ "Error: " + id }); showToast({ "Error: " + id });
} }
} break; } break;
case Error::Type::TmpPassword:
if (const auto box = _enterPasswordBox.data()) {
if (!box->handleCustomCheckError(id)) {
showToast({ "Error: Could not generate tmp password." });
}
}
break;
case Error::Type::Send: case Error::Type::Send:
if (const auto box = _enterPasswordBox.data()) {
box->closeBox();
}
if (_submitState == SubmitState::Finishing) { if (_submitState == SubmitState::Finishing) {
_submitState = SubmitState::None; _submitState = SubmitState::Validated;
} }
if (id == u"PAYMENT_FAILED"_q) { if (id == u"PAYMENT_FAILED"_q) {
showToast({ "Error: Payment Failed. Your card has not been billed." }); // #TODO payments errors message showToast({ "Error: Payment Failed. Your card has not been billed." }); // #TODO payments errors message
@ -256,6 +287,8 @@ void CheckoutProcess::handleError(const Error &error) {
|| id == u"PAYMENT_CREDENTIALS_INVALID"_q || id == u"PAYMENT_CREDENTIALS_INVALID"_q
|| id == u"PAYMENT_CREDENTIALS_ID_INVALID"_q) { || id == u"PAYMENT_CREDENTIALS_ID_INVALID"_q) {
showToast({ "Error: " + id + ". Your card has not been billed." }); showToast({ "Error: " + id + ". Your card has not been billed." });
} else if (id == u"TMP_PASSWORD_INVALID"_q) {
// #TODO payments save
} else { } else {
showToast({ "Error: " + id }); showToast({ "Error: " + id });
} }
@ -301,7 +334,7 @@ void CheckoutProcess::panelSubmit() {
if (_form->invoice().receipt.paid) { if (_form->invoice().receipt.paid) {
closeAndReactivate(); closeAndReactivate();
return; return;
} else if (_submitState == SubmitState::Validation } else if (_submitState == SubmitState::Validating
|| _submitState == SubmitState::Finishing) { || _submitState == SubmitState::Finishing) {
return; return;
} }
@ -310,25 +343,25 @@ void CheckoutProcess::panelSubmit() {
const auto &options = _form->shippingOptions(); const auto &options = _form->shippingOptions();
if (!options.list.empty() && options.selectedId.isEmpty()) { if (!options.list.empty() && options.selectedId.isEmpty()) {
chooseShippingOption(); chooseShippingOption();
return;
} else if (_submitState != SubmitState::Validated } else if (_submitState != SubmitState::Validated
&& options.list.empty() && options.list.empty()
&& (invoice.isShippingAddressRequested && (invoice.isShippingAddressRequested
|| invoice.isNameRequested || invoice.isNameRequested
|| invoice.isEmailRequested || invoice.isEmailRequested
|| invoice.isPhoneRequested)) { || invoice.isPhoneRequested)) {
_submitState = SubmitState::Validation; _submitState = SubmitState::Validating;
_form->validateInformation(_form->savedInformation()); _form->validateInformation(_form->savedInformation());
return;
} else if (!method.newCredentials && !method.savedCredentials) { } else if (!method.newCredentials && !method.savedCredentials) {
editPaymentMethod(); editPaymentMethod();
return; } else {
_submitState = SubmitState::Finishing;
_form->submit();
} }
_submitState = SubmitState::Finishing;
_form->submit();
} }
void CheckoutProcess::panelWebviewMessage(const QJsonDocument &message) { void CheckoutProcess::panelWebviewMessage(
const QJsonDocument &message,
bool saveInformation) {
if (!message.isArray()) { if (!message.isArray()) {
LOG(("Payments Error: " LOG(("Payments Error: "
"Not an array received in buy_callback arguments.")); "Not an array received in buy_callback arguments."));
@ -371,7 +404,7 @@ void CheckoutProcess::panelWebviewMessage(const QJsonDocument &message) {
.data = QJsonDocument( .data = QJsonDocument(
credentials.toObject() credentials.toObject()
).toJson(QJsonDocument::Compact), ).toJson(QJsonDocument::Compact),
.saveOnServer = false, // #TODO payments save .saveOnServer = saveInformation,
}); });
}); });
} }
@ -399,8 +432,10 @@ void CheckoutProcess::panelEditPaymentMethod() {
editPaymentMethod(); editPaymentMethod();
} }
void CheckoutProcess::panelValidateCard(Ui::UncheckedCardDetails data) { void CheckoutProcess::panelValidateCard(
_form->validateCard(data); Ui::UncheckedCardDetails data,
bool saveInformation) {
_form->validateCard(data, saveInformation);
} }
void CheckoutProcess::panelEditShippingInformation() { void CheckoutProcess::panelEditShippingInformation() {
@ -466,6 +501,70 @@ void CheckoutProcess::editPaymentMethod() {
_panel->choosePaymentMethod(_form->paymentMethod().ui); _panel->choosePaymentMethod(_form->paymentMethod().ui);
} }
void CheckoutProcess::requestSetPassword() {
_session->api().reloadPasswordState();
_panel->askSetPassword();
}
void CheckoutProcess::requestPassword() {
getPasswordState([=](const Core::CloudPasswordState &state) {
auto fields = PasscodeBox::CloudFields::From(state);
fields.customTitle = tr::lng_payments_password_title();
fields.customDescription = tr::lng_payments_password_description(
tr::now,
lt_card,
_form->paymentMethod().savedCredentials.title);
fields.customSubmitButton = tr::lng_payments_password_submit();
fields.customCheckCallback = [=](
const Core::CloudPasswordResult &result) {
_form->submit(result);
};
auto owned = Box<PasscodeBox>(_session, fields);
_enterPasswordBox = owned.data();
_panel->showBox(std::move(owned));
});
}
void CheckoutProcess::panelSetPassword() {
getPasswordState([=](const Core::CloudPasswordState &state) {
if (state.request) {
return;
}
auto owned = Box<PasscodeBox>(
_session,
PasscodeBox::CloudFields::From(state));
const auto box = owned.data();
rpl::merge(
box->newPasswordSet() | rpl::to_empty,
box->passwordReloadNeeded()
) | rpl::start_with_next([=] {
_session->api().reloadPasswordState();
}, box->lifetime());
box->clearUnconfirmedPassword(
) | rpl::start_with_next([=] {
_session->api().clearUnconfirmedPassword();
}, box->lifetime());
_panel->showBox(std::move(owned));
});
}
void CheckoutProcess::getPasswordState(
Fn<void(const Core::CloudPasswordState&)> callback) {
Expects(callback != nullptr);
if (_gettingPasswordState) {
return;
}
_session->api().passwordState(
) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
_gettingPasswordState.destroy();
callback(state);
}, _gettingPasswordState);
}
void CheckoutProcess::panelChooseShippingOption() { void CheckoutProcess::panelChooseShippingOption() {
if (_submitState != SubmitState::None) { if (_submitState != SubmitState::None) {
return; return;
@ -492,6 +591,9 @@ void CheckoutProcess::panelChangeTips(int64 value) {
void CheckoutProcess::panelValidateInformation( void CheckoutProcess::panelValidateInformation(
Ui::RequestedInformation data) { Ui::RequestedInformation data) {
if (_submitState == SubmitState::Validated) {
_submitState = SubmitState::None;
}
_form->validateInformation(data); _form->validateInformation(data);
} }

View file

@ -11,11 +11,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
class HistoryItem; class HistoryItem;
class PasscodeBox;
namespace Core {
class CloudPasswordState;
} // namespace Core
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Payments::Ui { namespace Payments::Ui {
class Panel; class Panel;
enum class InformationField; enum class InformationField;
@ -55,7 +64,7 @@ public:
private: private:
enum class SubmitState { enum class SubmitState {
None, None,
Validation, Validating,
Validated, Validated,
Finishing, Finishing,
}; };
@ -77,13 +86,22 @@ private:
void chooseTips(); void chooseTips();
void editPaymentMethod(); void editPaymentMethod();
void requestSetPassword();
void requestSetPasswordSure(QPointer<Ui::GenericBox> old);
void requestPassword();
void getPasswordState(
Fn<void(const Core::CloudPasswordState&)> callback);
void performInitialSilentValidation(); void performInitialSilentValidation();
void panelRequestClose() override; void panelRequestClose() override;
void panelCloseSure() override; void panelCloseSure() override;
void panelSubmit() override; void panelSubmit() override;
void panelWebviewMessage(const QJsonDocument &message) override; void panelWebviewMessage(
const QJsonDocument &message,
bool saveInformation) override;
bool panelWebviewNavigationAttempt(const QString &uri) override; bool panelWebviewNavigationAttempt(const QString &uri) override;
void panelSetPassword() override;
void panelCancelEdit() override; void panelCancelEdit() override;
void panelEditPaymentMethod() override; void panelEditPaymentMethod() override;
@ -97,7 +115,9 @@ private:
void panelChangeTips(int64 value) override; void panelChangeTips(int64 value) override;
void panelValidateInformation(Ui::RequestedInformation data) override; void panelValidateInformation(Ui::RequestedInformation data) override;
void panelValidateCard(Ui::UncheckedCardDetails data) override; void panelValidateCard(
Ui::UncheckedCardDetails data,
bool saveInformation) override;
void panelShowBox(object_ptr<Ui::BoxContent> box) override; void panelShowBox(object_ptr<Ui::BoxContent> box) override;
QString panelWebviewDataPath() override; QString panelWebviewDataPath() override;
@ -105,10 +125,12 @@ private:
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;
QPointer<PasscodeBox> _enterPasswordBox;
Fn<void()> _reactivate; Fn<void()> _reactivate;
SubmitState _submitState = SubmitState::None; SubmitState _submitState = SubmitState::None;
bool _initialSilentValidation = false; bool _initialSilentValidation = false;
rpl::lifetime _gettingPasswordState;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "stripe/stripe_card_validator.h" #include "stripe/stripe_card_validator.h"
#include "ui/image/image.h" #include "ui/image/image.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "core/core_cloud_password.h"
#include "styles/style_payments.h" // paymentsThumbnailSize. #include "styles/style_payments.h" // paymentsThumbnailSize.
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
@ -32,6 +33,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Payments { namespace Payments {
namespace { namespace {
constexpr auto kPasswordPeriod = 15 * TimeId(60);
[[nodiscard]] Ui::Address ParseAddress(const MTPPostAddress &address) { [[nodiscard]] Ui::Address ParseAddress(const MTPPostAddress &address) {
return address.match([](const MTPDpostAddress &data) { return address.match([](const MTPDpostAddress &data) {
return Ui::Address{ return Ui::Address{
@ -381,11 +384,10 @@ void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) {
void Form::processSavedCredentials( void Form::processSavedCredentials(
const MTPDpaymentSavedCredentialsCard &data) { const MTPDpaymentSavedCredentialsCard &data) {
// #TODO payments save _paymentMethod.savedCredentials = SavedCredentials{
//_nativePayment.savedCredentials = SavedCredentials{ .id = qs(data.vid()),
// .id = qs(data.vid()), .title = qs(data.vtitle()),
// .title = qs(data.vtitle()), };
//};
refreshPaymentMethodDetails(); refreshPaymentMethodDetails();
} }
@ -395,6 +397,9 @@ void Form::refreshPaymentMethodDetails() {
_paymentMethod.ui.title = entered ? entered.title : saved.title; _paymentMethod.ui.title = entered ? entered.title : saved.title;
_paymentMethod.ui.ready = entered || saved; _paymentMethod.ui.ready = entered || saved;
_paymentMethod.ui.native.defaultCountry = defaultCountry(); _paymentMethod.ui.native.defaultCountry = defaultCountry();
_paymentMethod.ui.canSaveInformation
= _paymentMethod.ui.native.canSaveInformation
= _details.canSaveCredentials || _details.passwordMissing;
} }
QString Form::defaultPhone() const { QString Form::defaultPhone() const {
@ -452,7 +457,16 @@ void Form::fillStripeNativeMethod() {
} }
void Form::submit() { void Form::submit() {
Expects(!_paymentMethod.newCredentials.data.isEmpty()); // #TODO payments save Expects(_paymentMethod.newCredentials
|| _paymentMethod.savedCredentials);
const auto password = _paymentMethod.newCredentials
? QByteArray()
: _session->validTmpPassword();
if (!_paymentMethod.newCredentials && password.isEmpty()) {
_updates.fire(TmpPasswordRequired{});
return;
}
using Flag = MTPpayments_SendPaymentForm::Flag; using Flag = MTPpayments_SendPaymentForm::Flag;
_api.request(MTPpayments_SendPaymentForm( _api.request(MTPpayments_SendPaymentForm(
@ -468,9 +482,16 @@ void Form::submit() {
MTP_int(_msgId), MTP_int(_msgId),
MTP_string(_requestedInformationId), MTP_string(_requestedInformationId),
MTP_string(_shippingOptions.selectedId), MTP_string(_shippingOptions.selectedId),
MTP_inputPaymentCredentials( (_paymentMethod.newCredentials
MTP_flags(0), ? MTP_inputPaymentCredentials(
MTP_dataJSON(MTP_bytes(_paymentMethod.newCredentials.data))), MTP_flags((_paymentMethod.newCredentials.saveOnServer
&& _details.canSaveCredentials)
? MTPDinputPaymentCredentials::Flag::f_save
: MTPDinputPaymentCredentials::Flag(0)),
MTP_dataJSON(MTP_bytes(_paymentMethod.newCredentials.data)))
: MTP_inputPaymentCredentialsSaved(
MTP_string(_paymentMethod.savedCredentials.id),
MTP_bytes(password))),
MTP_long(_invoice.tipsSelected) MTP_long(_invoice.tipsSelected)
)).done([=](const MTPpayments_PaymentResult &result) { )).done([=](const MTPpayments_PaymentResult &result) {
result.match([&](const MTPDpayments_paymentResult &data) { result.match([&](const MTPDpayments_paymentResult &data) {
@ -483,6 +504,27 @@ void Form::submit() {
}).send(); }).send();
} }
void Form::submit(const Core::CloudPasswordResult &result) {
if (_passwordRequestId) {
return;
}
_passwordRequestId = _api.request(MTPaccount_GetTmpPassword(
result.result,
MTP_int(kPasswordPeriod)
)).done([=](const MTPaccount_TmpPassword &result) {
_passwordRequestId = 0;
result.match([&](const MTPDaccount_tmpPassword &data) {
_session->setTmpPassword(
data.vtmp_password().v,
data.vvalid_until().v);
submit();
});
}).fail([=](const MTP::Error &error) {
_passwordRequestId = 0;
_updates.fire(Error{ Error::Type::TmpPassword, error.type() });
}).send();
}
void Form::validateInformation(const Ui::RequestedInformation &information) { void Form::validateInformation(const Ui::RequestedInformation &information) {
if (_validateRequestId) { if (_validateRequestId) {
if (_validatedInformation == information) { if (_validatedInformation == information) {
@ -573,7 +615,9 @@ Error Form::informationErrorLocal(
return Error(); return Error();
} }
void Form::validateCard(const Ui::UncheckedCardDetails &details) { void Form::validateCard(
const Ui::UncheckedCardDetails &details,
bool saveInformation) {
Expects(!v::is_null(_paymentMethod.native.data)); Expects(!v::is_null(_paymentMethod.native.data));
if (!validateCardLocal(details)) { if (!validateCardLocal(details)) {
@ -581,7 +625,7 @@ void Form::validateCard(const Ui::UncheckedCardDetails &details) {
} }
const auto &native = _paymentMethod.native.data; const auto &native = _paymentMethod.native.data;
if (const auto stripe = std::get_if<StripePaymentMethod>(&native)) { if (const auto stripe = std::get_if<StripePaymentMethod>(&native)) {
validateCard(*stripe, details); validateCard(*stripe, details, saveInformation);
} else { } else {
Unexpected("Native payment provider in Form::validateCard."); Unexpected("Native payment provider in Form::validateCard.");
} }
@ -635,7 +679,8 @@ Error Form::cardErrorLocal(const Ui::UncheckedCardDetails &details) const {
void Form::validateCard( void Form::validateCard(
const StripePaymentMethod &method, const StripePaymentMethod &method,
const Ui::UncheckedCardDetails &details) { const Ui::UncheckedCardDetails &details,
bool saveInformation) {
Expects(!method.publishableKey.isEmpty()); Expects(!method.publishableKey.isEmpty());
if (_stripe) { if (_stripe) {
@ -673,7 +718,7 @@ void Form::validateCard(
{ "type", "card" }, { "type", "card" },
{ "id", token.tokenId() }, { "id", token.tokenId() },
}).toJson(QJsonDocument::Compact), }).toJson(QJsonDocument::Compact),
.saveOnServer = false, // #TODO payments save .saveOnServer = saveInformation,
}); });
} }
})); }));
@ -683,8 +728,17 @@ void Form::setPaymentCredentials(const NewCredentials &credentials) {
Expects(!credentials.empty()); Expects(!credentials.empty());
_paymentMethod.newCredentials = credentials; _paymentMethod.newCredentials = credentials;
const auto requestNewPassword = credentials.saveOnServer
&& !_details.canSaveCredentials
&& _details.passwordMissing;
refreshPaymentMethodDetails(); refreshPaymentMethodDetails();
_updates.fire(PaymentMethodUpdate{}); _updates.fire(PaymentMethodUpdate{ requestNewPassword });
}
void Form::setHasPassword(bool has) {
if (_details.passwordMissing) {
_details.canSaveCredentials = has;
}
} }
void Form::setShippingOption(const QString &id) { void Form::setShippingOption(const QString &id) {

View file

@ -13,6 +13,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class Image; class Image;
namespace Core {
struct CloudPasswordResult;
} // namespace Core
namespace Stripe { namespace Stripe {
class APIClient; class APIClient;
} // namespace Stripe } // namespace Stripe
@ -107,10 +111,13 @@ struct ThumbnailUpdated {
QImage thumbnail; QImage thumbnail;
}; };
struct ValidateFinished {}; struct ValidateFinished {};
struct PaymentMethodUpdate {}; struct PaymentMethodUpdate {
bool requestNewPassword = false;
};
struct VerificationNeeded { struct VerificationNeeded {
QString url; QString url;
}; };
struct TmpPasswordRequired {};
struct PaymentFinished { struct PaymentFinished {
MTPUpdates updates; MTPUpdates updates;
}; };
@ -120,6 +127,7 @@ struct Error {
Form, Form,
Validate, Validate,
Stripe, Stripe,
TmpPassword,
Send, Send,
}; };
Type type = Type::None; Type type = Type::None;
@ -139,6 +147,7 @@ struct FormUpdate : std::variant<
ValidateFinished, ValidateFinished,
PaymentMethodUpdate, PaymentMethodUpdate,
VerificationNeeded, VerificationNeeded,
TmpPasswordRequired,
PaymentFinished, PaymentFinished,
Error> { Error> {
using variant::variant; using variant::variant;
@ -170,11 +179,15 @@ public:
} }
void validateInformation(const Ui::RequestedInformation &information); void validateInformation(const Ui::RequestedInformation &information);
void validateCard(const Ui::UncheckedCardDetails &details); void validateCard(
const Ui::UncheckedCardDetails &details,
bool saveInformation);
void setPaymentCredentials(const NewCredentials &credentials); void setPaymentCredentials(const NewCredentials &credentials);
void setHasPassword(bool has);
void setShippingOption(const QString &id); void setShippingOption(const QString &id);
void setTips(int64 value); void setTips(int64 value);
void submit(); void submit();
void submit(const Core::CloudPasswordResult &result);
private: private:
void fillInvoiceFromMessage(); void fillInvoiceFromMessage();
@ -208,7 +221,8 @@ private:
void validateCard( void validateCard(
const StripePaymentMethod &method, const StripePaymentMethod &method,
const Ui::UncheckedCardDetails &details); const Ui::UncheckedCardDetails &details,
bool saveInformation);
bool validateInformationLocal( bool validateInformationLocal(
const Ui::RequestedInformation &information) const; const Ui::RequestedInformation &information) const;
@ -235,6 +249,7 @@ private:
Ui::RequestedInformation _validatedInformation; Ui::RequestedInformation _validatedInformation;
mtpRequestId _validateRequestId = 0; mtpRequestId _validateRequestId = 0;
mtpRequestId _passwordRequestId = 0;
std::unique_ptr<Stripe::APIClient> _stripe; std::unique_ptr<Stripe::APIClient> _stripe;

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/checkbox.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/wrap/fade_wrap.h" #include "ui/wrap/fade_wrap.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -246,7 +247,7 @@ void EditCard::setupControls() {
const auto inner = setupContent(); const auto inner = setupContent();
_submit->addClickHandler([=] { _submit->addClickHandler([=] {
_delegate->panelValidateCard(collect()); _delegate->panelValidateCard(collect(), (_save && _save->checked()));
}); });
_cancel->addClickHandler([=] { _cancel->addClickHandler([=] {
_delegate->panelCancelEdit(); _delegate->panelCancelEdit();
@ -357,6 +358,14 @@ not_null<RpWidget*> EditCard::setupContent() {
}, lifetime()); }, lifetime());
} }
} }
if (_native.canSaveInformation) {
_save = inner->add(
object_ptr<Ui::Checkbox>(
inner,
tr::lng_payments_save_information(tr::now),
false),
st::paymentsSaveCheckboxPadding);
}
return inner; return inner;
} }

View file

@ -15,6 +15,7 @@ namespace Ui {
class ScrollArea; class ScrollArea;
class FadeShadow; class FadeShadow;
class RoundButton; class RoundButton;
class Checkbox;
} // namespace Ui } // namespace Ui
namespace Payments::Ui { namespace Payments::Ui {
@ -62,6 +63,7 @@ private:
std::unique_ptr<Field> _name; std::unique_ptr<Field> _name;
std::unique_ptr<Field> _country; std::unique_ptr<Field> _country;
std::unique_ptr<Field> _zip; std::unique_ptr<Field> _zip;
Checkbox *_save = nullptr;
CardField _focusField = CardField::Number; CardField _focusField = CardField::Number;

View file

@ -229,6 +229,18 @@ void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) {
showEditCard(method.native, CardField::Number); showEditCard(method.native, CardField::Number);
} else if (!showWebview(method.url, true)) { } else if (!showWebview(method.url, true)) {
// #TODO payments errors not supported // #TODO payments errors not supported
} else if (method.canSaveInformation) {
const auto &padding = st::paymentsPanelPadding;
_saveWebviewInformation = CreateChild<Checkbox>(
_webviewBottom.get(),
tr::lng_payments_save_information(tr::now),
false);
const auto height = padding.top()
+ _saveWebviewInformation->heightNoMargins()
+ padding.bottom();
_saveWebviewInformation->moveToLeft(padding.right(), padding.top());
_saveWebviewInformation->show();
_webviewBottom->resize(_webviewBottom->width(), height);
} }
} }
@ -244,7 +256,17 @@ bool Panel::showWebview(const QString &url, bool allowBack) {
bool Panel::createWebview() { bool Panel::createWebview() {
auto container = base::make_unique_q<RpWidget>(_widget.get()); auto container = base::make_unique_q<RpWidget>(_widget.get());
container->setGeometry(_widget->innerGeometry()); _webviewBottom = std::make_unique<RpWidget>(_widget.get());
const auto bottom = _webviewBottom.get();
bottom->show();
bottom->heightValue(
) | rpl::start_with_next([=, raw = container.get()](int height) {
const auto inner = _widget->innerGeometry();
bottom->move(inner.x(), inner.y() + inner.height() - height);
raw->resize(inner.width(), inner.height() - height);
bottom->resizeToWidth(inner.width());
}, bottom->lifetime());
container->show(); container->show();
_webview = std::make_unique<Webview::Window>( _webview = std::make_unique<Webview::Window>(
@ -257,6 +279,9 @@ bool Panel::createWebview() {
if (_webview.get() == raw) { if (_webview.get() == raw) {
_webview = nullptr; _webview = nullptr;
} }
if (_webviewBottom.get() == bottom) {
_webviewBottom = nullptr;
}
}); });
if (!raw->widget()) { if (!raw->widget()) {
return false; return false;
@ -268,7 +293,9 @@ bool Panel::createWebview() {
}, container->lifetime()); }, container->lifetime());
raw->setMessageHandler([=](const QJsonDocument &message) { raw->setMessageHandler([=](const QJsonDocument &message) {
_delegate->panelWebviewMessage(message); const auto save = _saveWebviewInformation
&& _saveWebviewInformation->checked();
_delegate->panelWebviewMessage(message, save);
}); });
raw->setNavigationHandler([=](const QString &uri) { raw->setNavigationHandler([=](const QString &uri) {
@ -308,6 +335,22 @@ void Panel::choosePaymentMethod(const PaymentMethodDetails &method) {
})); }));
} }
void Panel::askSetPassword() {
showBox(Box([=](not_null<Ui::GenericBox*> box) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_payments_need_password(),
st::boxLabel),
st::boxPadding);
box->addButton(tr::lng_continue(), [=] {
_delegate->panelSetPassword();
box->closeBox();
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}));
}
void Panel::showEditCard( void Panel::showEditCard(
const NativeMethodDetails &native, const NativeMethodDetails &native,
CardField field) { CardField field) {

View file

@ -10,8 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h" #include "base/object_ptr.h"
namespace Ui { namespace Ui {
class RpWidget;
class SeparatePanel; class SeparatePanel;
class BoxContent; class BoxContent;
class Checkbox;
} // namespace Ui } // namespace Ui
namespace Webview { namespace Webview {
@ -65,6 +67,7 @@ public:
void chooseShippingOption(const ShippingOptions &options); void chooseShippingOption(const ShippingOptions &options);
void chooseTips(const Invoice &invoice); void chooseTips(const Invoice &invoice);
void choosePaymentMethod(const PaymentMethodDetails &method); void choosePaymentMethod(const PaymentMethodDetails &method);
void askSetPassword();
bool showWebview(const QString &url, bool allowBack); bool showWebview(const QString &url, bool allowBack);
@ -81,6 +84,8 @@ private:
const not_null<PanelDelegate*> _delegate; const not_null<PanelDelegate*> _delegate;
std::unique_ptr<SeparatePanel> _widget; std::unique_ptr<SeparatePanel> _widget;
std::unique_ptr<Webview::Window> _webview; std::unique_ptr<Webview::Window> _webview;
std::unique_ptr<RpWidget> _webviewBottom;
QPointer<Checkbox> _saveWebviewInformation;
QPointer<FormSummary> _weakFormSummary; QPointer<FormSummary> _weakFormSummary;
QPointer<EditInformation> _weakEditInformation; QPointer<EditInformation> _weakEditInformation;
QPointer<EditCard> _weakEditCard; QPointer<EditCard> _weakEditCard;

View file

@ -154,6 +154,7 @@ struct NativeMethodDetails {
bool needCountry = false; bool needCountry = false;
bool needZip = false; bool needZip = false;
bool needCardholderName = false; bool needCardholderName = false;
bool canSaveInformation = false;
}; };
struct PaymentMethodDetails { struct PaymentMethodDetails {
@ -161,6 +162,7 @@ struct PaymentMethodDetails {
NativeMethodDetails native; NativeMethodDetails native;
QString url; QString url;
bool ready = false; bool ready = false;
bool canSaveInformation = false;
}; };
enum class CardField { enum class CardField {

View file

@ -28,8 +28,11 @@ public:
virtual void panelRequestClose() = 0; virtual void panelRequestClose() = 0;
virtual void panelCloseSure() = 0; virtual void panelCloseSure() = 0;
virtual void panelSubmit() = 0; virtual void panelSubmit() = 0;
virtual void panelWebviewMessage(const QJsonDocument &message) = 0; virtual void panelWebviewMessage(
const QJsonDocument &message,
bool saveInformation) = 0;
virtual bool panelWebviewNavigationAttempt(const QString &uri) = 0; virtual bool panelWebviewNavigationAttempt(const QString &uri) = 0;
virtual void panelSetPassword() = 0;
virtual void panelCancelEdit() = 0; virtual void panelCancelEdit() = 0;
virtual void panelEditPaymentMethod() = 0; virtual void panelEditPaymentMethod() = 0;
@ -43,7 +46,9 @@ public:
virtual void panelChangeTips(int64 value) = 0; virtual void panelChangeTips(int64 value) = 0;
virtual void panelValidateInformation(RequestedInformation data) = 0; virtual void panelValidateInformation(RequestedInformation data) = 0;
virtual void panelValidateCard(Ui::UncheckedCardDetails data) = 0; virtual void panelValidateCard(
Ui::UncheckedCardDetails data,
bool saveInformation) = 0;
virtual void panelShowBox(object_ptr<BoxContent> box) = 0; virtual void panelShowBox(object_ptr<BoxContent> box) = 0;
virtual QString panelWebviewDataPath() = 0; virtual QString panelWebviewDataPath() = 0;