1+ using Checkout . HandlePaymentsAndPayouts . Payments . POSTPaymentsIdReversals . Requests . ReverseAPaymentRequest ;
2+ using Checkout . HandlePaymentsAndPayouts . Payments . POSTPaymentsIdReversals . Responses . ReverseAPaymentResponse ;
3+ using Checkout . Payments ;
4+ using Moq ;
5+ using Shouldly ;
6+ using System . Threading ;
7+ using System . Threading . Tasks ;
8+ using Xunit ;
9+
10+ namespace Checkout . HandlePaymentsAndPayouts . Payments . POSTPaymentsIdReversals
11+ {
12+ public class HandleReversalsClientTest : UnitTestFixture
13+ {
14+ private const string TestPaymentId = "pay_test_12345678901234567890123456" ;
15+ private const string TestActionId = "act_test_12345678901234567890123456" ;
16+ private const string TestReference = "test-reversal-reference" ;
17+ private const string TestIdempotencyKey = "test-idempotency-key" ;
18+ private const string ReversalsPath = "payments/{0}/reversals" ;
19+
20+ private readonly SdkAuthorization _authorization =
21+ new SdkAuthorization ( PlatformType . DefaultOAuth , ValidDefaultSk ) ;
22+
23+ private readonly Mock < IApiClient > _apiClient = new Mock < IApiClient > ( ) ;
24+ private readonly Mock < SdkCredentials > _sdkCredentials = new Mock < SdkCredentials > ( PlatformType . DefaultOAuth ) ;
25+ private readonly Mock < IHttpClientFactory > _httpClientFactory = new Mock < IHttpClientFactory > ( ) ;
26+ private readonly Mock < CheckoutConfiguration > _configuration ;
27+ private readonly PaymentsClient _client ;
28+
29+ public HandleReversalsClientTest ( )
30+ {
31+ _sdkCredentials . Setup ( credentials => credentials . GetSdkAuthorization ( SdkAuthorizationType . SecretKeyOrOAuth ) )
32+ . Returns ( _authorization ) ;
33+
34+ _configuration = new Mock < CheckoutConfiguration > ( _sdkCredentials . Object , Environment . Sandbox ,
35+ _httpClientFactory . Object ) ;
36+
37+ _client = new PaymentsClient ( _apiClient . Object , _configuration . Object ) ;
38+ }
39+
40+ [ Fact ]
41+ public async Task ShouldReversePayment_WhenSuccessful ( )
42+ {
43+ // Arrange
44+ var request = CreateReverseAPaymentRequest ( ) ;
45+ var response = CreateReverseAPaymentResponse ( ) ;
46+
47+ SetupApiClientMock ( request , response ) ;
48+
49+ // Act
50+ var result = await _client . ReverseAPayment ( TestPaymentId , request ) ;
51+
52+ // Assert
53+ AssertSuccessfulResponse ( result , TestActionId , TestReference ) ;
54+ }
55+
56+ [ Fact ]
57+ public async Task ShouldReversePaymentWithNullRequest_WhenSuccessful ( )
58+ {
59+ // Arrange
60+ var response = CreateReverseAPaymentResponse ( includeReference : false ) ;
61+
62+ SetupApiClientMockForNullRequest ( response ) ;
63+
64+ // Act
65+ var result = await _client . ReverseAPayment ( TestPaymentId ) ;
66+
67+ // Assert
68+ AssertSuccessfulResponse ( result , TestActionId ) ;
69+ }
70+
71+ [ Fact ]
72+ public async Task ShouldThrowCheckoutArgumentException_WhenPaymentIdIsNull ( )
73+ {
74+ // Arrange
75+ var request = CreateReverseAPaymentRequest ( ) ;
76+
77+ // Act & Assert
78+ await Should . ThrowAsync < CheckoutArgumentException > ( async ( ) =>
79+ await _client . ReverseAPayment ( null , request ) ) ;
80+ }
81+
82+ [ Fact ]
83+ public async Task ShouldThrowCheckoutArgumentException_WhenPaymentIdIsEmpty ( )
84+ {
85+ // Arrange
86+ var request = CreateReverseAPaymentRequest ( ) ;
87+
88+ // Act & Assert
89+ await Should . ThrowAsync < CheckoutArgumentException > ( async ( ) =>
90+ await _client . ReverseAPayment ( string . Empty , request ) ) ;
91+ }
92+
93+ [ Fact ]
94+ public async Task ShouldReversePayment_WithIdempotencyKey_WhenSuccessful ( )
95+ {
96+ // Arrange
97+ var request = CreateReverseAPaymentRequest ( ) ;
98+ var response = CreateReverseAPaymentResponse ( ) ;
99+
100+ SetupApiClientMock ( request , response , TestIdempotencyKey ) ;
101+
102+ // Act
103+ var result = await _client . ReverseAPayment ( TestPaymentId , request , TestIdempotencyKey ) ;
104+
105+ // Assert
106+ AssertSuccessfulResponse ( result , TestActionId , TestReference ) ;
107+ }
108+
109+ [ Fact ]
110+ public async Task ShouldReversePayment_WithIdempotencyKeyAndNullRequest_WhenSuccessful ( )
111+ {
112+ // Arrange
113+ var response = CreateReverseAPaymentResponse ( includeReference : false ) ;
114+
115+ SetupApiClientMockForNullRequest ( response , TestIdempotencyKey ) ;
116+
117+ // Act
118+ var result = await _client . ReverseAPayment ( TestPaymentId , null , TestIdempotencyKey ) ;
119+
120+ // Assert
121+ AssertSuccessfulResponse ( result , TestActionId ) ;
122+ }
123+
124+ [ Fact ]
125+ public async Task ShouldReversePayment_WithCustomCancellationToken_WhenSuccessful ( )
126+ {
127+ // Arrange
128+ var request = CreateReverseAPaymentRequest ( ) ;
129+ var cancellationToken = new CancellationToken ( ) ;
130+ var response = CreateReverseAPaymentResponse ( ) ;
131+
132+ SetupApiClientMock ( request , response , cancellationToken : cancellationToken ) ;
133+
134+ // Act
135+ var result = await _client . ReverseAPayment ( TestPaymentId , request , null , cancellationToken ) ;
136+
137+ // Assert
138+ AssertSuccessfulResponse ( result , TestActionId , TestReference ) ;
139+ }
140+
141+ [ Fact ]
142+ public async Task ShouldReturnSameResult_WhenSameIdempotencyKeyUsedTwice ( )
143+ {
144+ // Arrange
145+ var request = CreateReverseAPaymentRequest ( ) ;
146+ var idempotencyKey = "test-idempotency-key-123" ;
147+ var expectedResponse = CreateReverseAPaymentResponse ( ) ;
148+
149+ SetupApiClientMock ( request , expectedResponse , idempotencyKey ) ;
150+
151+ // Act - First call
152+ var firstResult = await _client . ReverseAPayment ( TestPaymentId , request , idempotencyKey ) ;
153+
154+ // Act - Second call with same idempotency key
155+ var secondResult = await _client . ReverseAPayment ( TestPaymentId , request , idempotencyKey ) ;
156+
157+ // Assert - Both calls should return the same result
158+ AssertIdempotentResults ( firstResult , secondResult ) ;
159+
160+ // Verify the API was called twice with the same parameters
161+ VerifyApiClientCalledTwice ( request , idempotencyKey ) ;
162+ }
163+
164+ private ReverseAPaymentRequest CreateReverseAPaymentRequest ( )
165+ {
166+ return new ReverseAPaymentRequest
167+ {
168+ Reference = TestReference ,
169+ Metadata = new { OrderId = "order_123" , CustomField = "test_value" }
170+ } ;
171+ }
172+
173+ private ReverseAPaymentResponse CreateReverseAPaymentResponse ( bool includeReference = true )
174+ {
175+ var response = new ReverseAPaymentResponse
176+ {
177+ ActionId = TestActionId
178+ } ;
179+
180+ if ( includeReference )
181+ {
182+ response . Reference = TestReference ;
183+ }
184+
185+ return response ;
186+ }
187+
188+ private void SetupApiClientMock (
189+ ReverseAPaymentRequest request ,
190+ ReverseAPaymentResponse response ,
191+ string idempotencyKey = null ,
192+ CancellationToken cancellationToken = default )
193+ {
194+ _apiClient . Setup ( apiClient =>
195+ apiClient . Post < ReverseAPaymentResponse > (
196+ string . Format ( ReversalsPath , TestPaymentId ) ,
197+ _authorization ,
198+ request ,
199+ cancellationToken == default ? CancellationToken . None : cancellationToken ,
200+ idempotencyKey ) )
201+ . ReturnsAsync ( response ) ;
202+ }
203+
204+ private void SetupApiClientMockForNullRequest (
205+ ReverseAPaymentResponse response ,
206+ string idempotencyKey = null )
207+ {
208+ _apiClient . Setup ( apiClient =>
209+ apiClient . Post < ReverseAPaymentResponse > (
210+ string . Format ( ReversalsPath , TestPaymentId ) ,
211+ _authorization ,
212+ It . IsAny < ReverseAPaymentRequest > ( ) ,
213+ CancellationToken . None ,
214+ idempotencyKey ) )
215+ . ReturnsAsync ( response ) ;
216+ }
217+
218+ private static void AssertSuccessfulResponse ( ReverseAPaymentResponse result , string expectedActionId , string expectedReference = null )
219+ {
220+ result . ShouldNotBeNull ( ) ;
221+ result . ActionId . ShouldBe ( expectedActionId ) ;
222+
223+ if ( expectedReference != null )
224+ {
225+ result . Reference . ShouldBe ( expectedReference ) ;
226+ }
227+ }
228+
229+ private static void AssertIdempotentResults ( ReverseAPaymentResponse firstResult , ReverseAPaymentResponse secondResult )
230+ {
231+ firstResult . ShouldNotBeNull ( ) ;
232+ secondResult . ShouldNotBeNull ( ) ;
233+ firstResult . ActionId . ShouldBe ( secondResult . ActionId ) ;
234+ firstResult . Reference . ShouldBe ( secondResult . Reference ) ;
235+ firstResult . ActionId . ShouldBe ( TestActionId ) ;
236+ firstResult . Reference . ShouldBe ( TestReference ) ;
237+ }
238+
239+ private void VerifyApiClientCalledTwice ( ReverseAPaymentRequest request , string idempotencyKey )
240+ {
241+ _apiClient . Verify ( apiClient =>
242+ apiClient . Post < ReverseAPaymentResponse > (
243+ string . Format ( ReversalsPath , TestPaymentId ) ,
244+ _authorization ,
245+ request ,
246+ CancellationToken . None ,
247+ idempotencyKey ) , Times . Exactly ( 2 ) ) ;
248+ }
249+ }
250+ }
0 commit comments