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_pay_amount" = "Pay {amount}";
"lng_payments_payment_method" = "Payment Method";
"lng_payments_new_card" = "New Card...";
"lng_payments_payment_method_ph" = "Enter your card details";
"lng_payments_shipping_address" = "Shipping Information";
"lng_payments_shipping_address_ph" = "Enter your shipping information";

View file

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

View file

@ -12,17 +12,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem;
namespace Stripe {
class APIClient;
} // namespace Stripe
namespace Main {
class Session;
} // namespace Main
namespace Payments::Ui {
class Panel;
class WebviewWindow;
enum class InformationField;
} // namespace Payments::Ui
@ -67,7 +62,6 @@ private:
void editPaymentMethod();
void performInitialSilentValidation();
[[nodiscard]] QString webviewDataPath() const;
void panelRequestClose() override;
void panelCloseSure() override;
@ -87,11 +81,11 @@ private:
void panelValidateCard(Ui::UncheckedCardDetails data) override;
void panelShowBox(object_ptr<Ui::BoxContent> box) override;
QString panelWebviewDataPath() override;
const not_null<Main::Session*> _session;
const std::unique_ptr<Form> _form;
const std::unique_ptr<Ui::Panel> _panel;
std::unique_ptr<Ui::WebviewWindow> _webviewWindow;
std::unique_ptr<Stripe::APIClient> _stripe;
SubmitState _submitState = SubmitState::None;
bool _initialSilentValidation = false;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -29,7 +29,7 @@ public:
QWidget *parent,
const Invoice &invoice,
const RequestedInformation &current,
const NativePaymentDetails &native,
const PaymentMethodDetails &method,
const ShippingOptions &options,
not_null<PanelDelegate*> delegate);
@ -45,7 +45,7 @@ private:
const not_null<PanelDelegate*> _delegate;
Invoice _invoice;
NativePaymentDetails _native;
PaymentMethodDetails _method;
ShippingOptions _options;
RequestedInformation _information;
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/boxes/single_choice_box.h"
#include "lang/lang_keys.h"
#include "webview/webview_embed.h"
#include "styles/style_payments.h"
#include "styles/style_passport.h"
@ -37,7 +38,10 @@ Panel::Panel(not_null<PanelDelegate*> delegate)
}, _widget->lifetime());
}
Panel::~Panel() = default;
Panel::~Panel() {
// Destroy _widget before _webview.
_widget = nullptr;
}
void Panel::requestActivate() {
_widget->showAndActivate();
@ -46,14 +50,14 @@ void Panel::requestActivate() {
void Panel::showForm(
const Invoice &invoice,
const RequestedInformation &current,
const NativePaymentDetails &native,
const PaymentMethodDetails &method,
const ShippingOptions &options) {
_widget->showInner(
base::make_unique_q<FormSummary>(
_widget.get(),
invoice,
current,
native,
method,
options,
_delegate));
_widget->setBackAllowed(false);
@ -113,23 +117,84 @@ void Panel::chooseShippingOption(const ShippingOptions &options) {
}));
}
void Panel::choosePaymentMethod(const NativePaymentDetails &native) {
Expects(native.supported);
void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) {
if (method.native.supported) {
showEditCard(method.native, CardField::Number);
} else if (!showWebview(method.url, true)) {
// #TODO payments errors not supported
}
}
if (!native.ready) {
showEditCard(native, CardField::Number);
bool Panel::showWebview(const QString &url, bool allowBack) {
if (!_webview && !createWebview()) {
return false;
}
_webview->navigate(url);
_widget->setBackAllowed(allowBack);
return true;
}
bool Panel::createWebview() {
auto container = base::make_unique_q<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;
}
const auto title = native.credentialsTitle;
showBox(Box([=](not_null<Ui::GenericBox*> box) {
const auto save = [=](int option) {
if (option) {
showEditCard(native, CardField::Number);
showEditPaymentMethod(method);
}
};
SingleChoiceBox(box, {
.title = tr::lng_payments_payment_method(),
.options = { native.credentialsTitle, "New Card..." }, // #TODO payments lang
.options = { method.title, tr::lng_payments_new_card(tr::now) },
.initialSelection = 0,
.callback = save,
});
@ -137,8 +202,10 @@ void Panel::choosePaymentMethod(const NativePaymentDetails &native) {
}
void Panel::showEditCard(
const NativePaymentDetails &native,
const NativeMethodDetails &native,
CardField field) {
Expects(native.supported);
auto edit = base::make_unique_q<EditCard>(
_widget.get(),
native,
@ -151,7 +218,7 @@ void Panel::showEditCard(
}
void Panel::showCardError(
const NativePaymentDetails &native,
const NativeMethodDetails &native,
CardField field) {
if (_weakEditCard) {
_weakEditCard->showError(field);

View file

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

View file

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

View file

@ -42,6 +42,8 @@ public:
virtual void panelValidateInformation(RequestedInformation data) = 0;
virtual void panelValidateCard(Ui::UncheckedCardDetails data) = 0;
virtual void panelShowBox(object_ptr<BoxContent> box) = 0;
virtual QString panelWebviewDataPath() = 0;
};
} // 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) {
const auto active = QApplication::activeWindow();
const auto center = !active

View file

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

View file

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