From 2055e366f626ce33f59521d77fe2ef7591b5c94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Wed, 2 Apr 2025 17:12:44 +0200 Subject: [PATCH] Add control profiles issuing --- .../Requests/ControlProfileRequest.cs | 7 + .../Responses/ControlProfileResponse.cs | 16 ++ .../Responses/ControlProfilesResponse.cs | 9 + .../Issuing/IIssuingClient.ControlProfiles.cs | 36 ++++ src/CheckoutSdk/Issuing/IssuingClient.Base.cs | 3 + .../Issuing/IssuingClient.ControlProfiles.cs | 90 +++++++++ .../ControlProfilesClientTest.cs | 180 ++++++++++++++++++ .../ControlProfilesIntegrationTest.cs | 111 +++++++++++ 8 files changed, 452 insertions(+) create mode 100644 src/CheckoutSdk/Issuing/ControlProfiles/Requests/ControlProfileRequest.cs create mode 100644 src/CheckoutSdk/Issuing/ControlProfiles/Responses/ControlProfileResponse.cs create mode 100644 src/CheckoutSdk/Issuing/ControlProfiles/Responses/ControlProfilesResponse.cs create mode 100644 src/CheckoutSdk/Issuing/IIssuingClient.ControlProfiles.cs create mode 100644 src/CheckoutSdk/Issuing/IssuingClient.ControlProfiles.cs create mode 100644 test/CheckoutSdkTest/Issuing/ControlProfiles/ControlProfilesClientTest.cs create mode 100644 test/CheckoutSdkTest/Issuing/ControlProfiles/ControlProfilesIntegrationTest.cs diff --git a/src/CheckoutSdk/Issuing/ControlProfiles/Requests/ControlProfileRequest.cs b/src/CheckoutSdk/Issuing/ControlProfiles/Requests/ControlProfileRequest.cs new file mode 100644 index 00000000..c73a3823 --- /dev/null +++ b/src/CheckoutSdk/Issuing/ControlProfiles/Requests/ControlProfileRequest.cs @@ -0,0 +1,7 @@ +namespace Checkout.Issuing.ControlProfiles.Requests +{ + public class ControlProfileRequest + { + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/Issuing/ControlProfiles/Responses/ControlProfileResponse.cs b/src/CheckoutSdk/Issuing/ControlProfiles/Responses/ControlProfileResponse.cs new file mode 100644 index 00000000..81bdf20f --- /dev/null +++ b/src/CheckoutSdk/Issuing/ControlProfiles/Responses/ControlProfileResponse.cs @@ -0,0 +1,16 @@ +using Checkout.Common; +using System; + +namespace Checkout.Issuing.ControlProfiles.Responses +{ + public class ControlProfileResponse : Resource + { + public string Id { get; set; } + + public string Name { get; set; } + + public DateTime? CreatedDate { get; set; } + + public DateTime? LastModifiedDate { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/Issuing/ControlProfiles/Responses/ControlProfilesResponse.cs b/src/CheckoutSdk/Issuing/ControlProfiles/Responses/ControlProfilesResponse.cs new file mode 100644 index 00000000..ca4fc421 --- /dev/null +++ b/src/CheckoutSdk/Issuing/ControlProfiles/Responses/ControlProfilesResponse.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Checkout.Issuing.ControlProfiles.Responses +{ + public class ControlProfilesResponse : HttpMetadata + { + public IList ControlProfiles { get; set; } + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/Issuing/IIssuingClient.ControlProfiles.cs b/src/CheckoutSdk/Issuing/IIssuingClient.ControlProfiles.cs new file mode 100644 index 00000000..149688fa --- /dev/null +++ b/src/CheckoutSdk/Issuing/IIssuingClient.ControlProfiles.cs @@ -0,0 +1,36 @@ +using Checkout.Common; +using Checkout.Issuing.ControlProfiles.Requests; +using Checkout.Issuing.ControlProfiles.Responses; +using Checkout.Payments; +using System.Threading; +using System.Threading.Tasks; + +namespace Checkout.Issuing +{ + public partial interface IIssuingClient + { + Task CreateControlProfile(ControlProfileRequest controlProfileRequest, + CancellationToken cancellationToken = default); + + Task GetAllControlProfiles(string targetId, + CancellationToken cancellationToken = default); + + Task GetControlProfileDetails(string controlProfileId, + CancellationToken cancellationToken = default); + + Task UpdateControlProfile(string controlProfileId, + ControlProfileRequest controlProfileRequest, + CancellationToken cancellationToken = default); + + Task RemoveControlProfile(string controlProfileId, + CancellationToken cancellationToken = default); + + Task AddTargetToControlProfile(string controlProfileId, + string targetId, + CancellationToken cancellationToken = default); + + Task RemoveTargetFromControlProfile(string controlProfileId, + string targetId, + CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/CheckoutSdk/Issuing/IssuingClient.Base.cs b/src/CheckoutSdk/Issuing/IssuingClient.Base.cs index a98f1878..1f495bc0 100644 --- a/src/CheckoutSdk/Issuing/IssuingClient.Base.cs +++ b/src/CheckoutSdk/Issuing/IssuingClient.Base.cs @@ -16,6 +16,9 @@ public partial class IssuingClient : AbstractClient, IIssuingClient private const string PresentmentsPath = "presentments"; private const string ReversalsPath = "reversals"; private const string TransactionsPath = "transactions"; + private const string ControlProfilesPath = "control-profiles"; + private const string AddPath = "add"; + private const string RemovePath = "remove"; public IssuingClient(IApiClient apiClient, CheckoutConfiguration configuration) : base(apiClient, configuration, SdkAuthorizationType.OAuth) diff --git a/src/CheckoutSdk/Issuing/IssuingClient.ControlProfiles.cs b/src/CheckoutSdk/Issuing/IssuingClient.ControlProfiles.cs new file mode 100644 index 00000000..1448111f --- /dev/null +++ b/src/CheckoutSdk/Issuing/IssuingClient.ControlProfiles.cs @@ -0,0 +1,90 @@ +using Checkout.Common; +using Checkout.Issuing.ControlProfiles.Requests; +using Checkout.Issuing.ControlProfiles.Responses; +using Checkout.Payments; +using System.Threading; +using System.Threading.Tasks; + +namespace Checkout.Issuing +{ + public partial class IssuingClient + { + public Task CreateControlProfile(ControlProfileRequest controlProfileRequest, CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("controlProfileRequest", controlProfileRequest); + return ApiClient.Post( + BuildPath(IssuingPath, ControlsPath, ControlProfilesPath), + SdkAuthorization(), + controlProfileRequest, + cancellationToken + ); + } + + public Task GetAllControlProfiles(string targetId, CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("targetId", targetId); + return ApiClient.Get( + BuildPath(IssuingPath, ControlsPath, ControlProfilesPath), + SdkAuthorization(), + cancellationToken + ); + } + + public Task GetControlProfileDetails(string controlProfileId, CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("controlProfileId", controlProfileId); + return ApiClient.Get( + BuildPath(IssuingPath, ControlsPath, ControlProfilesPath, controlProfileId), + SdkAuthorization(), + cancellationToken + ); + } + + public Task UpdateControlProfile(string controlProfileId, ControlProfileRequest controlProfileRequest, + CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("controlProfileId", controlProfileId, "controlProfileRequest", controlProfileRequest); + return ApiClient.Patch( + BuildPath(IssuingPath, ControlsPath, ControlProfilesPath, controlProfileId), + SdkAuthorization(), + controlProfileRequest, + cancellationToken + ); + } + + public Task RemoveControlProfile(string controlProfileId, CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("controlProfileId", controlProfileId); + return ApiClient.Delete( + BuildPath(IssuingPath, ControlsPath, ControlProfilesPath, controlProfileId), + SdkAuthorization(), + cancellationToken + ); + } + + public Task AddTargetToControlProfile(string controlProfileId, string targetId, CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("controlProfileId", controlProfileId, "targetId", targetId); + return ApiClient.Post( + BuildPath(IssuingPath, ControlsPath, ControlProfilesPath, controlProfileId, AddPath, targetId), + SdkAuthorization(), + null, + cancellationToken, + null + ); + } + + public Task RemoveTargetFromControlProfile(string controlProfileId, string targetId, + CancellationToken cancellationToken = default) + { + CheckoutUtils.ValidateParams("controlProfileId", controlProfileId, "targetId", targetId); + return ApiClient.Post( + BuildPath(IssuingPath, ControlsPath, ControlProfilesPath, controlProfileId, RemovePath, targetId), + SdkAuthorization(), + null, + cancellationToken, + null + ); + } + } +} \ No newline at end of file diff --git a/test/CheckoutSdkTest/Issuing/ControlProfiles/ControlProfilesClientTest.cs b/test/CheckoutSdkTest/Issuing/ControlProfiles/ControlProfilesClientTest.cs new file mode 100644 index 00000000..1c91cb91 --- /dev/null +++ b/test/CheckoutSdkTest/Issuing/ControlProfiles/ControlProfilesClientTest.cs @@ -0,0 +1,180 @@ +using Checkout.Common; +using Checkout.Issuing.ControlProfiles.Requests; +using Checkout.Issuing.ControlProfiles.Responses; +using Checkout.Payments; +using Moq; +using Shouldly; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Checkout.Issuing.ControlProfiles +{ + public class ControlProfilesClientTest : 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 ControlProfilesClientTest() + { + _sdkCredentials.Setup(credentials => credentials.GetSdkAuthorization(SdkAuthorizationType.OAuth)) + .Returns(_authorization); + + _configuration = new Mock(_sdkCredentials.Object, + Environment.Sandbox, _httpClientFactory.Object); + } + + [Fact] + private async Task ShouldCreateControlProfile() + { + ControlProfileRequest controlProfileRequest = new ControlProfileRequest(); + ControlProfileResponse controlProfileResponse = new ControlProfileResponse(); + + _apiClient.Setup(apiClient => + apiClient.Post("issuing/controls/control-profiles", _authorization, + controlProfileRequest, + CancellationToken.None, null)) + .ReturnsAsync(() => controlProfileResponse); + + IIssuingClient client = + new IssuingClient(_apiClient.Object, _configuration.Object); + + ControlProfileResponse response = await client.CreateControlProfile(controlProfileRequest); + + response.ShouldNotBeNull(); + response.ShouldBeSameAs(controlProfileResponse); + } + + [Fact] + private async Task ShouldGetAllControlProfiles() + { + ControlProfilesResponse controlProfilesResponse = new ControlProfilesResponse(); + + _apiClient.Setup(apiClient => + apiClient.Get("issuing/controls/control-profiles", _authorization, + CancellationToken.None)) + .ReturnsAsync(() => controlProfilesResponse); + + IIssuingClient client = + new IssuingClient(_apiClient.Object, _configuration.Object); + + ControlProfilesResponse response = await client.GetAllControlProfiles("target_id"); + + response.ShouldNotBeNull(); + response.ShouldBeSameAs(controlProfilesResponse); + } + + [Fact] + private async Task ShouldGetControlProfileDetails() + { + ControlProfileResponse controlProfileResponse = new ControlProfileResponse(); + + _apiClient.Setup(apiClient => + apiClient.Get("issuing/controls/control-profiles/control_profile_id", + _authorization, + CancellationToken.None)) + .ReturnsAsync(() => controlProfileResponse); + + IIssuingClient client = + new IssuingClient(_apiClient.Object, _configuration.Object); + + ControlProfileResponse response = await client.GetControlProfileDetails("control_profile_id"); + + response.ShouldNotBeNull(); + response.ShouldBeSameAs(controlProfileResponse); + } + + [Fact] + private async Task ShouldUpdateControlProfile() + { + ControlProfileRequest controlProfileRequest = new ControlProfileRequest(); + ControlProfileResponse controlProfileResponse = new ControlProfileResponse(); + + _apiClient.Setup(apiClient => + apiClient.Patch("issuing/controls/control-profiles/control_profile_id", + _authorization, + controlProfileRequest, + CancellationToken.None, + null)) + .ReturnsAsync(() => controlProfileResponse); + + IIssuingClient client = + new IssuingClient(_apiClient.Object, _configuration.Object); + + ControlProfileResponse response = + await client.UpdateControlProfile("control_profile_id", controlProfileRequest); + + response.ShouldNotBeNull(); + response.ShouldBeSameAs(controlProfileResponse); + } + + [Fact] + private async Task ShouldDeleteControlProfile() + { + VoidResponse removeControlProfileResponse = new VoidResponse(); + + _apiClient.Setup(apiClient => + apiClient.Delete("issuing/controls/control-profiles/control_profile_id", + _authorization, + CancellationToken.None)) + .ReturnsAsync(() => removeControlProfileResponse); + + IIssuingClient client = + new IssuingClient(_apiClient.Object, _configuration.Object); + + VoidResponse response = await client.RemoveControlProfile("control_profile_id"); + + response.ShouldNotBeNull(); + response.ShouldBeSameAs(removeControlProfileResponse); + } + + [Fact] + private async Task ShouldAddTargetToControlProfile() + { + Resource response = new Resource(); + + _apiClient.Setup(apiClient => + apiClient.Post("issuing/controls/control-profiles/control_profile_id/add/target_id", + _authorization, + null, + CancellationToken.None, + null)) + .ReturnsAsync(() => response); + + IIssuingClient client = + new IssuingClient(_apiClient.Object, _configuration.Object); + + Resource result = await client.AddTargetToControlProfile("control_profile_id", "target_id"); + + result.ShouldNotBeNull(); + result.ShouldBeSameAs(response); + } + + [Fact] + private async Task ShouldRemoveTargetFromControlProfile() + { + Resource response = new Resource(); + + _apiClient.Setup(apiClient => + apiClient.Post("issuing/controls/control-profiles/control_profile_id/remove/target_id", + _authorization, + null, + CancellationToken.None, + null)) + .ReturnsAsync(() => response); + + IIssuingClient client = + new IssuingClient(_apiClient.Object, _configuration.Object); + + Resource result = await client.RemoveTargetFromControlProfile("control_profile_id", "target_id"); + + result.ShouldNotBeNull(); + result.ShouldBeSameAs(response); + } + } +} \ No newline at end of file diff --git a/test/CheckoutSdkTest/Issuing/ControlProfiles/ControlProfilesIntegrationTest.cs b/test/CheckoutSdkTest/Issuing/ControlProfiles/ControlProfilesIntegrationTest.cs new file mode 100644 index 00000000..7565ffe7 --- /dev/null +++ b/test/CheckoutSdkTest/Issuing/ControlProfiles/ControlProfilesIntegrationTest.cs @@ -0,0 +1,111 @@ +using Checkout.Common; +using Checkout.Issuing.Cardholders; +using Checkout.Issuing.Cards.Requests.Create; +using Checkout.Issuing.ControlProfiles.Requests; +using Checkout.Issuing.ControlProfiles.Responses; +using Checkout.Payments; +using Shouldly; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Checkout.Issuing.ControlProfiles +{ + public class ControlProfilesIntegrationTest : IssuingCommon, IAsyncLifetime + { + private ControlProfileResponse _controlProfile; + + public async Task InitializeAsync() + { + ControlProfileRequest request = new ControlProfileRequest { Name = "Test Control Profile" }; + _controlProfile = await Api.IssuingClient().CreateControlProfile(request); + } + + public Task DisposeAsync() + { + return Task.CompletedTask; + } + + [Fact(Skip = "Prevent limit exceeded")] + private Task ShouldCreateControlProfile() + { + _controlProfile.ShouldNotBeNull(); + _controlProfile.Id.ShouldNotBeNull(); + _controlProfile.Name.ShouldBe("Test Control Profile"); + _controlProfile.CreatedDate.ShouldNotBeNull(); + _controlProfile.LastModifiedDate.ShouldNotBeNull(); + _controlProfile.Links.ShouldNotBeNull(); + return Task.CompletedTask; + } + + [Fact(Skip = "Use on demand")] + private async Task ShouldGetAllControlProfiles() + { + ControlProfilesResponse response = await Api.IssuingClient().GetAllControlProfiles(_controlProfile.Id); + response.ShouldNotBeNull(); + response.ControlProfiles.ShouldNotBeNull(); + + var controlProfiles = response.ControlProfiles.ToList(); + var profile = controlProfiles.Find(profile => profile.Id == _controlProfile.Id); + profile.Name.ShouldBe("Test Control Profile"); + profile.CreatedDate.Value.ToShortDateString().ShouldBe(_controlProfile.CreatedDate.Value.ToShortDateString()); + profile.LastModifiedDate.Value.ToShortDateString().ShouldBe(_controlProfile.LastModifiedDate.Value.ToShortDateString()); + } + + [Fact(Skip = "Use on demand")] + private async Task ShouldGetControlProfile() + { + ControlProfileResponse response = await Api.IssuingClient().GetControlProfileDetails(_controlProfile.Id); + response.ShouldNotBeNull(); + response.Id.ShouldNotBeNull(); + response.Name.ShouldBe("Test Control Profile"); + response.CreatedDate.Value.ToShortDateString().ShouldBe(_controlProfile.CreatedDate.Value.ToShortDateString()); + response.LastModifiedDate.Value.ToShortDateString().ShouldBe(_controlProfile.LastModifiedDate.Value.ToShortDateString()); + } + + [Fact(Skip = "Use on demand")] + private async Task ShouldUpdateControlProfile() + { + ControlProfileRequest request = new ControlProfileRequest { Name = "Updated Control Profile" }; + ControlProfileResponse response = + await Api.IssuingClient().UpdateControlProfile(_controlProfile.Id, request); + response.ShouldNotBeNull(); + response.LastModifiedDate.Value.ToUniversalTime().ShouldBeGreaterThan(_controlProfile.LastModifiedDate.Value.ToUniversalTime()); + + ControlProfileResponse controlProfile = await Api.IssuingClient().GetControlProfileDetails(_controlProfile.Id); + controlProfile.ShouldNotBeNull(); + controlProfile.Id.ShouldNotBeNull(); + controlProfile.Name.ShouldBe("Updated Control Profile"); + controlProfile.CreatedDate.Value.ToShortDateString().ShouldBe(_controlProfile.CreatedDate.Value.ToShortDateString()); + controlProfile.LastModifiedDate.Value.ToShortDateString().ShouldBe(response.LastModifiedDate.Value.ToShortDateString()); + } + + [Fact(Skip = "Avoid creating cards all the time")] + private async Task ShouldAddTargetToControlProfile() + { + CardholderResponse cardholderResponse = await CreateCardholder(); + CardRequest cardRequest = await CreateVirtualCard(cardholderResponse.Id); + var card = await Api.IssuingClient().CreateCard(cardRequest); + + await Api.IssuingClient().ActivateCard(card.Id); + Resource response = + await Api.IssuingClient().AddTargetToControlProfile(_controlProfile.Id, card.Id); + response.ShouldNotBeNull(); + } + + [Fact(Skip = "unavailable")] + private async Task ShouldRemoveTargetFromControlProfile() + { + Resource response = + await Api.IssuingClient().RemoveTargetFromControlProfile(_controlProfile.Id, "card_1"); + response.ShouldNotBeNull(); + } + + [Fact(Skip = "Use on demand")] + private async Task ShouldRemoveControlProfile() + { + VoidResponse response = await Api.IssuingClient().RemoveControlProfile(_controlProfile.Id); + response.ShouldNotBeNull(); + } + } +} \ No newline at end of file