#include "eetv4client.h" #include "eetv4signer.h" #include "eetca.h" #include #include #include #include #include #include namespace { void printError(const QString &message) { QTextStream(stderr) << "error: " << message << Qt::endl; } bool requiredOption(const QCommandLineParser &parser, const QString &name) { if (!parser.isSet(name)) { printError(QStringLiteral("missing required option --%1").arg(name)); return false; } return true; } bool parseAmount(const QString &value, EetV4Amount *amount) { QString error; if (!EetV4Amount::fromString(value, amount, &error)) { printError(error); return false; } return true; } int preflight(const QCommandLineParser &parser) { if (!requiredOption(parser, QStringLiteral("endpoint")) || !requiredOption(parser, QStringLiteral("certificate")) || !requiredOption(parser, QStringLiteral("password-env"))) { return 2; } const QUrl endpoint(parser.value(QStringLiteral("endpoint"))); if (!endpoint.isValid() || endpoint.scheme() != QLatin1String("https") || endpoint.host().isEmpty()) { printError(QStringLiteral("endpoint must be a valid HTTPS URL")); return 2; } QFile certificate(parser.value(QStringLiteral("certificate"))); if (!certificate.open(QIODevice::ReadOnly) || certificate.size() == 0) { printError(QStringLiteral("certificate file cannot be read")); return 2; } const QByteArray password = qgetenv(parser.value(QStringLiteral("password-env")).toLatin1()); if (password.isEmpty()) { printError(QStringLiteral("PKCS#12 password environment variable is empty or unset")); return 2; } const QByteArray probeSoap = "" "" "" ""; QString signingError; if (EetV4Signer::signSoap(probeSoap, certificate.fileName(), password, &signingError).isEmpty()) { printError(signingError); return 2; } QTextStream(stdout) << "configuration looks usable" << Qt::endl; return 0; } int sendSale(const QCommandLineParser &parser) { const QStringList required{ QStringLiteral("endpoint"), QStringLiteral("certificate"), QStringLiteral("password-env"), QStringLiteral("eic"), QStringLiteral("unit"), QStringLiteral("register"), QStringLiteral("receipt"), QStringLiteral("amount")}; for (const QString &option : required) { if (!requiredOption(parser, option)) { return 2; } } EetV4SaleRequest request; request.taxpayerEic = parser.value(QStringLiteral("eic")); request.unitId = parser.value(QStringLiteral("unit")).toInt(); request.cashRegisterId = parser.value(QStringLiteral("register")); request.receiptNumber = parser.value(QStringLiteral("receipt")); if (!parseAmount(parser.value(QStringLiteral("amount")), &request.totalSale)) { return 2; } const QDateTime localNow = QDateTime::currentDateTime(); const QDateTime now = localNow.toOffsetFromUtc(localNow.offsetFromUtc()); request.sentAt = now; request.saleAt = now; if (parser.isSet(QStringLiteral("sale-at"))) { request.saleAt = QDateTime::fromString(parser.value(QStringLiteral("sale-at")), Qt::ISODate); } if (parser.isSet(QStringLiteral("sent-at"))) { request.sentAt = QDateTime::fromString(parser.value(QStringLiteral("sent-at")), Qt::ISODate); } if (parser.isSet(QStringLiteral("verification"))) { request.verificationMode = true; } if (parser.isSet(QStringLiteral("first-submission"))) { request.firstSubmission = parser.value(QStringLiteral("first-submission")) != QLatin1String("false"); } const QByteArray password = qgetenv(parser.value(QStringLiteral("password-env")).toLatin1()); if (password.isEmpty()) { printError(QStringLiteral("PKCS#12 password environment variable is empty or unset")); return 2; } EetV4Client client({QUrl(parser.value(QStringLiteral("endpoint"))), parser.value(QStringLiteral("timeout")).toInt()}); EetV4Response response; QString error; if (!client.sendSale(request, parser.value(QStringLiteral("certificate")), password, &response, &error)) { printError(error); return 1; } if (response.status == EetV4ResponseStatus::Success) { QTextStream(stdout) << "sale accepted" << Qt::endl << "POK: " << response.pok << Qt::endl; } else if (response.status == EetV4ResponseStatus::Error) { QTextStream(stdout) << "EET rejected the sale" << Qt::endl << "code: " << response.errorCode << Qt::endl << "message: " << response.message << Qt::endl; return 1; } else { QTextStream(stdout) << "SOAP Fault: " << response.message << Qt::endl; return 1; } if (!response.warningCodes.isEmpty()) { QTextStream output(stdout); output << "warnings:"; for (const int code : response.warningCodes) { output << ' ' << code; } output << Qt::endl; } return 0; } bool caOptions(const QCommandLineParser &parser, EetCaClient *client) { if (!requiredOption(parser, QStringLiteral("api-url")) || !requiredOption(parser, QStringLiteral("signing-certificate")) || !requiredOption(parser, QStringLiteral("signing-password-env"))) { return false; } const QByteArray password = qgetenv(parser.value(QStringLiteral("signing-password-env")).toLatin1()); if (password.isEmpty()) { printError(QStringLiteral("CA signing password environment variable is empty or unset")); return false; } *client = EetCaClient({ QUrl(parser.value(QStringLiteral("api-url"))), parser.value(QStringLiteral("signing-certificate")), password, parser.value(QStringLiteral("timeout")).toInt() }); return true; } bool parseRequestId(const QCommandLineParser &parser, QUuid *requestId) { if (!requiredOption(parser, QStringLiteral("request-id"))) { return false; } *requestId = QUuid(parser.value(QStringLiteral("request-id"))); if (requestId->isNull()) { printError(QStringLiteral("request-id must be a valid UUID")); return false; } return true; } bool writeSecretFile(const QString &path, const QByteArray &data, QString *error) { if (path.isEmpty() || data.isEmpty()) { *error = QStringLiteral("output path and secret data are required"); return false; } QSaveFile file(path); if (!file.open(QIODevice::WriteOnly) || file.write(data) != data.size() || !file.commit()) { *error = QStringLiteral("cannot securely write %1").arg(path); return false; } file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner); return true; } int caCommand(const QString &command, const QCommandLineParser &parser) { EetCaClient client({QUrl(), {}, {}, 0}); if (!caOptions(parser, &client)) { return 2; } QString error; if (command == QLatin1String("ca-renew")) { QUuid requestId; if (!client.requestRenewal(&requestId, &error)) { printError(error); return 1; } QTextStream(stdout) << requestId.toString(QUuid::WithoutBraces) << Qt::endl; return 0; } if (command == QLatin1String("ca-list")) { QList requestIds; if (!client.listUnfinished(&requestIds, &error)) { printError(error); return 1; } for (const QUuid &requestId : requestIds) { QTextStream(stdout) << requestId.toString(QUuid::WithoutBraces) << Qt::endl; } return 0; } QUuid requestId; if (!parseRequestId(parser, &requestId)) { return 2; } if (command == QLatin1String("ca-status")) { EetCaRequestStatus status; if (!client.requestStatus(requestId, &status, &error)) { printError(error); return 1; } QTextStream output(stdout); output << "state: " << status.state << Qt::endl; if (status.pollAfterSeconds > 0) { output << "poll-after-seconds: " << status.pollAfterSeconds << Qt::endl; } if (!status.rejectedReason.isEmpty()) { output << "rejected-reason: " << status.rejectedReason << Qt::endl; } return 0; } if (command == QLatin1String("ca-claim")) { if (!requiredOption(parser, QStringLiteral("output-p12")) || !requiredOption(parser, QStringLiteral("output-password"))) { return 2; } EetCaPkcs12 package; if (!client.claimDownload(requestId, &package, &error)) { printError(error); return 1; } if (!writeSecretFile(parser.value(QStringLiteral("output-p12")), package.pkcs12, &error) || !writeSecretFile(parser.value(QStringLiteral("output-password")), package.password, &error)) { printError(error); return 1; } QTextStream(stdout) << "certificate package saved; secret values were not printed" << Qt::endl; return 0; } if (command == QLatin1String("ca-ack")) { if (!client.acknowledgeDownload(requestId, &error)) { printError(error); return 1; } QTextStream(stdout) << "certificate download acknowledged" << Qt::endl; return 0; } return 2; } } int main(int argc, char **argv) { QCoreApplication application(argc, argv); QCoreApplication::setApplicationName(QStringLiteral("EetCppPlayground")); QCommandLineParser parser; parser.setApplicationDescription(QStringLiteral("Manual EET v4 test client")); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument(QStringLiteral("command"), QStringLiteral("preflight, send-sale, ca-renew, ca-status, " "ca-list, ca-claim or ca-ack")); parser.addOption({QStringLiteral("endpoint"), QStringLiteral("EET HTTPS endpoint."), QStringLiteral("url")}); parser.addOption({QStringLiteral("certificate"), QStringLiteral("PKCS#12 certificate path."), QStringLiteral("path")}); parser.addOption({QStringLiteral("password-env"), QStringLiteral("Environment variable containing the PKCS#12 password."), QStringLiteral("name")}); parser.addOption({QStringLiteral("eic"), QStringLiteral("Taxpayer EIC."), QStringLiteral("value")}); parser.addOption({QStringLiteral("unit"), QStringLiteral("Evidence unit ID."), QStringLiteral("number")}); parser.addOption({QStringLiteral("register"), QStringLiteral("Cash register ID."), QStringLiteral("value")}); parser.addOption({QStringLiteral("receipt"), QStringLiteral("Receipt number."), QStringLiteral("value")}); parser.addOption({QStringLiteral("amount"), QStringLiteral("Total sale amount."), QStringLiteral("amount")}); parser.addOption({QStringLiteral("sale-at"), QStringLiteral("Sale timestamp (ISO 8601)."), QStringLiteral("timestamp")}); parser.addOption({QStringLiteral("sent-at"), QStringLiteral("Submission timestamp (ISO 8601)."), QStringLiteral("timestamp")}); parser.addOption({QStringLiteral("timeout"), QStringLiteral("Timeout in milliseconds."), QStringLiteral("milliseconds"), QStringLiteral("30000")}); parser.addOption({QStringLiteral("verification"), QStringLiteral("Use verification mode.")}); parser.addOption({QStringLiteral("first-submission"), QStringLiteral("Whether this is the first submission (default true)."), QStringLiteral("true|false"), QStringLiteral("true")}); parser.addOption({QStringLiteral("api-url"), QStringLiteral("CA EET API base URL."), QStringLiteral("url")}); parser.addOption({QStringLiteral("signing-certificate"), QStringLiteral("PKCS#12 certificate used for CA JWT signing."), QStringLiteral("path")}); parser.addOption({QStringLiteral("signing-password-env"), QStringLiteral("Environment variable containing the CA signing password."), QStringLiteral("name")}); parser.addOption({QStringLiteral("request-id"), QStringLiteral("CA request UUID."), QStringLiteral("uuid")}); parser.addOption({QStringLiteral("output-p12"), QStringLiteral("Output path for downloaded PKCS#12 data."), QStringLiteral("path")}); parser.addOption({QStringLiteral("output-password"), QStringLiteral("Output path for downloaded PKCS#12 password."), QStringLiteral("path")}); parser.process(application); if (parser.positionalArguments().size() != 1) { printError(QStringLiteral("exactly one command is required")); return 2; } const QString command = parser.positionalArguments().first(); if (command == QLatin1String("preflight")) { return preflight(parser); } if (command == QLatin1String("send-sale")) { return sendSale(parser); } if (command.startsWith(QLatin1String("ca-"))) { return caCommand(command, parser); } printError(QStringLiteral("unknown command: %1").arg(command)); return 2; }