tdesktop/Telegram/SourceFiles/payments/smartglocal/smartglocal_api_client.cpp

178 lines
4.7 KiB
C++

/*
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