diff --git a/.vscode/settings.json b/.vscode/settings.json index f06f7a0..13ff46d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -115,6 +115,8 @@ "xstring": "cpp", "xtr1common": "cpp", "xtree": "cpp", - "xutility": "cpp" + "xutility": "cpp", + "string_view": "cpp", + "text_encoding": "cpp" } } \ No newline at end of file diff --git a/README.md b/README.md index 183e03d..322ac45 100644 --- a/README.md +++ b/README.md @@ -263,10 +263,10 @@ target_link_libraries(your_target PRIVATE BankID::bankid_lib) ```cpp #include "bankid.h" +const std::string socialSecurityNumber = "1234567891" + // Configure SSL for test environment -BankID::SSLConfig sslConfig(BankID::Environment::TEST, - "certs/bankid_cert.pem", - "certs/bankid_key.pem"); +BankID::SSLConfig sslConfig(BankID::Environment::TEST); // Enable/disable debug logging const bool showDebug = true; @@ -275,38 +275,62 @@ const bool showDebug = true; BankID::Session session(sslConfig, showDebug); // Initialize the session -if (!session.initialize()) { - std::cerr << "Failed to initialize BankID session" << std::endl; - return -1; +if (!session.initialize()) +{ + std::cerr << "Failed to initialize BankID session" << std::endl; + return -1; } -// Start authentication -BankID::API::AuthConfig authConfig("192.168.1.1"); // End user IP -auto authResult = session.auth(authConfig); +BankID::Requirement requirement; +requirement.personalNumber = socialSecurityNumber; // Example personal number from command line -if (authResult.has_value()) { - std::cout << "Authentication started. Order ref: " - << authResult->orderRef << std::endl; +// Start authentication +BankID::API::AuthConfig authConfig("192.168.1.1"); +authConfig.setRequirement(requirement); - // Poll for completion - BankID::API::CollectConfig collectConfig(authResult->orderRef); +auto authResult = session.auth(authConfig); - while (true) { - auto collectResult = session.collect(collectConfig); - if (collectResult.has_value()) { - if (collectResult->status == "COMPLETED") { - std::cout << "Authentication completed!" << std::endl; - break; - } else if (collectResult->status == "FAILED") { - std::cout << "Authentication failed!" << std::endl; - break; - } +if (authResult.has_value()) +{ + std::cout << "Authentication started. Order ref: " + << authResult->orderRef << std::endl; + + // Poll for completion + BankID::API::CollectConfig collectConfig(authResult->orderRef); + + while (true) + { + auto collectResult = session.collect(collectConfig); + if (collectResult.has_value()) + { + if (collectResult->status == BankID::API::CollectStatus::COMPLETE) + { + std::cout << "Authentication completed successfully!" << std::endl; + std::cout << "Order ref: " << collectResult->orderRef << std::endl; + if (collectResult->completionData) + { + std::cout << "Completion data: " << collectResult->completionData->user->givenName.value_or("N/A") << std::endl; } - std::this_thread::sleep_for(std::chrono::seconds(2)); + break; + } + else if (collectResult->status == BankID::API::CollectStatus::PENDING) + { + std::cout << "Authentication still pending..." << std::endl; + } + else if (collectResult->status == BankID::API::CollectStatus::FAILED) + { + std::cout << "Authentication falses!" << std::endl; + break; + } } -} else { - std::cerr << "Authentication failed: " << authResult.error().message << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(2)); + } +} +else +{ + std::cerr << "Authentication failed: " << authResult.error().details << std::endl; } + ``` ### Advanced Authentication with Requirements diff --git a/bankid/includes/api/payment.h b/bankid/includes/api/payment.h index 3e96f14..64336a8 100644 --- a/bankid/includes/api/payment.h +++ b/bankid/includes/api/payment.h @@ -90,7 +90,7 @@ namespace BankID::API /** * Constructor for PaymentConfig * @param endUserIp The IP address of the end user (required) - * @param userVisibleTransaction Transaction information displayed to user (required) + * @param userVisibleTransaction Transaction information displayed to user (required) */ PaymentConfig(const std::string &endUserIp, const UserVisibleTransaction &userVisibleTransaction) : m_endUserIp(endUserIp), m_userVisibleTransaction(userVisibleTransaction) {} diff --git a/bankid/includes/api/sign.h b/bankid/includes/api/sign.h index c584b3b..3a18a9b 100644 --- a/bankid/includes/api/sign.h +++ b/bankid/includes/api/sign.h @@ -35,7 +35,7 @@ namespace BankID::API /** * Constructor for SignConfig * @param endUserIp The IP address of the end user (required) - * @param userVisibleData Text displayed to the user during signing (required) + * @param userVisibleData Text displayed to the user during signing (required) BASE 64-encoded */ SignConfig(const std::string &endUserIp, const std::string &userVisibleData) : m_endUserIp(endUserIp), m_userVisibleData(userVisibleData) {} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 276d2c8..4912157 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,12 +17,12 @@ add_executable(bankid_tests test_ssl.cpp test_auth.cpp test_sign.cpp - # test_payment.cpp + test_payment.cpp # test_other_payment.cpp # test_phone_auth.cpp # test_phone_sign.cpp # test_collect.cpp - # test_cancel.cpp + test_cancel.cpp ) # Link libraries @@ -125,6 +125,12 @@ set_tests_properties(AuthTests PROPERTIES WORKING_DIRECTORY $) +add_test(NAME PaymentTests COMMAND bankid_tests --gtest_filter=PaymentTest.*) +set_tests_properties(PaymentTests PROPERTIES WORKING_DIRECTORY $) + +add_test(NAME CancelTests COMMAND bankid_tests --gtest_filter=CancelTest.*) +set_tests_properties(CancelTests PROPERTIES WORKING_DIRECTORY $) + # Set environment variables for tests to find shared libraries if(BUILD_SHARED_LIBS AND UNIX) set_tests_properties(SSLConfigTest AuthTests SignTests PROPERTIES diff --git a/tests/test_cancel.cpp b/tests/test_cancel.cpp new file mode 100644 index 0000000..e3b0e73 --- /dev/null +++ b/tests/test_cancel.cpp @@ -0,0 +1,99 @@ +#include +#include +#include "bankid.h" +#include "api/auth.h" +#include "api/responses.h" + +class CancelTest : public ::testing::Test +{ +public: + CancelTest() + : sslConfig(BankID::Environment::TEST), + session(std::make_unique(sslConfig)) + { + } + +protected: + void TearDown() override + { + session.reset(); + } + + BankID::SSLConfig sslConfig; + std::unique_ptr session; +}; + +TEST_F(CancelTest, CancelAuth) +{ + BankID::API::AuthConfig config("127.0.0.1"); + + EXPECT_TRUE(config.getReturnUrl().value_or("").empty()); + EXPECT_TRUE(config.getUserVisibleData().value_or("").empty()); + + // Make the API call + auto response = session->auth(config); + + EXPECT_TRUE(response.has_value()); + + // Check the response + const auto &orderResponse = response.value(); + EXPECT_EQ(orderResponse.httpStatus, 200); + EXPECT_FALSE(orderResponse.orderRef.empty()); + + BankID::API::CancelConfig cancelConfig(orderResponse.orderRef); + auto cancelResponse = session->cancel(cancelConfig); + EXPECT_TRUE(cancelResponse.has_value()); + EXPECT_EQ(cancelResponse.value().httpStatus, 200); +} + +TEST_F(CancelTest, CancelSign) +{ + BankID::API::SignConfig config("127.0.0.1", BankID::Base64::encode("Test Sign Data")); + + EXPECT_TRUE(config.getReturnUrl().value_or("").empty()); + EXPECT_FALSE(config.getUserVisibleData().empty()); + + // Make the API call + auto response = session->sign(config); + + EXPECT_TRUE(response.has_value()); + + // Check the response + const auto &orderResponse = response.value(); + EXPECT_EQ(orderResponse.httpStatus, 200); + EXPECT_FALSE(orderResponse.orderRef.empty()); + + BankID::API::CancelConfig cancelConfig(orderResponse.orderRef); + auto cancelResponse = session->cancel(cancelConfig); + EXPECT_TRUE(cancelResponse.has_value()); + EXPECT_EQ(cancelResponse.value().httpStatus, 200); +} + +TEST_F(CancelTest, CancelPayment) +{ + + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient.name = "Test Recipient"; + transaction.money = BankID::API::PaymentMoney{"100,00", BankID::API::CurrencyCode::EUR}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + EXPECT_TRUE(config.getReturnUrl().value_or("").empty()); + EXPECT_TRUE(config.getUserVisibleData().value_or("").empty()); + + // Make the API call + auto response = session->payment(config); + + EXPECT_TRUE(response.has_value()); + + // Check the response + const auto &orderResponse = response.value(); + EXPECT_EQ(orderResponse.httpStatus, 200); + EXPECT_FALSE(orderResponse.orderRef.empty()); + + BankID::API::CancelConfig cancelConfig(orderResponse.orderRef); + auto cancelResponse = session->cancel(cancelConfig); + EXPECT_TRUE(cancelResponse.has_value()); + EXPECT_EQ(cancelResponse.value().httpStatus, 200); +} diff --git a/tests/test_payment.cpp b/tests/test_payment.cpp new file mode 100644 index 0000000..bfb6eed --- /dev/null +++ b/tests/test_payment.cpp @@ -0,0 +1,476 @@ +#include +#include +#include "bankid.h" +#include "api/auth.h" +#include "api/responses.h" + +class Payment : public ::testing::Test +{ +public: + Payment() + : sslConfig(BankID::Environment::TEST), + session(std::make_unique(sslConfig, false)) + { + } + +protected: + void TearDown() override + { + session.reset(); + } + + void CancelOrder(std::string orderRef) + { + BankID::API::CancelConfig cancelConfig(orderRef); + auto cancelResponse = session->cancel(cancelConfig); + EXPECT_TRUE(cancelResponse.has_value()); + EXPECT_EQ(cancelResponse.value().httpStatus, 200); + } + + BankID::SSLConfig sslConfig; + std::unique_ptr session; +}; + +TEST_F(Payment, ConfigPaymentParameters) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + transaction.money = BankID::API::PaymentMoney{"100.00", BankID::API::CurrencyCode::SEK}; + + BankID::API::PaymentConfig config("192.168.1.1", transaction); + + BankID::Requirement requirement; + + EXPECT_EQ(config.getEndUserIp(), "192.168.1.1"); + EXPECT_FALSE(config.getAppConfig().has_value()); + EXPECT_FALSE(config.getWebConfig().has_value()); + EXPECT_FALSE(config.getReturnUrl().has_value()); + + // Create AppConfig + BankID::AppConfig appConfig; + appConfig.appIdentifier = "com.example.app"; + appConfig.deviceOS = "Android"; + appConfig.deviceIdentifier = "device123"; + + // Set AppConfig + config.setAppConfig(appConfig); + EXPECT_TRUE(config.getAppConfig().has_value()); + + EXPECT_FALSE(config.getAppConfig()->deviceModelName.length() > 0); + + appConfig.deviceModelName = "Pixel 5"; + EXPECT_NE(config.getAppConfig()->deviceModelName, "Pixel 5"); + EXPECT_EQ(appConfig.deviceModelName, "Pixel 5"); + + // Create webConfig + BankID::WebConfig webConfig; + webConfig.deviceIdentifier = "webDevice123"; + webConfig.referringDomain = "example.com"; + webConfig.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"; + + EXPECT_THROW(config.setWebConfig(webConfig), std::invalid_argument); +} + +TEST_F(Payment, AppStartedPayment) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + transaction.money = BankID::API::PaymentMoney{"100,00", BankID::API::CurrencyCode::SEK}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + config.setUserVisibleData(BankID::Base64::encode("This is a sample text to be signed")); + + // Create the appConfig + BankID::AppConfig appConfig; + appConfig.appIdentifier = "com.opsynk.com"; + appConfig.deviceOS = "IOS 14.4"; + appConfig.deviceModelName = "iPhone 12"; + appConfig.deviceIdentifier = "device123"; + + config.setAppConfig(appConfig); + + EXPECT_TRUE(config.getAppConfig().has_value()); + EXPECT_TRUE(config.getAppConfig()->deviceModelName == "iPhone 12"); + + EXPECT_TRUE(config.getEndUserIp() == "127.0.0.1"); + + EXPECT_FALSE(config.getReturnUrl().has_value()); + EXPECT_FALSE(config.getUserNonVisibleData().has_value()); + EXPECT_FALSE(config.getReturnRisk().has_value()); + + // Set all the optional parameters + config.setReturnUrl("https://example.com/return") + .setUserNonVisibleData(BankID::Base64::encode("New Payment text")) + .setReturnRisk(true); + + EXPECT_TRUE(config.getReturnUrl().has_value()); + EXPECT_EQ(config.getReturnUrl().value(), "https://example.com/return"); + EXPECT_TRUE(config.getUserNonVisibleData().has_value()); + EXPECT_TRUE(config.getReturnRisk().has_value()); + EXPECT_EQ(config.getUserNonVisibleData().value(), BankID::Base64::encode("New Payment text")); + EXPECT_EQ(config.getReturnRisk().value(), true); + + // Make the API call + auto response = session->payment(config); + + if (!response.has_value()) + { + FAIL() << "Sign request failed: " << response.error().details; + } + + EXPECT_TRUE(response.has_value()); + + // Check the response + const auto &orderResponse = response.value(); + EXPECT_EQ(orderResponse.httpStatus, 200); + EXPECT_FALSE(orderResponse.orderRef.empty()); + EXPECT_FALSE(orderResponse.autoStartToken.empty()); + EXPECT_FALSE(orderResponse.qrStartToken.empty()); + EXPECT_FALSE(orderResponse.qrStartSecret.empty()); + + // Cancel the order + CancelOrder(orderResponse.orderRef); +} + +TEST_F(Payment, WebStartedPayment) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + transaction.money = BankID::API::PaymentMoney{"100,00", BankID::API::CurrencyCode::SEK}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + // Create the webConfig + BankID::WebConfig webConfig; + webConfig.deviceIdentifier = "f1e3813ab36f114d4b0c2b3636617511467adb353ce8e5ae6c83500d932f2269"; + webConfig.referringDomain = "example.com"; + webConfig.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"; + + EXPECT_NO_THROW(config.setWebConfig(webConfig)); + + // Create the appConfig + BankID::AppConfig appConfig; + appConfig.appIdentifier = "com.opsynk.com"; + appConfig.deviceOS = "IOS 14.4"; + appConfig.deviceModelName = "iPhone 12"; + appConfig.deviceIdentifier = "device123"; + + config.setUserVisibleData(BankID::Base64::encode("This is a sample text to be signed")); + + EXPECT_THROW(config.setAppConfig(appConfig), std::invalid_argument); + EXPECT_FALSE(config.getAppConfig().has_value()); + + EXPECT_TRUE(config.getWebConfig().has_value()); + EXPECT_TRUE(config.getWebConfig()->deviceIdentifier == "f1e3813ab36f114d4b0c2b3636617511467adb353ce8e5ae6c83500d932f2269"); + EXPECT_TRUE(config.getWebConfig()->referringDomain == "example.com"); + EXPECT_TRUE(config.getWebConfig()->userAgent == "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"); + + EXPECT_FALSE(config.getReturnUrl().has_value()); + EXPECT_EQ(config.getUserVisibleData(), BankID::Base64::encode("This is a sample text to be signed")); + EXPECT_FALSE(config.getReturnRisk().has_value()); + + // Set all the optional parameters + config.setReturnUrl("https://example.com/return") + .setUserNonVisibleData(BankID::Base64::encode("New Payment text")) + .setReturnRisk(false); + + EXPECT_TRUE(config.getReturnUrl().has_value()); + EXPECT_EQ(config.getReturnUrl().value(), "https://example.com/return"); + EXPECT_TRUE(config.getUserNonVisibleData().has_value()); + EXPECT_TRUE(config.getReturnRisk().has_value()); + EXPECT_EQ(config.getUserNonVisibleData().value(), BankID::Base64::encode("New Payment text")); + EXPECT_EQ(config.getReturnRisk().value(), false); + + // Make the API call + auto response = session->payment(config); + + EXPECT_TRUE(response.has_value()); + + // Check the response + const auto &orderResponse = response.value(); + EXPECT_EQ(orderResponse.httpStatus, 200); + EXPECT_FALSE(orderResponse.orderRef.empty()); + EXPECT_FALSE(orderResponse.autoStartToken.empty()); + EXPECT_FALSE(orderResponse.qrStartToken.empty()); + EXPECT_FALSE(orderResponse.qrStartSecret.empty()); + + CancelOrder(orderResponse.orderRef); +} + +TEST_F(Payment, PersonalNumberRequirementsPayment) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + transaction.money = BankID::API::PaymentMoney{"100,00", BankID::API::CurrencyCode::SEK}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + // Requirements + BankID::Requirement requirement; + requirement.personalNumber = "200003121145"; + + EXPECT_NO_THROW(config.setRequirement(requirement)); + EXPECT_TRUE(config.getRequirement().has_value()); + EXPECT_EQ(config.getRequirement()->personalNumber, "200003121145"); + EXPECT_FALSE(config.getRequirement()->cardReader.has_value()); + + // Set all the optional parameters + config.setReturnUrl("https://example.com/return") + .setUserVisibleData(BankID::Base64::encode("New Payment visible text")) + .setUserNonVisibleData(BankID::Base64::encode("New Payment text")); + + EXPECT_TRUE(config.getReturnUrl().has_value()); + EXPECT_EQ(config.getReturnUrl().value(), "https://example.com/return"); + EXPECT_TRUE(config.getUserNonVisibleData().has_value()); + EXPECT_EQ(config.getUserNonVisibleData().value(), BankID::Base64::encode("New Payment text")); + EXPECT_EQ(config.getUserVisibleData(), BankID::Base64::encode("New Payment visible text")); + + // Make the API call + auto response = session->payment(config); + + if (!response.has_value()) + { + std::cerr << "Payment request failed: " << response.error().details << std::endl; + FAIL() << "Payment request failed: " << response.error().details; + } + + EXPECT_TRUE(response.has_value()); + + // Check the response + const auto &orderResponse = response.value(); + EXPECT_EQ(orderResponse.httpStatus, 200); + EXPECT_FALSE(orderResponse.orderRef.empty()); + EXPECT_FALSE(orderResponse.autoStartToken.empty()); + EXPECT_FALSE(orderResponse.qrStartToken.empty()); + EXPECT_FALSE(orderResponse.qrStartSecret.empty()); + + CancelOrder(orderResponse.orderRef); +} + +TEST_F(Payment, SimplePayment) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + EXPECT_TRUE(config.getReturnUrl().value_or("").empty()); + EXPECT_FALSE(config.getUserVisibleTransaction().transactionType.empty()); + EXPECT_FALSE(config.getUserVisibleTransaction().recipient.name.empty()); + EXPECT_FALSE(config.getUserVisibleTransaction().money.has_value()); + + // Make the API call + auto response = session->payment(config); + + EXPECT_TRUE(response.has_value()); + + // Check the response + const auto &orderResponse = response.value(); + EXPECT_EQ(orderResponse.httpStatus, 200); + EXPECT_FALSE(orderResponse.orderRef.empty()); + EXPECT_FALSE(orderResponse.autoStartToken.empty()); + EXPECT_FALSE(orderResponse.qrStartToken.empty()); + EXPECT_FALSE(orderResponse.qrStartSecret.empty()); + + CancelOrder(orderResponse.orderRef); +} + +TEST_F(Payment, PaymentInvalidEnduserIp) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + + BankID::API::PaymentConfig config("aaaaaaaaaaaaaaaaa", transaction); + + EXPECT_TRUE(config.getReturnUrl().value_or("").empty()); + EXPECT_FALSE(config.getUserVisibleTransaction().transactionType.empty()); + EXPECT_FALSE(config.getUserVisibleTransaction().recipient.name.empty()); + + // Make the API call + auto response = session->payment(config); + + EXPECT_FALSE(response.has_value()); + + if (!response.has_value()) + { + EXPECT_EQ(response.error().details, R"({"errorCode":"invalidParameters","details":"Invalid endUserIp"})"); + EXPECT_EQ(response.error().errorCode, BankID::BankIdErrorCode::INVALID_PARAMETERS); + EXPECT_EQ(response.error().httpStatus, 400); + } +} + +TEST_F(Payment, PaymentInvalidUserVisibleTransaction) +{ + + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "random"; // Invalid transaction type + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + // Create requirement with invalid personal number + BankID::Requirement requirement; + requirement.personalNumber = "invalid_personal_number"; + config.setRequirement(requirement); + + // Make the API call + auto response = session->payment(config); + EXPECT_FALSE(response.has_value()); + if (!response.has_value()) + { + EXPECT_EQ(response.error().details, R"({"errorCode":"invalidParameters","details":"Incorrect personalNumber"})"); + EXPECT_EQ(response.error().errorCode, BankID::BankIdErrorCode::INVALID_PARAMETERS); + EXPECT_EQ(response.error().httpStatus, 400); + } + + requirement.personalNumber = "200003121145"; // Valid personal number + requirement.cardReader = "some_invalid_card_reader"; + config.setRequirement(requirement); + + // Make the API call + response = session->payment(config); + EXPECT_FALSE(response.has_value()); + + if (!response.has_value()) + { + EXPECT_EQ(response.error().details, R"({"errorCode":"invalidParameters","details":"transactionType is not a valid transaction type"})"); + EXPECT_EQ(response.error().errorCode, BankID::BankIdErrorCode::INVALID_PARAMETERS); + EXPECT_EQ(response.error().httpStatus, 400); + } +} + +// Invalid parameters test +TEST_F(Payment, PersonalNumberPaymentAlreadyInProgress) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + BankID::Requirement requirement; + requirement.personalNumber = "200003121145"; + + config.setRequirement(requirement); + + // Make the API call + auto response = session->payment(config); + + EXPECT_TRUE(response.has_value()); + + const auto &orderResponse = response.value(); + EXPECT_EQ(orderResponse.httpStatus, 200); + EXPECT_FALSE(orderResponse.orderRef.empty()); + EXPECT_FALSE(orderResponse.autoStartToken.empty()); + EXPECT_FALSE(orderResponse.qrStartToken.empty()); + EXPECT_FALSE(orderResponse.qrStartSecret.empty()); + + // Make the same API call again to trigger ALREADY_IN_PROGRESS error + response = session->payment(config); + + EXPECT_FALSE(response.has_value()); + + if (!response.has_value()) + { + EXPECT_EQ(response.error().details, R"({"errorCode":"alreadyInProgress","details":"Order already in progress for pno"})"); + EXPECT_EQ(response.error().errorCode, BankID::BankIdErrorCode::ALREADY_IN_PROGRESS); + EXPECT_EQ(response.error().httpStatus, 400); + } +} + +TEST_F(Payment, PaymentInvalidMoneyAmount) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + transaction.money = BankID::API::PaymentMoney{"100.00", BankID::API::CurrencyCode::SEK}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + // Make the API call + auto response = session->payment(config); + EXPECT_FALSE(response.has_value()); + + if (!response.has_value()) + { + EXPECT_EQ(response.error().details, R"({"errorCode":"invalidParameters","details":"money has invalid amount or currency"})"); + EXPECT_EQ(response.error().errorCode, BankID::BankIdErrorCode::INVALID_PARAMETERS); + EXPECT_EQ(response.error().httpStatus, 400); + } + +} + +TEST_F(Payment, PaymentInvalidCurrency) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + + // Invalid currency code (simulate it by casting an invalid enum) + transaction.money = BankID::API::PaymentMoney{"100.00", static_cast(9999)}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + auto response = session->payment(config); + EXPECT_FALSE(response.has_value()); + + if (!response.has_value()) + { + EXPECT_EQ(response.error().details, R"({"errorCode":"invalidParameters","details":"money has invalid amount or currency"})"); + EXPECT_EQ(response.error().errorCode, BankID::BankIdErrorCode::INVALID_PARAMETERS); + EXPECT_EQ(response.error().httpStatus, 400); + } +} + + +TEST_F(Payment, PaymentInvalidRecipient) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + + // Invalid recipient (empty name) + transaction.recipient = BankID::API::PaymentRecipient{""}; + transaction.money = BankID::API::PaymentMoney{"100,00", BankID::API::CurrencyCode::SEK}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + auto response = session->payment(config); + EXPECT_FALSE(response.has_value()); + + if (!response.has_value()) + { + EXPECT_EQ(response.error().details, R"({"errorCode":"invalidParameters","details":"Recipient name is invalid"})"); + EXPECT_EQ(response.error().errorCode, BankID::BankIdErrorCode::INVALID_PARAMETERS); + EXPECT_EQ(response.error().httpStatus, 400); + } +} + +TEST_F(Payment, PaymentInvalidUserNonVisibleData) +{ + BankID::API::UserVisibleTransaction transaction; + transaction.transactionType = "card"; + transaction.recipient = BankID::API::PaymentRecipient{"Test Recipient"}; + transaction.money = BankID::API::PaymentMoney{"100,00", BankID::API::CurrencyCode::SEK}; + + BankID::API::PaymentConfig config("127.0.0.1", transaction); + + // Set only userNonVisibleData without setting userVisibleData + config.setUserNonVisibleData(BankID::Base64::encode("Hidden text")); + + auto response = session->payment(config); + EXPECT_FALSE(response.has_value()); + + if (!response.has_value()) + { + EXPECT_EQ(response.error().details, R"({"errorCode":"invalidParameters","details":"userNonVisible data requires userVisibleData"})"); + EXPECT_EQ(response.error().errorCode, BankID::BankIdErrorCode::INVALID_PARAMETERS); + EXPECT_EQ(response.error().httpStatus, 400); + } +}