From 76b4e185189a88f3d62288ae05564d5598f5459d Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 24 Mar 2021 21:59:30 +0400 Subject: [PATCH] Port required parts of Stripe SDK to lib_stripe. --- Telegram/CMakeLists.txt | 2 + .../payments/stripe/stripe_address.h | 18 ++ .../payments/stripe/stripe_api_client.cpp | 167 ++++++++++++++++++ .../payments/stripe/stripe_api_client.h | 44 +++++ .../payments/stripe/stripe_callbacks.h | 19 ++ .../payments/stripe/stripe_card.cpp | 117 ++++++++++++ .../SourceFiles/payments/stripe/stripe_card.h | 80 +++++++++ .../payments/stripe/stripe_card_params.cpp | 33 ++++ .../payments/stripe/stripe_card_params.h | 33 ++++ .../payments/stripe/stripe_decode.cpp | 23 +++ .../payments/stripe/stripe_decode.h | 18 ++ .../payments/stripe/stripe_error.cpp | 83 +++++++++ .../payments/stripe/stripe_error.h | 65 +++++++ .../payments/stripe/stripe_form_encodable.h | 24 +++ .../payments/stripe/stripe_form_encoder.cpp | 40 +++++ .../payments/stripe/stripe_form_encoder.h | 21 +++ .../stripe/stripe_payment_configuration.h | 26 +++ .../SourceFiles/payments/stripe/stripe_pch.h | 13 ++ .../payments/stripe/stripe_token.cpp | 65 +++++++ .../payments/stripe/stripe_token.h | 49 +++++ Telegram/cmake/lib_stripe.cmake | 47 +++++ 21 files changed, 987 insertions(+) create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_address.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_api_client.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_callbacks.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_card.cpp create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_card.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_card_params.cpp create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_card_params.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_decode.cpp create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_decode.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_error.cpp create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_error.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_form_encodable.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_form_encoder.cpp create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_form_encoder.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_payment_configuration.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_pch.h create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_token.cpp create mode 100644 Telegram/SourceFiles/payments/stripe/stripe_token.h create mode 100644 Telegram/cmake/lib_stripe.cmake diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ec5aa69c8..9f5defbd5 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -27,6 +27,7 @@ get_filename_component(res_loc Resources REALPATH) include(cmake/telegram_options.cmake) include(cmake/lib_ffmpeg.cmake) +include(cmake/lib_stripe.cmake) include(cmake/lib_tgvoip.cmake) include(cmake/lib_tgcalls.cmake) include(cmake/td_export.cmake) @@ -58,6 +59,7 @@ PRIVATE desktop-app::lib_qr desktop-app::lib_webview desktop-app::lib_ffmpeg + desktop-app::lib_stripe desktop-app::external_lz4 desktop-app::external_rlottie desktop-app::external_zlib diff --git a/Telegram/SourceFiles/payments/stripe/stripe_address.h b/Telegram/SourceFiles/payments/stripe/stripe_address.h new file mode 100644 index 000000000..5be06f6cc --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_address.h @@ -0,0 +1,18 @@ +/* +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 Stripe { + +enum class BillingAddressFields { + None, + Zip, + Full, +}; + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp b/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp new file mode 100644 index 000000000..5189fa506 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp @@ -0,0 +1,167 @@ +/* +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 "stripe/stripe_api_client.h" + +#include "stripe/stripe_error.h" +#include "stripe/stripe_token.h" +#include "stripe/stripe_form_encoder.h" + +#include +#include +#include +#include +#include + +namespace Stripe { +namespace { + +[[nodiscard]] QString APIURLBase() { + return "api.stripe.com/v1"; +} + +[[nodiscard]] QString TokenEndpoint() { + return "tokens"; +} + +[[nodiscard]] QString StripeAPIVersion() { + return "2015-10-12"; +} + +[[nodiscard]] QString SDKVersion() { + return "9.1.0"; +} + +[[nodiscard]] QString StripeUserAgentDetails() { + const auto details = QJsonObject{ + { "lang", "objective-c" }, + { "bindings_version", SDKVersion() }, + }; + return QString::fromUtf8( + QJsonDocument(details).toJson(QJsonDocument::Compact)); +} + +} // namespace + +APIClient::APIClient(PaymentConfiguration configuration) +: _apiUrl("https://" + APIURLBase()) +, _configuration(configuration) { + _additionalHttpHeaders = { + { "X-Stripe-User-Agent", StripeUserAgentDetails() }, + { "Stripe-Version", StripeAPIVersion() }, + { "Authorization", "Bearer " + _configuration.publishableKey }, + }; +} + +APIClient::~APIClient() { + const auto destroy = std::move(_old); +} + +void APIClient::createTokenWithCard( + CardParams card, + TokenCompletionCallback completion) { + createTokenWithData( + FormEncoder::formEncodedDataForObject(card), + std::move(completion)); +} + +void APIClient::createTokenWithData( + QByteArray data, + TokenCompletionCallback completion) { + const auto url = QUrl(_apiUrl + '/' + TokenEndpoint()); + auto request = QNetworkRequest(url); + 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()); + 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 Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_api_client.h b/Telegram/SourceFiles/payments/stripe/stripe_api_client.h new file mode 100644 index 000000000..020df992d --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_api_client.h @@ -0,0 +1,44 @@ +/* +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_payment_configuration.h" +#include "stripe/stripe_card_params.h" +#include "stripe/stripe_callbacks.h" + +#include +#include +#include + +namespace Stripe { + +class APIClient final { +public: + explicit APIClient(PaymentConfiguration configuration); + ~APIClient(); + + void createTokenWithCard( + 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 Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_callbacks.h b/Telegram/SourceFiles/payments/stripe/stripe_callbacks.h new file mode 100644 index 000000000..8e0d0a2a5 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_callbacks.h @@ -0,0 +1,19 @@ +/* +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 + +namespace Stripe { + +class Error; +class Token; + +using TokenCompletionCallback = std::function; + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_card.cpp b/Telegram/SourceFiles/payments/stripe/stripe_card.cpp new file mode 100644 index 000000000..af37edb9c --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_card.cpp @@ -0,0 +1,117 @@ +/* +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 "stripe/stripe_card.h" + +#include "stripe/stripe_decode.h" + +namespace Stripe { + +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) { + if (brand == "visa") { + return CardBrand::Visa; + } else if (brand == "american express") { + return CardBrand::Amex; + } else if (brand == "mastercard") { + return CardBrand::MasterCard; + } else if (brand == "discover") { + return CardBrand::Discover; + } else if (brand == "jcb") { + return CardBrand::JCB; + } else if (brand == "diners club") { + return CardBrand::DinersClub; + } else { + return CardBrand::Unknown; + } +} + +[[nodiscard]] CardFundingType FundingFromString(const QString &funding) { + if (funding == "credit") { + return CardFundingType::Credit; + } else if (funding == "debit") { + return CardFundingType::Debit; + } else if (funding == "prepaid") { + return CardFundingType::Prepaid; + } else { + return CardFundingType::Other; + } +} + +Card Card::DecodedObjectFromAPIResponse(QJsonObject object) { + if (!ContainsFields(object, { + u"id", + u"last4", + u"brand", + u"exp_month", + u"exp_year" + })) { + return Card::Empty(); + } + + const auto string = [&](QStringView key) { + return object.value(key).toString(); + }; + const auto cardId = string(u"id"); + const auto last4 = string(u"last4"); + const auto brand = BrandFromString(string(u"brand").toLower()); + const auto expMonth = object.value("exp_month").toInt(); + const auto expYear = object.value("exp_year").toInt(); + 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._fingerprint = string(u"fingerprint"); + result._country = string(u"country"); + result._currency = string(u"currency"); + result._addressLine1 = string(u"address_line1"); + result._addressLine2 = string(u"address_line2"); + result._addressCity = string(u"address_city"); + result._addressState = string(u"address_state"); + result._addressZip = string(u"address_zip"); + result._addressCountry = string(u"address_country"); + + // TODO incomplete, not used. + //result._allResponseFields = object; + + return result; +} + +bool Card::empty() const { + return _cardId.isEmpty(); +} + +QString CardBrandToString(CardBrand brand) { + switch (brand) { + case CardBrand::Amex: return "American Express"; + case CardBrand::DinersClub: return "Diners Club"; + case CardBrand::Discover: return "Discover"; + case CardBrand::JCB: return "JCB"; + case CardBrand::MasterCard: return "MasterCard"; + case CardBrand::Unknown: return "Unknown"; + case CardBrand::Visa: return "Visa"; + } + std::abort(); +} + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_card.h b/Telegram/SourceFiles/payments/stripe/stripe_card.h new file mode 100644 index 000000000..d7cb63627 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_card.h @@ -0,0 +1,80 @@ +/* +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 Stripe { + +enum class CardBrand { + Visa, + Amex, + MasterCard, + Discover, + JCB, + DinersClub, + Unknown, +}; + +enum class CardFundingType { + Debit, + Credit, + Prepaid, + Other, +}; + +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]] bool empty() const; + [[nodiscard]] explicit operator bool() const { + return !empty(); + } + +private: + Card( + QString id, + QString last4, + CardBrand brand, + quint32 expMonth, + quint32 expYear); + + QString _cardId; + QString _name; + QString _last4; + QString _dynamicLast4; + CardBrand _brand = CardBrand::Unknown; + CardFundingType _funding = CardFundingType::Other; + QString _fingerprint; + QString _country; + QString _currency; + quint32 _expMonth = 0; + quint32 _expYear = 0; + QString _addressLine1; + QString _addressLine2; + QString _addressCity; + QString _addressState; + QString _addressZip; + QString _addressCountry; + +}; + +[[nodiscard]] QString CardBrandToString(CardBrand brand); + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_card_params.cpp b/Telegram/SourceFiles/payments/stripe/stripe_card_params.cpp new file mode 100644 index 000000000..06894fe87 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_card_params.cpp @@ -0,0 +1,33 @@ +/* +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 "stripe/stripe_card_params.h" + +namespace Stripe { + +QString CardParams::RootObjectName() const { + return "card"; +} + +std::map CardParams::formFieldValues() const { + return { + { "number", number }, + { "cvc", cvc }, + { "name", name }, + { "address_line1", addressLine1 }, + { "address_line2", addressLine2 }, + { "address_city", addressCity }, + { "address_state", addressState }, + { "address_zip", addressZip }, + { "address_country", addressCountry }, + { "exp_month", QString::number(expMonth) }, + { "exp_year", QString::number(expYear) }, + { "currency", currency }, + }; +} + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_card_params.h b/Telegram/SourceFiles/payments/stripe/stripe_card_params.h new file mode 100644 index 000000000..811ab67a8 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_card_params.h @@ -0,0 +1,33 @@ +/* +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_form_encodable.h" + +namespace Stripe { + +class CardParams final : public FormEncodable { +public: + QString RootObjectName() const override; + std::map formFieldValues() const override; + + QString number; + quint32 expMonth = 0; + quint32 expYear = 0; + QString cvc; + QString name; + QString addressLine1; + QString addressLine2; + QString addressCity; + QString addressState; + QString addressZip; + QString addressCountry; + QString currency; +}; + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_decode.cpp b/Telegram/SourceFiles/payments/stripe/stripe_decode.cpp new file mode 100644 index 000000000..acd2dac0b --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_decode.cpp @@ -0,0 +1,23 @@ +/* +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 "stripe/stripe_decode.h" + +namespace Stripe { + +[[nodiscard]] bool ContainsFields( + const QJsonObject &object, + std::vector keys) { + for (const auto &key : keys) { + if (object.value(key).isUndefined()) { + return false; + } + } + return true; +} + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_decode.h b/Telegram/SourceFiles/payments/stripe/stripe_decode.h new file mode 100644 index 000000000..41edb3445 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_decode.h @@ -0,0 +1,18 @@ +/* +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 + +namespace Stripe { + +[[nodiscard]] bool ContainsFields( + const QJsonObject &object, + std::vector keys); + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_error.cpp b/Telegram/SourceFiles/payments/stripe/stripe_error.cpp new file mode 100644 index 000000000..675d41d8f --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_error.cpp @@ -0,0 +1,83 @@ +/* +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 "stripe/stripe_error.h" + +#include "stripe/stripe_decode.h" + +namespace Stripe { + +Error Error::DecodedObjectFromResponse(QJsonObject object) { + const auto entry = object.value("error"); + if (!entry.isObject()) { + return Error::None(); + } + const auto error = entry.toObject(); + const auto string = [&](QStringView key) { + return error.value(key).toString(); + }; + const auto type = string(u"type"); + const auto message = string(u"message"); + const auto parameterSnakeCase = string(u"param"); + + // There should always be a message and type for the error + if (message.isEmpty() || type.isEmpty()) { + return { + Code::API, + "GenericError", + "Could not interpret the error response " + "that was returned from Stripe." + }; + } + + auto parameterWords = parameterSnakeCase.isEmpty() + ? QStringList() + : parameterSnakeCase.split('_', Qt::SkipEmptyParts); + auto first = true; + for (auto &word : parameterWords) { + if (first) { + first = false; + } else { + word = word[0].toUpper() + word.midRef(1); + } + } + const auto parameter = parameterWords.join(QString()); + if (type == "api_error") { + return { Code::API, "GenericError", message, parameter }; + } else if (type == "invalid_request_error") { + return { Code::InvalidRequest, "GenericError", message, parameter }; + } else if (type != "card_error") { + return { Code::Unknown, type, message, parameter }; + } + const auto code = string(u"code"); + const auto cardError = [&](const QString &description) { + return Error{ Code::Card, description, message, parameter }; + }; + if (code == "incorrect_number") { + return cardError("IncorrectNumber"); + } else if (code == "invalid_number") { + return cardError("InvalidNumber"); + } else if (code == "invalid_expiry_month") { + return cardError("InvalidExpiryMonth"); + } else if (code == "invalid_expiry_year") { + return cardError("InvalidExpiryYear"); + } else if (code == "invalid_cvc") { + return cardError("InvalidCVC"); + } else if (code == "expired_card") { + return cardError("ExpiredCard"); + } else if (code == "incorrect_cvc") { + return cardError("IncorrectCVC"); + } else if (code == "card_declined") { + return cardError("CardDeclined"); + } else if (code == "processing_error") { + return cardError("ProcessingError"); + } else { + return cardError(code); + } +} + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_error.h b/Telegram/SourceFiles/payments/stripe/stripe_error.h new file mode 100644 index 000000000..9a0a46ec3 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_error.h @@ -0,0 +1,65 @@ +/* +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 Stripe { + +class Error { +public: + enum class Code { + None = 0, // Non-Stripe errors. + JsonParse = -1, + JsonFormat = -2, + Network = -3, + + Unknown = 8, + Connection = 40, // Trouble connecting to Stripe. + InvalidRequest = 50, // Your request had invalid parameters. + API = 60, // General-purpose API error (should be rare). + Card = 70, // Something was wrong with the given card (most common). + Cancellation = 80, // The operation was cancelled. + CheckoutUnknown = 5000, // Checkout failed + CheckoutTooManyAttempts = 5001, // Too many incorrect code attempts + }; + + Error( + Code code, + const QString &description, + const QString &message, + const QString ¶meter = QString()) + : _code(code) + , _description(description) + , _message(message) + , _parameter(parameter) { + } + + [[nodiscard]] static Error None() { + return Error(Code::None, QString(), QString()); + } + [[nodiscard]] static Error DecodedObjectFromResponse(QJsonObject object); + + [[nodiscard]] bool empty() const { + return (_code == Code::None); + } + [[nodiscard]] explicit operator bool() const { + return !empty(); + } + +private: + Code _code = Code::None; + QString _description; + QString _message; + QString _parameter; + +}; + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_form_encodable.h b/Telegram/SourceFiles/payments/stripe/stripe_form_encodable.h new file mode 100644 index 000000000..1a09d69b1 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_form_encodable.h @@ -0,0 +1,24 @@ +/* +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 +#include + +namespace Stripe { + +class FormEncodable { +public: + [[nodiscard]] virtual QString RootObjectName() const = 0; + + // TODO incomplete, not used: nested complex structures not supported. + [[nodiscard]] virtual std::map formFieldValues() const + = 0; +}; + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.cpp b/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.cpp new file mode 100644 index 000000000..8ce752108 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.cpp @@ -0,0 +1,40 @@ +/* +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 "stripe/stripe_form_encoder.h" + +#include +#include + +namespace Stripe { + +QByteArray FormEncoder::formEncodedDataForObject( + FormEncodable &object) { + const auto root = object.RootObjectName(); + const auto values = object.formFieldValues(); + auto result = QByteArray(); + auto keys = std::vector(); + for (const auto &[key, value] : values) { + if (!value.isEmpty()) { + keys.push_back(key); + } + } + std::sort(begin(keys), end(keys)); + const auto encode = [](const QString &value) { + return QUrl::toPercentEncoding(value); + }; + for (const auto &key : keys) { + const auto fullKey = root.isEmpty() ? key : (root + '[' + key + ']'); + if (!result.isEmpty()) { + result += '&'; + } + result += encode(fullKey) + '=' + encode(values.at(key)); + } + return result; +} + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.h b/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.h new file mode 100644 index 000000000..798de928c --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_form_encoder.h @@ -0,0 +1,21 @@ +/* +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_form_encodable.h" + +namespace Stripe { + +class FormEncoder { +public: + [[nodiscard]] static QByteArray formEncodedDataForObject( + FormEncodable &object); + +}; + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_payment_configuration.h b/Telegram/SourceFiles/payments/stripe/stripe_payment_configuration.h new file mode 100644 index 000000000..f2b2867ae --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_payment_configuration.h @@ -0,0 +1,26 @@ +/* +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_address.h" + +#include + +namespace Stripe { + +struct PaymentConfiguration { + QString publishableKey; + // PaymentMethodType additionalPaymentMethods; // Apply Pay + BillingAddressFields requiredBillingAddressFields + = BillingAddressFields::None; + QString companyName; + // QString appleMerchantIdentifier; // Apple Pay + // bool smsAutofillDisabled = true; // Mobile only +}; + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_pch.h b/Telegram/SourceFiles/payments/stripe/stripe_pch.h new file mode 100644 index 000000000..b7c2e9fee --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_pch.h @@ -0,0 +1,13 @@ +/* +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 +#include +#include +#include +#include diff --git a/Telegram/SourceFiles/payments/stripe/stripe_token.cpp b/Telegram/SourceFiles/payments/stripe/stripe_token.cpp new file mode 100644 index 000000000..66a131a36 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_token.cpp @@ -0,0 +1,65 @@ +/* +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 "stripe/stripe_token.h" + +#include "stripe/stripe_decode.h" + +namespace Stripe { + +QString Token::tokenId() const { + return _tokenId; +} + +bool Token::livemode() const { + return _livemode; +} + +Card Token::card() const { + return _card; +} + +Token Token::Empty() { + return Token(QString(), false, QDateTime()); +} + +Token Token::DecodedObjectFromAPIResponse(QJsonObject object) { + if (!ContainsFields(object, { u"id", u"livemode", u"created" })) { + return Token::Empty(); + } + const auto tokenId = object.value("id").toString(); + const auto livemode = object.value("livemode").toBool(); + const auto created = QDateTime::fromTime_t( + object.value("created").toDouble()); + auto result = Token(tokenId, livemode, created); + const auto card = object.value("card"); + if (card.isObject()) { + result._card = Card::DecodedObjectFromAPIResponse(card.toObject()); + } + + // TODO incomplete, not used. + //const auto bankAccount = object.value("bank_account"); + //if (bankAccount.isObject()) { + // result._bankAccount = bankAccount::DecodedObjectFromAPIResponse( + // bankAccount.toObject()); + //} + //result._allResponseFields = object; + + return result; +} + +bool Token::empty() const { + return _tokenId.isEmpty(); +} + +Token::Token(QString tokenId, bool livemode, QDateTime created) +: _tokenId(std::move(tokenId)) +, _livemode(livemode) +, _created(std::move(created)) { +} + +} // namespace Stripe diff --git a/Telegram/SourceFiles/payments/stripe/stripe_token.h b/Telegram/SourceFiles/payments/stripe/stripe_token.h new file mode 100644 index 000000000..f22aefe60 --- /dev/null +++ b/Telegram/SourceFiles/payments/stripe/stripe_token.h @@ -0,0 +1,49 @@ +/* +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.h" + +#include + +class QJsonObject; + +namespace Stripe { + +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: + Token(QString tokenId, bool livemode, QDateTime created); + + QString _tokenId; + bool _livemode = false; + QDateTime _created; + Card _card = Card::Empty(); + +}; + +} // namespace Stripe diff --git a/Telegram/cmake/lib_stripe.cmake b/Telegram/cmake/lib_stripe.cmake new file mode 100644 index 000000000..6785d983b --- /dev/null +++ b/Telegram/cmake/lib_stripe.cmake @@ -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 + +add_library(lib_stripe OBJECT) +add_library(desktop-app::lib_stripe ALIAS lib_stripe) +init_target(lib_stripe) + +set(stripe_src_loc ${src_loc}/payments) + +target_precompile_headers(lib_stripe PRIVATE ${stripe_src_loc}/stripe/stripe_pch.h) +nice_target_sources(lib_stripe ${stripe_src_loc} +PRIVATE + stripe/stripe_address.h + stripe/stripe_api_client.cpp + stripe/stripe_api_client.h + stripe/stripe_callbacks.h + stripe/stripe_card.cpp + stripe/stripe_card.h + stripe/stripe_card_params.cpp + stripe/stripe_card_params.h + stripe/stripe_decode.cpp + stripe/stripe_decode.h + stripe/stripe_error.cpp + stripe/stripe_error.h + stripe/stripe_form_encodable.h + stripe/stripe_form_encoder.cpp + stripe/stripe_form_encoder.h + stripe/stripe_payment_configuration.h + stripe/stripe_token.cpp + stripe/stripe_token.h + + stripe/stripe_pch.h +) + +target_include_directories(lib_stripe +PUBLIC + ${stripe_src_loc} +) + +target_link_libraries(lib_stripe +PUBLIC + desktop-app::lib_crl + desktop-app::external_qt +)