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:
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
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 <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <crl/crl_on_main.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ComputeApiUrl(PaymentConfiguration configuration) {
|
||||
const auto url = configuration.tokenizeUrl;
|
||||
if (url.startsWith("https://")
|
||||
&& url.endsWith(".smart-glocal.com/cds/v1/tokenize/card")) {
|
||||
return url;
|
||||
}
|
||||
return QString("https://%1/%2")
|
||||
.arg(APIURLBase(configuration.isTest))
|
||||
.arg(TokenEndpoint());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
APIClient::APIClient(PaymentConfiguration configuration)
|
||||
: _apiUrl(ComputeApiUrl(configuration))
|
||||
, _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);
|
||||
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<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 SmartGlocal
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtCore/QString>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace SmartGlocal {
|
||||
|
||||
struct PaymentConfiguration {
|
||||
QString publicToken;
|
||||
QString tokenizeUrl;
|
||||
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<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 SmartGlocal
|
||||
@@ -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 SmartGlocal {
|
||||
|
||||
class Token;
|
||||
class Error;
|
||||
|
||||
using TokenCompletionCallback = std::function<void(Token, Error)>;
|
||||
|
||||
} // namespace SmartGlocal
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
#include <QtCore/QRegularExpression>
|
||||
|
||||
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) {
|
||||
static const auto RegExp = QRegularExpression("[^\\d]\\d*(\\d{4})$");
|
||||
const auto masked = card.maskedNumber();
|
||||
const auto m = RegExp.match(masked);
|
||||
return m.hasMatch() ? m.captured(1) : QString();
|
||||
}
|
||||
|
||||
} // namespace SmartGlocal
|
||||
51
Telegram/SourceFiles/payments/smartglocal/smartglocal_card.h
Normal file
51
Telegram/SourceFiles/payments/smartglocal/smartglocal_card.h
Normal file
@@ -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 <QtCore/QString>
|
||||
|
||||
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
|
||||
@@ -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
|
||||
@@ -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 <QtCore/QString>
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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 <QtCore/QDateTime>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user