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