Add native card input support through smartglocal.
This commit is contained in:
parent
79f7aa703a
commit
61d0cc38b0
|
@ -272,9 +272,25 @@ void CheckoutProcess::handleError(const Error &error) {
|
||||||
} else if (id == u"ProcessingError"_q) {
|
} else if (id == u"ProcessingError"_q) {
|
||||||
showToast({ "Sorry, a processing error occurred." });
|
showToast({ "Sorry, a processing error occurred." });
|
||||||
} else {
|
} else {
|
||||||
showToast({ "Error: " + id });
|
showToast({ "Stripe Error: " + id });
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
case Error::Type::SmartGlocal: {
|
||||||
|
//using Field = Ui::CardField;
|
||||||
|
//if (id == u"InvalidNumber"_q || id == u"IncorrectNumber"_q) {
|
||||||
|
// showCardError(Field::Number);
|
||||||
|
//} else if (id == u"InvalidCVC"_q || id == u"IncorrectCVC"_q) {
|
||||||
|
// showCardError(Field::Cvc);
|
||||||
|
//} else if (id == u"InvalidExpiryMonth"_q
|
||||||
|
// || id == u"InvalidExpiryYear"_q
|
||||||
|
// || id == u"ExpiredCard"_q) {
|
||||||
|
// showCardError(Field::ExpireDate);
|
||||||
|
//} else if (id == u"CardDeclined"_q) {
|
||||||
|
// showToast({ tr::lng_payments_card_declined(tr::now) });
|
||||||
|
//} else {
|
||||||
|
showToast({ "SmartGlocal Error: " + id });
|
||||||
|
//}
|
||||||
|
} break;
|
||||||
case Error::Type::TmpPassword:
|
case Error::Type::TmpPassword:
|
||||||
if (const auto box = _enterPasswordBox.data()) {
|
if (const auto box = _enterPasswordBox.data()) {
|
||||||
if (!box->handleCustomCheckError(id)) {
|
if (!box->handleCustomCheckError(id)) {
|
||||||
|
|
|
@ -21,6 +21,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "stripe/stripe_error.h"
|
#include "stripe/stripe_error.h"
|
||||||
#include "stripe/stripe_token.h"
|
#include "stripe/stripe_token.h"
|
||||||
#include "stripe/stripe_card_validator.h"
|
#include "stripe/stripe_card_validator.h"
|
||||||
|
#include "smartglocal/smartglocal_api_client.h"
|
||||||
|
#include "smartglocal/smartglocal_error.h"
|
||||||
|
#include "smartglocal/smartglocal_token.h"
|
||||||
#include "storage/storage_account.h"
|
#include "storage/storage_account.h"
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
@ -96,6 +99,13 @@ constexpr auto kPasswordPeriod = 15 * TimeId(60);
|
||||||
+ card.last4();
|
+ card.last4();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QString CardTitle(const SmartGlocal::Card &card) {
|
||||||
|
// Like server stores saved_credentials title.
|
||||||
|
return card.type().toLower()
|
||||||
|
+ " *"
|
||||||
|
+ SmartGlocal::Last4(card);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Form::Form(not_null<PeerData*> peer, MsgId itemId, bool receipt)
|
Form::Form(not_null<PeerData*> peer, MsgId itemId, bool receipt)
|
||||||
|
@ -427,33 +437,39 @@ void Form::fillPaymentMethodInformation() {
|
||||||
_paymentMethod.native = NativePaymentMethod();
|
_paymentMethod.native = NativePaymentMethod();
|
||||||
_paymentMethod.ui.native = Ui::NativeMethodDetails();
|
_paymentMethod.ui.native = Ui::NativeMethodDetails();
|
||||||
_paymentMethod.ui.url = _details.url;
|
_paymentMethod.ui.url = _details.url;
|
||||||
if (_details.nativeProvider == "stripe") {
|
if (!_details.nativeProvider.isEmpty()) {
|
||||||
fillStripeNativeMethod();
|
auto error = QJsonParseError();
|
||||||
|
auto document = QJsonDocument::fromJson(
|
||||||
|
_details.nativeParamsJson,
|
||||||
|
&error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
LOG(("Payment Error: Could not decode native_params, error %1: %2"
|
||||||
|
).arg(error.error
|
||||||
|
).arg(error.errorString()));
|
||||||
|
} else if (!document.isObject()) {
|
||||||
|
LOG(("Payment Error: Not an object in native_params."));
|
||||||
|
} else {
|
||||||
|
const auto object = document.object();
|
||||||
|
if (_details.nativeProvider == "stripe") {
|
||||||
|
fillStripeNativeMethod(object);
|
||||||
|
} else if (_details.nativeProvider == "smartglocal") {
|
||||||
|
fillSmartGlocalNativeMethod(object);
|
||||||
|
} else {
|
||||||
|
LOG(("Payment Error: Unknown native provider '%1'."
|
||||||
|
).arg(_details.nativeProvider));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
refreshPaymentMethodDetails();
|
refreshPaymentMethodDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Form::fillStripeNativeMethod() {
|
void Form::fillStripeNativeMethod(QJsonObject object) {
|
||||||
auto error = QJsonParseError();
|
|
||||||
auto document = QJsonDocument::fromJson(
|
|
||||||
_details.nativeParamsJson,
|
|
||||||
&error);
|
|
||||||
if (error.error != QJsonParseError::NoError) {
|
|
||||||
LOG(("Payment Error: Could not decode native_params, error %1: %2"
|
|
||||||
).arg(error.error
|
|
||||||
).arg(error.errorString()));
|
|
||||||
return;
|
|
||||||
} else if (!document.isObject()) {
|
|
||||||
LOG(("Payment Error: Not an object in native_params."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto object = document.object();
|
|
||||||
const auto value = [&](QStringView key) {
|
const auto value = [&](QStringView key) {
|
||||||
return object.value(key);
|
return object.value(key);
|
||||||
};
|
};
|
||||||
const auto key = value(u"publishable_key").toString();
|
const auto key = value(u"publishable_key").toString();
|
||||||
if (key.isEmpty()) {
|
if (key.isEmpty()) {
|
||||||
LOG(("Payment Error: No publishable_key in native_params."));
|
LOG(("Payment Error: No publishable_key in stripe native_params."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_paymentMethod.native = NativePaymentMethod{
|
_paymentMethod.native = NativePaymentMethod{
|
||||||
|
@ -469,6 +485,29 @@ void Form::fillStripeNativeMethod() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Form::fillSmartGlocalNativeMethod(QJsonObject object) {
|
||||||
|
const auto value = [&](QStringView key) {
|
||||||
|
return object.value(key);
|
||||||
|
};
|
||||||
|
const auto key = value(u"public_token").toString();
|
||||||
|
if (key.isEmpty()) {
|
||||||
|
LOG(("Payment Error: "
|
||||||
|
"No public_token in smartglocal native_params."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_paymentMethod.native = NativePaymentMethod{
|
||||||
|
.data = SmartGlocalPaymentMethod{
|
||||||
|
.publicToken = key,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
_paymentMethod.ui.native = Ui::NativeMethodDetails{
|
||||||
|
.supported = true,
|
||||||
|
.needCountry = false,
|
||||||
|
.needZip = false,
|
||||||
|
.needCardholderName = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void Form::submit() {
|
void Form::submit() {
|
||||||
Expects(_paymentMethod.newCredentials
|
Expects(_paymentMethod.newCredentials
|
||||||
|| _paymentMethod.savedCredentials);
|
|| _paymentMethod.savedCredentials);
|
||||||
|
@ -602,6 +641,7 @@ bool Form::hasChanges() const {
|
||||||
: _information;
|
: _information;
|
||||||
return (information != _savedInformation)
|
return (information != _savedInformation)
|
||||||
|| (_stripe != nullptr)
|
|| (_stripe != nullptr)
|
||||||
|
|| (_smartglocal != nullptr)
|
||||||
|| !_paymentMethod.newCredentials.empty();
|
|| !_paymentMethod.newCredentials.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,7 +695,10 @@ void Form::validateCard(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto &native = _paymentMethod.native.data;
|
const auto &native = _paymentMethod.native.data;
|
||||||
if (const auto stripe = std::get_if<StripePaymentMethod>(&native)) {
|
if (const auto smartglocal = std::get_if<SmartGlocalPaymentMethod>(
|
||||||
|
&native)) {
|
||||||
|
validateCard(*smartglocal, details, saveInformation);
|
||||||
|
} else if (const auto stripe = std::get_if<StripePaymentMethod>(&native)) {
|
||||||
validateCard(*stripe, details, saveInformation);
|
validateCard(*stripe, details, saveInformation);
|
||||||
} else {
|
} else {
|
||||||
Unexpected("Native payment provider in Form::validateCard.");
|
Unexpected("Native payment provider in Form::validateCard.");
|
||||||
|
@ -755,6 +798,57 @@ void Form::validateCard(
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Form::validateCard(
|
||||||
|
const SmartGlocalPaymentMethod &method,
|
||||||
|
const Ui::UncheckedCardDetails &details,
|
||||||
|
bool saveInformation) {
|
||||||
|
Expects(!method.publicToken.isEmpty());
|
||||||
|
|
||||||
|
if (_smartglocal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto configuration = SmartGlocal::PaymentConfiguration{
|
||||||
|
.publicToken = method.publicToken,
|
||||||
|
.isTest = _invoice.isTest,
|
||||||
|
};
|
||||||
|
_smartglocal = std::make_unique<SmartGlocal::APIClient>(
|
||||||
|
std::move(configuration));
|
||||||
|
auto card = Stripe::CardParams{
|
||||||
|
.number = details.number,
|
||||||
|
.expMonth = details.expireMonth,
|
||||||
|
.expYear = details.expireYear,
|
||||||
|
.cvc = details.cvc,
|
||||||
|
.name = details.cardholderName,
|
||||||
|
.addressZip = details.addressZip,
|
||||||
|
.addressCountry = details.addressCountry,
|
||||||
|
};
|
||||||
|
_smartglocal->createTokenWithCard(std::move(card), crl::guard(this, [=](
|
||||||
|
SmartGlocal::Token token,
|
||||||
|
SmartGlocal::Error error) {
|
||||||
|
_smartglocal = nullptr;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
LOG(("SmartGlocal Error %1: %2 (%3)"
|
||||||
|
).arg(int(error.code())
|
||||||
|
).arg(error.description()
|
||||||
|
).arg(error.message()));
|
||||||
|
_updates.fire(Error{
|
||||||
|
Error::Type::SmartGlocal,
|
||||||
|
error.description(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setPaymentCredentials({
|
||||||
|
.title = CardTitle(token.card()),
|
||||||
|
.data = QJsonDocument(QJsonObject{
|
||||||
|
{ "token", token.tokenId() },
|
||||||
|
{ "type", "card" },
|
||||||
|
}).toJson(QJsonDocument::Compact),
|
||||||
|
.saveOnServer = saveInformation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
void Form::setPaymentCredentials(const NewCredentials &credentials) {
|
void Form::setPaymentCredentials(const NewCredentials &credentials) {
|
||||||
Expects(!credentials.empty());
|
Expects(!credentials.empty());
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "mtproto/sender.h"
|
#include "mtproto/sender.h"
|
||||||
|
|
||||||
class Image;
|
class Image;
|
||||||
|
class QJsonObject;
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
struct CloudPasswordResult;
|
struct CloudPasswordResult;
|
||||||
|
@ -21,6 +22,10 @@ namespace Stripe {
|
||||||
class APIClient;
|
class APIClient;
|
||||||
} // namespace Stripe
|
} // namespace Stripe
|
||||||
|
|
||||||
|
namespace SmartGlocal {
|
||||||
|
class APIClient;
|
||||||
|
} // namespace SmartGlocal
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
@ -86,10 +91,15 @@ struct StripePaymentMethod {
|
||||||
QString publishableKey;
|
QString publishableKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SmartGlocalPaymentMethod {
|
||||||
|
QString publicToken;
|
||||||
|
};
|
||||||
|
|
||||||
struct NativePaymentMethod {
|
struct NativePaymentMethod {
|
||||||
std::variant<
|
std::variant<
|
||||||
v::null_t,
|
v::null_t,
|
||||||
StripePaymentMethod> data;
|
StripePaymentMethod,
|
||||||
|
SmartGlocalPaymentMethod> data;
|
||||||
|
|
||||||
[[nodiscard]] bool valid() const {
|
[[nodiscard]] bool valid() const {
|
||||||
return !v::is_null(data);
|
return !v::is_null(data);
|
||||||
|
@ -131,6 +141,7 @@ struct Error {
|
||||||
Form,
|
Form,
|
||||||
Validate,
|
Validate,
|
||||||
Stripe,
|
Stripe,
|
||||||
|
SmartGlocal,
|
||||||
TmpPassword,
|
TmpPassword,
|
||||||
Send,
|
Send,
|
||||||
};
|
};
|
||||||
|
@ -221,7 +232,8 @@ private:
|
||||||
const MTPDpaymentSavedCredentialsCard &data);
|
const MTPDpaymentSavedCredentialsCard &data);
|
||||||
void processShippingOptions(const QVector<MTPShippingOption> &data);
|
void processShippingOptions(const QVector<MTPShippingOption> &data);
|
||||||
void fillPaymentMethodInformation();
|
void fillPaymentMethodInformation();
|
||||||
void fillStripeNativeMethod();
|
void fillStripeNativeMethod(QJsonObject object);
|
||||||
|
void fillSmartGlocalNativeMethod(QJsonObject object);
|
||||||
void refreshPaymentMethodDetails();
|
void refreshPaymentMethodDetails();
|
||||||
[[nodiscard]] QString defaultPhone() const;
|
[[nodiscard]] QString defaultPhone() const;
|
||||||
[[nodiscard]] QString defaultCountry() const;
|
[[nodiscard]] QString defaultCountry() const;
|
||||||
|
@ -230,6 +242,10 @@ private:
|
||||||
const StripePaymentMethod &method,
|
const StripePaymentMethod &method,
|
||||||
const Ui::UncheckedCardDetails &details,
|
const Ui::UncheckedCardDetails &details,
|
||||||
bool saveInformation);
|
bool saveInformation);
|
||||||
|
void validateCard(
|
||||||
|
const SmartGlocalPaymentMethod &method,
|
||||||
|
const Ui::UncheckedCardDetails &details,
|
||||||
|
bool saveInformation);
|
||||||
|
|
||||||
bool validateInformationLocal(
|
bool validateInformationLocal(
|
||||||
const Ui::RequestedInformation &information) const;
|
const Ui::RequestedInformation &information) const;
|
||||||
|
@ -259,6 +275,7 @@ private:
|
||||||
mtpRequestId _passwordRequestId = 0;
|
mtpRequestId _passwordRequestId = 0;
|
||||||
|
|
||||||
std::unique_ptr<Stripe::APIClient> _stripe;
|
std::unique_ptr<Stripe::APIClient> _stripe;
|
||||||
|
std::unique_ptr<SmartGlocal::APIClient> _smartglocal;
|
||||||
|
|
||||||
Ui::ShippingOptions _shippingOptions;
|
Ui::ShippingOptions _shippingOptions;
|
||||||
QString _requestedInformationId;
|
QString _requestedInformationId;
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "smartglocal/smartglocal_api_client.h"
|
||||||
|
|
||||||
|
#include "smartglocal/smartglocal_error.h"
|
||||||
|
#include "smartglocal/smartglocal_token.h"
|
||||||
|
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
#include <crl/crl_on_main.h>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <debugapi.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
APIClient::APIClient(PaymentConfiguration configuration)
|
||||||
|
: _apiUrl("https://" + APIURLBase(configuration.isTest))
|
||||||
|
, _configuration(configuration) {
|
||||||
|
_additionalHttpHeaders = {
|
||||||
|
{ "X-PUBLIC-TOKEN", _configuration.publicToken },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
APIClient::~APIClient() {
|
||||||
|
const auto destroy = std::move(_old);
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIClient::createTokenWithCard(
|
||||||
|
Stripe::CardParams card,
|
||||||
|
TokenCompletionCallback completion) {
|
||||||
|
createTokenWithData(ToJson(card), std::move(completion));
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIClient::createTokenWithData(
|
||||||
|
QByteArray data,
|
||||||
|
TokenCompletionCallback completion) {
|
||||||
|
const auto url = QUrl(_apiUrl + '/' + TokenEndpoint());
|
||||||
|
auto request = QNetworkRequest(url);
|
||||||
|
request.setHeader(
|
||||||
|
QNetworkRequest::ContentTypeHeader,
|
||||||
|
"application/json");
|
||||||
|
for (const auto &[name, value] : _additionalHttpHeaders) {
|
||||||
|
request.setRawHeader(name.toUtf8(), value.toUtf8());
|
||||||
|
}
|
||||||
|
destroyReplyDelayed(std::move(_reply));
|
||||||
|
_reply.reset(_manager.post(request, data));
|
||||||
|
const auto finish = [=](Token token, Error error) {
|
||||||
|
crl::on_main([
|
||||||
|
completion,
|
||||||
|
token = std::move(token),
|
||||||
|
error = std::move(error)
|
||||||
|
] {
|
||||||
|
completion(std::move(token), std::move(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const auto finishWithError = [=](Error error) {
|
||||||
|
finish(Token::Empty(), std::move(error));
|
||||||
|
};
|
||||||
|
const auto finishWithToken = [=](Token token) {
|
||||||
|
finish(std::move(token), Error::None());
|
||||||
|
};
|
||||||
|
QObject::connect(_reply.get(), &QNetworkReply::finished, [=] {
|
||||||
|
const auto replyError = int(_reply->error());
|
||||||
|
const auto replyErrorString = _reply->errorString();
|
||||||
|
const auto bytes = _reply->readAll();
|
||||||
|
destroyReplyDelayed(std::move(_reply));
|
||||||
|
|
||||||
|
auto parseError = QJsonParseError();
|
||||||
|
const auto document = QJsonDocument::fromJson(bytes, &parseError);
|
||||||
|
if (!bytes.isEmpty()) {
|
||||||
|
if (parseError.error != QJsonParseError::NoError) {
|
||||||
|
const auto code = int(parseError.error);
|
||||||
|
finishWithError({
|
||||||
|
Error::Code::JsonParse,
|
||||||
|
QString("InvalidJson%1").arg(code),
|
||||||
|
parseError.errorString(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else if (!document.isObject()) {
|
||||||
|
finishWithError({
|
||||||
|
Error::Code::JsonFormat,
|
||||||
|
"InvalidJsonRoot",
|
||||||
|
"Not an object in JSON reply.",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto object = document.object();
|
||||||
|
if (auto error = Error::DecodedObjectFromResponse(object)) {
|
||||||
|
finishWithError(std::move(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (replyError != QNetworkReply::NoError) {
|
||||||
|
finishWithError({
|
||||||
|
Error::Code::Network,
|
||||||
|
QString("RequestError%1").arg(replyError),
|
||||||
|
replyErrorString,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto token = Token::DecodedObjectFromAPIResponse(
|
||||||
|
document.object().value("data").toObject());
|
||||||
|
if (!token) {
|
||||||
|
finishWithError({
|
||||||
|
Error::Code::JsonFormat,
|
||||||
|
"InvalidTokenJson",
|
||||||
|
"Could not parse token.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finishWithToken(std::move(token));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIClient::destroyReplyDelayed(std::unique_ptr<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,48 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "stripe/stripe_card_params.h"
|
||||||
|
#include "smartglocal/smartglocal_callbacks.h"
|
||||||
|
|
||||||
|
#include <QtNetwork/QNetworkAccessManager>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace SmartGlocal {
|
||||||
|
|
||||||
|
struct PaymentConfiguration {
|
||||||
|
QString publicToken;
|
||||||
|
bool isTest = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class APIClient final {
|
||||||
|
public:
|
||||||
|
explicit APIClient(PaymentConfiguration configuration);
|
||||||
|
~APIClient();
|
||||||
|
|
||||||
|
void createTokenWithCard(
|
||||||
|
Stripe::CardParams card,
|
||||||
|
TokenCompletionCallback completion);
|
||||||
|
void createTokenWithData(
|
||||||
|
QByteArray data,
|
||||||
|
TokenCompletionCallback completion);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void destroyReplyDelayed(std::unique_ptr<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,17 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace SmartGlocal {
|
||||||
|
|
||||||
|
class Token;
|
||||||
|
class Error;
|
||||||
|
|
||||||
|
using TokenCompletionCallback = std::function<void(Token, Error)>;
|
||||||
|
|
||||||
|
} // namespace SmartGlocal
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "smartglocal/smartglocal_card.h"
|
||||||
|
|
||||||
|
namespace SmartGlocal {
|
||||||
|
|
||||||
|
Card::Card(
|
||||||
|
QString type,
|
||||||
|
QString network,
|
||||||
|
QString maskedNumber)
|
||||||
|
: _type(type)
|
||||||
|
, _network(network)
|
||||||
|
, _maskedNumber(maskedNumber) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Card Card::Empty() {
|
||||||
|
return Card(QString(), QString(), QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Card Card::DecodedObjectFromAPIResponse(QJsonObject object) {
|
||||||
|
const auto string = [&](QStringView key) {
|
||||||
|
return object.value(key).toString();
|
||||||
|
};
|
||||||
|
const auto type = string(u"card_type");
|
||||||
|
const auto network = string(u"card_network");
|
||||||
|
const auto maskedNumber = string(u"masked_card_number");
|
||||||
|
if (type.isEmpty() || maskedNumber.isEmpty()) {
|
||||||
|
return Card::Empty();
|
||||||
|
}
|
||||||
|
return Card(type, network, maskedNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Card::type() const {
|
||||||
|
return _type;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Card::network() const {
|
||||||
|
return _network;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Card::maskedNumber() const {
|
||||||
|
return _maskedNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Card::empty() const {
|
||||||
|
return _type.isEmpty() || _maskedNumber.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Last4(const Card &card) {
|
||||||
|
const auto masked = card.maskedNumber();
|
||||||
|
const auto m = QRegularExpression("[^\\d]\\d*(\\d{4})$").match(masked);
|
||||||
|
return m.hasMatch() ? m.captured(1) : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace SmartGlocal
|
|
@ -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
|
|
@ -51,7 +51,7 @@ APIClient::APIClient(PaymentConfiguration configuration)
|
||||||
: _apiUrl("https://" + APIURLBase())
|
: _apiUrl("https://" + APIURLBase())
|
||||||
, _configuration(configuration) {
|
, _configuration(configuration) {
|
||||||
_additionalHttpHeaders = {
|
_additionalHttpHeaders = {
|
||||||
{ "X-Stripe-User-Agent", StripeUserAgentDetails() },
|
{ "X-Stripe-User-Agent", StripeUserAgentDetails() },
|
||||||
{ "Stripe-Version", StripeAPIVersion() },
|
{ "Stripe-Version", StripeAPIVersion() },
|
||||||
{ "Authorization", "Bearer " + _configuration.publishableKey },
|
{ "Authorization", "Bearer " + _configuration.publishableKey },
|
||||||
};
|
};
|
||||||
|
|
|
@ -225,6 +225,11 @@ struct SimpleFieldState {
|
||||||
).toDouble();
|
).toDouble();
|
||||||
return QString::number(
|
return QString::number(
|
||||||
int64(std::round(real * std::pow(10., rule.exponent))));
|
int64(std::round(real * std::pow(10., rule.exponent))));
|
||||||
|
} else if (config.type == FieldType::CardNumber
|
||||||
|
|| config.type == FieldType::CardCVC) {
|
||||||
|
return QString(parsed).replace(
|
||||||
|
QRegularExpression("[^0-9\\.]"),
|
||||||
|
QString());
|
||||||
}
|
}
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,16 @@ PRIVATE
|
||||||
stripe/stripe_payment_configuration.h
|
stripe/stripe_payment_configuration.h
|
||||||
stripe/stripe_token.cpp
|
stripe/stripe_token.cpp
|
||||||
stripe/stripe_token.h
|
stripe/stripe_token.h
|
||||||
|
|
||||||
|
smartglocal/smartglocal_api_client.cpp
|
||||||
|
smartglocal/smartglocal_api_client.h
|
||||||
|
smartglocal/smartglocal_callbacks.h
|
||||||
|
smartglocal/smartglocal_card.cpp
|
||||||
|
smartglocal/smartglocal_card.h
|
||||||
|
smartglocal/smartglocal_error.cpp
|
||||||
|
smartglocal/smartglocal_error.h
|
||||||
|
smartglocal/smartglocal_token.cpp
|
||||||
|
smartglocal/smartglocal_token.h
|
||||||
|
|
||||||
stripe/stripe_pch.h
|
stripe/stripe_pch.h
|
||||||
)
|
)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 49887261a55665f6e195049bcc22b6495a44cc36
|
Subproject commit c1548226d49db23f68bbf35f34cc820171aed65c
|
Loading…
Reference in New Issue