diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index e55fb6b858..0c6690cee0 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -272,9 +272,25 @@ void CheckoutProcess::handleError(const Error &error) { } else if (id == u"ProcessingError"_q) { showToast({ "Sorry, a processing error occurred." }); } else { - showToast({ "Error: " + id }); + showToast({ "Stripe Error: " + id }); } } break; + case Error::Type::SmartGlocal: { + //using Field = Ui::CardField; + //if (id == u"InvalidNumber"_q || id == u"IncorrectNumber"_q) { + // showCardError(Field::Number); + //} else if (id == u"InvalidCVC"_q || id == u"IncorrectCVC"_q) { + // showCardError(Field::Cvc); + //} else if (id == u"InvalidExpiryMonth"_q + // || id == u"InvalidExpiryYear"_q + // || id == u"ExpiredCard"_q) { + // showCardError(Field::ExpireDate); + //} else if (id == u"CardDeclined"_q) { + // showToast({ tr::lng_payments_card_declined(tr::now) }); + //} else { + showToast({ "SmartGlocal Error: " + id }); + //} + } break; case Error::Type::TmpPassword: if (const auto box = _enterPasswordBox.data()) { if (!box->handleCustomCheckError(id)) { diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 33b1ded9f7..c101f0b7c6 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -21,6 +21,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "stripe/stripe_error.h" #include "stripe/stripe_token.h" #include "stripe/stripe_card_validator.h" +#include "smartglocal/smartglocal_api_client.h" +#include "smartglocal/smartglocal_error.h" +#include "smartglocal/smartglocal_token.h" #include "storage/storage_account.h" #include "ui/image/image.h" #include "apiwrap.h" @@ -96,6 +99,13 @@ constexpr auto kPasswordPeriod = 15 * TimeId(60); + card.last4(); } +[[nodiscard]] QString CardTitle(const SmartGlocal::Card &card) { + // Like server stores saved_credentials title. + return card.type().toLower() + + " *" + + SmartGlocal::Last4(card); +} + } // namespace Form::Form(not_null peer, MsgId itemId, bool receipt) @@ -427,33 +437,39 @@ void Form::fillPaymentMethodInformation() { _paymentMethod.native = NativePaymentMethod(); _paymentMethod.ui.native = Ui::NativeMethodDetails(); _paymentMethod.ui.url = _details.url; - if (_details.nativeProvider == "stripe") { - fillStripeNativeMethod(); + if (!_details.nativeProvider.isEmpty()) { + 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())); + } else if (!document.isObject()) { + LOG(("Payment Error: Not an object in native_params.")); + } else { + const auto object = document.object(); + if (_details.nativeProvider == "stripe") { + fillStripeNativeMethod(object); + } else if (_details.nativeProvider == "smartglocal") { + fillSmartGlocalNativeMethod(object); + } else { + LOG(("Payment Error: Unknown native provider '%1'." + ).arg(_details.nativeProvider)); + } + } } refreshPaymentMethodDetails(); } -void Form::fillStripeNativeMethod() { - 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(); +void Form::fillStripeNativeMethod(QJsonObject 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.")); + LOG(("Payment Error: No publishable_key in stripe native_params.")); return; } _paymentMethod.native = NativePaymentMethod{ @@ -469,6 +485,29 @@ void Form::fillStripeNativeMethod() { }; } +void Form::fillSmartGlocalNativeMethod(QJsonObject object) { + const auto value = [&](QStringView key) { + return object.value(key); + }; + const auto key = value(u"public_token").toString(); + if (key.isEmpty()) { + LOG(("Payment Error: " + "No public_token in smartglocal native_params.")); + return; + } + _paymentMethod.native = NativePaymentMethod{ + .data = SmartGlocalPaymentMethod{ + .publicToken = key, + }, + }; + _paymentMethod.ui.native = Ui::NativeMethodDetails{ + .supported = true, + .needCountry = false, + .needZip = false, + .needCardholderName = false, + }; +} + void Form::submit() { Expects(_paymentMethod.newCredentials || _paymentMethod.savedCredentials); @@ -602,6 +641,7 @@ bool Form::hasChanges() const { : _information; return (information != _savedInformation) || (_stripe != nullptr) + || (_smartglocal != nullptr) || !_paymentMethod.newCredentials.empty(); } @@ -655,7 +695,10 @@ void Form::validateCard( return; } const auto &native = _paymentMethod.native.data; - if (const auto stripe = std::get_if(&native)) { + if (const auto smartglocal = std::get_if( + &native)) { + validateCard(*smartglocal, details, saveInformation); + } else if (const auto stripe = std::get_if(&native)) { validateCard(*stripe, details, saveInformation); } else { Unexpected("Native payment provider in Form::validateCard."); @@ -755,6 +798,57 @@ void Form::validateCard( })); } +void Form::validateCard( + const SmartGlocalPaymentMethod &method, + const Ui::UncheckedCardDetails &details, + bool saveInformation) { + Expects(!method.publicToken.isEmpty()); + + if (_smartglocal) { + return; + } + auto configuration = SmartGlocal::PaymentConfiguration{ + .publicToken = method.publicToken, + .isTest = _invoice.isTest, + }; + _smartglocal = std::make_unique( + 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, + }; + _smartglocal->createTokenWithCard(std::move(card), crl::guard(this, [=]( + SmartGlocal::Token token, + SmartGlocal::Error error) { + _smartglocal = nullptr; + + if (error) { + LOG(("SmartGlocal Error %1: %2 (%3)" + ).arg(int(error.code()) + ).arg(error.description() + ).arg(error.message())); + _updates.fire(Error{ + Error::Type::SmartGlocal, + error.description(), + }); + } else { + setPaymentCredentials({ + .title = CardTitle(token.card()), + .data = QJsonDocument(QJsonObject{ + { "token", token.tokenId() }, + { "type", "card" }, + }).toJson(QJsonDocument::Compact), + .saveOnServer = saveInformation, + }); + } + })); +} + void Form::setPaymentCredentials(const NewCredentials &credentials) { Expects(!credentials.empty()); diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 71c2690c82..441b874d17 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/sender.h" class Image; +class QJsonObject; namespace Core { struct CloudPasswordResult; @@ -21,6 +22,10 @@ namespace Stripe { class APIClient; } // namespace Stripe +namespace SmartGlocal { +class APIClient; +} // namespace SmartGlocal + namespace Main { class Session; } // namespace Main @@ -86,10 +91,15 @@ struct StripePaymentMethod { QString publishableKey; }; +struct SmartGlocalPaymentMethod { + QString publicToken; +}; + struct NativePaymentMethod { std::variant< v::null_t, - StripePaymentMethod> data; + StripePaymentMethod, + SmartGlocalPaymentMethod> data; [[nodiscard]] bool valid() const { return !v::is_null(data); @@ -131,6 +141,7 @@ struct Error { Form, Validate, Stripe, + SmartGlocal, TmpPassword, Send, }; @@ -221,7 +232,8 @@ private: const MTPDpaymentSavedCredentialsCard &data); void processShippingOptions(const QVector &data); void fillPaymentMethodInformation(); - void fillStripeNativeMethod(); + void fillStripeNativeMethod(QJsonObject object); + void fillSmartGlocalNativeMethod(QJsonObject object); void refreshPaymentMethodDetails(); [[nodiscard]] QString defaultPhone() const; [[nodiscard]] QString defaultCountry() const; @@ -230,6 +242,10 @@ private: const StripePaymentMethod &method, const Ui::UncheckedCardDetails &details, bool saveInformation); + void validateCard( + const SmartGlocalPaymentMethod &method, + const Ui::UncheckedCardDetails &details, + bool saveInformation); bool validateInformationLocal( const Ui::RequestedInformation &information) const; @@ -259,6 +275,7 @@ private: mtpRequestId _passwordRequestId = 0; std::unique_ptr _stripe; + std::unique_ptr _smartglocal; Ui::ShippingOptions _shippingOptions; QString _requestedInformationId; diff --git a/Telegram/SourceFiles/payments/smartglocal/smartglocal_api_client.cpp b/Telegram/SourceFiles/payments/smartglocal/smartglocal_api_client.cpp new file mode 100644 index 0000000000..70984460cd --- /dev/null +++ b/Telegram/SourceFiles/payments/smartglocal/smartglocal_api_client.cpp @@ -0,0 +1,169 @@ +/* +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 "smartglocal/smartglocal_api_client.h" + +#include "smartglocal/smartglocal_error.h" +#include "smartglocal/smartglocal_token.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace SmartGlocal { +namespace { + +[[nodiscard]] QString APIURLBase(bool isTest) { + return isTest + ? "tgb-playground.smart-glocal.com/cds/v1" + : "tgb.smart-glocal.com/cds/v1"; +} + +[[nodiscard]] QString TokenEndpoint() { + return "tokenize/card"; +} + +[[nodiscard]] QByteArray ToJson(const Stripe::CardParams &card) { + const auto zero = QChar('0'); + const auto month = QString("%1").arg(card.expMonth, 2, 10, zero); + const auto year = QString("%1").arg(card.expYear % 100, 2, 10, zero); + + return QJsonDocument(QJsonObject{ + { "card", QJsonObject{ + { "number", card.number }, + { "expiration_month", month }, + { "expiration_year", year }, + { "security_code", card.cvc }, + } }, + }).toJson(QJsonDocument::Compact); +} + +} // namespace + +APIClient::APIClient(PaymentConfiguration configuration) +: _apiUrl("https://" + APIURLBase(configuration.isTest)) +, _configuration(configuration) { + _additionalHttpHeaders = { + { "X-PUBLIC-TOKEN", _configuration.publicToken }, + }; +} + +APIClient::~APIClient() { + const auto destroy = std::move(_old); +} + +void APIClient::createTokenWithCard( + Stripe::CardParams card, + TokenCompletionCallback completion) { + createTokenWithData(ToJson(card), std::move(completion)); +} + +void APIClient::createTokenWithData( + QByteArray data, + TokenCompletionCallback completion) { + const auto url = QUrl(_apiUrl + '/' + TokenEndpoint()); + auto request = QNetworkRequest(url); + request.setHeader( + QNetworkRequest::ContentTypeHeader, + "application/json"); + for (const auto &[name, value] : _additionalHttpHeaders) { + request.setRawHeader(name.toUtf8(), value.toUtf8()); + } + destroyReplyDelayed(std::move(_reply)); + _reply.reset(_manager.post(request, data)); + const auto finish = [=](Token token, Error error) { + crl::on_main([ + completion, + token = std::move(token), + error = std::move(error) + ] { + completion(std::move(token), std::move(error)); + }); + }; + const auto finishWithError = [=](Error error) { + finish(Token::Empty(), std::move(error)); + }; + const auto finishWithToken = [=](Token token) { + finish(std::move(token), Error::None()); + }; + QObject::connect(_reply.get(), &QNetworkReply::finished, [=] { + const auto replyError = int(_reply->error()); + const auto replyErrorString = _reply->errorString(); + const auto bytes = _reply->readAll(); + destroyReplyDelayed(std::move(_reply)); + + auto parseError = QJsonParseError(); + const auto document = QJsonDocument::fromJson(bytes, &parseError); + if (!bytes.isEmpty()) { + if (parseError.error != QJsonParseError::NoError) { + const auto code = int(parseError.error); + finishWithError({ + Error::Code::JsonParse, + QString("InvalidJson%1").arg(code), + parseError.errorString(), + }); + return; + } else if (!document.isObject()) { + finishWithError({ + Error::Code::JsonFormat, + "InvalidJsonRoot", + "Not an object in JSON reply.", + }); + return; + } + const auto object = document.object(); + if (auto error = Error::DecodedObjectFromResponse(object)) { + finishWithError(std::move(error)); + return; + } + } + if (replyError != QNetworkReply::NoError) { + finishWithError({ + Error::Code::Network, + QString("RequestError%1").arg(replyError), + replyErrorString, + }); + return; + } + auto token = Token::DecodedObjectFromAPIResponse( + document.object().value("data").toObject()); + if (!token) { + finishWithError({ + Error::Code::JsonFormat, + "InvalidTokenJson", + "Could not parse token.", + }); + } + finishWithToken(std::move(token)); + }); +} + +void APIClient::destroyReplyDelayed(std::unique_ptr reply) { + if (!reply) { + return; + } + const auto raw = reply.get(); + _old.push_back(std::move(reply)); + QObject::disconnect(raw, &QNetworkReply::finished, nullptr, nullptr); + raw->deleteLater(); + QObject::connect(raw, &QObject::destroyed, [=] { + for (auto i = begin(_old); i != end(_old); ++i) { + if (i->get() == raw) { + i->release(); + _old.erase(i); + break; + } + } + }); +} + +} // namespace SmartGlocal diff --git a/Telegram/SourceFiles/payments/smartglocal/smartglocal_api_client.h b/Telegram/SourceFiles/payments/smartglocal/smartglocal_api_client.h new file mode 100644 index 0000000000..6bf866d493 --- /dev/null +++ b/Telegram/SourceFiles/payments/smartglocal/smartglocal_api_client.h @@ -0,0 +1,48 @@ +/* +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 "stripe/stripe_card_params.h" +#include "smartglocal/smartglocal_callbacks.h" + +#include +#include +#include + +namespace SmartGlocal { + +struct PaymentConfiguration { + QString publicToken; + bool isTest = false; +}; + +class APIClient final { +public: + explicit APIClient(PaymentConfiguration configuration); + ~APIClient(); + + void createTokenWithCard( + Stripe::CardParams card, + TokenCompletionCallback completion); + void createTokenWithData( + QByteArray data, + TokenCompletionCallback completion); + +private: + void destroyReplyDelayed(std::unique_ptr reply); + + QString _apiUrl; + PaymentConfiguration _configuration; + std::map _additionalHttpHeaders; + QNetworkAccessManager _manager; + std::unique_ptr _reply; + std::vector> _old; + +}; + +} // namespace SmartGlocal diff --git a/Telegram/SourceFiles/payments/smartglocal/smartglocal_callbacks.h b/Telegram/SourceFiles/payments/smartglocal/smartglocal_callbacks.h new file mode 100644 index 0000000000..e200aec3b4 --- /dev/null +++ b/Telegram/SourceFiles/payments/smartglocal/smartglocal_callbacks.h @@ -0,0 +1,17 @@ +/* +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 + +namespace SmartGlocal { + +class Token; +class Error; + +using TokenCompletionCallback = std::function; + +} // namespace SmartGlocal diff --git a/Telegram/SourceFiles/payments/smartglocal/smartglocal_card.cpp b/Telegram/SourceFiles/payments/smartglocal/smartglocal_card.cpp new file mode 100644 index 0000000000..f0f4b95a9e --- /dev/null +++ b/Telegram/SourceFiles/payments/smartglocal/smartglocal_card.cpp @@ -0,0 +1,60 @@ +/* +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 "smartglocal/smartglocal_card.h" + +namespace SmartGlocal { + +Card::Card( + QString type, + QString network, + QString maskedNumber) +: _type(type) +, _network(network) +, _maskedNumber(maskedNumber) { +} + +Card Card::Empty() { + return Card(QString(), QString(), QString()); +} + +Card Card::DecodedObjectFromAPIResponse(QJsonObject object) { + const auto string = [&](QStringView key) { + return object.value(key).toString(); + }; + const auto type = string(u"card_type"); + const auto network = string(u"card_network"); + const auto maskedNumber = string(u"masked_card_number"); + if (type.isEmpty() || maskedNumber.isEmpty()) { + return Card::Empty(); + } + return Card(type, network, maskedNumber); +} + +QString Card::type() const { + return _type; +} + +QString Card::network() const { + return _network; +} + +QString Card::maskedNumber() const { + return _maskedNumber; +} + +bool Card::empty() const { + return _type.isEmpty() || _maskedNumber.isEmpty(); +} + +QString Last4(const Card &card) { + const auto masked = card.maskedNumber(); + const auto m = QRegularExpression("[^\\d]\\d*(\\d{4})$").match(masked); + return m.hasMatch() ? m.captured(1) : QString(); +} + +} // namespace SmartGlocal diff --git a/Telegram/SourceFiles/payments/smartglocal/smartglocal_card.h b/Telegram/SourceFiles/payments/smartglocal/smartglocal_card.h new file mode 100644 index 0000000000..6ebd8bc0ca --- /dev/null +++ b/Telegram/SourceFiles/payments/smartglocal/smartglocal_card.h @@ -0,0 +1,51 @@ +/* +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 + +class QJsonObject; + +namespace SmartGlocal { + +class Card final { +public: + Card(const Card &other) = default; + Card &operator=(const Card &other) = default; + Card(Card &&other) = default; + Card &operator=(Card &&other) = default; + ~Card() = default; + + [[nodiscard]] static Card Empty(); + [[nodiscard]] static Card DecodedObjectFromAPIResponse( + QJsonObject object); + + [[nodiscard]] QString type() const; + [[nodiscard]] QString network() const; + [[nodiscard]] QString maskedNumber() const; + + [[nodiscard]] bool empty() const; + [[nodiscard]] explicit operator bool() const { + return !empty(); + } + +private: + Card( + QString type, + QString network, + QString maskedNumber); + + QString _type; + QString _network; + QString _maskedNumber; + +}; + +[[nodiscard]] QString Last4(const Card &card); + +} // namespace SmartGlocal diff --git a/Telegram/SourceFiles/payments/smartglocal/smartglocal_error.cpp b/Telegram/SourceFiles/payments/smartglocal/smartglocal_error.cpp new file mode 100644 index 0000000000..cc764a07f2 --- /dev/null +++ b/Telegram/SourceFiles/payments/smartglocal/smartglocal_error.cpp @@ -0,0 +1,69 @@ +/* +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 "smartglocal/smartglocal_error.h" + +namespace SmartGlocal { + +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) { + if (object.value("status").toString() == "ok") { + return Error::None(); + } + const auto entry = object.value("error"); + if (!entry.isObject()) { + return { + Code::Unknown, + "GenericError", + "Could not read the error response " + "that was returned from SmartGlocal." + }; + } + const auto error = entry.toObject(); + const auto string = [&](QStringView key) { + return error.value(key).toString(); + }; + const auto code = string(u"code"); + const auto description = string(u"description"); + + // There should always be a message and type for the error + if (code.isEmpty() || description.isEmpty()) { + return { + Code::Unknown, + "GenericError", + "Could not interpret the error response " + "that was returned from SmartGlocal." + }; + } + + return { Code::Unknown, code, description }; +} + +bool Error::empty() const { + return (_code == Code::None); +} + +} // namespace SmartGlocal diff --git a/Telegram/SourceFiles/payments/smartglocal/smartglocal_error.h b/Telegram/SourceFiles/payments/smartglocal/smartglocal_error.h new file mode 100644 index 0000000000..e3829ff455 --- /dev/null +++ b/Telegram/SourceFiles/payments/smartglocal/smartglocal_error.h @@ -0,0 +1,59 @@ +/* +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 + +class QJsonObject; + +namespace SmartGlocal { + +class Error { +public: + enum class Code { + None = 0, // Non-SmartGlocal errors. + JsonParse = -1, + JsonFormat = -2, + Network = -3, + + Unknown = 8, + }; + + Error( + Code code, + const QString &description, + const QString &message, + const QString ¶meter = QString()) + : _code(code) + , _description(description) + , _message(message) + , _parameter(parameter) { + } + + [[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; + [[nodiscard]] explicit operator bool() const { + return !empty(); + } + +private: + Code _code = Code::None; + QString _description; + QString _message; + QString _parameter; + +}; + +} // namespace SmartGlocal diff --git a/Telegram/SourceFiles/payments/smartglocal/smartglocal_token.cpp b/Telegram/SourceFiles/payments/smartglocal/smartglocal_token.cpp new file mode 100644 index 0000000000..ee8725ca0c --- /dev/null +++ b/Telegram/SourceFiles/payments/smartglocal/smartglocal_token.cpp @@ -0,0 +1,46 @@ +/* +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 "smartglocal/smartglocal_token.h" + +namespace SmartGlocal { + +QString Token::tokenId() const { + return _tokenId; +} + +Card Token::card() const { + return _card; +} + +Token Token::Empty() { + return Token(QString()); +} + +Token Token::DecodedObjectFromAPIResponse(QJsonObject object) { + const auto tokenId = object.value("token").toString(); + if (tokenId.isEmpty()) { + return Token::Empty(); + } + auto result = Token(tokenId); + const auto card = object.value("info"); + if (card.isObject()) { + result._card = Card::DecodedObjectFromAPIResponse(card.toObject()); + } + return result; +} + +bool Token::empty() const { + return _tokenId.isEmpty(); +} + +Token::Token(QString tokenId) +: _tokenId(std::move(tokenId)) { +} + +} // namespace SmartGlocal + diff --git a/Telegram/SourceFiles/payments/smartglocal/smartglocal_token.h b/Telegram/SourceFiles/payments/smartglocal/smartglocal_token.h new file mode 100644 index 0000000000..43f822da09 --- /dev/null +++ b/Telegram/SourceFiles/payments/smartglocal/smartglocal_token.h @@ -0,0 +1,47 @@ +/* +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 "smartglocal/smartglocal_card.h" + +#include + +class QJsonObject; + +namespace SmartGlocal { + +class Token { +public: + Token(const Token &other) = default; + Token &operator=(const Token &other) = default; + Token(Token &&other) = default; + Token &operator=(Token &&other) = default; + ~Token() = default; + + [[nodiscard]] QString tokenId() const; + [[nodiscard]] bool livemode() const; + [[nodiscard]] Card card() const; + + [[nodiscard]] static Token Empty(); + [[nodiscard]] static Token DecodedObjectFromAPIResponse( + QJsonObject object); + + [[nodiscard]] bool empty() const; + [[nodiscard]] explicit operator bool() const { + return !empty(); + } + +private: + explicit Token(QString tokenId); + + QString _tokenId; + Card _card = Card::Empty(); + +}; + +} // namespace SmartGlocal diff --git a/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp b/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp index 798741c96b..853dc1498c 100644 --- a/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp +++ b/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp @@ -51,7 +51,7 @@ APIClient::APIClient(PaymentConfiguration configuration) : _apiUrl("https://" + APIURLBase()) , _configuration(configuration) { _additionalHttpHeaders = { - { "X-Stripe-User-Agent", StripeUserAgentDetails() }, + { "X-Stripe-User-Agent", StripeUserAgentDetails() }, { "Stripe-Version", StripeAPIVersion() }, { "Authorization", "Bearer " + _configuration.publishableKey }, }; diff --git a/Telegram/SourceFiles/payments/ui/payments_field.cpp b/Telegram/SourceFiles/payments/ui/payments_field.cpp index 8296a7be48..1e80217997 100644 --- a/Telegram/SourceFiles/payments/ui/payments_field.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_field.cpp @@ -225,6 +225,11 @@ struct SimpleFieldState { ).toDouble(); return QString::number( int64(std::round(real * std::pow(10., rule.exponent)))); + } else if (config.type == FieldType::CardNumber + || config.type == FieldType::CardCVC) { + return QString(parsed).replace( + QRegularExpression("[^0-9\\.]"), + QString()); } return parsed; } diff --git a/Telegram/cmake/lib_stripe.cmake b/Telegram/cmake/lib_stripe.cmake index 49b0f791a6..8581b5a27d 100644 --- a/Telegram/cmake/lib_stripe.cmake +++ b/Telegram/cmake/lib_stripe.cmake @@ -33,6 +33,16 @@ PRIVATE stripe/stripe_payment_configuration.h stripe/stripe_token.cpp stripe/stripe_token.h + + smartglocal/smartglocal_api_client.cpp + smartglocal/smartglocal_api_client.h + smartglocal/smartglocal_callbacks.h + smartglocal/smartglocal_card.cpp + smartglocal/smartglocal_card.h + smartglocal/smartglocal_error.cpp + smartglocal/smartglocal_error.h + smartglocal/smartglocal_token.cpp + smartglocal/smartglocal_token.h stripe/stripe_pch.h ) diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 49887261a5..c1548226d4 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 49887261a55665f6e195049bcc22b6495a44cc36 +Subproject commit c1548226d49db23f68bbf35f34cc820171aed65c