diff --git a/buckaroo/builders/base_builder.py b/buckaroo/builders/base_builder.py index 314b56b..54538cb 100644 --- a/buckaroo/builders/base_builder.py +++ b/buckaroo/builders/base_builder.py @@ -202,8 +202,10 @@ def add_parameter( # Convert value to string for API compatibility str_value = str(value).lower() if isinstance(value, bool) else str(value) + # Top-level names keep internal caps ("CustomerFirstName"); grouped + # names stay flattened via capitalize(). parameter = Parameter( - name=key.capitalize(), + name=key.capitalize() if group_type else _upper_first(key), value=str_value, group_type=_upper_first(group_type) if group_type else None, group_id=group_id, diff --git a/buckaroo/builders/solutions/credit_management_builder.py b/buckaroo/builders/solutions/credit_management_builder.py index 62619fa..3189041 100644 --- a/buckaroo/builders/solutions/credit_management_builder.py +++ b/buckaroo/builders/solutions/credit_management_builder.py @@ -111,9 +111,9 @@ class CreditManagementBuilder(SolutionBuilder): # type, then caller-friendly field name -> wire field name (matched # case-insensitively against the already-added ``Parameter``s). # DebtorInfo maps the debtor's code to wire name "Debtorcode" (not - # "Code", unlike AddOrUpdateDebtor/CreateInvoice); the wire name is run - # through ``.capitalize()`` like every other parameter name, so the - # camelCase written here does not survive as-is. + # "Code", unlike AddOrUpdateDebtor/CreateInvoice); as a grouped parameter + # its wire name is run through ``.capitalize()``, so the camelCase written + # here does not survive as-is. _WIRE_NAMES: Dict[str, Dict[str, Dict[str, str]]] = { "debtorinfo": {"Debtor": {"code": "DebtorCode"}}, } diff --git a/tests/feature/payments/test_banking.py b/tests/feature/payments/test_banking.py index b3b6ee8..69efceb 100644 --- a/tests/feature/payments/test_banking.py +++ b/tests/feature/payments/test_banking.py @@ -51,9 +51,9 @@ def test_banking_payment_order_returns_success(self, buckaroo, mock_strategy): service = sent["Services"]["ServiceList"][0] assert service["Name"] == "Banking" assert service["Action"] == "PaymentOrder" - assert {"Name": "Accountholdername", "Value": "Arensman"} in [ + assert {"Name": "AccountHolderName", "Value": "Arensman"} in [ {"Name": p["Name"], "Value": p["Value"]} for p in service["Parameters"] ] - assert {"Name": "Iban", "Value": "NL44RABO0123456789"} in [ + assert {"Name": "IBAN", "Value": "NL44RABO0123456789"} in [ {"Name": p["Name"], "Value": p["Value"]} for p in service["Parameters"] ] diff --git a/tests/feature/payments/test_giftcards.py b/tests/feature/payments/test_giftcards.py index ea77dab..5cb5c35 100644 --- a/tests/feature/payments/test_giftcards.py +++ b/tests/feature/payments/test_giftcards.py @@ -21,9 +21,10 @@ def test_giftcards_pay_sends_cardnumber_and_pin_on_the_wire( ): """service_parameters dict must reach ServiceList[0].Parameters. - The SDK capitalizes parameter names, so ``"PIN"`` becomes ``"Pin"`` - on the wire. Assert both the value pairing and the presence of both - keys so a builder that silently drops service_parameters would fail. + Top-level param names keep their internal casing, so ``"PIN"`` reaches + the wire as ``"PIN"``. Assert both the value pairing and the presence + of both keys so a builder that silently drops service_parameters would + fail. """ recording_mock.queue( BuckarooMockRequest.json( @@ -44,7 +45,7 @@ def test_giftcards_pay_sends_cardnumber_and_pin_on_the_wire( assert recorded_action(recording_mock) == "Pay" params = {p["Name"]: p["Value"] for p in recorded_service_parameters(recording_mock)} assert params.get("Cardnumber") == "1234567890123456" - assert params.get("Pin") == "1234" + assert params.get("PIN") == "1234" def test_giftcards_pay_redirect_mode_omits_card_parameters( self, recording_buckaroo, recording_mock diff --git a/tests/feature/payments/test_klarna.py b/tests/feature/payments/test_klarna.py index faac666..81ff1f0 100644 --- a/tests/feature/payments/test_klarna.py +++ b/tests/feature/payments/test_klarna.py @@ -58,7 +58,7 @@ def test_klarna_pay_as_capture_attaches_data_request_key(self, buckaroo, mock_st body = json.loads(mock_strategy.calls[-1]["data"]) services = body["Services"]["ServiceList"] names = [p["Name"] for p in services[0]["Parameters"]] - assert "Datarequestkey" in names + assert "DataRequestKey" in names def test_klarna_cancel_reservation_posts_data_request_key(self, buckaroo, mock_strategy): """Follow-up actions carry the Buckaroo DataRequestKey (no reservation @@ -79,7 +79,7 @@ def test_klarna_cancel_reservation_posts_data_request_key(self, buckaroo, mock_s assert service["Action"] == "CancelReservation" assert "OriginalTransactionKey" not in body names = [p["Name"] for p in service["Parameters"]] - assert "Datarequestkey" in names + assert "DataRequestKey" in names def test_klarna_update_reservation_dispatches_to_data_request(self, buckaroo, mock_strategy): response_body = Helpers.pending_redirect_response("klarna", action="UpdateReservation") @@ -136,5 +136,5 @@ def test_klarna_pay_carries_shipping_details_on_transaction(self, buckaroo, mock service = json.loads(mock_strategy.calls[-1]["data"])["Services"]["ServiceList"][0] assert service["Action"] == "Pay" names = [p["Name"] for p in service["Parameters"]] - assert "Datarequestkey" in names - assert "Trackingnumber" in names + assert "DataRequestKey" in names + assert "TrackingNumber" in names diff --git a/tests/feature/solutions/test_credit_management.py b/tests/feature/solutions/test_credit_management.py index 10dfe45..9f957ea 100644 --- a/tests/feature/solutions/test_credit_management.py +++ b/tests/feature/solutions/test_credit_management.py @@ -67,9 +67,9 @@ def test_create_invoice_posts_service_parameters_and_returns_invoice_key( } assert ("Invoice", "") not in params assert ("Currency", "") not in params - assert params[("Invoiceamount", "")] == "250.00" - assert params[("Duedate", "")] == "2026-09-01" - assert params[("Schemekey", "")] == "SCHEME-2" + assert params[("InvoiceAmount", "")] == "250.00" + assert params[("DueDate", "")] == "2026-09-01" + assert params[("SchemeKey", "")] == "SCHEME-2" assert params[("Code", "Debtor")] == "DEBTOR-999" def test_create_invoice_without_required_params_raises_before_wire(self, buckaroo): @@ -201,7 +201,7 @@ def test_debtor_file_actions_post_debtor_file_guid( assert response.status.code.code == 190 assert recorded_action(mock_strategy) == action params = {p["Name"]: p["Value"] for p in recorded_service_parameters(mock_strategy)} - assert params["Debtorfileguid"] == "FILE-GUID-999" + assert params["DebtorFileGuid"] == "FILE-GUID-999" @pytest.mark.parametrize("method_name", ["resume_debtor_file", "pause_debtor_file"]) def test_debtor_file_actions_without_debtor_file_guid_raise_before_wire( @@ -271,7 +271,7 @@ def test_create_credit_note_posts_original_invoice_and_debtor(self, buckaroo, mo for p in recorded_service_parameters(mock_strategy) } assert ("Invoice", "") not in params - assert params[("Originalinvoicenumber", "")] == "INV-999" + assert params[("OriginalInvoiceNumber", "")] == "INV-999" assert params[("Code", "Debtor")] == "DEBTOR-999" def test_create_credit_note_without_debtor_raises_before_wire(self, buckaroo): @@ -314,7 +314,7 @@ def test_add_or_update_product_lines_posts_indexed_articles(self, buckaroo, mock (p["Name"], p["GroupType"], p["GroupID"]): p["Value"] for p in recorded_service_parameters(mock_strategy) } - assert params[("Invoicekey", "", "")] == "INVK-999" + assert params[("InvoiceKey", "", "")] == "INVK-999" assert params[("Productid", "ProductLine", "1")] == "SKU-1" assert params[("Productname", "ProductLine", "1")] == "Widget" assert params[("Quantity", "ProductLine", "1")] == "2" @@ -365,13 +365,13 @@ def test_create_payment_plan_posts_installment_and_dossier_fields( assert request["Description"] == "3-month plan" params = {p["Name"]: p["Value"] for p in recorded_service_parameters(mock_strategy)} - assert params["Includedinvoicekey"] == "INVK-999" - assert params["Dossiernumber"] == "DOSSIER-999" - assert params["Startdate"] == "2026-09-01" + assert params["IncludedInvoiceKey"] == "INVK-999" + assert params["DossierNumber"] == "DOSSIER-999" + assert params["StartDate"] == "2026-09-01" assert params["Interval"] == "Month" - assert params["Paymentplancostamount"] == "5.00" - assert params["Recipientemail"] == "debtor@example.com" - assert params["Installmentcount"] == "3" + assert params["PaymentPlanCostAmount"] == "5.00" + assert params["RecipientEmail"] == "debtor@example.com" + assert params["InstallmentCount"] == "3" assert "Description" not in params def test_create_payment_plan_without_included_invoice_key_raises_before_wire(self, buckaroo): @@ -408,7 +408,7 @@ def test_terminate_payment_plan_posts_included_invoice_key(self, buckaroo, mock_ assert response.status.code.code == 190 assert recorded_action(mock_strategy) == "TerminatePaymentPlan" params = {p["Name"]: p["Value"] for p in recorded_service_parameters(mock_strategy)} - assert params["Includedinvoicekey"] == "INVK-999" + assert params["IncludedInvoiceKey"] == "INVK-999" def test_terminate_payment_plan_without_included_invoice_key_raises_before_wire(self, buckaroo): builder = buckaroo.solutions.create_solution("creditmanagement") @@ -473,7 +473,7 @@ def test_create_combined_invoice_combines_into_ideal_pay(self, buckaroo, mock_st (p["Name"], p["GroupType"]): p["Value"] for p in services[1]["Parameters"] } assert ("Invoice", "") not in invoice_params - assert invoice_params[("Invoiceamount", "")] == "95.00" + assert invoice_params[("InvoiceAmount", "")] == "95.00" assert invoice_params[("Code", "Debtor")] == "DEBTOR-999" assert response.get_service_parameter("InvoiceKey") == "INVK-COMBINED-1" diff --git a/tests/feature/solutions/test_emandate.py b/tests/feature/solutions/test_emandate.py index e43750b..df56a4c 100644 --- a/tests/feature/solutions/test_emandate.py +++ b/tests/feature/solutions/test_emandate.py @@ -76,9 +76,9 @@ def test_create_mandate_posts_service_parameters_and_returns_mandate_id( assert recorded_action(mock_strategy) == "CreateMandate" params = {p["Name"]: p["Value"] for p in recorded_service_parameters(mock_strategy)} - assert params["Debtorreference"] == "DEBTOR-999" - assert params["Sequencetype"] == "1" - assert params["Purchaseid"] == "PUR-1" + assert params["DebtorReference"] == "DEBTOR-999" + assert params["SequenceType"] == "1" + assert params["PurchaseId"] == "PUR-1" def test_create_mandate_without_debtor_reference_raises_before_wire(self, buckaroo): builder = buckaroo.solutions.create_solution("emandate") @@ -114,7 +114,7 @@ def test_status_posts_mandate_id_and_returns_status(self, buckaroo, mock_strateg assert recorded_action(mock_strategy) == "GetStatus" params = {p["Name"]: p["Value"] for p in recorded_service_parameters(mock_strategy)} - assert params["Mandateid"] == "MND-777" + assert params["MandateId"] == "MND-777" def test_status_without_mandate_id_raises_before_wire(self, buckaroo): builder = buckaroo.solutions.create_solution("emandate") @@ -157,8 +157,8 @@ def test_modify_mandate_posts_service_parameters_and_returns_mandate_id( assert recorded_action(mock_strategy) == "ModifyMandate" params = {p["Name"]: p["Value"] for p in recorded_service_parameters(mock_strategy)} - assert params["Mandateid"] == "MND-555" - assert params["Maxamount"] == "1000.00" + assert params["MandateId"] == "MND-555" + assert params["MaxAmount"] == "1000.00" def test_modify_mandate_without_mandate_id_raises_before_wire(self, buckaroo): builder = buckaroo.solutions.create_solution("emandate") @@ -200,8 +200,8 @@ def test_cancel_mandate_posts_service_parameters_and_returns_response( assert recorded_action(mock_strategy) == "CancelMandate" params = {p["Name"]: p["Value"] for p in recorded_service_parameters(mock_strategy)} - assert params["Mandateid"] == "MND-321" - assert params["Purchaseid"] == "PUR-2" + assert params["MandateId"] == "MND-321" + assert params["PurchaseId"] == "PUR-2" def test_cancel_mandate_without_mandate_id_raises_before_wire(self, buckaroo): builder = buckaroo.solutions.create_solution("emandate") diff --git a/tests/feature/solutions/test_marketplaces.py b/tests/feature/solutions/test_marketplaces.py index b418bb0..b78ef04 100644 --- a/tests/feature/solutions/test_marketplaces.py +++ b/tests/feature/solutions/test_marketplaces.py @@ -85,7 +85,7 @@ def test_split_combines_marketplaces_into_ideal_pay(self, buckaroo, mock_strateg split = { (p["Name"], p["GroupType"], p["GroupID"]): p["Value"] for p in services[1]["Parameters"] } - assert split[("Daysuntiltransfer", "", "")] == "2" + assert split[("DaysUntilTransfer", "", "")] == "2" assert split[("Amount", "Marketplace", "")] == "10.00" assert split[("Accountid", "Seller", "1")] == "789C60F316D24B088ACD471" assert split[("Accountid", "Seller", "2")] == "369C60F316D24B088ACD238" @@ -145,7 +145,7 @@ def test_transfer_with_splits_posts_grouped_params(self, buckaroo, mock_strategy assert params[("Amount", "Marketplace", "")] == "10.00" assert params[("Accountid", "Seller", "1")] == "789C60F316D24B088ACD471" # DaysUntilTransfer never applies to a Transfer. - assert not any(name == "Daysuntiltransfer" for name, _, _ in params) + assert not any(name == "DaysUntilTransfer" for name, _, _ in params) def test_transfer_requires_original_key(self, buckaroo): with pytest.raises(ValueError): @@ -245,10 +245,10 @@ def test_manual_transfer_posts_accounts_and_amount(self, buckaroo, mock_strategy assert "AmountDebit" not in body params = {p["Name"]: p["Value"] for p in service["Parameters"]} - assert params["Fromaccountid"] == "AAAAAAAAAAAAAAAAAAA" - assert params["Toaccountid"] == "BBBBBBBBBBBBBBBBBBB" - assert params["Fromdescription"] == "Deduction monthly fee" - assert params["Todescription"] == "Monthly fee third party ABC" + assert params["FromAccountId"] == "AAAAAAAAAAAAAAAAAAA" + assert params["ToAccountId"] == "BBBBBBBBBBBBBBBBBBB" + assert params["FromDescription"] == "Deduction monthly fee" + assert params["ToDescription"] == "Monthly fee third party ABC" assert response.status.code.code == 190 def test_manual_transfer_requires_all_fields(self, buckaroo): diff --git a/tests/unit/builders/payments/capabilities/test_fast_checkout_capable.py b/tests/unit/builders/payments/capabilities/test_fast_checkout_capable.py index b86f507..67dc911 100644 --- a/tests/unit/builders/payments/capabilities/test_fast_checkout_capable.py +++ b/tests/unit/builders/payments/capabilities/test_fast_checkout_capable.py @@ -102,4 +102,4 @@ def test_validate_false_skips_parameter_validation(self): service = recorded_request(mock)["Services"]["ServiceList"][0] parameter_names = [p["Name"] for p in (service.get("Parameters") or [])] - assert "Someunknownparam" in parameter_names + assert "SomeUnknownParam" in parameter_names diff --git a/tests/unit/builders/payments/test_giftcards_builder.py b/tests/unit/builders/payments/test_giftcards_builder.py index ea0c5af..1561a2f 100644 --- a/tests/unit/builders/payments/test_giftcards_builder.py +++ b/tests/unit/builders/payments/test_giftcards_builder.py @@ -187,7 +187,7 @@ def test_refund_build_validates_when_intersolve_email_absent(client): service = request.to_dict()["Services"]["ServiceList"][0] names = {p["Name"] for p in service["Parameters"]} - assert "Lastname" in names + assert "LastName" in names assert "Email" not in names @@ -205,7 +205,7 @@ def test_refund_build_keeps_intersolve_email_when_supplied(client): service = request.to_dict()["Services"]["ServiceList"][0] names = {p["Name"] for p in service["Parameters"]} - assert {"Lastname", "Email"} <= names + assert {"LastName", "Email"} <= names def test_get_allowed_service_parameters_refund_is_case_insensitive(client): diff --git a/tests/unit/builders/payments/test_klarna_builder.py b/tests/unit/builders/payments/test_klarna_builder.py index 739e46c..b2bcf7d 100644 --- a/tests/unit/builders/payments/test_klarna_builder.py +++ b/tests/unit/builders/payments/test_klarna_builder.py @@ -276,7 +276,7 @@ def test_posts_cancel_reservation_to_data_request_without_original_key(self): sent = recorded_request(mock) assert "OriginalTransactionKey" not in sent names = [p["Name"] for p in sent["Services"]["ServiceList"][0]["Parameters"]] - assert "Datarequestkey" in names + assert "DataRequestKey" in names assert response.key == "KL-CAN-1" mock.assert_all_consumed() diff --git a/tests/unit/builders/solutions/test_credit_management_builder.py b/tests/unit/builders/solutions/test_credit_management_builder.py index a4d0574..0eb477b 100644 --- a/tests/unit/builders/solutions/test_credit_management_builder.py +++ b/tests/unit/builders/solutions/test_credit_management_builder.py @@ -338,7 +338,7 @@ def test_debtor_file_actions_post_debtor_file_guid_to_data_request( service = recorded_request(mock_strategy)["Services"]["ServiceList"][0] params = {p["Name"]: p["Value"] for p in service["Parameters"]} - assert params["Debtorfileguid"] == "FILE-GUID-001" + assert params["DebtorFileGuid"] == "FILE-GUID-001" @pytest.mark.parametrize("method_name", ["resume_debtor_file", "pause_debtor_file"]) @@ -439,9 +439,9 @@ def test_create_credit_note_posts_invoice_top_level_and_debtor_group_to_data_req service = request["Services"]["ServiceList"][0] params = {(p["Name"], p["GroupType"]): p["Value"] for p in service["Parameters"]} - assert params[("Originalinvoicenumber", "")] == "INV-001" - assert params[("Invoicedate", "")] == "2026-07-16" - assert params[("Invoiceamount", "")] == "10.0" + assert params[("OriginalInvoiceNumber", "")] == "INV-001" + assert params[("InvoiceDate", "")] == "2026-07-16" + assert params[("InvoiceAmount", "")] == "10.0" assert params[("Code", "Debtor")] == "DEBTOR-001" @@ -500,7 +500,7 @@ def test_add_or_update_product_lines_posts_indexed_grouped_articles(client, mock service = recorded_request(mock_strategy)["Services"]["ServiceList"][0] params = {(p["Name"], p["GroupType"], p["GroupID"]): p["Value"] for p in service["Parameters"]} - assert params[("Invoicekey", "", "")] == "INVK-001" + assert params[("InvoiceKey", "", "")] == "INVK-001" assert params[("Productid", "ProductLine", "1")] == "SKU-1" assert params[("Productname", "ProductLine", "1")] == "Widget" assert params[("Quantity", "ProductLine", "1")] == "2" @@ -669,13 +669,13 @@ def test_create_payment_plan_posts_to_data_request(client, mock_strategy): service = request["Services"]["ServiceList"][0] params = {p["Name"]: p["Value"] for p in service["Parameters"]} - assert params["Includedinvoicekey"] == "INVK-001" - assert params["Dossiernumber"] == "DOSSIER-1" - assert params["Startdate"] == "2026-09-01" + assert params["IncludedInvoiceKey"] == "INVK-001" + assert params["DossierNumber"] == "DOSSIER-1" + assert params["StartDate"] == "2026-09-01" assert params["Interval"] == "Month" - assert params["Paymentplancostamount"] == "5.00" - assert params["Recipientemail"] == "debtor@example.com" - assert params["Installmentcount"] == "3" + assert params["PaymentPlanCostAmount"] == "5.00" + assert params["RecipientEmail"] == "debtor@example.com" + assert params["InstallmentCount"] == "3" assert "Description" not in params @@ -720,7 +720,7 @@ def test_terminate_payment_plan_posts_to_data_request(client, mock_strategy): service = recorded_request(mock_strategy)["Services"]["ServiceList"][0] params = {p["Name"]: p["Value"] for p in service["Parameters"]} - assert params["Includedinvoicekey"] == "INVK-001" + assert params["IncludedInvoiceKey"] == "INVK-001" def test_terminate_payment_plan_raises_when_included_invoice_key_missing(client): diff --git a/tests/unit/builders/test_base_builder.py b/tests/unit/builders/test_base_builder.py index 9e31d2f..93dda9c 100644 --- a/tests/unit/builders/test_base_builder.py +++ b/tests/unit/builders/test_base_builder.py @@ -298,6 +298,23 @@ def test_add_parameter_flat_capitalizes_name_and_stringifies_value(): ] +def test_add_parameter_flat_keeps_internal_capitals(): + # Top-level names keep internal caps: "customerFirstName" -> "CustomerFirstName". + builder = populate_required_fields(_make_builder(), amount=10.50) + builder.add_parameter("customerFirstName", "Jan") + + request = builder.build(validate=False).to_dict() + service = request["Services"]["ServiceList"][0] + assert service["Parameters"] == [ + { + "Name": "CustomerFirstName", + "GroupType": "", + "GroupID": "", + "Value": "Jan", + } + ] + + def test_add_parameter_grouped_sets_group_type_and_group_id(): builder = populate_required_fields(_make_builder(), amount=10.50) builder.add_parameter("firstName", "Jane", group_type="customer", group_id="7")