diff --git a/src/CheckoutSdk/AgenticCommerce/AgenticClient.cs b/src/CheckoutSdk/AgenticCommerce/AgenticClient.cs new file mode 100644 index 00000000..56477868 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/AgenticClient.cs @@ -0,0 +1,113 @@ +using System.Threading; +using System.Threading.Tasks; +using Checkout.AgenticCommerce.Requests; +using Checkout.AgenticCommerce.Responses; + +namespace Checkout.AgenticCommerce +{ + /// + /// Agentic Commerce API Client + /// + public class AgenticClient : AbstractClient, IAgenticClient + { + private const string AgenticPath = "agentic"; + private const string EnrollPath = "enroll"; + private const string PurchaseIntentPath = "purchase-intent"; + private const string CredentialsPath = "credentials"; + + public AgenticClient(IApiClient apiClient, CheckoutConfiguration configuration) : + base(apiClient, configuration, SdkAuthorizationType.OAuth) + { + } + + // Agentic Commerce enrollment + // ---------------------------------------------------------------- + + /// + /// Enroll a card for use with agentic commerce + /// [BETA] + /// + public Task EnrollACard( + EnrollACardRequest enrollACardRequest, + CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("agenticEnrollRequest", enrollACardRequest); + return ApiClient.Post( + BuildPath(AgenticPath, EnrollPath), + SdkAuthorization(), + enrollACardRequest, + cancellationToken + ); + } + + // Purchase intents + // ---------------------------------------------------------------- + + /// + /// Create an agentic commerce purchase intent + /// [BETA] + /// + public Task CreatePurchaseIntent( + PurchaseIntentCreateRequest purchaseIntentCreateRequest, + CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("agenticPurchaseIntentCreateRequest", purchaseIntentCreateRequest); + return ApiClient.Post( + BuildPath(AgenticPath, PurchaseIntentPath), + SdkAuthorization(), + purchaseIntentCreateRequest, + cancellationToken + ); + } + + /// + /// Create credentials for an agentic commerce purchase intent. + /// [BETA] + /// + public Task CreatePurchaseIntentCredentials(string id, + PurchaseIntentCredentialsCreateRequest purchaseIntentCredentialsCreateRequest, + CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("id", id,"agenticPurchaseIntentCredentialsCreateRequest", + purchaseIntentCredentialsCreateRequest); + return ApiClient.Post( + BuildPath(AgenticPath, PurchaseIntentPath, id, CredentialsPath), + SdkAuthorization(), + purchaseIntentCredentialsCreateRequest, + cancellationToken + ); + } + + /// + /// Update an agentic commerce purchase intent + /// [BETA] + /// + public Task UpdatePurchaseIntent(string id, + PurchaseIntentUpdateRequest purchaseIntentUpdateRequest, + CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("id", id, "agenticPurchaseIntentUpdateRequest", purchaseIntentUpdateRequest); + return ApiClient.Put( + BuildPath(AgenticPath, PurchaseIntentPath, id), + SdkAuthorization(), + purchaseIntentUpdateRequest, + cancellationToken + ); + } + + /// + /// Delete a purchase intent + /// Deletes a purchase intent for agentic commerce + /// + public Task DeletePurchaseIntent(string id, + CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("id", id); + return ApiClient.Delete( + BuildPath(AgenticPath, PurchaseIntentPath, id), + SdkAuthorization(), + cancellationToken + ); + } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Common/Customer.cs b/src/CheckoutSdk/AgenticCommerce/Common/Customer.cs new file mode 100644 index 00000000..be345e08 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Common/Customer.cs @@ -0,0 +1,29 @@ +using Checkout.Common; + +namespace Checkout.AgenticCommerce.Common +{ + /// + /// The customer's details + /// + public class Customer + { + /// + /// The customer's email address + /// [Required] + /// + public string Email { get; set; } + + /// + /// The customer's country, as a two-letter ISO 3166 country code + /// (https://www.checkout.com/docs/resources/codes/country-codes) + /// [Required] + /// + public CountryCode? CountryCode { get; set; } + + /// + /// The customer's language, as a two-letter ISO 639 language code + /// [Required] + /// + public string LanguageCode { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Common/Device.cs b/src/CheckoutSdk/AgenticCommerce/Common/Device.cs new file mode 100644 index 00000000..fb7c18e5 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Common/Device.cs @@ -0,0 +1,32 @@ +namespace Checkout.AgenticCommerce.Common +{ + /// + /// The user's device + /// + public class Device + { + /// + /// The device's IP address + /// [Required] + /// + public string IpAddress { get; set; } + + /// + /// The device's user agent + /// [Required] + /// + public string UserAgent { get; set; } + + /// + /// The device brand + /// [Optional] + /// + public string DeviceBrand { get; set; } + + /// + /// The device type + /// [Optional] + /// + public string DeviceType { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Common/Mandate.cs b/src/CheckoutSdk/AgenticCommerce/Common/Mandate.cs new file mode 100644 index 00000000..4d98c511 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Common/Mandate.cs @@ -0,0 +1,9 @@ +namespace Checkout.AgenticCommerce.Common +{ + /// + /// Mandate configuration for purchase intents + /// + public class Mandate : MandateBase + { + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Common/MandateBase.cs b/src/CheckoutSdk/AgenticCommerce/Common/MandateBase.cs new file mode 100644 index 00000000..edb142e3 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Common/MandateBase.cs @@ -0,0 +1,29 @@ +using System; + +namespace Checkout.AgenticCommerce.Common +{ + /// + /// Mandate configuration for purchase intents + /// + public class MandateBase + { + /// + /// Purchase threshold configuration + /// + public PurchaseThreshold PurchaseThreshold { get; set; } + + /// + /// A brief description of the purchase intent + /// <= 255 characters + /// [Required] + /// + public string Description { get; set; } + + /// + /// The date and time when the purchase intent expires, in ISO 8601 format. + /// This value must be set to a date and time in the future + /// [Required] + /// + public DateTime? ExpirationDate { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Common/MandateExtended.cs b/src/CheckoutSdk/AgenticCommerce/Common/MandateExtended.cs new file mode 100644 index 00000000..ee35ba26 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Common/MandateExtended.cs @@ -0,0 +1,16 @@ +using System; + +namespace Checkout.AgenticCommerce.Common +{ + /// + /// Mandate configuration for purchase intents + /// + public class MandateExtended: MandateBase + { + /// + /// The unique identifier for the mandate + /// [Required] + /// + public string Id { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Common/PurchaseIntentStatusType.cs b/src/CheckoutSdk/AgenticCommerce/Common/PurchaseIntentStatusType.cs new file mode 100644 index 00000000..801d2080 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Common/PurchaseIntentStatusType.cs @@ -0,0 +1,25 @@ +using System.Runtime.Serialization; + +namespace Checkout.AgenticCommerce.Common +{ + public enum PurchaseIntentStatusType + { + [EnumMember(Value = "active")] + Active, + + [EnumMember(Value = "created")] + Created, + + [EnumMember(Value = "cancelled")] + Cancelled, + + [EnumMember(Value = "expired")] + Expired, + + [EnumMember(Value = "declined")] + Declined, + + [EnumMember(Value = "completed")] + Completed, + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Common/PurchaseThreshold.cs b/src/CheckoutSdk/AgenticCommerce/Common/PurchaseThreshold.cs new file mode 100644 index 00000000..02666a7f --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Common/PurchaseThreshold.cs @@ -0,0 +1,25 @@ +using Checkout.Common; + +namespace Checkout.AgenticCommerce.Common +{ + /// + /// Purchase threshold configuration + /// + public class PurchaseThreshold + { + /// + /// The maximum amount for the purchase + /// Format the amount (https://www.checkout.com/docs/payments/accept-payments/format-the-amount-value) + /// according to the currency_code + /// [Required] + /// + public int Amount { get; set; } + + /// + /// The currency to use for the purchase, as an ISO 4217 currency code + /// (https://www.checkout.com/docs/resources/codes/currency-codes) + /// [Required] + /// + public Currency? CurrencyCode { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Common/Source.cs b/src/CheckoutSdk/AgenticCommerce/Common/Source.cs new file mode 100644 index 00000000..714e1a35 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Common/Source.cs @@ -0,0 +1,39 @@ +namespace Checkout.AgenticCommerce.Common +{ + /// + /// The payment source to enroll + /// + public class Source + { + /// + /// The full card number, without separators + /// [Required] + /// + public string Number { get; set; } + + /// + /// Card expiry month + /// [ 13 .. 19 ] characters + /// [Required] + /// + public int ExpiryMonth { get; set; } + + /// + /// Card expiry year + /// [Required] + /// + public int ExpiryYear { get; set; } + + /// + /// Payment source type + /// [Required] + /// + public string Type { get; set; } + + /// + /// The card's 3 or 4 digit card verification value (CVV) or security code + /// [Optional] + /// + public string Cvv { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Common/TransactionAmount.cs b/src/CheckoutSdk/AgenticCommerce/Common/TransactionAmount.cs new file mode 100644 index 00000000..e875de9a --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Common/TransactionAmount.cs @@ -0,0 +1,25 @@ +using Checkout.Common; + +namespace Checkout.AgenticCommerce.Common +{ + /// + /// Transaction amount information + /// + public class TransactionAmount + { + /// + /// The transaction amount + /// Format the amount (https://www.checkout.com/docs/payments/accept-payments/format-the-amount-value) + /// according to the currency_code + /// [Required] + /// + public int Amount { get; set; } + + /// + /// The transaction currency, as an ISO 4217 currency code + /// (https://www.checkout.com/docs/resources/codes/currency-codes) + /// [Required] + /// + public Currency? CurrencyCode { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Common/TransactionData.cs b/src/CheckoutSdk/AgenticCommerce/Common/TransactionData.cs new file mode 100644 index 00000000..fca8c8e4 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Common/TransactionData.cs @@ -0,0 +1,42 @@ +using Checkout.Common; + +namespace Checkout.AgenticCommerce.Common +{ + /// + /// List of transaction data for the purchase + /// + public class TransactionData + { + /// + /// The merchant's country, as a two-letter ISO 3166 country code + /// (https://www.checkout.com/docs/resources/codes/country-codes) + /// [Required] + /// + public CountryCode? MerchantCountryCode { get; set; } + + /// + /// The merchant's name + /// [Required] + /// + public string MerchantName { get; set; } + + /// + /// The merchant's category code + /// (https://www.checkout.com/docs/developer-resources/codes/merchant-category-codes) + /// [Required] + /// + public string MerchantCategoryCode { get; set; } + + /// + /// Transaction amount information + /// [Required] + /// + public TransactionAmount TransactionAmount { get; set; } + + /// + /// The merchant's website URL + /// [Optional] + /// + public string MerchantUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/IAgenticClient.cs b/src/CheckoutSdk/AgenticCommerce/IAgenticClient.cs new file mode 100644 index 00000000..d716d4cf --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/IAgenticClient.cs @@ -0,0 +1,58 @@ +using System.Threading; +using System.Threading.Tasks; +using Checkout.AgenticCommerce.Requests; +using Checkout.AgenticCommerce.Responses; + +namespace Checkout.AgenticCommerce +{ + /// + /// Agentic Commerce API Client + /// + public interface IAgenticClient + { + // Agentic Commerce enrollment + // ---------------------------------------------------------------- + + /// + /// Enroll a card for use with agentic commerce + /// [BETA] + /// + Task EnrollACard( + EnrollACardRequest enrollACardRequest, + CancellationToken cancellationToken = default); + + // Purchase intents + // ---------------------------------------------------------------- + + /// + /// Create an agentic commerce purchase intent + /// [BETA] + /// + Task CreatePurchaseIntent( + PurchaseIntentCreateRequest purchaseIntentCreateRequest, + CancellationToken cancellationToken = default); + + /// + /// Create credentials for an agentic commerce purchase intent. + /// [BETA] + /// + Task CreatePurchaseIntentCredentials(string id, + PurchaseIntentCredentialsCreateRequest purchaseIntentCredentialsCreateRequest, + CancellationToken cancellationToken = default); + + /// + /// Update an agentic commerce purchase intent + /// [BETA] + /// + Task UpdatePurchaseIntent(string id, + PurchaseIntentUpdateRequest purchaseIntentUpdateRequest, + CancellationToken cancellationToken = default); + + /// + /// Cancel an agentic commerce purchase intent + /// [BETA] + /// + Task DeletePurchaseIntent(string id, + CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Requests/EnrollACardRequest.cs b/src/CheckoutSdk/AgenticCommerce/Requests/EnrollACardRequest.cs new file mode 100644 index 00000000..9ab748fa --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Requests/EnrollACardRequest.cs @@ -0,0 +1,25 @@ +using Checkout.AgenticCommerce.Common; + +namespace Checkout.AgenticCommerce.Requests +{ + /// + /// Enroll a card for use with agentic commerce + /// + public class EnrollACardRequest + { + /// + /// The payment source to enroll + /// + public Source Source { get; set; } + + /// + /// The user's device + /// + public Device Device { get; set; } + + /// + /// The customer's details + /// + public Customer Customer { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Requests/PurchaseIntentCreateRequest.cs b/src/CheckoutSdk/AgenticCommerce/Requests/PurchaseIntentCreateRequest.cs new file mode 100644 index 00000000..99409362 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Requests/PurchaseIntentCreateRequest.cs @@ -0,0 +1,22 @@ +using Checkout.AgenticCommerce.Common; + +namespace Checkout.AgenticCommerce.Requests +{ + /// + /// Create an agentic commerce purchase intent + /// + public class PurchaseIntentCreateRequest : PurchaseIntentUpdateRequest + { + /// + /// The unique identifier for the network token + /// [Required] + /// + public string NetworkTokenId { get; set; } + + /// + /// The user's device + /// [Required] + /// + public Device Device { get; set; } + } +} diff --git a/src/CheckoutSdk/AgenticCommerce/Requests/PurchaseIntentCredentialsCreateRequest.cs b/src/CheckoutSdk/AgenticCommerce/Requests/PurchaseIntentCredentialsCreateRequest.cs new file mode 100644 index 00000000..c7065f9c --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Requests/PurchaseIntentCredentialsCreateRequest.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Checkout.AgenticCommerce.Common; + +namespace Checkout.AgenticCommerce.Requests +{ + /// + /// Request to create a purchase intent credentials for agentic commerce + /// + public class PurchaseIntentCredentialsCreateRequest + { + /// + /// Array of transaction data for the purchase intent credentials + /// + public IList TransactionData { get; set; } + } +} diff --git a/src/CheckoutSdk/AgenticCommerce/Requests/PurchaseIntentUpdateRequest.cs b/src/CheckoutSdk/AgenticCommerce/Requests/PurchaseIntentUpdateRequest.cs new file mode 100644 index 00000000..aa9af295 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Requests/PurchaseIntentUpdateRequest.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Checkout.AgenticCommerce.Common; + +namespace Checkout.AgenticCommerce.Requests +{ + /// + /// Create an agentic commerce purchase intent + /// + public class PurchaseIntentUpdateRequest + { + /// + /// A list of mandates associated with the purchase intent + /// + public IList Mandates { get; set; } + + /// + /// A prompt or message for the customer. You can display this during the purchase process to provide additional + /// context or instructions + /// <= 4098 characters + /// + public string CustomerPrompt { get; set; } + } +} diff --git a/src/CheckoutSdk/AgenticCommerce/Responses/EnrollACardResponse.cs b/src/CheckoutSdk/AgenticCommerce/Responses/EnrollACardResponse.cs new file mode 100644 index 00000000..65bb1373 --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Responses/EnrollACardResponse.cs @@ -0,0 +1,35 @@ +using System; + +namespace Checkout.AgenticCommerce.Responses +{ + /// + /// Card enrolled successfully + /// + public class EnrollACardResponse : HttpMetadata + { + /// + /// The unique identifier for the provisioned token + /// [Required] + /// + public string TokenId { get; set; } + + /// + /// The unique identifier for the provisioned token + /// [Required] + /// + public string NetworkTokenId { get; set; } + + /// + /// The status of the enrollment + /// Value: "enrolled" + /// [Required] + /// + public string Status { get; set; } + + /// + /// The date and time the enrollment was created, in ISO 8601 format + /// [Optional] + /// + public DateTime? CreatedAt { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/AgenticCommerce/Responses/PurchaseIntentResponse.cs b/src/CheckoutSdk/AgenticCommerce/Responses/PurchaseIntentResponse.cs new file mode 100644 index 00000000..266fffcc --- /dev/null +++ b/src/CheckoutSdk/AgenticCommerce/Responses/PurchaseIntentResponse.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using Checkout.AgenticCommerce.Common; +using Checkout.Common; + +namespace Checkout.AgenticCommerce.Responses +{ + /// + /// Response from purchase intent creation + /// + public class PurchaseIntentResponse : Resource + { + /// + /// The unique identifier for the purchase intent + /// + public string Id { get; set; } + + /// + /// The current status of the purchase intent + /// + public PurchaseIntentStatusType? Status { get; set; } + + /// + /// The scheme of the purchase intent + /// + public string Scheme { get; set; } + + /// + /// The tokenid of the purchase intent + /// + public string TokenId { get; set; } + + /// + /// The device information of the purchase intent + /// + public Device DeviceData { get; set; } + + /// + /// The customer prompt of the purchase intent + /// + public string CustomerPrompt { get; set; } + + /// + /// List of mandates for the purchase intent + /// + public IList Mandates { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/CheckoutApi.cs b/src/CheckoutSdk/CheckoutApi.cs index db137cd4..afe2b4f5 100644 --- a/src/CheckoutSdk/CheckoutApi.cs +++ b/src/CheckoutSdk/CheckoutApi.cs @@ -1,4 +1,5 @@ using Checkout.Accounts; +using Checkout.AgenticCommerce; using Checkout.Authentication; using Checkout.Balances; using Checkout.Issuing; @@ -47,6 +48,7 @@ public class CheckoutApi : ICheckoutApi private readonly IPaymentSessionsClient _paymentSessionsClient; private readonly IForwardClient _forwardClient; private readonly INetworkTokensClient _networkTokensClient; + private readonly IAgenticClient _agenticClient; public CheckoutApi(CheckoutConfiguration configuration) { @@ -78,6 +80,7 @@ public CheckoutApi(CheckoutConfiguration configuration) _paymentSessionsClient = new PaymentSessionsClient(baseApiClient, configuration); _forwardClient = new ForwardClient(baseApiClient, configuration); _networkTokensClient = new NetworkTokensClient(baseApiClient, configuration); + _agenticClient = new AgenticClient(baseApiClient, configuration); } private static ApiClient BaseApiClient(CheckoutConfiguration configuration) @@ -221,5 +224,9 @@ public INetworkTokensClient NetworkTokensClient() return _networkTokensClient; } + public IAgenticClient AgenticClient() + { + return _agenticClient; + } } } diff --git a/src/CheckoutSdk/ICheckoutApi.cs b/src/CheckoutSdk/ICheckoutApi.cs index 37262804..3959c5f2 100644 --- a/src/CheckoutSdk/ICheckoutApi.cs +++ b/src/CheckoutSdk/ICheckoutApi.cs @@ -1,4 +1,5 @@ using Checkout.Accounts; +using Checkout.AgenticCommerce; using Checkout.Authentication; using Checkout.Balances; using Checkout.Issuing; @@ -68,5 +69,7 @@ public interface ICheckoutApi : ICheckoutApiClient IForwardClient ForwardClient(); INetworkTokensClient NetworkTokensClient(); + + IAgenticClient AgenticClient(); } } diff --git a/src/CheckoutSdk/OAuthScope.cs b/src/CheckoutSdk/OAuthScope.cs index 1ae96aa1..f2ecc1a7 100644 --- a/src/CheckoutSdk/OAuthScope.cs +++ b/src/CheckoutSdk/OAuthScope.cs @@ -53,5 +53,6 @@ public enum OAuthScope [OAuthScope("Payment Context")] PaymentContext, [OAuthScope("forward")] Forward, [OAuthScope("vault:network-tokens")] VaultNetworkTokens, + [OAuthScope("agentic:enroll")] AgenticEnroll, } } diff --git a/test/CheckoutSdkTest/AgenticCommerce/AgenticClientTest.cs b/test/CheckoutSdkTest/AgenticCommerce/AgenticClientTest.cs new file mode 100644 index 00000000..7ce42f26 --- /dev/null +++ b/test/CheckoutSdkTest/AgenticCommerce/AgenticClientTest.cs @@ -0,0 +1,896 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using Shouldly; +using Xunit; + +using Checkout.AgenticCommerce.Common; +using Checkout.AgenticCommerce.Requests; +using Checkout.AgenticCommerce.Responses; +using Checkout.Common; + +namespace Checkout.AgenticCommerce +{ + public class AgenticClientTest : UnitTestFixture + { + private readonly SdkAuthorization _authorization = new SdkAuthorization(PlatformType.DefaultOAuth, ValidDefaultSk); + private readonly Mock _apiClient = new Mock(); + private readonly Mock _sdkCredentials = new Mock(PlatformType.DefaultOAuth); + private readonly Mock _httpClientFactory = new Mock(); + private readonly Mock _configuration; + + public AgenticClientTest() + { + _sdkCredentials.Setup(credentials => credentials.GetSdkAuthorization(SdkAuthorizationType.OAuth)) + .Returns(_authorization); + + _configuration = new Mock(_sdkCredentials.Object, + Environment.Sandbox, _httpClientFactory.Object); + } + + [Fact] + private async Task EnrollShouldEnroll() + { + var agenticEnrollRequest = new EnrollACardRequest + { + Source = new Source + { + Number = "4242424242424242", + ExpiryMonth = 12, + ExpiryYear = 2025, + Cvv = "123", + Type = "Card" + }, + Device = new Device + { + IpAddress = "192.168.1.1", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" + }, + Customer = new Customer + { + Email = "test@example.com", + CountryCode = CountryCode.US, + LanguageCode = "en" + } + }; + + var expectedResponse = new EnrollACardResponse + { + TokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Status = "enrolled", + CreatedAt = System.DateTime.UtcNow + }; + + _apiClient.Setup(apiClient => + apiClient.Post("agentic/enroll", _authorization, agenticEnrollRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => expectedResponse); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var response = await client.EnrollACard(agenticEnrollRequest, CancellationToken.None); + + response.ShouldNotBeNull(); + response.TokenId.ShouldBe(expectedResponse.TokenId); + response.Status.ShouldBe(expectedResponse.Status); + response.CreatedAt.ShouldBe(expectedResponse.CreatedAt); + } + + [Fact] + private async Task EnrollShouldThrowExceptionWhenEnrollRequestIsNull() + { + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var exception = await Should.ThrowAsync( + async () => await client.EnrollACard(null, CancellationToken.None)); + + exception.Message.ShouldContain("agenticEnrollRequest"); + } + + [Fact] + private async Task EnrollShouldCallCorrectEnrollEndpoint() + { + var agenticEnrollRequest = new EnrollACardRequest + { + Source = new Source + { + Number = "4242424242424242", + ExpiryMonth = 12, + ExpiryYear = 2025, + Cvv = "123", + Type = "card" + }, + Device = new Device + { + IpAddress = "192.168.1.1", + UserAgent = "Mozilla/5.0 Test" + }, + Customer = new Customer + { + Email = "test@example.com", + CountryCode = CountryCode.US, + LanguageCode = "en" + } + }; + + _apiClient.Setup(apiClient => + apiClient.Post("agentic/enroll", _authorization, agenticEnrollRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => new EnrollACardResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + await client.EnrollACard(agenticEnrollRequest, CancellationToken.None); + + _apiClient.Verify(apiClient => + apiClient.Post("agentic/enroll", _authorization, agenticEnrollRequest, + CancellationToken.None, null), Times.Once); + } + + [Fact] + private async Task EnrollShouldUseCorrectAuthorizationForEnroll() + { + var agenticEnrollRequest = new EnrollACardRequest + { + Source = new Source { Type = "card" }, + Device = new Device(), + Customer = new Customer { Email = "test@example.com", CountryCode = CountryCode.US, LanguageCode = "en" } + }; + + _apiClient.Setup(apiClient => + apiClient.Post(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(() => new EnrollACardResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + await client.EnrollACard(agenticEnrollRequest, CancellationToken.None); + + _apiClient.Verify(apiClient => + apiClient.Post(It.IsAny(), _authorization, + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + private async Task CreatePurchaseIntentShouldCreatePurchaseIntent() + { + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + DeviceBrand = "apple", + DeviceType = "tablet" + }, + CustomerPrompt = "I'm looking for running shoes in a size 10, for under $150.", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 100, + CurrencyCode = Currency.USD + }, + Description = "Purchase running shoes in size 10.", + ExpirationDate = DateTime.Parse("2026-08-31T23:59:59.000Z") + } + } + }; + + var expectedResponse = new PurchaseIntentResponse + { + Id = "pi_f3egwppx6rde3hg6itlqzp3h7e", + Scheme = "visa", + Status = PurchaseIntentStatusType.Created, + TokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + DeviceData = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + DeviceBrand = "apple", + DeviceType = "tablet" + }, + CustomerPrompt = "Hey AI, I need Nike running shoes in size 10 under $130.00", + Mandates = new List + { + new MandateExtended + { + Id = "mandate_123", + PurchaseThreshold = new PurchaseThreshold + { + Amount = 100, + CurrencyCode = Currency.USD + }, + Description = "Purchase Nike Air Max 270 running shoes in size 10", + ExpirationDate = System.DateTime.Parse("2026-08-31T23:59:59.000Z") + } + }, + Links = new Dictionary + { + { "self", new Link { Href = "https://api.example.com/agentic/purchase-intents/intent_789" } }, + { "create-credentials", new Link { Href = "https://api.example.com/agentic/purchase-intents/intent_789/credentials" } }, + { "update", new Link { Href = "https://api.example.com/agentic/purchase-intents/intent_789" } }, + { "cancel", new Link { Href = "https://api.example.com/agentic/purchase-intents/intent_789/cancel" } } + } + }; + + _apiClient.Setup(apiClient => + apiClient.Post("agentic/purchase-intent", _authorization, createPurchaseIntentRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => expectedResponse); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var response = await client.CreatePurchaseIntent(createPurchaseIntentRequest, CancellationToken.None); + + response.ShouldNotBeNull(); + response.Id.ShouldBe(expectedResponse.Id); + response.Scheme.ShouldBe(expectedResponse.Scheme); + response.Status.ShouldBe(expectedResponse.Status); + response.TokenId.ShouldBe(expectedResponse.TokenId); + response.CustomerPrompt.ShouldBe(expectedResponse.CustomerPrompt); + response.Links.ShouldNotBeNull(); + response.Links["self"].Href.ShouldBe(expectedResponse.Links["self"].Href); + } + + [Fact] + private async Task CreatePurchaseIntentShouldThrowExceptionWhenCreatePurchaseIntentRequestIsNull() + { + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var exception = await Should.ThrowAsync( + async () => await client.CreatePurchaseIntent(null, CancellationToken.None)); + + exception.Message.ShouldContain("agenticPurchaseIntentCreateRequest"); + } + + [Fact] + private async Task CreatePurchaseIntentShouldCallCorrectCreatePurchaseIntentEndpoint() + { + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_test_123", + Device = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 Test" + }, + CustomerPrompt = "Test prompt", + Mandates = new List + { + new Mandate + { + Description = "Test mandate" + } + } + }; + + _apiClient.Setup(apiClient => + apiClient.Post("agentic/purchase-intent", _authorization, createPurchaseIntentRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => new PurchaseIntentResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + await client.CreatePurchaseIntent(createPurchaseIntentRequest, CancellationToken.None); + + _apiClient.Verify(apiClient => + apiClient.Post("agentic/purchase-intent", _authorization, createPurchaseIntentRequest, + CancellationToken.None, null), Times.Once); + } + + [Fact] + private async Task CreatePurchaseIntentShouldUseCorrectAuthorizationForCreatePurchaseIntent() + { + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_test_123", + Device = new Device(), + CustomerPrompt = "Test prompt" + }; + + _apiClient.Setup(apiClient => + apiClient.Post(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(() => new PurchaseIntentResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + await client.CreatePurchaseIntent(createPurchaseIntentRequest, CancellationToken.None); + + _apiClient.Verify(apiClient => + apiClient.Post(It.IsAny(), _authorization, + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + private async Task CreatePurchaseIntentCredentialsShouldCreatePurchaseIntentCredentials() + { + var purchaseIntentId = "pi_f3egwppx6rde3hg6itlqzp3h7e"; + var createCredentialsRequest = new PurchaseIntentCredentialsCreateRequest + { + TransactionData = new List + { + new TransactionData + { + MerchantCountryCode = CountryCode.US, + MerchantName = "Nike Store", + MerchantCategoryCode = "5661", + MerchantUrl = "https://www.nike.com", + TransactionAmount = new TransactionAmount + { + Amount = 12999, + CurrencyCode = Currency.USD + } + } + } + }; + + var expectedResponse = new PurchaseIntentResponse + { + Id = purchaseIntentId, + Scheme = "visa", + Status = PurchaseIntentStatusType.Created, + TokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + DeviceData = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + DeviceBrand = "apple", + DeviceType = "tablet" + }, + CustomerPrompt = "Hey AI, I need Nike running shoes in size 10 under $130.00", + Mandates = new[] + { + new MandateExtended + { + Id = "mandate_123", + PurchaseThreshold = new PurchaseThreshold + { + Amount = 100, + CurrencyCode = Currency.USD + }, + Description = "Purchase Nike Air Max 270 running shoes in size 10", + ExpirationDate = System.DateTime.Parse("2026-08-31T23:59:59.000Z") + } + }, + Links = new Dictionary + { + { "self", new Link { Href = "https://api.example.com/agentic/purchase-intents/pi_f3egwppx6rde3hg6itlqzp3h7e" } }, + { "create-credentials", new Link { Href = "https://api.example.com/agentic/purchase-intents/pi_f3egwppx6rde3hg6itlqzp3h7e/credentials" } }, + { "update", new Link { Href = "https://api.example.com/agentic/purchase-intents/pi_f3egwppx6rde3hg6itlqzp3h7e" } }, + { "cancel", new Link { Href = "https://api.example.com/agentic/purchase-intents/pi_f3egwppx6rde3hg6itlqzp3h7e/cancel" } } + } + }; + + _apiClient.Setup(apiClient => + apiClient.Post("agentic/purchase-intent/pi_f3egwppx6rde3hg6itlqzp3h7e/credentials", _authorization, createCredentialsRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => expectedResponse); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var response = await client.CreatePurchaseIntentCredentials(purchaseIntentId, createCredentialsRequest, CancellationToken.None); + + response.ShouldNotBeNull(); + response.Id.ShouldBe(expectedResponse.Id); + response.Scheme.ShouldBe(expectedResponse.Scheme); + response.Status.ShouldBe(expectedResponse.Status); + response.TokenId.ShouldBe(expectedResponse.TokenId); + response.CustomerPrompt.ShouldBe(expectedResponse.CustomerPrompt); + response.Links.ShouldNotBeNull(); + response.Links["self"].Href.ShouldBe(expectedResponse.Links["self"].Href); + } + + [Fact] + private async Task CreatePurchaseIntentCredentialsShouldThrowExceptionWhenIdIsNull() + { + var createCredentialsRequest = new PurchaseIntentCredentialsCreateRequest + { + TransactionData = new[] + { + new TransactionData + { + MerchantName = "Test Store", + TransactionAmount = new TransactionAmount { Amount = 1000, CurrencyCode = Currency.USD } + } + } + }; + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var exception = await Should.ThrowAsync( + async () => await client.CreatePurchaseIntentCredentials(null, createCredentialsRequest, CancellationToken.None)); + + exception.Message.ShouldContain("id"); + } + + [Fact] + private async Task CreatePurchaseIntentCredentialsShouldThrowExceptionWhenRequestIsNull() + { + var purchaseIntentId = "pi_test_123"; + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var exception = await Should.ThrowAsync( + async () => await client.CreatePurchaseIntentCredentials(purchaseIntentId, null, CancellationToken.None)); + + exception.Message.ShouldContain("agenticPurchaseIntentCredentialsCreateRequest"); + } + + [Fact] + private async Task CreatePurchaseIntentCredentialsShouldCallCorrectEndpoint() + { + var purchaseIntentId = "pi_test_endpoint_123"; + var createCredentialsRequest = new PurchaseIntentCredentialsCreateRequest + { + TransactionData = new[] + { + new TransactionData + { + MerchantName = "Test Merchant", + MerchantCountryCode = CountryCode.US, + MerchantCategoryCode = "5411", + TransactionAmount = new TransactionAmount + { + Amount = 5000, + CurrencyCode = Currency.USD + } + } + } + }; + + _apiClient.Setup(apiClient => + apiClient.Post($"agentic/purchase-intent/{purchaseIntentId}/credentials", _authorization, createCredentialsRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => new PurchaseIntentResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + await client.CreatePurchaseIntentCredentials(purchaseIntentId, createCredentialsRequest, CancellationToken.None); + + _apiClient.Verify(apiClient => + apiClient.Post($"agentic/purchase-intent/{purchaseIntentId}/credentials", _authorization, createCredentialsRequest, + CancellationToken.None, null), Times.Once); + } + + [Fact] + private async Task CreatePurchaseIntentCredentialsShouldUseCorrectAuthorization() + { + var purchaseIntentId = "pi_auth_test_123"; + var createCredentialsRequest = new PurchaseIntentCredentialsCreateRequest + { + TransactionData = new[] + { + new TransactionData + { + MerchantName = "Auth Test Store", + TransactionAmount = new TransactionAmount { Amount = 2500, CurrencyCode = Currency.EUR } + } + } + }; + + _apiClient.Setup(apiClient => + apiClient.Post(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(() => new PurchaseIntentResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + await client.CreatePurchaseIntentCredentials(purchaseIntentId, createCredentialsRequest, CancellationToken.None); + + _apiClient.Verify(apiClient => + apiClient.Post(It.IsAny(), _authorization, + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + private async Task CreatePurchaseIntentCredentialsShouldHandleMultipleTransactionData() + { + var purchaseIntentId = "pi_multi_test_123"; + var createCredentialsRequest = new PurchaseIntentCredentialsCreateRequest + { + TransactionData = new[] + { + new TransactionData + { + MerchantCountryCode = CountryCode.US, + MerchantName = "Electronics Store", + MerchantCategoryCode = "5732", + MerchantUrl = "https://electronics.example.com", + TransactionAmount = new TransactionAmount + { + Amount = 79999, + CurrencyCode = Currency.USD + } + }, + new TransactionData + { + MerchantCountryCode = CountryCode.GB, + MerchantName = "UK Fashion Store", + MerchantCategoryCode = "5651", + MerchantUrl = "https://fashion.co.uk", + TransactionAmount = new TransactionAmount + { + Amount = 4500, + CurrencyCode = Currency.GBP + } + } + } + }; + + var expectedResponse = new PurchaseIntentResponse + { + Id = purchaseIntentId, + Status = PurchaseIntentStatusType.Created, + TokenId = "nt_multi_test_token" + }; + + _apiClient.Setup(apiClient => + apiClient.Post($"agentic/purchase-intent/{purchaseIntentId}/credentials", _authorization, createCredentialsRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => expectedResponse); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var response = await client.CreatePurchaseIntentCredentials(purchaseIntentId, createCredentialsRequest, CancellationToken.None); + + response.ShouldNotBeNull(); + response.Id.ShouldBe(expectedResponse.Id); + response.Status.ShouldBe(expectedResponse.Status); + response.TokenId.ShouldBe(expectedResponse.TokenId); + } + + [Fact] + private async Task UpdatePurchaseIntentShouldUpdatePurchaseIntent() + { + var purchaseIntentId = "pi_f3egwppx6rde3hg6itlqzp3h7e"; + var updatePurchaseIntentRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "Updated prompt: I'm looking for Nike running shoes in size 10.5, for under $200.", + Mandates = new List() + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 20000, + CurrencyCode = Currency.USD + }, + Description = "Updated: Purchase Nike running shoes in size 10.5.", + ExpirationDate = System.DateTime.Parse("2026-12-31T23:59:59.000Z") + } + } + }; + + var expectedResponse = new PurchaseIntentResponse + { + Id = purchaseIntentId, + Scheme = "visa", + Status = PurchaseIntentStatusType.Active, + TokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + DeviceData = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + DeviceBrand = "apple", + DeviceType = "tablet" + }, + CustomerPrompt = "Updated prompt: I'm looking for Nike running shoes in size 10.5, for under $200.", + Mandates = new[] + { + new MandateExtended + { + Id = "mandate_updated_123", + PurchaseThreshold = new PurchaseThreshold + { + Amount = 20000, + CurrencyCode = Currency.USD + }, + Description = "Updated: Purchase Nike running shoes in size 10.5.", + ExpirationDate = System.DateTime.Parse("2026-12-31T23:59:59.000Z") + } + }, + Links = new Dictionary + { + { "self", new Link { Href = "https://api.example.com/agentic/purchase-intents/pi_f3egwppx6rde3hg6itlqzp3h7e" } }, + { "create-credentials", new Link { Href = "https://api.example.com/agentic/purchase-intents/pi_f3egwppx6rde3hg6itlqzp3h7e/credentials" } }, + { "update", new Link { Href = "https://api.example.com/agentic/purchase-intents/pi_f3egwppx6rde3hg6itlqzp3h7e" } }, + { "cancel", new Link { Href = "https://api.example.com/agentic/purchase-intents/pi_f3egwppx6rde3hg6itlqzp3h7e/cancel" } } + } + }; + + _apiClient.Setup(apiClient => + apiClient.Put($"agentic/purchase-intent/{purchaseIntentId}", _authorization, updatePurchaseIntentRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => expectedResponse); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var response = await client.UpdatePurchaseIntent(purchaseIntentId, updatePurchaseIntentRequest, CancellationToken.None); + + response.ShouldNotBeNull(); + response.Id.ShouldBe(expectedResponse.Id); + response.Scheme.ShouldBe(expectedResponse.Scheme); + response.Status.ShouldBe(expectedResponse.Status); + response.TokenId.ShouldBe(expectedResponse.TokenId); + response.CustomerPrompt.ShouldBe(expectedResponse.CustomerPrompt); + response.Links.ShouldNotBeNull(); + response.Links["self"].Href.ShouldBe(expectedResponse.Links["self"].Href); + } + + [Fact] + private async Task UpdatePurchaseIntentShouldThrowExceptionWhenIdIsNull() + { + var updatePurchaseIntentRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "Test prompt", + Mandates = new List() + { + new Mandate + { + Description = "Test mandate" + } + } + }; + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var exception = await Should.ThrowAsync( + async () => await client.UpdatePurchaseIntent(null, updatePurchaseIntentRequest, CancellationToken.None)); + + exception.Message.ShouldContain("id"); + } + + [Fact] + private async Task UpdatePurchaseIntentShouldThrowExceptionWhenRequestIsNull() + { + var purchaseIntentId = "pi_test_123"; + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var exception = await Should.ThrowAsync( + async () => await client.UpdatePurchaseIntent(purchaseIntentId, null, CancellationToken.None)); + + exception.Message.ShouldContain("agenticPurchaseIntentUpdateRequest"); + } + + [Fact] + private async Task UpdatePurchaseIntentShouldThrowExceptionWhenIdIsEmpty() + { + var updatePurchaseIntentRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "Test prompt" + }; + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var exception = await Should.ThrowAsync( + async () => await client.UpdatePurchaseIntent("", updatePurchaseIntentRequest, CancellationToken.None)); + + exception.Message.ShouldContain("id"); + } + + [Fact] + private async Task UpdatePurchaseIntentShouldCallCorrectEndpoint() + { + var purchaseIntentId = "pi_test_update_123"; + var updatePurchaseIntentRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "Test update prompt", + Mandates = new List() + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 15000, + CurrencyCode = Currency.EUR + }, + Description = "Test update mandate" + } + } + }; + + _apiClient.Setup(apiClient => + apiClient.Put($"agentic/purchase-intent/{purchaseIntentId}", _authorization, updatePurchaseIntentRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => new PurchaseIntentResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + await client.UpdatePurchaseIntent(purchaseIntentId, updatePurchaseIntentRequest, CancellationToken.None); + + _apiClient.Verify(apiClient => + apiClient.Put($"agentic/purchase-intent/{purchaseIntentId}", _authorization, updatePurchaseIntentRequest, + CancellationToken.None, null), Times.Once); + } + + [Fact] + private async Task UpdatePurchaseIntentShouldUseCorrectAuthorization() + { + var purchaseIntentId = "pi_auth_test_123"; + var updatePurchaseIntentRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "Auth test prompt", + Mandates = new List + { + new Mandate + { + Description = "Auth test mandate", + PurchaseThreshold = new PurchaseThreshold + { + Amount = 0, + CurrencyCode = null + }, + ExpirationDate = new DateTime() + } + } + }; + + _apiClient.Setup(apiClient => + apiClient.Put(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(() => new PurchaseIntentResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + await client.UpdatePurchaseIntent(purchaseIntentId, updatePurchaseIntentRequest, CancellationToken.None); + + _apiClient.Verify(apiClient => + apiClient.Put(It.IsAny(), _authorization, + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + private async Task UpdatePurchaseIntentShouldHandlePartialUpdates() + { + var purchaseIntentId = "pi_partial_update_123"; + var updatePurchaseIntentRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "Only updating the customer prompt" + // Mandates is null - partial update + }; + + var expectedResponse = new PurchaseIntentResponse + { + Id = purchaseIntentId, + Status = PurchaseIntentStatusType.Created, + CustomerPrompt = "Only updating the customer prompt", + TokenId = "nt_partial_update_token" + }; + + _apiClient.Setup(apiClient => + apiClient.Put($"agentic/purchase-intent/{purchaseIntentId}", _authorization, updatePurchaseIntentRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => expectedResponse); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var response = await client.UpdatePurchaseIntent(purchaseIntentId, updatePurchaseIntentRequest, CancellationToken.None); + + response.ShouldNotBeNull(); + response.Id.ShouldBe(expectedResponse.Id); + response.Status.ShouldBe(expectedResponse.Status); + response.CustomerPrompt.ShouldBe(expectedResponse.CustomerPrompt); + response.TokenId.ShouldBe(expectedResponse.TokenId); + } + + [Fact] + private async Task DeletePurchaseIntentShouldDeletePurchaseIntent() + { + var purchaseIntentId = "pi_f3egwppx6rde3hg6itlqzp3h7e"; + + var expectedResponse = new EmptyResponse + { + HttpStatusCode = 200 + }; + + _apiClient.Setup(apiClient => + apiClient.Delete($"agentic/purchase-intent/{purchaseIntentId}", _authorization, + CancellationToken.None)) + .ReturnsAsync(() => expectedResponse); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var response = await client.DeletePurchaseIntent(purchaseIntentId, CancellationToken.None); + + response.ShouldNotBeNull(); + response.HttpStatusCode.ShouldBe(200); + } + + [Fact] + private async Task DeletePurchaseIntentShouldThrowExceptionWhenIdIsNull() + { + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var exception = await Should.ThrowAsync( + async () => await client.DeletePurchaseIntent(null, CancellationToken.None)); + + exception.Message.ShouldContain("id"); + } + + [Fact] + private async Task DeletePurchaseIntentShouldThrowExceptionWhenIdIsEmpty() + { + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var exception = await Should.ThrowAsync( + async () => await client.DeletePurchaseIntent("", CancellationToken.None)); + + exception.Message.ShouldContain("id"); + } + + [Fact] + private async Task DeletePurchaseIntentShouldCallCorrectEndpoint() + { + var purchaseIntentId = "pi_test_delete_123"; + + _apiClient.Setup(apiClient => + apiClient.Delete($"agentic/purchase-intent/{purchaseIntentId}", _authorization, + CancellationToken.None)) + .ReturnsAsync(() => new EmptyResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + await client.DeletePurchaseIntent(purchaseIntentId, CancellationToken.None); + + _apiClient.Verify(apiClient => + apiClient.Delete($"agentic/purchase-intent/{purchaseIntentId}", _authorization, + CancellationToken.None), Times.Once); + } + + [Fact] + private async Task DeletePurchaseIntentShouldUseCorrectAuthorization() + { + var purchaseIntentId = "pi_auth_test_123"; + + _apiClient.Setup(apiClient => + apiClient.Delete(It.IsAny(), It.IsAny(), + It.IsAny())) + .ReturnsAsync(() => new EmptyResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + await client.DeletePurchaseIntent(purchaseIntentId, CancellationToken.None); + + _apiClient.Verify(apiClient => + apiClient.Delete(It.IsAny(), _authorization, + It.IsAny()), Times.Once); + } + + [Fact] + private async Task DeletePurchaseIntentShouldHandleDifferentPurchaseIntentIdFormats() + { + var testIds = new[] + { + "pi_short123", + "pi_f3egwppx6rde3hg6itlqzp3h7e", + "pi_very_long_purchase_intent_id_with_underscores_123456789" + }; + + foreach (var purchaseIntentId in testIds) + { + _apiClient.Setup(apiClient => + apiClient.Delete($"agentic/purchase-intent/{purchaseIntentId}", _authorization, + CancellationToken.None)) + .ReturnsAsync(() => new EmptyResponse()); + + IAgenticClient client = new AgenticClient(_apiClient.Object, _configuration.Object); + + var response = await client.DeletePurchaseIntent(purchaseIntentId, CancellationToken.None); + + response.ShouldNotBeNull(); + + _apiClient.Verify(apiClient => + apiClient.Delete($"agentic/purchase-intent/{purchaseIntentId}", _authorization, + CancellationToken.None), Times.Once); + } + } + } +} \ No newline at end of file diff --git a/test/CheckoutSdkTest/AgenticCommerce/AgenticIntegrationTest.cs b/test/CheckoutSdkTest/AgenticCommerce/AgenticIntegrationTest.cs new file mode 100644 index 00000000..aca8ec43 --- /dev/null +++ b/test/CheckoutSdkTest/AgenticCommerce/AgenticIntegrationTest.cs @@ -0,0 +1,928 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using Shouldly; +using Xunit; + +using Checkout.AgenticCommerce.Common; +using Checkout.AgenticCommerce.Requests; +using Checkout.Common; + +namespace Checkout.AgenticCommerce +{ + public class AgenticIntegrationTest : SandboxTestFixture + { + public AgenticIntegrationTest() : base(PlatformType.DefaultOAuth) + { + } + + [Fact] + private async Task EnrollShouldEnroll() + { + var agenticEnrollRequest = new EnrollACardRequest + { + Source = new Source + { + Number = "4543474002249996", + ExpiryMonth = 12, + ExpiryYear = 2025, + Cvv = "100", + Type = "card" + }, + Device = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + DeviceBrand = "Chrome", + DeviceType = "desktop" + }, + Customer = new Customer + { + Email = GenerateRandomEmail(), + CountryCode = CountryCode.US, + LanguageCode = "en" + } + }; + + var response = await DefaultApi.AgenticClient().EnrollACard(agenticEnrollRequest); + + response.ShouldNotBeNull(); + response.Status.ShouldBe("enrolled"); + response.CreatedAt.ShouldNotBe(default(DateTime)); + } + + [Fact] + private async Task EnrollShouldEnrollWithMinimalData() + { + var agenticEnrollRequest = new EnrollACardRequest + { + Source = new Source + { + Number = "4543474002249996", + ExpiryMonth = 6, + ExpiryYear = 2026, + Type = "card" + }, + Device = new Device + { + IpAddress = "10.0.0.1", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + }, + Customer = new Customer + { + Email = GenerateRandomEmail(), + CountryCode = CountryCode.US, + LanguageCode = "en" + } + }; + + var response = await DefaultApi.AgenticClient().EnrollACard(agenticEnrollRequest); + + response.ShouldNotBeNull(); + response.Status.ShouldBe("enrolled"); + } + + [Fact] + private async Task EnrollShouldHandleInternationalCustomers() + { + var internationalRequest = new EnrollACardRequest + { + Source = new Source + { + Number = "4543474002249996", + ExpiryMonth = 3, + ExpiryYear = 2029, + Cvv = "100", + Type = "card" + }, + Device = new Device + { + IpAddress = "80.112.12.34", // European IP + UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", + DeviceBrand = "Safari", + DeviceType = "mobile" + }, + Customer = new Customer + { + Email = GenerateRandomEmail(), + CountryCode = CountryCode.ES, + LanguageCode = "es" + } + }; + + var response = await DefaultApi.AgenticClient().EnrollACard(internationalRequest); + + response.ShouldNotBeNull(); + response.Status.ShouldBe("enrolled"); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task CreatePurchaseIntentShouldCreatePurchaseIntent() + { + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + DeviceBrand = "apple", + DeviceType = "tablet" + }, + CustomerPrompt = "I'm looking for running shoes in a size 10, for under $150.", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 100, + CurrencyCode = Currency.USD + }, + Description = "Purchase running shoes in size 10.", + ExpirationDate = DateTime.Parse("2026-08-31T23:59:59.000Z") + } + } + }; + + var response = await DefaultApi.AgenticClient().CreatePurchaseIntent(createPurchaseIntentRequest); + + response.ShouldNotBeNull(); + response.Id.ShouldNotBeNullOrEmpty(); + response.Status.ShouldNotBeNull(); + response.TokenId.ShouldNotBeNullOrEmpty(); + response.CustomerPrompt.ShouldNotBeNullOrEmpty(); + + // Validate that purchase intent ID follows expected pattern + response.Id.ShouldStartWith("pi_"); + + // Validate status is one of expected values + response.Status.ShouldBe(PurchaseIntentStatusType.Active); + + // Validate links are present + response.Links.ShouldNotBeNull(); + response.Links["self"].Href.ShouldNotBeNullOrEmpty(); + response.Links["create-credentials"].Href.ShouldNotBeNullOrEmpty(); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task CreatePurchaseIntentShouldCreatePurchaseIntentWithMinimalData() + { + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "10.0.0.1" + }, + CustomerPrompt = "Basic purchase request" + }; + + var response = await DefaultApi.AgenticClient().CreatePurchaseIntent(createPurchaseIntentRequest); + + response.ShouldNotBeNull(); + response.Id.ShouldNotBeNullOrEmpty(); + response.Status.ShouldBe(PurchaseIntentStatusType.Active); + response.TokenId.ShouldNotBeNullOrEmpty(); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task CreatePurchaseIntentShouldHandleDifferentMandateTypes() + { + // Test with single mandate + var singleMandateRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "203.0.113.195", + UserAgent = "Test Agent", + DeviceBrand = "chrome", + DeviceType = "desktop" + }, + CustomerPrompt = "Looking for electronics under $500", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 500, + CurrencyCode = Currency.USD + }, + Description = "Electronics purchase", + ExpirationDate = DateTime.Parse("2026-12-31T23:59:59.000Z") + } + } + }; + + var singleResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(singleMandateRequest); + singleResponse.ShouldNotBeNull(); + singleResponse.Status.ShouldBe(PurchaseIntentStatusType.Active); + singleResponse.Mandates.Count.ShouldBe(1); + + // Test with multiple mandates + var multipleMandateRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "198.51.100.42" + }, + CustomerPrompt = "Shopping for clothing and accessories", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 200, + CurrencyCode = Currency.USD + }, + Description = "Clothing purchase", + ExpirationDate = DateTime.Parse("2026-06-30T23:59:59.000Z") + }, + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 100, + CurrencyCode = Currency.USD + }, + Description = "Accessories purchase", + ExpirationDate = DateTime.Parse("2026-09-30T23:59:59.000Z") + } + } + }; + + var multipleResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(multipleMandateRequest); + multipleResponse.ShouldNotBeNull(); + multipleResponse.Status.ShouldBe(PurchaseIntentStatusType.Active); + multipleResponse.Mandates.Count.ShouldBe(2); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task CreatePurchaseIntentShouldHandleInternationalCurrencies() + { + var internationalRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "80.112.12.34", // European IP + UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", + DeviceBrand = "safari", + DeviceType = "mobile" + }, + CustomerPrompt = "Buscando zapatos deportivos en talla 42", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 150, + CurrencyCode = Currency.EUR + }, + Description = "Compra de zapatos deportivos", + ExpirationDate = DateTime.Parse("2026-08-31T23:59:59.000Z") + } + } + }; + + var response = await DefaultApi.AgenticClient().CreatePurchaseIntent(internationalRequest); + + response.ShouldNotBeNull(); + response.Status.ShouldBe(PurchaseIntentStatusType.Active); + response.Id.ShouldStartWith("pi_"); + response.Mandates.ShouldNotBeNull(); + response.Mandates[0].PurchaseThreshold.CurrencyCode.ShouldBe(Currency.EUR); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task CreatePurchaseIntentCredentialsShouldCreateCredentials() + { + // First create a purchase intent to get an ID + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" + }, + CustomerPrompt = "I need running shoes in size 10, under $150.", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 15000, + CurrencyCode = Currency.USD + }, + Description = "Purchase running shoes for credentials test.", + ExpirationDate = DateTime.Parse("2026-12-31T23:59:59.000Z") + } + } + }; + + var purchaseIntentResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(createPurchaseIntentRequest); + purchaseIntentResponse.ShouldNotBeNull(); + purchaseIntentResponse.Id.ShouldNotBeNullOrEmpty(); + + // Now create credentials for this purchase intent + var createCredentialsRequest = new PurchaseIntentCredentialsCreateRequest + { + TransactionData = new[] + { + new TransactionData + { + MerchantCountryCode = CountryCode.US, + MerchantName = "Nike Store", + MerchantCategoryCode = "5661", + MerchantUrl = "https://www.nike.com", + TransactionAmount = new TransactionAmount + { + Amount = 12999, + CurrencyCode = Currency.USD + } + } + } + }; + + var credentialsResponse = await DefaultApi.AgenticClient().CreatePurchaseIntentCredentials( + purchaseIntentResponse.Id, createCredentialsRequest); + + credentialsResponse.ShouldNotBeNull(); + credentialsResponse.Id.ShouldBe(purchaseIntentResponse.Id); + credentialsResponse.Status.ShouldNotBeNull(); + credentialsResponse.TokenId.ShouldNotBeNullOrEmpty(); + + // Validate that the purchase intent ID follows expected pattern + credentialsResponse.Id.ShouldStartWith("pi_"); + + // Validate links are present and updated + credentialsResponse.Links.ShouldNotBeNull(); + credentialsResponse.Links["self"].Href.ShouldNotBeNullOrEmpty(); + credentialsResponse.Links["create-credentials"].Href.ShouldNotBeNullOrEmpty(); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task CreatePurchaseIntentCredentialsShouldHandleMultipleTransactionData() + { + // First create a purchase intent + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "203.0.113.195", + UserAgent = "Test Agent Multi Transaction" + }, + CustomerPrompt = "I need multiple items: electronics and clothing.", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 100000, + CurrencyCode = Currency.USD + }, + Description = "Purchase multiple items across different categories.", + ExpirationDate = DateTime.Parse("2026-12-31T23:59:59.000Z") + } + } + }; + + var purchaseIntentResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(createPurchaseIntentRequest); + purchaseIntentResponse.ShouldNotBeNull(); + + // Create credentials with multiple transaction data + var createCredentialsRequest = new PurchaseIntentCredentialsCreateRequest + { + TransactionData = new[] + { + new TransactionData + { + MerchantCountryCode = CountryCode.US, + MerchantName = "Best Electronics", + MerchantCategoryCode = "5732", + MerchantUrl = "https://bestelectronics.com", + TransactionAmount = new TransactionAmount + { + Amount = 79999, + CurrencyCode = Currency.USD + } + }, + new TransactionData + { + MerchantCountryCode = CountryCode.US, + MerchantName = "Fashion Central", + MerchantCategoryCode = "5651", + MerchantUrl = "https://fashioncentral.com", + TransactionAmount = new TransactionAmount + { + Amount = 15999, + CurrencyCode = Currency.USD + } + } + } + }; + + var credentialsResponse = await DefaultApi.AgenticClient().CreatePurchaseIntentCredentials( + purchaseIntentResponse.Id, createCredentialsRequest); + + credentialsResponse.ShouldNotBeNull(); + credentialsResponse.Id.ShouldBe(purchaseIntentResponse.Id); + credentialsResponse.Status.ShouldNotBeNull(); + credentialsResponse.TokenId.ShouldNotBeNullOrEmpty(); + + // Validate response structure + credentialsResponse.Id.ShouldStartWith("pi_"); + credentialsResponse.Links.ShouldNotBeNull(); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task CreatePurchaseIntentCredentialsShouldHandleInternationalTransactions() + { + // Create purchase intent + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "80.112.12.34", // European IP + UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" + }, + CustomerPrompt = "Looking for luxury items in different currencies.", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 200000, + CurrencyCode = Currency.EUR + }, + Description = "International luxury purchases.", + ExpirationDate = DateTime.Parse("2026-12-31T23:59:59.000Z") + } + } + }; + + var purchaseIntentResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(createPurchaseIntentRequest); + purchaseIntentResponse.ShouldNotBeNull(); + + // Create credentials with international transaction data + var createCredentialsRequest = new PurchaseIntentCredentialsCreateRequest + { + TransactionData = new[] + { + new TransactionData + { + MerchantCountryCode = CountryCode.FR, + MerchantName = "Luxury Paris Boutique", + MerchantCategoryCode = "5944", + MerchantUrl = "https://luxuryparis.fr", + TransactionAmount = new TransactionAmount + { + Amount = 89999, + CurrencyCode = Currency.EUR + } + }, + new TransactionData + { + MerchantCountryCode = CountryCode.GB, + MerchantName = "London Fashion House", + MerchantCategoryCode = "5651", + MerchantUrl = "https://londonfashion.co.uk", + TransactionAmount = new TransactionAmount + { + Amount = 45000, + CurrencyCode = Currency.GBP + } + } + } + }; + + var credentialsResponse = await DefaultApi.AgenticClient().CreatePurchaseIntentCredentials( + purchaseIntentResponse.Id, createCredentialsRequest); + + credentialsResponse.ShouldNotBeNull(); + credentialsResponse.Id.ShouldBe(purchaseIntentResponse.Id); + credentialsResponse.Status.ShouldNotBeNull(); + credentialsResponse.TokenId.ShouldNotBeNullOrEmpty(); + + // Validate international handling + credentialsResponse.Id.ShouldStartWith("pi_"); + credentialsResponse.Links.ShouldNotBeNull(); + credentialsResponse.Links["self"].Href.ShouldContain(purchaseIntentResponse.Id); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task CreatePurchaseIntentCredentialsShouldHandleMinimalTransactionData() + { + // Create purchase intent first + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "10.0.0.1" + }, + CustomerPrompt = "Basic purchase for minimal test" + }; + + var purchaseIntentResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(createPurchaseIntentRequest); + purchaseIntentResponse.ShouldNotBeNull(); + + // Create credentials with minimal transaction data + var createCredentialsRequest = new PurchaseIntentCredentialsCreateRequest + { + TransactionData = new[] + { + new TransactionData + { + MerchantName = "Basic Store", + TransactionAmount = new TransactionAmount + { + Amount = 1000, + CurrencyCode = Currency.USD + } + } + } + }; + + var credentialsResponse = await DefaultApi.AgenticClient().CreatePurchaseIntentCredentials( + purchaseIntentResponse.Id, createCredentialsRequest); + + credentialsResponse.ShouldNotBeNull(); + credentialsResponse.Id.ShouldBe(purchaseIntentResponse.Id); + credentialsResponse.Status.ShouldNotBeNull(); + credentialsResponse.TokenId.ShouldNotBeNullOrEmpty(); + credentialsResponse.Id.ShouldStartWith("pi_"); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task UpdatePurchaseIntentShouldUpdatePurchaseIntent() + { + // First create a purchase intent to update + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + DeviceBrand = "apple", + DeviceType = "tablet" + }, + CustomerPrompt = "I'm looking for running shoes in a size 10, for under $150.", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 15000, + CurrencyCode = Currency.USD + }, + Description = "Purchase running shoes in size 10.", + ExpirationDate = System.DateTime.Parse("2026-08-31T23:59:59.000Z") + } + } + }; + + var purchaseIntentResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(createPurchaseIntentRequest); + + purchaseIntentResponse.ShouldNotBeNull(); + purchaseIntentResponse.Id.ShouldNotBeNullOrEmpty(); + + // Now update the purchase intent + var updatePurchaseIntentRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "Updated: I'm looking for Nike running shoes in size 10.5, for under $200.", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 20000, + CurrencyCode = Currency.USD + }, + Description = "Updated: Purchase Nike running shoes in size 10.5.", + ExpirationDate = System.DateTime.Parse("2026-12-31T23:59:59.000Z") + } + } + }; + + var updateResponse = await DefaultApi.AgenticClient().UpdatePurchaseIntent(purchaseIntentResponse.Id, updatePurchaseIntentRequest); + + updateResponse.ShouldNotBeNull(); + updateResponse.Id.ShouldBe(purchaseIntentResponse.Id); + updateResponse.CustomerPrompt.ShouldBe(updatePurchaseIntentRequest.CustomerPrompt); + updateResponse.Status.ShouldNotBeNull(); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task UpdatePurchaseIntentShouldHandleNonExistentPurchaseIntent() + { + var nonExistentId = "pi_nonexistent_123456"; + var updatePurchaseIntentRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "This should fail" + }; + + var exception = await Should.ThrowAsync( + async () => await DefaultApi.AgenticClient().UpdatePurchaseIntent(nonExistentId, updatePurchaseIntentRequest)); + + exception.HttpStatusCode.ShouldBeOneOf(HttpStatusCode.NotFound, HttpStatusCode.BadRequest); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task UpdatePurchaseIntentShouldValidateInputParameters() + { + // Test with null ID + var updateRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "Valid prompt" + }; + + var exception = await Should.ThrowAsync( + async () => await DefaultApi.AgenticClient().UpdatePurchaseIntent(null, updateRequest)); + + exception.Message.ShouldContain("id"); + + // Test with null request + var validId = "pi_valid_123"; + var nullRequestException = await Should.ThrowAsync( + async () => await DefaultApi.AgenticClient().UpdatePurchaseIntent(validId, null)); + + nullRequestException.Message.ShouldContain("agenticPurchaseIntentUpdateRequest"); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task UpdatePurchaseIntentShouldHandlePartialUpdates() + { + // First create a purchase intent + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 Test Browser" + }, + CustomerPrompt = "Original prompt", + Mandates = new List + { + new Mandate + { + Description = "Original mandate" + } + } + }; + + var purchaseIntentResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(createPurchaseIntentRequest); + + // Update only the customer prompt (partial update) + var partialUpdateRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "Updated prompt only" + // Mandates is null - testing partial update + }; + + var updateResponse = await DefaultApi.AgenticClient().UpdatePurchaseIntent( + purchaseIntentResponse.Id, partialUpdateRequest); + + updateResponse.ShouldNotBeNull(); + updateResponse.Id.ShouldBe(purchaseIntentResponse.Id); + updateResponse.CustomerPrompt.ShouldBe(partialUpdateRequest.CustomerPrompt); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task UpdatePurchaseIntentShouldHandleDifferentPurchaseIntentStates() + { + // Create purchase intent + var createRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 State Test" + }, + CustomerPrompt = "Test different states", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 10000, + CurrencyCode = Currency.USD + }, + Description = "State test mandate" + } + } + }; + + var purchaseIntentResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(createRequest); + + // Try to update the purchase intent regardless of its current state + var updateRequest = new PurchaseIntentUpdateRequest + { + CustomerPrompt = "Updated for state testing", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 12000, + CurrencyCode = Currency.USD + }, + Description = "Updated state test mandate" + } + } + }; + + // This should work regardless of the purchase intent's current state + var updateResponse = await DefaultApi.AgenticClient().UpdatePurchaseIntent( + purchaseIntentResponse.Id, updateRequest); + + updateResponse.ShouldNotBeNull(); + updateResponse.Id.ShouldBe(purchaseIntentResponse.Id); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task DeletePurchaseIntentShouldDeletePurchaseIntent() + { + // First create a purchase intent to delete + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "192.168.1.100", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" + }, + CustomerPrompt = "I need running shoes for deletion test.", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 10000, + CurrencyCode = Currency.USD + }, + Description = "Purchase intent for deletion test.", + ExpirationDate = DateTime.Parse("2026-12-31T23:59:59.000Z") + } + } + }; + + var purchaseIntentResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(createPurchaseIntentRequest); + purchaseIntentResponse.ShouldNotBeNull(); + purchaseIntentResponse.Id.ShouldNotBeNullOrEmpty(); + purchaseIntentResponse.Id.ShouldStartWith("pi_"); + + // Now delete the purchase intent + var deleteResponse = await DefaultApi.AgenticClient().DeletePurchaseIntent(purchaseIntentResponse.Id); + + deleteResponse.ShouldNotBeNull(); + + // Validate successful deletion (typically 200 or 204) + deleteResponse.HttpStatusCode.ShouldBeOneOf(200, 204); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task DeletePurchaseIntentShouldHandleNonExistentPurchaseIntent() + { + var nonExistentId = "pi_non_existent_123456789"; + + // Attempt to delete non-existent purchase intent should handle gracefully + var exception = await Should.ThrowAsync( + async () => await DefaultApi.AgenticClient().DeletePurchaseIntent(nonExistentId)); + + // Should return 404 Not Found for non-existent purchase intent + exception.HttpStatusCode.ShouldBe(HttpStatusCode.NotFound); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task DeletePurchaseIntentShouldHandleAlreadyDeletedPurchaseIntent() + { + // First create a purchase intent + var createPurchaseIntentRequest = new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "203.0.113.195", + UserAgent = "Test Agent Double Delete" + }, + CustomerPrompt = "Purchase intent for double deletion test.", + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 5000, + CurrencyCode = Currency.USD + }, + Description = "Double deletion test mandate.", + ExpirationDate = DateTime.Parse("2026-12-31T23:59:59.000Z") + } + } + }; + + var purchaseIntentResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(createPurchaseIntentRequest); + purchaseIntentResponse.ShouldNotBeNull(); + + // Delete it once + var firstDeleteResponse = await DefaultApi.AgenticClient().DeletePurchaseIntent(purchaseIntentResponse.Id); + firstDeleteResponse.ShouldNotBeNull(); + + // Try to delete it again - should handle gracefully (404 or other appropriate response) + var exception = await Should.ThrowAsync( + async () => await DefaultApi.AgenticClient().DeletePurchaseIntent(purchaseIntentResponse.Id)); + + // Should return 404 Not Found for already deleted purchase intent + exception.HttpStatusCode.ShouldBe(HttpStatusCode.NotFound); + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task DeletePurchaseIntentShouldHandleDifferentPurchaseIntentStates() + { + // Test deleting purchase intents in different states + var testCases = new[] + { + new { Description = "Basic purchase intent", CustomerPrompt = "Basic deletion test" }, + new { Description = "Complex purchase intent", CustomerPrompt = "Complex purchase intent with multiple mandates for deletion test" } + }; + + foreach (var createRequest in testCases.Select(testCase => new PurchaseIntentCreateRequest + { + NetworkTokenId = "nt_e7fjr77crbgmlhpjvuq3bj6jba", + Device = new Device + { + IpAddress = "10.0.0.1", + UserAgent = "Test Agent States" + }, + CustomerPrompt = testCase.CustomerPrompt, + Mandates = new List + { + new Mandate + { + PurchaseThreshold = new PurchaseThreshold + { + Amount = 7500, + CurrencyCode = Currency.USD + }, + Description = $"Mandate for {testCase.Description}", + ExpirationDate = DateTime.Parse("2026-12-31T23:59:59.000Z") + } + } + })) + { + var purchaseIntentResponse = await DefaultApi.AgenticClient().CreatePurchaseIntent(createRequest); + purchaseIntentResponse.ShouldNotBeNull(); + purchaseIntentResponse.Id.ShouldStartWith("pi_"); + + // Delete the purchase intent + var deleteResponse = await DefaultApi.AgenticClient().DeletePurchaseIntent(purchaseIntentResponse.Id); + + deleteResponse.ShouldNotBeNull(); + deleteResponse.HttpStatusCode.ShouldBeOneOf(200, 204); + } + } + + [Fact(Skip = "This test is unsupported currently, not ready to test in the sandbox")] + private async Task DeletePurchaseIntentShouldHandleInvalidIdFormat() + { + var invalidIds = new[] + { + "invalid_id_format", + "pi_", + "not_a_purchase_intent_id", + "123456789" + }; + + foreach (var invalidId in invalidIds) + { + var exception = await Should.ThrowAsync( + async () => await DefaultApi.AgenticClient().DeletePurchaseIntent(invalidId)); + + // Should return 400 Bad Request or 404 Not Found for invalid ID format + exception.HttpStatusCode.ShouldBeOneOf(HttpStatusCode.BadRequest, HttpStatusCode.NotFound); + } + } + } +} \ No newline at end of file diff --git a/test/CheckoutSdkTest/Instruments/InstrumentsIntegrationTest.cs b/test/CheckoutSdkTest/Instruments/InstrumentsIntegrationTest.cs index abdc840c..61505b42 100644 --- a/test/CheckoutSdkTest/Instruments/InstrumentsIntegrationTest.cs +++ b/test/CheckoutSdkTest/Instruments/InstrumentsIntegrationTest.cs @@ -149,7 +149,6 @@ private async Task ShouldUpdateCardInstrument() cardResponse.Fingerprint.ShouldNotBeNull(); cardResponse.ExpiryMonth.ShouldBe(12); cardResponse.ExpiryYear.ShouldBe(2030); - cardResponse.Customer.Default.ShouldBeTrue(); cardResponse.AccountHolder.FirstName.ShouldBe("John"); cardResponse.AccountHolder.LastName.ShouldBe("Doe"); cardResponse.CardType.ShouldNotBeNull(); diff --git a/test/CheckoutSdkTest/SandboxTestFixture.cs b/test/CheckoutSdkTest/SandboxTestFixture.cs index fcecaf6a..02bed1e3 100644 --- a/test/CheckoutSdkTest/SandboxTestFixture.cs +++ b/test/CheckoutSdkTest/SandboxTestFixture.cs @@ -60,7 +60,7 @@ protected SandboxTestFixture(PlatformType platformType) OAuthScope.Accounts, OAuthScope.SessionsApp, OAuthScope.SessionsBrowser, OAuthScope.Vault, OAuthScope.PayoutsBankDetails, OAuthScope.TransfersCreate, OAuthScope.TransfersView, OAuthScope.BalancesView, OAuthScope.VaultCardMetadata, - OAuthScope.FinancialActions, OAuthScope.Forward) + OAuthScope.FinancialActions, OAuthScope.Forward, OAuthScope.AgenticEnroll) .Environment(Environment.Sandbox) //.HttpClientFactory(new CustomClientFactory("3.0")) //.EnvironmentSubdomain(System.Environment.GetEnvironmentVariable("CHECKOUT_MERCHANT_SUBDOMAIN"))