init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
18
Telegram/SourceFiles/payments/stripe/stripe_address.h
Normal file
18
Telegram/SourceFiles/payments/stripe/stripe_address.h
Normal file
@@ -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
|
||||
167
Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp
Normal file
167
Telegram/SourceFiles/payments/stripe/stripe_api_client.cpp
Normal file
@@ -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 <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <crl/crl_on_main.h>
|
||||
|
||||
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(MakeEncodable(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<QNetworkReply> 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
|
||||
45
Telegram/SourceFiles/payments/stripe/stripe_api_client.h
Normal file
45
Telegram/SourceFiles/payments/stripe/stripe_api_client.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
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 <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtCore/QString>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
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<QNetworkReply> reply);
|
||||
|
||||
QString _apiUrl;
|
||||
PaymentConfiguration _configuration;
|
||||
std::map<QString, QString> _additionalHttpHeaders;
|
||||
QNetworkAccessManager _manager;
|
||||
std::unique_ptr<QNetworkReply> _reply;
|
||||
std::vector<std::unique_ptr<QNetworkReply>> _old;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Stripe
|
||||
19
Telegram/SourceFiles/payments/stripe/stripe_callbacks.h
Normal file
19
Telegram/SourceFiles/payments/stripe/stripe_callbacks.h
Normal file
@@ -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 <functional>
|
||||
|
||||
namespace Stripe {
|
||||
|
||||
class Error;
|
||||
class Token;
|
||||
|
||||
using TokenCompletionCallback = std::function<void(Token, Error)>;
|
||||
|
||||
} // namespace Stripe
|
||||
188
Telegram/SourceFiles/payments/stripe/stripe_card.cpp
Normal file
188
Telegram/SourceFiles/payments/stripe/stripe_card.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
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 {
|
||||
namespace {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
} // 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",
|
||||
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").toLower());
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
99
Telegram/SourceFiles/payments/stripe/stripe_card.h
Normal file
99
Telegram/SourceFiles/payments/stripe/stripe_card.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
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 <QtCore/QString>
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
namespace Stripe {
|
||||
|
||||
enum class CardBrand {
|
||||
Visa,
|
||||
Amex,
|
||||
MasterCard,
|
||||
Discover,
|
||||
JCB,
|
||||
DinersClub,
|
||||
UnionPay,
|
||||
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]] 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();
|
||||
}
|
||||
|
||||
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
|
||||
33
Telegram/SourceFiles/payments/stripe/stripe_card_params.cpp
Normal file
33
Telegram/SourceFiles/payments/stripe/stripe_card_params.cpp
Normal file
@@ -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() {
|
||||
return "card";
|
||||
}
|
||||
|
||||
std::map<QString, QString> 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
|
||||
32
Telegram/SourceFiles/payments/stripe/stripe_card_params.h
Normal file
32
Telegram/SourceFiles/payments/stripe/stripe_card_params.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
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 {
|
||||
|
||||
struct CardParams {
|
||||
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;
|
||||
|
||||
[[nodiscard]] static QString rootObjectName();
|
||||
[[nodiscard]] std::map<QString, QString> formFieldValues() const;
|
||||
};
|
||||
|
||||
} // namespace Stripe
|
||||
304
Telegram/SourceFiles/payments/stripe/stripe_card_validator.cpp
Normal file
304
Telegram/SourceFiles/payments/stripe/stripe_card_validator.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
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_validator.h"
|
||||
|
||||
#include "base/qt/qt_string_view.h"
|
||||
|
||||
#include <QtCore/QDate>
|
||||
#include <QtCore/QRegularExpression>
|
||||
|
||||
namespace Stripe {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMinCvcLength = 3;
|
||||
|
||||
struct BinRange {
|
||||
QString low;
|
||||
QString high;
|
||||
int length = 0;
|
||||
CardBrand brand = CardBrand::Unknown;
|
||||
};
|
||||
|
||||
[[nodiscard]] const std::vector<BinRange> &AllRanges() {
|
||||
static auto kResult = std::vector<BinRange>{
|
||||
// Unknown
|
||||
{ "", "", 19, CardBrand::Unknown },
|
||||
// American Express
|
||||
{ "34", "34", 15, CardBrand::Amex },
|
||||
{ "37", "37", 15, CardBrand::Amex },
|
||||
// Diners Club
|
||||
{ "30", "30", 16, CardBrand::DinersClub },
|
||||
{ "36", "36", 14, CardBrand::DinersClub },
|
||||
{ "38", "39", 16, CardBrand::DinersClub },
|
||||
// Discover
|
||||
{ "60", "60", 16, CardBrand::Discover },
|
||||
{ "64", "65", 16, CardBrand::Discover },
|
||||
// JCB
|
||||
{ "35", "35", 16, CardBrand::JCB },
|
||||
// Mastercard
|
||||
{ "50", "59", 16, CardBrand::MasterCard },
|
||||
{ "22", "27", 16, CardBrand::MasterCard },
|
||||
{ "67", "67", 16, CardBrand::MasterCard }, // Maestro
|
||||
// UnionPay
|
||||
{ "62", "62", 16, CardBrand::UnionPay },
|
||||
{ "81", "81", 16, CardBrand::UnionPay },
|
||||
// Visa
|
||||
{ "40", "49", 16, CardBrand::Visa },
|
||||
{ "413600", "413600", 13, CardBrand::Visa },
|
||||
{ "444509", "444509", 13, CardBrand::Visa },
|
||||
{ "444509", "444509", 13, CardBrand::Visa },
|
||||
{ "444550", "444550", 13, CardBrand::Visa },
|
||||
{ "450603", "450603", 13, CardBrand::Visa },
|
||||
{ "450617", "450617", 13, CardBrand::Visa },
|
||||
{ "450628", "450629", 13, CardBrand::Visa },
|
||||
{ "450636", "450636", 13, CardBrand::Visa },
|
||||
{ "450640", "450641", 13, CardBrand::Visa },
|
||||
{ "450662", "450662", 13, CardBrand::Visa },
|
||||
{ "463100", "463100", 13, CardBrand::Visa },
|
||||
{ "476142", "476142", 13, CardBrand::Visa },
|
||||
{ "476143", "476143", 13, CardBrand::Visa },
|
||||
{ "492901", "492902", 13, CardBrand::Visa },
|
||||
{ "492920", "492920", 13, CardBrand::Visa },
|
||||
{ "492923", "492923", 13, CardBrand::Visa },
|
||||
{ "492928", "492930", 13, CardBrand::Visa },
|
||||
{ "492937", "492937", 13, CardBrand::Visa },
|
||||
{ "492939", "492939", 13, CardBrand::Visa },
|
||||
{ "492960", "492960", 13, CardBrand::Visa },
|
||||
};
|
||||
return kResult;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool BinRangeMatchesNumber(
|
||||
const BinRange &range,
|
||||
const QString &sanitized) {
|
||||
const auto minWithLow = std::min(sanitized.size(), range.low.size());
|
||||
if (base::StringViewMid(sanitized, 0, minWithLow).toInt()
|
||||
< base::StringViewMid(range.low, 0, minWithLow).toInt()) {
|
||||
return false;
|
||||
}
|
||||
const auto minWithHigh = std::min(sanitized.size(), range.high.size());
|
||||
if (base::StringViewMid(sanitized, 0, minWithHigh).toInt()
|
||||
> base::StringViewMid(range.high, 0, minWithHigh).toInt()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsNumeric(const QString &value) {
|
||||
static const auto RegExp = QRegularExpression("^[0-9]*$");
|
||||
return RegExp.match(value).hasMatch();
|
||||
}
|
||||
|
||||
[[nodiscard]] QString RemoveWhitespaces(QString value) {
|
||||
static const auto RegExp = QRegularExpression("\\s");
|
||||
return value.replace(RegExp, QString());
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<BinRange> BinRangesForNumber(
|
||||
const QString &sanitized) {
|
||||
const auto &all = AllRanges();
|
||||
auto result = std::vector<BinRange>();
|
||||
result.reserve(all.size());
|
||||
for (const auto &range : all) {
|
||||
if (BinRangeMatchesNumber(range, sanitized)) {
|
||||
result.push_back(range);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] BinRange MostSpecificBinRangeForNumber(
|
||||
const QString &sanitized) {
|
||||
auto possible = BinRangesForNumber(sanitized);
|
||||
const auto compare = [&](const BinRange &a, const BinRange &b) {
|
||||
if (sanitized.isEmpty()) {
|
||||
const auto aUnknown = (a.brand == CardBrand::Unknown);
|
||||
const auto bUnknown = (b.brand == CardBrand::Unknown);
|
||||
if (aUnknown && !bUnknown) {
|
||||
return true;
|
||||
} else if (!aUnknown && bUnknown) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return a.low.size() < b.low.size();
|
||||
};
|
||||
std::sort(begin(possible), end(possible), compare);
|
||||
return possible.back();
|
||||
}
|
||||
|
||||
[[nodiscard]] int MaxCvcLengthForBranch(CardBrand brand) {
|
||||
switch (brand) {
|
||||
case CardBrand::Amex:
|
||||
case CardBrand::Unknown:
|
||||
return 4;
|
||||
default:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<CardBrand> PossibleBrandsForNumber(
|
||||
const QString &sanitized) {
|
||||
const auto ranges = BinRangesForNumber(sanitized);
|
||||
auto result = std::vector<CardBrand>();
|
||||
for (const auto &range : ranges) {
|
||||
const auto brand = range.brand;
|
||||
if (brand == CardBrand::Unknown
|
||||
|| (std::find(begin(result), end(result), brand)
|
||||
!= end(result))) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(brand);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] CardBrand BrandForNumber(const QString &number) {
|
||||
const auto sanitized = RemoveWhitespaces(number);
|
||||
if (!IsNumeric(sanitized)) {
|
||||
return CardBrand::Unknown;
|
||||
}
|
||||
const auto possible = PossibleBrandsForNumber(sanitized);
|
||||
return (possible.size() == 1) ? possible.front() : CardBrand::Unknown;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsValidLuhn(const QString &sanitized) {
|
||||
auto odd = true;
|
||||
auto sum = 0;
|
||||
for (auto i = sanitized.end(); i != sanitized.begin();) {
|
||||
--i;
|
||||
auto digit = int(i->unicode() - '0');
|
||||
odd = !odd;
|
||||
if (odd) {
|
||||
digit *= 2;
|
||||
}
|
||||
if (digit > 9) {
|
||||
digit -= 9;
|
||||
}
|
||||
sum += digit;
|
||||
}
|
||||
return (sum % 10) == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CardValidationResult ValidateCard(const QString &number) {
|
||||
const auto sanitized = RemoveWhitespaces(number);
|
||||
if (!IsNumeric(sanitized)) {
|
||||
return { .state = ValidationState::Invalid };
|
||||
} else if (sanitized.isEmpty()) {
|
||||
return { .state = ValidationState::Incomplete };
|
||||
}
|
||||
const auto range = MostSpecificBinRangeForNumber(sanitized);
|
||||
const auto brand = range.brand;
|
||||
|
||||
static const auto &all = AllRanges();
|
||||
static const auto compare = [](const BinRange &a, const BinRange &b) {
|
||||
return a.length < b.length;
|
||||
};
|
||||
static const auto kMinLength = std::min_element(
|
||||
begin(all),
|
||||
end(all),
|
||||
compare)->length;
|
||||
static const auto kMaxLength = std::max_element(
|
||||
begin(all),
|
||||
end(all),
|
||||
compare)->length;
|
||||
|
||||
if (sanitized.size() > kMaxLength) {
|
||||
return { .state = ValidationState::Invalid, .brand = brand };
|
||||
} else if (sanitized.size() < kMinLength) {
|
||||
return { .state = ValidationState::Incomplete, .brand = brand };
|
||||
} else if (!IsValidLuhn(sanitized)) {
|
||||
return { .state = ValidationState::Invalid, .brand = brand };
|
||||
} else if (sanitized.size() < kMaxLength) {
|
||||
return { .state = ValidationState::Valid, .brand = brand };
|
||||
}
|
||||
return {
|
||||
.state = ValidationState::Valid,
|
||||
.brand = brand,
|
||||
.finished = true,
|
||||
};
|
||||
}
|
||||
|
||||
ExpireDateValidationResult ValidateExpireDate(
|
||||
const QString &date,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold) {
|
||||
const auto sanitized = RemoveWhitespaces(date).replace('/', QString());
|
||||
if (!IsNumeric(sanitized)) {
|
||||
return { ValidationState::Invalid };
|
||||
} else if (sanitized.size() < 2) {
|
||||
return { ValidationState::Incomplete };
|
||||
}
|
||||
const auto normalized = (sanitized[0] > '1' ? "0" : "") + sanitized;
|
||||
const auto month = base::StringViewMid(normalized, 0, 2).toInt();
|
||||
if (month < 1 || month > 12) {
|
||||
return { ValidationState::Invalid };
|
||||
} else if (normalized.size() < 4) {
|
||||
return { ValidationState::Incomplete };
|
||||
} else if (normalized.size() > 4) {
|
||||
return { ValidationState::Invalid };
|
||||
}
|
||||
const auto year = 2000 + base::StringViewMid(normalized, 2).toInt();
|
||||
|
||||
const auto thresholdDate = overrideExpireDateThreshold.value_or(
|
||||
QDate::currentDate());
|
||||
const auto thresholdMonth = thresholdDate.month();
|
||||
const auto thresholdYear = thresholdDate.year();
|
||||
if (year < thresholdYear) {
|
||||
return { ValidationState::Invalid };
|
||||
} else if (year == thresholdYear && month < thresholdMonth) {
|
||||
return { ValidationState::Invalid };
|
||||
}
|
||||
return { ValidationState::Valid, true };
|
||||
}
|
||||
|
||||
ValidationState ValidateParsedExpireDate(
|
||||
quint32 month,
|
||||
quint32 year,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold) {
|
||||
if ((year / 100) != 20) {
|
||||
return ValidationState::Invalid;
|
||||
}
|
||||
const auto date = QString("%1%2"
|
||||
).arg(month, 2, 10, QChar('0')
|
||||
).arg(year % 100, 2, 10, QChar('0'));
|
||||
|
||||
return ValidateExpireDate(date, overrideExpireDateThreshold).state;
|
||||
}
|
||||
|
||||
CvcValidationResult ValidateCvc(
|
||||
const QString &number,
|
||||
const QString &cvc) {
|
||||
if (!IsNumeric(cvc)) {
|
||||
return { ValidationState::Invalid };
|
||||
} else if (cvc.size() < kMinCvcLength) {
|
||||
return { ValidationState::Incomplete };
|
||||
}
|
||||
const auto maxLength = MaxCvcLengthForBranch(BrandForNumber(number));
|
||||
if (cvc.size() > maxLength) {
|
||||
return { ValidationState::Invalid };
|
||||
}
|
||||
return { ValidationState::Valid, (cvc.size() == maxLength) };
|
||||
}
|
||||
|
||||
std::vector<int> CardNumberFormat(const QString &number) {
|
||||
static const auto kDefault = std::vector{ 4, 4, 4, 4 };
|
||||
const auto sanitized = RemoveWhitespaces(number);
|
||||
if (!IsNumeric(sanitized)) {
|
||||
return kDefault;
|
||||
}
|
||||
const auto range = MostSpecificBinRangeForNumber(sanitized);
|
||||
if (range.brand == CardBrand::DinersClub && range.length == 14) {
|
||||
return { 4, 6, 4 };
|
||||
} else if (range.brand == CardBrand::Amex) {
|
||||
return { 4, 6, 5 };
|
||||
}
|
||||
return kDefault;
|
||||
}
|
||||
|
||||
} // namespace Stripe
|
||||
57
Telegram/SourceFiles/payments/stripe/stripe_card_validator.h
Normal file
57
Telegram/SourceFiles/payments/stripe/stripe_card_validator.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 <vector>
|
||||
#include <optional>
|
||||
|
||||
class QDate;
|
||||
|
||||
namespace Stripe {
|
||||
|
||||
enum class ValidationState {
|
||||
Invalid,
|
||||
Incomplete,
|
||||
Valid,
|
||||
};
|
||||
|
||||
struct CardValidationResult {
|
||||
ValidationState state = ValidationState::Invalid;
|
||||
CardBrand brand = CardBrand::Unknown;
|
||||
bool finished = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] CardValidationResult ValidateCard(const QString &number);
|
||||
|
||||
struct ExpireDateValidationResult {
|
||||
ValidationState state = ValidationState::Invalid;
|
||||
bool finished = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] ExpireDateValidationResult ValidateExpireDate(
|
||||
const QString &date,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold);
|
||||
|
||||
[[nodiscard]] ValidationState ValidateParsedExpireDate(
|
||||
quint32 month,
|
||||
quint32 year,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold);
|
||||
|
||||
struct CvcValidationResult {
|
||||
ValidationState state = ValidationState::Invalid;
|
||||
bool finished = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] CvcValidationResult ValidateCvc(
|
||||
const QString &number,
|
||||
const QString &cvc);
|
||||
|
||||
[[nodiscard]] std::vector<int> CardNumberFormat(const QString &number);
|
||||
|
||||
} // namespace Stripe
|
||||
23
Telegram/SourceFiles/payments/stripe/stripe_decode.cpp
Normal file
23
Telegram/SourceFiles/payments/stripe/stripe_decode.cpp
Normal file
@@ -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<QStringView> keys) {
|
||||
for (const auto &key : keys) {
|
||||
if (object.value(key).isUndefined()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Stripe
|
||||
19
Telegram/SourceFiles/payments/stripe/stripe_decode.h
Normal file
19
Telegram/SourceFiles/payments/stripe/stripe_decode.h
Normal file
@@ -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 <QtCore/QJsonObject>
|
||||
#include <vector>
|
||||
|
||||
namespace Stripe {
|
||||
|
||||
[[nodiscard]] bool ContainsFields(
|
||||
const QJsonObject &object,
|
||||
std::vector<QStringView> keys);
|
||||
|
||||
} // namespace Stripe
|
||||
107
Telegram/SourceFiles/payments/stripe/stripe_error.cpp
Normal file
107
Telegram/SourceFiles/payments/stripe/stripe_error.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
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::Code Error::code() const {
|
||||
return _code;
|
||||
}
|
||||
|
||||
QString Error::description() const {
|
||||
return _description;
|
||||
}
|
||||
|
||||
QString Error::message() const {
|
||||
return _message;
|
||||
}
|
||||
|
||||
QString Error::parameter() const {
|
||||
return _parameter;
|
||||
}
|
||||
|
||||
Error Error::None() {
|
||||
return Error(Code::None, {}, {}, {});
|
||||
}
|
||||
|
||||
Error Error::DecodedObjectFromResponse(QJsonObject object) {
|
||||
const auto entry = object.value("error");
|
||||
if (!entry.isObject()) {
|
||||
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.mid(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);
|
||||
}
|
||||
}
|
||||
|
||||
bool Error::empty() const {
|
||||
return (_code == Code::None);
|
||||
}
|
||||
|
||||
} // namespace Stripe
|
||||
66
Telegram/SourceFiles/payments/stripe/stripe_error.h
Normal file
66
Telegram/SourceFiles/payments/stripe/stripe_error.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 <QtCore/QString>
|
||||
|
||||
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]] 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 Stripe
|
||||
39
Telegram/SourceFiles/payments/stripe/stripe_form_encodable.h
Normal file
39
Telegram/SourceFiles/payments/stripe/stripe_form_encodable.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
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 <QtCore/QString>
|
||||
#include <map>
|
||||
|
||||
namespace Stripe {
|
||||
|
||||
class FormEncodable {
|
||||
public:
|
||||
[[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;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Stripe
|
||||
41
Telegram/SourceFiles/payments/stripe/stripe_form_encoder.cpp
Normal file
41
Telegram/SourceFiles/payments/stripe/stripe_form_encoder.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
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 <QStringList>
|
||||
#include <QUrl>
|
||||
#include <vector>
|
||||
|
||||
namespace Stripe {
|
||||
|
||||
QByteArray FormEncoder::formEncodedDataForObject(
|
||||
FormEncodable &&object) {
|
||||
const auto root = object.rootObjectName();
|
||||
const auto values = object.formFieldValues();
|
||||
auto result = QByteArray();
|
||||
auto keys = std::vector<QString>();
|
||||
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
|
||||
21
Telegram/SourceFiles/payments/stripe/stripe_form_encoder.h
Normal file
21
Telegram/SourceFiles/payments/stripe/stripe_form_encoder.h
Normal file
@@ -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
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
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 <QtCore/QString>
|
||||
|
||||
namespace Stripe {
|
||||
|
||||
struct PaymentConfiguration {
|
||||
QString publishableKey;
|
||||
// PaymentMethodType additionalPaymentMethods; // Apply Pay
|
||||
|
||||
// TODO incomplete, not used.
|
||||
//BillingAddressFields requiredBillingAddressFields
|
||||
// = BillingAddressFields::None;
|
||||
|
||||
QString companyName;
|
||||
// QString appleMerchantIdentifier; // Apple Pay
|
||||
// bool smsAutofillDisabled = true; // Mobile only
|
||||
};
|
||||
|
||||
} // namespace Stripe
|
||||
13
Telegram/SourceFiles/payments/stripe/stripe_pch.h
Normal file
13
Telegram/SourceFiles/payments/stripe/stripe_pch.h
Normal file
@@ -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 <QtCore/QString>
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonValue>
|
||||
#include <QtCore/QJsonDocument>
|
||||
65
Telegram/SourceFiles/payments/stripe/stripe_token.cpp
Normal file
65
Telegram/SourceFiles/payments/stripe/stripe_token.cpp
Normal file
@@ -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::fromSecsSinceEpoch(
|
||||
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
|
||||
49
Telegram/SourceFiles/payments/stripe/stripe_token.h
Normal file
49
Telegram/SourceFiles/payments/stripe/stripe_token.h
Normal file
@@ -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 <QtCore/QDateTime>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user