diff --git a/build.cake b/build.cake
index 107ed953..ccb4b168 100644
--- a/build.cake
+++ b/build.cake
@@ -1,9 +1,9 @@
// Install .NET Core Global tools.
-#tool "dotnet:?package=dotnet-reportgenerator-globaltool&version=5.4.1"
+#tool "dotnet:?package=dotnet-reportgenerator-globaltool&version=5.4.9"
#tool "dotnet:?package=coveralls.net&version=4.0.1"
// Install addins
-#addin nuget:?package=Cake.Coverlet&version=4.0.1
+#addin nuget:?package=Cake.Coverlet&version=5.1.1
///////////////////////////////////////////////////////////////////////////////
// ARGUMENTS
diff --git a/src/TrueLayer/Payments/Model/CreatePaymentRequest.cs b/src/TrueLayer/Payments/Model/CreatePaymentRequest.cs
index 8d4fe62b..89128728 100644
--- a/src/TrueLayer/Payments/Model/CreatePaymentRequest.cs
+++ b/src/TrueLayer/Payments/Model/CreatePaymentRequest.cs
@@ -24,6 +24,7 @@ public class CreatePaymentRequest
/// If provided, the start authorization flow endpoint does not need to be called
/// Add to the payment a list of custom key-value pairs as metadata
/// The risk assessment and the payment_creditable webhook configuration.
+ /// Sub-merchants information for the payment
public CreatePaymentRequest(
long amountInMinor,
string currency,
@@ -32,7 +33,8 @@ public CreatePaymentRequest(
RelatedProducts? relatedProducts = null,
StartAuthorizationFlowRequest? authorizationFlow = null,
Dictionary? metadata = null,
- RiskAssessment? riskAssessment = null)
+ RiskAssessment? riskAssessment = null,
+ SubMerchants? subMerchants = null)
{
AmountInMinor = amountInMinor.GreaterThan(0, nameof(amountInMinor));
Currency = currency.NotNullOrWhiteSpace(nameof(currency));
@@ -42,6 +44,7 @@ public CreatePaymentRequest(
AuthorizationFlow = authorizationFlow;
Metadata = metadata;
RiskAssessment = riskAssessment;
+ SubMerchants = subMerchants;
}
///
@@ -84,5 +87,10 @@ public CreatePaymentRequest(
/// Gets the risk assessment configuration
///
public RiskAssessment? RiskAssessment { get; }
+
+ ///
+ /// Gets the sub-merchants information for the payment
+ ///
+ public SubMerchants? SubMerchants { get; }
}
}
diff --git a/src/TrueLayer/Payments/Model/SubMerchants.cs b/src/TrueLayer/Payments/Model/SubMerchants.cs
new file mode 100644
index 00000000..b5f85f4d
--- /dev/null
+++ b/src/TrueLayer/Payments/Model/SubMerchants.cs
@@ -0,0 +1,130 @@
+using OneOf;
+using TrueLayer.Common;
+using TrueLayer.Serialization;
+
+namespace TrueLayer.Payments.Model
+{
+ using UltimateCounterpartyUnion = OneOf;
+
+ ///
+ /// Represents sub-merchants information for payment requests
+ ///
+ public class SubMerchants
+ {
+ ///
+ /// Creates a new instance
+ ///
+ /// The ultimate counterparty information
+ public SubMerchants(UltimateCounterpartyUnion ultimateCounterparty)
+ {
+ UltimateCounterparty = ultimateCounterparty;
+ }
+
+ ///
+ /// Gets the ultimate counterparty information
+ ///
+ public UltimateCounterpartyUnion UltimateCounterparty { get; }
+
+ ///
+ /// Represents a business division counterparty
+ ///
+ [JsonDiscriminator("business_division")]
+ public class BusinessDivision
+ {
+ ///
+ /// Creates a new instance
+ ///
+ /// UUID generated by you
+ /// Name of the division
+ public BusinessDivision(string id, string name)
+ {
+ Type = "business_division";
+ Id = id.NotNullOrWhiteSpace(nameof(id));
+ Name = name.NotNullOrWhiteSpace(nameof(name));
+ }
+
+ ///
+ /// Gets the type of the counterparty
+ ///
+ public string Type { get; }
+
+ ///
+ /// Gets the UUID generated by you
+ ///
+ public string Id { get; }
+
+ ///
+ /// Gets the name of the division
+ ///
+ public string Name { get; }
+ }
+
+ ///
+ /// Represents a business client counterparty
+ ///
+ [JsonDiscriminator("business_client")]
+ public class BusinessClient
+ {
+ ///
+ /// Creates a new instance
+ ///
+ /// Trading name of the merchant
+ /// Commercial name different from trading name (optional)
+ /// Business website URL (optional)
+ /// Merchant category code (optional)
+ /// Business registration number (optional if address provided)
+ /// Business address (optional)
+ public BusinessClient(
+ string tradingName,
+ string? commercialName = null,
+ string? url = null,
+ string? mcc = null,
+ string? registrationNumber = null,
+ Address? address = null)
+ {
+ Type = "business_client";
+ TradingName = tradingName.NotNullOrWhiteSpace(nameof(tradingName));
+ CommercialName = commercialName.NotEmptyOrWhiteSpace(nameof(commercialName));
+ Url = url.NotEmptyOrWhiteSpace(nameof(url));
+ Mcc = mcc.NotEmptyOrWhiteSpace(nameof(mcc));
+ RegistrationNumber = registrationNumber.NotEmptyOrWhiteSpace(nameof(registrationNumber));
+ Address = address;
+ }
+
+ ///
+ /// Gets the type of the counterparty
+ ///
+ public string Type { get; }
+
+ ///
+ /// Gets the trading name of the merchant
+ ///
+ public string TradingName { get; }
+
+ ///
+ /// Gets the commercial name different from trading name
+ ///
+ public string? CommercialName { get; }
+
+ ///
+ /// Gets the business website URL
+ ///
+ public string? Url { get; }
+
+ ///
+ /// Gets the merchant category code
+ ///
+ public string? Mcc { get; }
+
+ ///
+ /// Gets the business registration number
+ ///
+ public string? RegistrationNumber { get; }
+
+ ///
+ /// Gets the business address
+ ///
+ public Address? Address { get; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/TrueLayer/Payouts/Model/AccountIdentifier.cs b/src/TrueLayer/Payouts/Model/AccountIdentifier.cs
index dba4b159..65b4a0ae 100644
--- a/src/TrueLayer/Payouts/Model/AccountIdentifier.cs
+++ b/src/TrueLayer/Payouts/Model/AccountIdentifier.cs
@@ -73,5 +73,39 @@ public SortCodeAccountNumber(string sortCode, string accountNumber)
public string AccountNumber { get; }
}
+
+ ///
+ /// Defines a bank account identified by a Polish NRB
+ ///
+ ///
+ [JsonDiscriminator(Discriminator)]
+ public record Nrb : IDiscriminated
+ {
+ public const string Discriminator = "nrb";
+
+ ///
+ /// Creates a new instance
+ ///
+ ///
+ /// Valid Polish NRB (no spaces).
+ /// Consists of 2 check digits, followed by an 8 digit bank branch number, and then by a 16 digit bank account number.
+ /// Equivalent to a Polish IBAN with the country code removed.
+ ///
+ public Nrb(string value)
+ {
+ Value = value.NotNullOrWhiteSpace(nameof(value));
+ }
+
+ ///
+ /// Gets the scheme identifier type
+ ///
+ public string Type => Discriminator;
+
+ ///
+ /// Gets the NRB value
+ ///
+ [JsonPropertyName(Discriminator)]
+ public string Value { get; }
+ }
}
}
diff --git a/src/TrueLayer/Payouts/Model/Beneficiary.cs b/src/TrueLayer/Payouts/Model/Beneficiary.cs
index 06528f0f..d900ae1e 100644
--- a/src/TrueLayer/Payouts/Model/Beneficiary.cs
+++ b/src/TrueLayer/Payouts/Model/Beneficiary.cs
@@ -7,7 +7,7 @@
namespace TrueLayer.Payouts.Model
{
- using AccountIdentifierUnion = OneOf;
+ using AccountIdentifierUnion = OneOf;
public static class Beneficiary
{
diff --git a/test/TrueLayer.AcceptanceTests/PaymentTests.cs b/test/TrueLayer.AcceptanceTests/PaymentTests.cs
index 23af6e2e..216107bc 100644
--- a/test/TrueLayer.AcceptanceTests/PaymentTests.cs
+++ b/test/TrueLayer.AcceptanceTests/PaymentTests.cs
@@ -164,6 +164,67 @@ public async Task Can_Create_Merchant_Account_Eur_Payment()
hppUri.Should().NotBeNullOrWhiteSpace();
}
+ [Fact]
+ public async Task Can_Create_Payment_With_SubMerchants_BusinessDivision()
+ {
+ var subMerchants = new SubMerchants(new SubMerchants.BusinessDivision(
+ id: Guid.NewGuid().ToString(),
+ name: "Test Division"));
+
+ var paymentRequest = CreateTestPaymentRequest(
+ new Provider.UserSelected
+ {
+ Filter = new ProviderFilter { ProviderIds = ["mock-payments-gb-redirect"] },
+ SchemeSelection = new SchemeSelection.InstantOnly { AllowRemitterFee = true },
+ },
+ subMerchants: subMerchants);
+
+ var response = await _fixture.TlClients[0].Payments.CreatePayment(
+ paymentRequest, idempotencyKey: Guid.NewGuid().ToString());
+
+ response.StatusCode.Should().Be(HttpStatusCode.Created);
+ var authorizationRequired = response.Data.AsT0;
+
+ authorizationRequired.Id.Should().NotBeNullOrWhiteSpace();
+ authorizationRequired.ResourceToken.Should().NotBeNullOrWhiteSpace();
+ authorizationRequired.User.Should().NotBeNull();
+ authorizationRequired.User.Id.Should().NotBeNullOrWhiteSpace();
+ authorizationRequired.Status.Should().Be("authorization_required");
+ }
+
+ [Fact]
+ public async Task Can_Create_Payment_With_SubMerchants_BusinessClient()
+ {
+ var address = new Address("London", "England", "EC1R 4RB", "GB", "1 Hardwick St");
+ var subMerchants = new SubMerchants(new SubMerchants.BusinessClient(
+ tradingName: "Test Trading Company",
+ commercialName: "Test Commercial Name",
+ url: "https://example.com",
+ mcc: "1234",
+ registrationNumber: "REG123456",
+ address: address));
+
+ var paymentRequest = CreateTestPaymentRequest(
+ new Provider.UserSelected
+ {
+ Filter = new ProviderFilter { ProviderIds = ["mock-payments-gb-redirect"] },
+ SchemeSelection = new SchemeSelection.InstantOnly { AllowRemitterFee = true },
+ },
+ subMerchants: subMerchants);
+
+ var response = await _fixture.TlClients[0].Payments.CreatePayment(
+ paymentRequest, idempotencyKey: Guid.NewGuid().ToString());
+
+ response.StatusCode.Should().Be(HttpStatusCode.Created);
+ var authorizationRequired = response.Data.AsT0;
+
+ authorizationRequired.Id.Should().NotBeNullOrWhiteSpace();
+ authorizationRequired.ResourceToken.Should().NotBeNullOrWhiteSpace();
+ authorizationRequired.User.Should().NotBeNull();
+ authorizationRequired.User.Id.Should().NotBeNullOrWhiteSpace();
+ authorizationRequired.Status.Should().Be("authorization_required");
+ }
+
[Fact]
public async Task Can_Create_Payment_With_Auth_Flow()
{
@@ -530,7 +591,8 @@ private static CreatePaymentRequest CreateTestPaymentRequest(
RelatedProducts? relatedProducts = null,
BeneficiaryUnion? beneficiary = null,
Retry.BaseRetry? retry = null,
- bool initAuthorizationFlow = false)
+ bool initAuthorizationFlow = false,
+ SubMerchants? subMerchants = null)
{
accountIdentifier ??= new AccountIdentifier.SortCodeAccountNumber("567890", "12345678");
providerSelection ??= new Provider.Preselected("mock-payments-gb-redirect",
@@ -568,7 +630,8 @@ private static CreatePaymentRequest CreateTestPaymentRequest(
["test-key-1"] = "test-value-1",
["test-key-2"] = "test-value-2",
},
- riskAssessment: new RiskAssessment("test")
+ riskAssessment: new RiskAssessment("test"),
+ subMerchants: subMerchants
);
}
diff --git a/test/TrueLayer.AcceptanceTests/PayoutTests.cs b/test/TrueLayer.AcceptanceTests/PayoutTests.cs
index cd13727c..8e5fce3b 100644
--- a/test/TrueLayer.AcceptanceTests/PayoutTests.cs
+++ b/test/TrueLayer.AcceptanceTests/PayoutTests.cs
@@ -30,6 +30,18 @@ public async Task Can_create_payout()
response.Data!.Id.Should().NotBeNullOrWhiteSpace();
}
+ [Fact]
+ public async Task Can_create_pln_payout()
+ {
+ CreatePayoutRequest payoutRequest = CreatePlnPayoutRequest();
+
+ var response = await _fixture.TlClients[0].Payouts.CreatePayout(payoutRequest);
+
+ response.StatusCode.Should().Be(HttpStatusCode.Accepted);
+ response.Data.Should().NotBeNull();
+ response.Data!.Id.Should().NotBeNullOrWhiteSpace();
+ }
+
[Fact]
public async Task Can_get_payout()
{
@@ -83,5 +95,20 @@ private CreatePayoutRequest CreatePayoutRequest()
metadata: new() { { "a", "b" } },
schemeSelection: new SchemeSelection.InstantOnly()
);
+
+ private static CreatePayoutRequest CreatePlnPayoutRequest()
+ => new(
+ "fdb6007b-78c0-dbc0-60dd-d4c6f6908e3b", //pln merchant account
+ 100,
+ Currencies.PLN,
+ new Beneficiary.ExternalAccount(
+ "Ms. Lucky",
+ "truelayer-dotnet",
+ new AccountIdentifier.Iban("GB25CLRB04066800046876"),
+ dateOfBirth: new DateTime(1970, 12, 31),
+ address: new Address("London", "England", "EC1R 4RB", "GB", "1 Hardwick St")),
+ metadata: new() { { "a", "b" } },
+ schemeSelection: new SchemeSelection.InstantOnly()
+ );
}
}
diff --git a/test/TrueLayer.Tests/Payments/SubMerchantsTests.cs b/test/TrueLayer.Tests/Payments/SubMerchantsTests.cs
new file mode 100644
index 00000000..009afa51
--- /dev/null
+++ b/test/TrueLayer.Tests/Payments/SubMerchantsTests.cs
@@ -0,0 +1,153 @@
+using System;
+using FluentAssertions;
+using TrueLayer.Common;
+using TrueLayer.Payments.Model;
+using Xunit;
+
+namespace TrueLayer.Tests.Payments
+{
+ public class SubMerchantsTests
+ {
+ [Fact]
+ public void BusinessDivision_Constructor_Should_Set_Properties_Correctly()
+ {
+ // Arrange
+ var id = Guid.NewGuid().ToString();
+ var name = "Test Division";
+
+ // Act
+ var businessDivision = new SubMerchants.BusinessDivision(id, name);
+
+ // Assert
+ businessDivision.Type.Should().Be("business_division");
+ businessDivision.Id.Should().Be(id);
+ businessDivision.Name.Should().Be(name);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void BusinessDivision_Constructor_Should_Throw_When_Id_Is_Invalid(string? id)
+ {
+ // Act & Assert
+ var exception = Assert.Throws(() => new SubMerchants.BusinessDivision(id!, "Test Division"));
+ exception.ParamName.Should().Be("id");
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void BusinessDivision_Constructor_Should_Throw_When_Name_Is_Invalid(string? name)
+ {
+ // Act & Assert
+ var exception = Assert.Throws(() => new SubMerchants.BusinessDivision("test-id", name!));
+ exception.ParamName.Should().Be("name");
+ }
+
+ [Fact]
+ public void BusinessClient_Constructor_Should_Set_Required_Properties_Correctly()
+ {
+ // Arrange
+ var tradingName = "Test Trading Name";
+
+ // Act
+ var businessClient = new SubMerchants.BusinessClient(tradingName);
+
+ // Assert
+ businessClient.Type.Should().Be("business_client");
+ businessClient.TradingName.Should().Be(tradingName);
+ businessClient.CommercialName.Should().BeNull();
+ businessClient.Url.Should().BeNull();
+ businessClient.Mcc.Should().BeNull();
+ businessClient.RegistrationNumber.Should().BeNull();
+ businessClient.Address.Should().BeNull();
+ }
+
+ [Fact]
+ public void BusinessClient_Constructor_Should_Set_All_Properties_Correctly()
+ {
+ // Arrange
+ var tradingName = "Test Trading Name";
+ var commercialName = "Test Commercial Name";
+ var url = "https://example.com";
+ var mcc = "1234";
+ var registrationNumber = "REG123456";
+ var address = new Address("London", "England", "EC1R 4RB", "GB", "1 Hardwick St");
+
+ // Act
+ var businessClient = new SubMerchants.BusinessClient(
+ tradingName, commercialName, url, mcc, registrationNumber, address);
+
+ // Assert
+ businessClient.Type.Should().Be("business_client");
+ businessClient.TradingName.Should().Be(tradingName);
+ businessClient.CommercialName.Should().Be(commercialName);
+ businessClient.Url.Should().Be(url);
+ businessClient.Mcc.Should().Be(mcc);
+ businessClient.RegistrationNumber.Should().Be(registrationNumber);
+ businessClient.Address.Should().Be(address);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void BusinessClient_Constructor_Should_Throw_When_TradingName_Is_Invalid(string? tradingName)
+ {
+ // Act & Assert
+ var exception = Assert.Throws(() => new SubMerchants.BusinessClient(tradingName!));
+ exception.ParamName.Should().Be("tradingName");
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void BusinessClient_Constructor_Should_Throw_When_Optional_Strings_Are_Empty_Or_Whitespace(string emptyValue)
+ {
+ // Act & Assert
+ var exception = Assert.Throws(() => new SubMerchants.BusinessClient(
+ "Trading Name", emptyValue, emptyValue, emptyValue, emptyValue));
+ exception.ParamName.Should().Be("commercialName");
+ }
+
+ [Fact]
+ public void SubMerchants_Constructor_Should_Set_UltimateCounterparty_With_BusinessDivision()
+ {
+ // Arrange
+ var businessDivision = new SubMerchants.BusinessDivision("test-id", "Test Division");
+
+ // Act
+ var subMerchants = new SubMerchants(businessDivision);
+
+ // Assert
+ subMerchants.UltimateCounterparty.IsT0.Should().BeTrue(); // BusinessDivision
+ var result = subMerchants.UltimateCounterparty.AsT0;
+ result.Type.Should().Be("business_division");
+ result.Id.Should().Be("test-id");
+ result.Name.Should().Be("Test Division");
+ }
+
+ [Fact]
+ public void SubMerchants_Constructor_Should_Set_UltimateCounterparty_With_BusinessClient()
+ {
+ // Arrange
+ var businessClient = new SubMerchants.BusinessClient("Test Trading Name");
+
+ // Act
+ var subMerchants = new SubMerchants(businessClient);
+
+ // Assert
+ subMerchants.UltimateCounterparty.IsT1.Should().BeTrue(); // BusinessClient
+ var result = subMerchants.UltimateCounterparty.AsT1;
+ result.Type.Should().Be("business_client");
+ result.TradingName.Should().Be("Test Trading Name");
+ result.CommercialName.Should().BeNull();
+ result.Url.Should().BeNull();
+ result.Mcc.Should().BeNull();
+ result.RegistrationNumber.Should().BeNull();
+ result.Address.Should().BeNull();
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/TrueLayer.Tests/Serialization/SubMerchantsSerializationTests.cs b/test/TrueLayer.Tests/Serialization/SubMerchantsSerializationTests.cs
new file mode 100644
index 00000000..0d0c8ec2
--- /dev/null
+++ b/test/TrueLayer.Tests/Serialization/SubMerchantsSerializationTests.cs
@@ -0,0 +1,132 @@
+using System.Text.Json;
+using FluentAssertions;
+using TrueLayer.Common;
+using TrueLayer.Payments.Model;
+using TrueLayer.Serialization;
+using Xunit;
+
+namespace TrueLayer.Tests.Serialization
+{
+ public class SubMerchantsSerializationTests
+ {
+ [Fact]
+ public void BusinessDivision_Should_Serialize_And_Deserialize_Correctly()
+ {
+ // Arrange
+ var businessDivision = new SubMerchants.BusinessDivision("test-id-123", "Test Division Name");
+ var subMerchants = new SubMerchants(businessDivision);
+
+ // Act
+ string json = JsonSerializer.Serialize(subMerchants, SerializerOptions.Default);
+ var deserialized = JsonSerializer.Deserialize(json, SerializerOptions.Default);
+
+ // Assert
+ deserialized.Should().NotBeNull();
+ deserialized!.UltimateCounterparty.IsT0.Should().BeTrue();
+ var deserializedBusinessDivision = deserialized.UltimateCounterparty.AsT0;
+ deserializedBusinessDivision.Type.Should().Be("business_division");
+ deserializedBusinessDivision.Id.Should().Be("test-id-123");
+ deserializedBusinessDivision.Name.Should().Be("Test Division Name");
+ }
+
+ [Fact]
+ public void BusinessClient_With_All_Properties_Should_Serialize_And_Deserialize_Correctly()
+ {
+ // Arrange
+ var address = new Address("London", "England", "EC1R 4RB", "GB", "1 Hardwick St");
+ var businessClient = new SubMerchants.BusinessClient(
+ tradingName: "Test Trading Company",
+ commercialName: "Test Commercial Name",
+ url: "https://example.com",
+ mcc: "1234",
+ registrationNumber: "REG123456",
+ address: address);
+ var subMerchants = new SubMerchants(businessClient);
+
+ // Act
+ string json = JsonSerializer.Serialize(subMerchants, SerializerOptions.Default);
+ var deserialized = JsonSerializer.Deserialize(json, SerializerOptions.Default);
+
+ // Assert
+ deserialized.Should().NotBeNull();
+ deserialized!.UltimateCounterparty.IsT1.Should().BeTrue();
+ var deserializedBusinessClient = deserialized.UltimateCounterparty.AsT1;
+ deserializedBusinessClient.Type.Should().Be("business_client");
+ deserializedBusinessClient.TradingName.Should().Be("Test Trading Company");
+ deserializedBusinessClient.CommercialName.Should().Be("Test Commercial Name");
+ deserializedBusinessClient.Url.Should().Be("https://example.com");
+ deserializedBusinessClient.Mcc.Should().Be("1234");
+ deserializedBusinessClient.RegistrationNumber.Should().Be("REG123456");
+ deserializedBusinessClient.Address.Should().NotBeNull();
+ deserializedBusinessClient.Address!.AddressLine1.Should().Be("1 Hardwick St");
+ deserializedBusinessClient.Address.City.Should().Be("London");
+ deserializedBusinessClient.Address.State.Should().Be("England");
+ deserializedBusinessClient.Address.Zip.Should().Be("EC1R 4RB");
+ deserializedBusinessClient.Address.CountryCode.Should().Be("GB");
+ }
+
+ [Fact]
+ public void BusinessClient_With_Required_Properties_Only_Should_Serialize_And_Deserialize_Correctly()
+ {
+ // Arrange
+ var businessClient = new SubMerchants.BusinessClient("Test Trading Company");
+ var subMerchants = new SubMerchants(businessClient);
+
+ // Act
+ string json = JsonSerializer.Serialize(subMerchants, SerializerOptions.Default);
+ var deserialized = JsonSerializer.Deserialize(json, SerializerOptions.Default);
+
+ // Assert
+ deserialized.Should().NotBeNull();
+ deserialized!.UltimateCounterparty.IsT1.Should().BeTrue();
+ var deserializedBusinessClient = deserialized.UltimateCounterparty.AsT1;
+ deserializedBusinessClient.Type.Should().Be("business_client");
+ deserializedBusinessClient.TradingName.Should().Be("Test Trading Company");
+ deserializedBusinessClient.CommercialName.Should().BeNull();
+ deserializedBusinessClient.Url.Should().BeNull();
+ deserializedBusinessClient.Mcc.Should().BeNull();
+ deserializedBusinessClient.RegistrationNumber.Should().BeNull();
+ deserializedBusinessClient.Address.Should().BeNull();
+ }
+
+ [Fact]
+ public void BusinessDivision_Should_Serialize_With_Snake_Case_Property_Names()
+ {
+ // Arrange
+ var businessDivision = new SubMerchants.BusinessDivision("test-id-123", "Test Division Name");
+ var subMerchants = new SubMerchants(businessDivision);
+
+ // Act
+ string json = JsonSerializer.Serialize(subMerchants, SerializerOptions.Default);
+
+ // Assert
+ json.Should().Contain("\"ultimate_counterparty\"");
+ json.Should().Contain("\"type\":\"business_division\"");
+ json.Should().Contain("\"id\":\"test-id-123\"");
+ json.Should().Contain("\"name\":\"Test Division Name\"");
+ }
+
+ [Fact]
+ public void BusinessClient_Should_Serialize_With_Snake_Case_Property_Names()
+ {
+ // Arrange
+ var businessClient = new SubMerchants.BusinessClient(
+ tradingName: "Test Trading Company",
+ commercialName: "Test Commercial Name",
+ url: "https://example.com",
+ mcc: "1234",
+ registrationNumber: "REG123456");
+ var subMerchants = new SubMerchants(businessClient);
+
+ // Act
+ string json = JsonSerializer.Serialize(subMerchants, SerializerOptions.Default);
+
+ // Assert
+ json.Should().Contain("\"ultimate_counterparty\"");
+ json.Should().Contain("\"type\":\"business_client\"");
+ json.Should().Contain("\"trading_name\":\"Test Trading Company\"");
+ json.Should().Contain("\"commercial_name\":\"Test Commercial Name\"");
+ json.Should().Contain("\"registration_number\":\"REG123456\"");
+ }
+ }
+}
\ No newline at end of file