Handle native / non-native payment methods (same way).

This commit is contained in:
John Preston 2021-03-25 20:58:52 +04:00
parent 5e4bc200c2
commit 56031a6402
20 changed files with 360 additions and 355 deletions

View file

@ -1864,6 +1864,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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"; "lng_payments_payment_method" = "Payment Method";
"lng_payments_new_card" = "New Card...";
"lng_payments_payment_method_ph" = "Enter your card details"; "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_address_ph" = "Enter your shipping information";

View file

@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_form.h" #include "payments/payments_form.h"
#include "payments/ui/payments_panel.h" #include "payments/ui/payments_panel.h"
#include "payments/ui/payments_webview.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_account.h" #include "main/main_account.h"
#include "main/main_domain.h" #include "main/main_domain.h"
@ -17,10 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history.h" #include "history/history.h"
#include "core/local_url_handlers.h" // TryConvertUrlToLocal. #include "core/local_url_handlers.h" // TryConvertUrlToLocal.
#include "core/file_utilities.h" // File::OpenUrl.
#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"
@ -57,13 +54,6 @@ 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) {
@ -110,7 +100,7 @@ not_null<Ui::PanelDelegate*> CheckoutProcess::panelDelegate() {
} }
void CheckoutProcess::handleFormUpdate(const FormUpdate &update) { void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
v::match(update.data, [&](const FormReady &) { v::match(update, [&](const FormReady &) {
performInitialSilentValidation(); performInitialSilentValidation();
if (!_initialSilentValidation) { if (!_initialSilentValidation) {
showForm(); showForm();
@ -124,17 +114,12 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
_submitState = SubmitState::Validated; _submitState = SubmitState::Validated;
panelSubmit(); panelSubmit();
} }
}, [&](const PaymentMethodUpdate&) {
showForm();
}, [&](const VerificationNeeded &info) { }, [&](const VerificationNeeded &info) {
if (_webviewWindow) { if (!_panel->showWebview(info.url, false)) {
_webviewWindow->navigate(info.url); File::OpenUrl(info.url);
} else { panelCloseSure();
_webviewWindow = std::make_unique<Ui::WebviewWindow>(
webviewDataPath(),
info.url,
panelDelegate());
if (!_webviewWindow->shown()) {
// #TODO payments errors
}
} }
}, [&](const PaymentFinished &result) { }, [&](const PaymentFinished &result) {
const auto weak = base::make_weak(this); const auto weak = base::make_weak(this);
@ -249,7 +234,7 @@ void CheckoutProcess::panelSubmit() {
|| _submitState == SubmitState::Finishing) { || _submitState == SubmitState::Finishing) {
return; return;
} }
const auto &native = _form->nativePayment(); const auto &method = _form->paymentMethod();
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()) {
@ -264,24 +249,12 @@ void CheckoutProcess::panelSubmit() {
_submitState = SubmitState::Validation; _submitState = SubmitState::Validation;
_form->validateInformation(_form->savedInformation()); _form->validateInformation(_form->savedInformation());
return; return;
} else if (native } else if (!method.newCredentials && !method.savedCredentials) {
&& !native.newCredentials
&& !native.savedCredentials) {
editPaymentMethod(); editPaymentMethod();
return; return;
} }
_submitState = SubmitState::Finishing; _submitState = SubmitState::Finishing;
if (!native) { _form->submit();
_webviewWindow = std::make_unique<Ui::WebviewWindow>(
webviewDataPath(),
_form->details().url,
panelDelegate());
if (!_webviewWindow->shown()) {
// #TODO payments errors
}
} else if (native.newCredentials) {
_form->send(native.newCredentials.data);
}
} }
void CheckoutProcess::panelWebviewMessage(const QJsonDocument &message) { void CheckoutProcess::panelWebviewMessage(const QJsonDocument &message) {
@ -321,19 +294,25 @@ void CheckoutProcess::panelWebviewMessage(const QJsonDocument &message) {
"Not an object received in payment credentials.")); "Not an object received in payment credentials."));
return; return;
} }
const auto serializedCredentials = QJsonDocument( crl::on_main(this, [=] {
credentials.toObject() _form->setPaymentCredentials(NewCredentials{
).toJson(QJsonDocument::Compact); .title = title,
.data = QJsonDocument(
_form->send(serializedCredentials); credentials.toObject()
).toJson(QJsonDocument::Compact),
.saveOnServer = false, // #TODO payments save
});
});
} }
bool CheckoutProcess::panelWebviewNavigationAttempt(const QString &uri) { bool CheckoutProcess::panelWebviewNavigationAttempt(const QString &uri) {
if (Core::TryConvertUrlToLocal(uri) == uri) { if (Core::TryConvertUrlToLocal(uri) == uri) {
return true; return true;
} }
panelCloseSure(); crl::on_main(this, [=] {
App::wnd()->activate(); panelCloseSure();
App::wnd()->activate();
});
return false; return false;
} }
@ -346,46 +325,7 @@ void CheckoutProcess::panelEditPaymentMethod() {
} }
void CheckoutProcess::panelValidateCard(Ui::UncheckedCardDetails data) { void CheckoutProcess::panelValidateCard(Ui::UncheckedCardDetails data) {
Expects(_form->nativePayment().type == NativePayment::Type::Stripe); _form->validateCard(data);
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() {
@ -408,7 +348,7 @@ void CheckoutProcess::showForm() {
_panel->showForm( _panel->showForm(
_form->invoice(), _form->invoice(),
_form->savedInformation(), _form->savedInformation(),
_form->nativePayment().details, _form->paymentMethod().ui,
_form->shippingOptions()); _form->shippingOptions());
} }
@ -437,7 +377,7 @@ void CheckoutProcess::chooseShippingOption() {
} }
void CheckoutProcess::editPaymentMethod() { void CheckoutProcess::editPaymentMethod() {
_panel->choosePaymentMethod(_form->nativePayment().details); _panel->choosePaymentMethod(_form->paymentMethod().ui);
} }
void CheckoutProcess::panelChooseShippingOption() { void CheckoutProcess::panelChooseShippingOption() {
@ -474,7 +414,7 @@ void CheckoutProcess::performInitialSilentValidation() {
_form->validateInformation(saved); _form->validateInformation(saved);
} }
QString CheckoutProcess::webviewDataPath() const { QString CheckoutProcess::panelWebviewDataPath() {
return _session->domain().local().webviewDataPath(); return _session->domain().local().webviewDataPath();
} }

View file

@ -12,17 +12,12 @@ 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
namespace Payments::Ui { namespace Payments::Ui {
class Panel; class Panel;
class WebviewWindow;
enum class InformationField; enum class InformationField;
} // namespace Payments::Ui } // namespace Payments::Ui
@ -67,7 +62,6 @@ private:
void editPaymentMethod(); void editPaymentMethod();
void performInitialSilentValidation(); void performInitialSilentValidation();
[[nodiscard]] QString webviewDataPath() const;
void panelRequestClose() override; void panelRequestClose() override;
void panelCloseSure() override; void panelCloseSure() override;
@ -87,11 +81,11 @@ private:
void panelValidateCard(Ui::UncheckedCardDetails data) override; void panelValidateCard(Ui::UncheckedCardDetails data) override;
void panelShowBox(object_ptr<Ui::BoxContent> box) override; void panelShowBox(object_ptr<Ui::BoxContent> box) override;
QString panelWebviewDataPath() 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<Stripe::APIClient> _stripe;
SubmitState _submitState = SubmitState::None; SubmitState _submitState = SubmitState::None;
bool _initialSilentValidation = false; bool _initialSilentValidation = false;

View file

@ -10,6 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "stripe/stripe_api_client.h"
#include "stripe/stripe_error.h"
#include "stripe/stripe_token.h"
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
@ -67,6 +70,13 @@ namespace {
MTP_string(information.shippingAddress.postcode))); MTP_string(information.shippingAddress.postcode)));
} }
[[nodiscard]] QString CardTitle(const Stripe::Card &card) {
// Like server stores saved_credentials title.
return Stripe::CardBrandToString(card.brand()).toLower()
+ " *"
+ card.last4();
}
} // namespace } // namespace
Form::Form(not_null<Main::Session*> session, FullMsgId itemId) Form::Form(not_null<Main::Session*> session, FullMsgId itemId)
@ -76,6 +86,8 @@ Form::Form(not_null<Main::Session*> session, FullMsgId itemId)
requestForm(); requestForm();
} }
Form::~Form() = default;
void Form::requestForm() { void Form::requestForm() {
_api.request(MTPpayments_GetPaymentForm( _api.request(MTPpayments_GetPaymentForm(
MTP_int(_msgId) MTP_int(_msgId)
@ -84,7 +96,7 @@ void Form::requestForm() {
processForm(data); processForm(data);
}); });
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_updates.fire({ Error{ Error::Type::Form, error.type() } }); _updates.fire(Error{ Error::Type::Form, error.type() });
}).send(); }).send();
} }
@ -105,8 +117,8 @@ void Form::processForm(const MTPDpayments_paymentForm &data) {
processSavedCredentials(data); processSavedCredentials(data);
}); });
} }
fillNativePaymentInformation(); fillPaymentMethodInformation();
_updates.fire({ FormReady{} }); _updates.fire(FormReady{});
} }
void Form::processInvoice(const MTPDinvoice &data) { void Form::processInvoice(const MTPDinvoice &data) {
@ -156,30 +168,32 @@ void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) {
void Form::processSavedCredentials( void Form::processSavedCredentials(
const MTPDpaymentSavedCredentialsCard &data) { const MTPDpaymentSavedCredentialsCard &data) {
// #TODO payments not yet supported // #TODO payments save
//_nativePayment.savedCredentials = SavedCredentials{ //_nativePayment.savedCredentials = SavedCredentials{
// .id = qs(data.vid()), // .id = qs(data.vid()),
// .title = qs(data.vtitle()), // .title = qs(data.vtitle()),
//}; //};
refreshNativePaymentDetails(); refreshPaymentMethodDetails();
} }
void Form::refreshNativePaymentDetails() { void Form::refreshPaymentMethodDetails() {
const auto &saved = _nativePayment.savedCredentials; const auto &saved = _paymentMethod.savedCredentials;
const auto &entered = _nativePayment.newCredentials; const auto &entered = _paymentMethod.newCredentials;
_nativePayment.details.credentialsTitle = entered _paymentMethod.ui.title = entered ? entered.title : saved.title;
? entered.title _paymentMethod.ui.ready = entered || saved;
: saved.title;
_nativePayment.details.ready = entered || saved;
} }
void Form::fillNativePaymentInformation() { void Form::fillPaymentMethodInformation() {
auto saved = std::move(_nativePayment.savedCredentials); _paymentMethod.native = NativePaymentMethod();
auto entered = std::move(_nativePayment.newCredentials); _paymentMethod.ui.native = Ui::NativeMethodDetails();
_nativePayment = NativePayment(); _paymentMethod.ui.url = _details.url;
if (_details.nativeProvider != "stripe") { if (_details.nativeProvider == "stripe") {
return; fillStripeNativeMethod();
} }
refreshPaymentMethodDetails();
}
void Form::fillStripeNativeMethod() {
auto error = QJsonParseError(); auto error = QJsonParseError();
auto document = QJsonDocument::fromJson( auto document = QJsonDocument::fromJson(
_details.nativeParamsJson, _details.nativeParamsJson,
@ -202,22 +216,22 @@ void Form::fillNativePaymentInformation() {
LOG(("Payment Error: No publishable_key in native_params.")); LOG(("Payment Error: No publishable_key in native_params."));
return; return;
} }
_nativePayment = NativePayment{ _paymentMethod.native = NativePaymentMethod{
.type = NativePayment::Type::Stripe, .data = StripePaymentMethod{
.stripePublishableKey = key, .publishableKey = 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(); _paymentMethod.ui.native = Ui::NativeMethodDetails{
.supported = true,
.needCountry = value(u"need_country").toBool(),
.needZip = value(u"need_zip").toBool(),
.needCardholderName = value(u"need_cardholder_name").toBool(),
};
} }
void Form::send(const QByteArray &serializedCredentials) { void Form::submit() {
Expects(!_paymentMethod.newCredentials.data.isEmpty()); // #TODO payments save
using Flag = MTPpayments_SendPaymentForm::Flag; using Flag = MTPpayments_SendPaymentForm::Flag;
_api.request(MTPpayments_SendPaymentForm( _api.request(MTPpayments_SendPaymentForm(
MTP_flags((_requestedInformationId.isEmpty() MTP_flags((_requestedInformationId.isEmpty()
@ -231,15 +245,15 @@ void Form::send(const QByteArray &serializedCredentials) {
MTP_string(_shippingOptions.selectedId), MTP_string(_shippingOptions.selectedId),
MTP_inputPaymentCredentials( MTP_inputPaymentCredentials(
MTP_flags(0), MTP_flags(0),
MTP_dataJSON(MTP_bytes(serializedCredentials))) MTP_dataJSON(MTP_bytes(_paymentMethod.newCredentials.data)))
)).done([=](const MTPpayments_PaymentResult &result) { )).done([=](const MTPpayments_PaymentResult &result) {
result.match([&](const MTPDpayments_paymentResult &data) { result.match([&](const MTPDpayments_paymentResult &data) {
_updates.fire({ PaymentFinished{ data.vupdates() } }); _updates.fire(PaymentFinished{ data.vupdates() });
}, [&](const MTPDpayments_paymentVerificationNeeded &data) { }, [&](const MTPDpayments_paymentVerificationNeeded &data) {
_updates.fire({ VerificationNeeded{ qs(data.vurl()) } }); _updates.fire(VerificationNeeded{ qs(data.vurl()) });
}); });
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_updates.fire({ Error{ Error::Type::Send, error.type() } }); _updates.fire(Error{ Error::Type::Send, error.type() });
}).send(); }).send();
} }
@ -273,18 +287,76 @@ void Form::validateInformation(const Ui::RequestedInformation &information) {
_shippingOptions.selectedId = _shippingOptions.list.front().id; _shippingOptions.selectedId = _shippingOptions.list.front().id;
} }
_savedInformation = _validatedInformation; _savedInformation = _validatedInformation;
_updates.fire({ ValidateFinished{} }); _updates.fire(ValidateFinished{});
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_validateRequestId = 0; _validateRequestId = 0;
_updates.fire({ Error{ Error::Type::Validate, error.type() } }); _updates.fire(Error{ Error::Type::Validate, error.type() });
}).send(); }).send();
} }
void Form::validateCard(const Ui::UncheckedCardDetails &details) {
Expects(!v::is_null(_paymentMethod.native.data));
const auto &native = _paymentMethod.native.data;
if (const auto stripe = std::get_if<StripePaymentMethod>(&native)) {
validateCard(*stripe, details);
} else {
Unexpected("Native payment provider in Form::validateCard.");
}
}
void Form::validateCard(
const StripePaymentMethod &method,
const Ui::UncheckedCardDetails &details) {
Expects(!method.publishableKey.isEmpty());
if (_stripe) {
return;
}
auto configuration = Stripe::PaymentConfiguration{
.publishableKey = method.publishableKey,
.companyName = "Telegram",
};
_stripe = std::make_unique<Stripe::APIClient>(std::move(configuration));
auto card = Stripe::CardParams{
.number = details.number,
.expMonth = details.expireMonth,
.expYear = details.expireYear,
.cvc = details.cvc,
.name = details.cardholderName,
.addressZip = details.addressZip,
.addressCountry = details.addressCountry,
};
_stripe->createTokenWithCard(std::move(card), crl::guard(this, [=](
Stripe::Token token,
Stripe::Error error) {
_stripe = nullptr;
if (error) {
LOG(("Stripe Error %1: %2 (%3)"
).arg(int(error.code())
).arg(error.description()
).arg(error.message()));
_updates.fire(Error{ Error::Type::Stripe, error.description() });
} else {
setPaymentCredentials({
.title = CardTitle(token.card()),
.data = QJsonDocument(QJsonObject{
{ "type", "card" },
{ "id", token.tokenId() },
}).toJson(QJsonDocument::Compact),
.saveOnServer = false, // #TODO payments save
});
}
}));
}
void Form::setPaymentCredentials(const NewCredentials &credentials) { void Form::setPaymentCredentials(const NewCredentials &credentials) {
Expects(!credentials.empty()); Expects(!credentials.empty());
_nativePayment.newCredentials = credentials; _paymentMethod.newCredentials = credentials;
refreshNativePaymentDetails(); refreshPaymentMethodDetails();
_updates.fire(PaymentMethodUpdate{});
} }
void Form::setShippingOption(const QString &id) { void Form::setShippingOption(const QString &id) {

View file

@ -8,8 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "payments/ui/payments_panel_data.h" #include "payments/ui/payments_panel_data.h"
#include "base/weak_ptr.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
namespace Stripe {
class APIClient;
} // namespace Stripe
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -58,55 +63,64 @@ struct NewCredentials {
} }
}; };
struct NativePayment { struct StripePaymentMethod {
enum class Type { QString publishableKey;
None, };
Stripe,
}; struct NativePaymentMethod {
Type type = Type::None; std::variant<
QString stripePublishableKey; v::null_t,
SavedCredentials savedCredentials; StripePaymentMethod> data;
NewCredentials newCredentials;
Ui::NativePaymentDetails details;
[[nodiscard]] bool valid() const { [[nodiscard]] bool valid() const {
return (type != Type::None); return !v::is_null(data);
} }
[[nodiscard]] explicit operator bool() const { [[nodiscard]] explicit operator bool() const {
return valid(); return valid();
} }
}; };
struct PaymentMethod {
NativePaymentMethod native;
SavedCredentials savedCredentials;
NewCredentials newCredentials;
Ui::PaymentMethodDetails ui;
};
struct FormReady {}; struct FormReady {};
struct ValidateFinished {}; struct ValidateFinished {};
struct Error { struct PaymentMethodUpdate {};
enum class Type {
Form,
Validate,
Send,
};
Type type = Type::Form;
QString id;
};
struct VerificationNeeded { struct VerificationNeeded {
QString url; QString url;
}; };
struct PaymentFinished { struct PaymentFinished {
MTPUpdates updates; MTPUpdates updates;
}; };
struct Error {
struct FormUpdate { enum class Type {
std::variant< Form,
FormReady, Validate,
VerificationNeeded, Stripe,
ValidateFinished, Send,
PaymentFinished, };
Error> data; Type type = Type::Form;
QString id;
}; };
class Form final { struct FormUpdate : std::variant<
FormReady,
ValidateFinished,
PaymentMethodUpdate,
VerificationNeeded,
PaymentFinished,
Error> {
using variant::variant;
};
class Form final : public base::has_weak_ptr {
public: public:
Form(not_null<Main::Session*> session, FullMsgId itemId); Form(not_null<Main::Session*> session, FullMsgId itemId);
~Form();
[[nodiscard]] const Ui::Invoice &invoice() const { [[nodiscard]] const Ui::Invoice &invoice() const {
return _invoice; return _invoice;
@ -117,8 +131,8 @@ public:
[[nodiscard]] const Ui::RequestedInformation &savedInformation() const { [[nodiscard]] const Ui::RequestedInformation &savedInformation() const {
return _savedInformation; return _savedInformation;
} }
[[nodiscard]] const NativePayment &nativePayment() const { [[nodiscard]] const PaymentMethod &paymentMethod() const {
return _nativePayment; return _paymentMethod;
} }
[[nodiscard]] const Ui::ShippingOptions &shippingOptions() const { [[nodiscard]] const Ui::ShippingOptions &shippingOptions() const {
return _shippingOptions; return _shippingOptions;
@ -129,9 +143,10 @@ public:
} }
void validateInformation(const Ui::RequestedInformation &information); void validateInformation(const Ui::RequestedInformation &information);
void validateCard(const Ui::UncheckedCardDetails &details);
void setPaymentCredentials(const NewCredentials &credentials); void setPaymentCredentials(const NewCredentials &credentials);
void setShippingOption(const QString &id); void setShippingOption(const QString &id);
void send(const QByteArray &serializedCredentials); void submit();
private: private:
void requestForm(); void requestForm();
@ -142,8 +157,13 @@ 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 fillPaymentMethodInformation();
void refreshNativePaymentDetails(); void fillStripeNativeMethod();
void refreshPaymentMethodDetails();
void validateCard(
const StripePaymentMethod &method,
const Ui::UncheckedCardDetails &details);
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
MTP::Sender _api; MTP::Sender _api;
@ -152,11 +172,13 @@ private:
Ui::Invoice _invoice; Ui::Invoice _invoice;
FormDetails _details; FormDetails _details;
Ui::RequestedInformation _savedInformation; Ui::RequestedInformation _savedInformation;
NativePayment _nativePayment; PaymentMethod _paymentMethod;
Ui::RequestedInformation _validatedInformation; Ui::RequestedInformation _validatedInformation;
mtpRequestId _validateRequestId = 0; mtpRequestId _validateRequestId = 0;
std::unique_ptr<Stripe::APIClient> _stripe;
Ui::ShippingOptions _shippingOptions; Ui::ShippingOptions _shippingOptions;
QString _requestedInformationId; QString _requestedInformationId;

View file

@ -11,6 +11,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Stripe { namespace Stripe {
Error::Code Error::code() const {
return _code;
}
QString Error::description() const {
return _description;
}
QString Error::message() const {
return _message;
}
QString Error::parameter() const {
return _parameter;
}
Error Error::None() {
return Error(Code::None, {}, {}, {});
}
Error Error::DecodedObjectFromResponse(QJsonObject object) { Error Error::DecodedObjectFromResponse(QJsonObject object) {
const auto entry = object.value("error"); const auto entry = object.value("error");
if (!entry.isObject()) { if (!entry.isObject()) {
@ -80,4 +100,8 @@ Error Error::DecodedObjectFromResponse(QJsonObject object) {
} }
} }
bool Error::empty() const {
return (_code == Code::None);
}
} // namespace Stripe } // namespace Stripe

View file

@ -42,14 +42,15 @@ public:
, _parameter(parameter) { , _parameter(parameter) {
} }
[[nodiscard]] static Error None() { [[nodiscard]] Code code() const;
return Error(Code::None, QString(), QString()); [[nodiscard]] QString description() const;
} [[nodiscard]] QString message() const;
[[nodiscard]] QString parameter() const;
[[nodiscard]] static Error None();
[[nodiscard]] static Error DecodedObjectFromResponse(QJsonObject object); [[nodiscard]] static Error DecodedObjectFromResponse(QJsonObject object);
[[nodiscard]] bool empty() const { [[nodiscard]] bool empty() const;
return (_code == Code::None);
}
[[nodiscard]] explicit operator bool() const { [[nodiscard]] explicit operator bool() const {
return !empty(); return !empty();
} }

View file

@ -35,7 +35,7 @@ constexpr auto kMaxPostcodeSize = 10;
EditCard::EditCard( EditCard::EditCard(
QWidget *parent, QWidget *parent,
const NativePaymentDetails &native, const NativeMethodDetails &native,
CardField field, CardField field,
not_null<PanelDelegate*> delegate) not_null<PanelDelegate*> delegate)
: _delegate(delegate) : _delegate(delegate)

View file

@ -31,7 +31,7 @@ class EditCard final : public RpWidget {
public: public:
EditCard( EditCard(
QWidget *parent, QWidget *parent,
const NativePaymentDetails &native, const NativeMethodDetails &native,
CardField field, CardField field,
not_null<PanelDelegate*> delegate); not_null<PanelDelegate*> delegate);
@ -52,7 +52,7 @@ private:
[[nodiscard]] UncheckedCardDetails collect() const; [[nodiscard]] UncheckedCardDetails collect() const;
const not_null<PanelDelegate*> _delegate; const not_null<PanelDelegate*> _delegate;
NativePaymentDetails _native; NativeMethodDetails _native;
object_ptr<ScrollArea> _scroll; object_ptr<ScrollArea> _scroll;
object_ptr<FadeShadow> _topShadow; object_ptr<FadeShadow> _topShadow;

View file

@ -30,12 +30,12 @@ FormSummary::FormSummary(
QWidget *parent, QWidget *parent,
const Invoice &invoice, const Invoice &invoice,
const RequestedInformation &current, const RequestedInformation &current,
const NativePaymentDetails &native, const PaymentMethodDetails &method,
const ShippingOptions &options, const ShippingOptions &options,
not_null<PanelDelegate*> delegate) not_null<PanelDelegate*> delegate)
: _delegate(delegate) : _delegate(delegate)
, _invoice(invoice) , _invoice(invoice)
, _native(native) , _method(method)
, _options(options) , _options(options)
, _information(current) , _information(current)
, _scroll(this, st::passportPanelScroll) , _scroll(this, st::passportPanelScroll)
@ -136,20 +136,18 @@ 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));
const auto method = inner->add(object_ptr<FormRow>(inner)); method->addClickHandler([=] {
method->addClickHandler([=] { _delegate->panelEditPaymentMethod();
_delegate->panelEditPaymentMethod(); });
}); method->updateContent(
method->updateContent( tr::lng_payments_payment_method(tr::now),
tr::lng_payments_payment_method(tr::now), (_method.ready
(_native.ready ? _method.title
? _native.credentialsTitle : tr::lng_payments_payment_method_ph(tr::now)),
: tr::lng_payments_payment_method_ph(tr::now)), _method.ready,
_native.ready, false,
false, anim::type::instant);
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([=] {

View file

@ -29,7 +29,7 @@ public:
QWidget *parent, QWidget *parent,
const Invoice &invoice, const Invoice &invoice,
const RequestedInformation &current, const RequestedInformation &current,
const NativePaymentDetails &native, const PaymentMethodDetails &method,
const ShippingOptions &options, const ShippingOptions &options,
not_null<PanelDelegate*> delegate); not_null<PanelDelegate*> delegate);
@ -45,7 +45,7 @@ private:
const not_null<PanelDelegate*> _delegate; const not_null<PanelDelegate*> _delegate;
Invoice _invoice; Invoice _invoice;
NativePaymentDetails _native; PaymentMethodDetails _method;
ShippingOptions _options; ShippingOptions _options;
RequestedInformation _information; RequestedInformation _information;
object_ptr<ScrollArea> _scroll; object_ptr<ScrollArea> _scroll;

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "webview/webview_embed.h"
#include "styles/style_payments.h" #include "styles/style_payments.h"
#include "styles/style_passport.h" #include "styles/style_passport.h"
@ -37,7 +38,10 @@ Panel::Panel(not_null<PanelDelegate*> delegate)
}, _widget->lifetime()); }, _widget->lifetime());
} }
Panel::~Panel() = default; Panel::~Panel() {
// Destroy _widget before _webview.
_widget = nullptr;
}
void Panel::requestActivate() { void Panel::requestActivate() {
_widget->showAndActivate(); _widget->showAndActivate();
@ -46,14 +50,14 @@ void Panel::requestActivate() {
void Panel::showForm( void Panel::showForm(
const Invoice &invoice, const Invoice &invoice,
const RequestedInformation &current, const RequestedInformation &current,
const NativePaymentDetails &native, const PaymentMethodDetails &method,
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, method,
options, options,
_delegate)); _delegate));
_widget->setBackAllowed(false); _widget->setBackAllowed(false);
@ -113,23 +117,84 @@ void Panel::chooseShippingOption(const ShippingOptions &options) {
})); }));
} }
void Panel::choosePaymentMethod(const NativePaymentDetails &native) { void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) {
Expects(native.supported); if (method.native.supported) {
showEditCard(method.native, CardField::Number);
} else if (!showWebview(method.url, true)) {
// #TODO payments errors not supported
}
}
if (!native.ready) { bool Panel::showWebview(const QString &url, bool allowBack) {
showEditCard(native, CardField::Number); if (!_webview && !createWebview()) {
return false;
}
_webview->navigate(url);
_widget->setBackAllowed(allowBack);
return true;
}
bool Panel::createWebview() {
auto container = base::make_unique_q<RpWidget>(_widget.get());
container->setGeometry(_widget->innerGeometry());
container->show();
_webview = std::make_unique<Webview::Window>(
container.get(),
Webview::WindowConfig{
.userDataPath = _delegate->panelWebviewDataPath(),
});
const auto raw = _webview.get();
QObject::connect(container.get(), &QObject::destroyed, [=] {
if (_webview.get() == raw) {
_webview = nullptr;
}
});
if (!raw->widget()) {
return false;
}
container->geometryValue(
) | rpl::start_with_next([=](QRect geometry) {
raw->widget()->setGeometry(geometry);
}, container->lifetime());
raw->setMessageHandler([=](const QJsonDocument &message) {
_delegate->panelWebviewMessage(message);
});
raw->setNavigationHandler([=](const QString &uri) {
return _delegate->panelWebviewNavigationAttempt(uri);
});
raw->init(R"(
window.TelegramWebviewProxy = {
postEvent: function(eventType, eventData) {
if (window.external && window.external.invoke) {
window.external.invoke(JSON.stringify([eventType, eventData]));
}
}
};)");
_widget->showInner(std::move(container));
return true;
}
void Panel::choosePaymentMethod(const PaymentMethodDetails &method) {
if (!method.ready) {
showEditPaymentMethod(method);
return; return;
} }
const auto title = native.credentialsTitle;
showBox(Box([=](not_null<Ui::GenericBox*> box) { showBox(Box([=](not_null<Ui::GenericBox*> box) {
const auto save = [=](int option) { const auto save = [=](int option) {
if (option) { if (option) {
showEditCard(native, CardField::Number); showEditPaymentMethod(method);
} }
}; };
SingleChoiceBox(box, { SingleChoiceBox(box, {
.title = tr::lng_payments_payment_method(), .title = tr::lng_payments_payment_method(),
.options = { native.credentialsTitle, "New Card..." }, // #TODO payments lang .options = { method.title, tr::lng_payments_new_card(tr::now) },
.initialSelection = 0, .initialSelection = 0,
.callback = save, .callback = save,
}); });
@ -137,8 +202,10 @@ void Panel::choosePaymentMethod(const NativePaymentDetails &native) {
} }
void Panel::showEditCard( void Panel::showEditCard(
const NativePaymentDetails &native, const NativeMethodDetails &native,
CardField field) { CardField field) {
Expects(native.supported);
auto edit = base::make_unique_q<EditCard>( auto edit = base::make_unique_q<EditCard>(
_widget.get(), _widget.get(),
native, native,
@ -151,7 +218,7 @@ void Panel::showEditCard(
} }
void Panel::showCardError( void Panel::showCardError(
const NativePaymentDetails &native, const NativeMethodDetails &native,
CardField field) { CardField field) {
if (_weakEditCard) { if (_weakEditCard) {
_weakEditCard->showError(field); _weakEditCard->showError(field);

View file

@ -14,6 +14,10 @@ class SeparatePanel;
class BoxContent; class BoxContent;
} // namespace Ui } // namespace Ui
namespace Webview {
class Window;
} // namespace Webview
namespace Payments::Ui { namespace Payments::Ui {
using namespace ::Ui; using namespace ::Ui;
@ -26,7 +30,8 @@ enum class InformationField;
enum class CardField; enum class CardField;
class EditInformation; class EditInformation;
class EditCard; class EditCard;
struct NativePaymentDetails; struct PaymentMethodDetails;
struct NativeMethodDetails;
class Panel final { class Panel final {
public: public:
@ -38,7 +43,7 @@ public:
void showForm( void showForm(
const Invoice &invoice, const Invoice &invoice,
const RequestedInformation &current, const RequestedInformation &current,
const NativePaymentDetails &native, const PaymentMethodDetails &method,
const ShippingOptions &options); const ShippingOptions &options);
void showEditInformation( void showEditInformation(
const Invoice &invoice, const Invoice &invoice,
@ -48,14 +53,17 @@ public:
const Invoice &invoice, const Invoice &invoice,
const RequestedInformation &current, const RequestedInformation &current,
InformationField field); InformationField field);
void showEditPaymentMethod(const PaymentMethodDetails &method);
void showEditCard( void showEditCard(
const NativePaymentDetails &native, const NativeMethodDetails &native,
CardField field); CardField field);
void showCardError( void showCardError(
const NativePaymentDetails &native, const NativeMethodDetails &native,
CardField field); CardField field);
void chooseShippingOption(const ShippingOptions &options); void chooseShippingOption(const ShippingOptions &options);
void choosePaymentMethod(const NativePaymentDetails &native); void choosePaymentMethod(const PaymentMethodDetails &method);
bool showWebview(const QString &url, bool allowBack);
[[nodiscard]] rpl::producer<> backRequests() const; [[nodiscard]] rpl::producer<> backRequests() const;
@ -65,8 +73,11 @@ public:
[[nodiscard]] rpl::lifetime &lifetime(); [[nodiscard]] rpl::lifetime &lifetime();
private: private:
bool createWebview();
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;
QPointer<EditInformation> _weakEditInformation; QPointer<EditInformation> _weakEditInformation;
QPointer<EditCard> _weakEditCard; QPointer<EditCard> _weakEditCard;

View file

@ -115,15 +115,20 @@ enum class InformationField {
Phone, Phone,
}; };
struct NativePaymentDetails { struct NativeMethodDetails {
QString credentialsTitle;
bool ready = false;
bool supported = false; bool supported = false;
bool needCountry = false; bool needCountry = false;
bool needZip = false; bool needZip = false;
bool needCardholderName = false; bool needCardholderName = false;
}; };
struct PaymentMethodDetails {
QString title;
NativeMethodDetails native;
QString url;
bool ready = false;
};
enum class CardField { enum class CardField {
Number, Number,
CVC, CVC,

View file

@ -42,6 +42,8 @@ public:
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) = 0;
virtual void panelShowBox(object_ptr<BoxContent> box) = 0; virtual void panelShowBox(object_ptr<BoxContent> box) = 0;
virtual QString panelWebviewDataPath() = 0;
}; };
} // namespace Payments::Ui } // namespace Payments::Ui

View file

@ -1,97 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "payments/ui/payments_webview.h"
#include "payments/ui/payments_panel_delegate.h"
#include "webview/webview_embed.h"
#include "webview/webview_interface.h"
#include "ui/widgets/window.h"
#include "ui/toast/toast.h"
#include "lang/lang_keys.h"
namespace Payments::Ui {
using namespace ::Ui;
class PanelDelegate;
WebviewWindow::WebviewWindow(
const QString &userDataPath,
const QString &url,
not_null<PanelDelegate*> delegate) {
if (!url.startsWith("https://", Qt::CaseInsensitive)) {
return;
}
const auto window = &_window;
window->setGeometry({
style::ConvertScale(100),
style::ConvertScale(100),
style::ConvertScale(640),
style::ConvertScale(480)
});
window->show();
window->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
if (e->type() == QEvent::Close) {
delegate->panelCloseSure();
}
}, window->lifetime());
const auto body = window->body();
body->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
QPainter(body).fillRect(clip, st::windowBg);
}, body->lifetime());
const auto path =
_webview = Ui::CreateChild<Webview::Window>(
window,
window,
Webview::WindowConfig{ .userDataPath = userDataPath });
if (!_webview->widget()) {
return;
}
body->geometryValue(
) | rpl::start_with_next([=](QRect geometry) {
_webview->widget()->setGeometry(geometry);
}, body->lifetime());
_webview->setMessageHandler([=](const QJsonDocument &message) {
delegate->panelWebviewMessage(message);
});
_webview->setNavigationHandler([=](const QString &uri) {
return delegate->panelWebviewNavigationAttempt(uri);
});
_webview->init(R"(
window.TelegramWebviewProxy = {
postEvent: function(eventType, eventData) {
if (window.external && window.external.invoke) {
window.external.invoke(JSON.stringify([eventType, eventData]));
}
}
};)");
navigate(url);
}
[[nodiscard]] bool WebviewWindow::shown() const {
return _webview && _webview->widget();
}
void WebviewWindow::navigate(const QString &url) {
if (shown()) {
_webview->navigate(url);
}
}
} // namespace Payments::Ui

View file

@ -1,38 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/widgets/window.h"
namespace Webview {
class Window;
} // namespace Webview
namespace Payments::Ui {
using namespace ::Ui;
class PanelDelegate;
class WebviewWindow final {
public:
WebviewWindow(
const QString &userDataPath,
const QString &url,
not_null<PanelDelegate*> delegate);
[[nodiscard]] bool shown() const;
void navigate(const QString &url);
private:
Ui::Window _window;
Webview::Window *_webview = nullptr;
};
} // namespace Payments::Ui

View file

@ -344,6 +344,10 @@ void SeparatePanel::setInnerSize(QSize size) {
} }
} }
QRect SeparatePanel::innerGeometry() const {
return _body->geometry();
}
void SeparatePanel::initGeometry(QSize size) { void SeparatePanel::initGeometry(QSize size) {
const auto active = QApplication::activeWindow(); const auto active = QApplication::activeWindow();
const auto center = !active const auto center = !active

View file

@ -28,6 +28,7 @@ public:
void setTitle(rpl::producer<QString> title); void setTitle(rpl::producer<QString> title);
void setInnerSize(QSize size); void setInnerSize(QSize size);
[[nodiscard]] QRect innerGeometry() const;
void setHideOnDeactivate(bool hideOnDeactivate); void setHideOnDeactivate(bool hideOnDeactivate);
void showAndActivate(); void showAndActivate();
@ -41,9 +42,9 @@ public:
void showToast(const TextWithEntities &text); void showToast(const TextWithEntities &text);
void destroyLayer(); void destroyLayer();
rpl::producer<> backRequests() const; [[nodiscard]] rpl::producer<> backRequests() const;
rpl::producer<> closeRequests() const; [[nodiscard]] rpl::producer<> closeRequests() const;
rpl::producer<> closeEvents() const; [[nodiscard]] rpl::producer<> closeEvents() const;
void setBackAllowed(bool allowed); void setBackAllowed(bool allowed);
protected: protected:

View file

@ -79,8 +79,6 @@ PRIVATE
payments/ui/payments_panel.h payments/ui/payments_panel.h
payments/ui/payments_panel_data.h payments/ui/payments_panel_data.h
payments/ui/payments_panel_delegate.h payments/ui/payments_panel_delegate.h
payments/ui/payments_webview.cpp
payments/ui/payments_webview.h
platform/mac/file_bookmark_mac.h platform/mac/file_bookmark_mac.h
platform/mac/file_bookmark_mac.mm platform/mac/file_bookmark_mac.mm