From 9c4b3b759e0147175e04f07e7a3ce3274fe46e40 Mon Sep 17 00:00:00 2001 From: Ahmad Salim <54475330+SlimAhmad@users.noreply.github.com> Date: Thu, 17 Sep 2026 00:16:23 +0100 Subject: [PATCH] Add resend transaction webhook endpoint FlutterWave's docs (https://developer.flutterwave.com/reference/resend-transaction-webhook) expose POST /v3/transactions/{id}/resend-hook to resend webhooks from failed sending queues, but the SDK had no coverage for it. Add it end to end following the existing Transactions patterns: - Broker: PostResendTransactionWebhookAsync(transactionId, wait) - Service: validation, exception mapping, and response conversion - Client: wraps the service call and maps exceptions to client types - Models: ResendTransactionWebhook/-Response (and the external DTO) - Unit tests: logic, validation, and exception-mapping coverage Co-Authored-By: Claude Sonnet 5 --- ...Exceptions.PostResendTransactionWebhook.cs | 301 ++++++++++++++++++ ...ests.Logic.PostResendTransactionWebhook.cs | 57 ++++ ...alidations.PostResendTransactionWebhook.cs | 47 +++ .../Transactions/TransactionServiceTests.cs | 14 + .../FlutterWaveBroker.Transactions.cs | 7 + .../IFlutterWaveBroker.Transactions.cs | 3 + .../Transactions/ITransactionsClient.cs | 3 + .../Transactions/TransactionsClient.cs | 45 +++ ...xternalResendTransactionWebhookResponse.cs | 16 + .../Transactions/ResendTransactionWebhook.cs | 7 + .../ResendTransactionWebhookResponse.cs | 9 + .../Transactions/ITransactionsService.cs | 3 + .../TransactionsService.Exceptions.cs | 75 +++++ .../TransactionsService.Validations.cs | 5 + .../Transactions/TransactionsService.cs | 23 ++ 15 files changed, 615 insertions(+) create mode 100644 FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Exceptions.PostResendTransactionWebhook.cs create mode 100644 FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Logic.PostResendTransactionWebhook.cs create mode 100644 FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Validations.PostResendTransactionWebhook.cs create mode 100644 FlutterWave.Core/Models/Services/Foundations/ExternalFlutterWave/ExternalTransactions/ExternalResendTransactionWebhookResponse.cs create mode 100644 FlutterWave.Core/Models/Services/Foundations/FlutterWave/Transactions/ResendTransactionWebhook.cs create mode 100644 FlutterWave.Core/Models/Services/Foundations/FlutterWave/Transactions/ResendTransactionWebhookResponse.cs diff --git a/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Exceptions.PostResendTransactionWebhook.cs b/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Exceptions.PostResendTransactionWebhook.cs new file mode 100644 index 0000000..908679c --- /dev/null +++ b/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Exceptions.PostResendTransactionWebhook.cs @@ -0,0 +1,301 @@ +using FluentAssertions; +using FlutterWave.Core.Models.Services.Foundations.FlutterWave.Transactions; +using Moq; +using RESTFulSense.Exceptions; + +namespace FlutterWave.Core.Tests.Unit.Services.Foundations.Transactions +{ + public partial class TransactionServiceTests + { + [Fact] + public async Task ShouldThrowDependencyExceptionOnPostResendTransactionWebhookIfUrlNotFoundAsync() + { + // given + int someTransactionId = GetRandomNumber(); + int someWait = GetRandomNumber(); + + var httpResponseUrlNotFoundException = + new HttpResponseUrlNotFoundException(); + + var invalidConfigurationTransactionsException = + new InvalidConfigurationTransactionsException( + httpResponseUrlNotFoundException); + + var expectedTransactionsDependencyException = + new TransactionsDependencyException( + invalidConfigurationTransactionsException); + + this.flutterWaveBrokerMock.Setup(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait)) + .ThrowsAsync(httpResponseUrlNotFoundException); + + // when + ValueTask resendTransactionWebhookTask = + this.transactionsService.PostResendTransactionWebhookAsync(someTransactionId, someWait); + + TransactionsDependencyException + actualTransactionsDependencyException = + await Assert.ThrowsAsync( + resendTransactionWebhookTask.AsTask); + + // then + actualTransactionsDependencyException.Should().BeEquivalentTo( + expectedTransactionsDependencyException); + + this.flutterWaveBrokerMock.Verify(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait), + Times.Once); + + this.flutterWaveBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + + [Theory] + [MemberData(nameof(UnauthorizedExceptions))] + public async Task ShouldThrowDependencyExceptionOnPostResendTransactionWebhookIfUnauthorizedAsync( + HttpResponseException unauthorizedException) + { + // given + int someTransactionId = GetRandomNumber(); + int someWait = GetRandomNumber(); + + var unauthorizedTransactionsException = + new UnauthorizedTransactionsException(unauthorizedException); + + var expectedTransactionsDependencyException = + new TransactionsDependencyException(unauthorizedTransactionsException); + + this.flutterWaveBrokerMock.Setup(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait)) + .ThrowsAsync(unauthorizedException); + + // when + ValueTask resendTransactionWebhookTask = + this.transactionsService.PostResendTransactionWebhookAsync(someTransactionId, someWait); + + TransactionsDependencyException + actualTransactionsDependencyException = + await Assert.ThrowsAsync( + resendTransactionWebhookTask.AsTask); + + // then + actualTransactionsDependencyException.Should().BeEquivalentTo( + expectedTransactionsDependencyException); + + this.flutterWaveBrokerMock.Verify(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait), + Times.Once); + + this.flutterWaveBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldThrowDependencyValidationExceptionOnPostResendTransactionWebhookIfNotFoundOccurredAsync() + { + // given + int someTransactionId = GetRandomNumber(); + int someWait = GetRandomNumber(); + + var httpResponseNotFoundException = + new HttpResponseNotFoundException(); + + var notFoundTransactionsException = + new NotFoundTransactionsException( + httpResponseNotFoundException); + + var expectedTransactionsDependencyValidationException = + new TransactionsDependencyValidationException( + notFoundTransactionsException); + + this.flutterWaveBrokerMock.Setup(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait)) + .ThrowsAsync(httpResponseNotFoundException); + + // when + ValueTask resendTransactionWebhookTask = + this.transactionsService.PostResendTransactionWebhookAsync(someTransactionId, someWait); + + TransactionsDependencyValidationException + actualTransactionsDependencyValidationException = + await Assert.ThrowsAsync( + resendTransactionWebhookTask.AsTask); + + // then + actualTransactionsDependencyValidationException.Should().BeEquivalentTo( + expectedTransactionsDependencyValidationException); + + this.flutterWaveBrokerMock.Verify(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait), + Times.Once); + + this.flutterWaveBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldThrowDependencyValidationExceptionOnPostResendTransactionWebhookIfBadRequestOccurredAsync() + { + // given + int someTransactionId = GetRandomNumber(); + int someWait = GetRandomNumber(); + + var httpResponseBadRequestException = + new HttpResponseBadRequestException(); + + var invalidTransactionsException = + new InvalidTransactionsException( + httpResponseBadRequestException); + + var expectedTransactionsDependencyValidationException = + new TransactionsDependencyValidationException( + invalidTransactionsException); + + this.flutterWaveBrokerMock.Setup(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait)) + .ThrowsAsync(httpResponseBadRequestException); + + // when + ValueTask resendTransactionWebhookTask = + this.transactionsService.PostResendTransactionWebhookAsync(someTransactionId, someWait); + + TransactionsDependencyValidationException + actualTransactionsDependencyValidationException = + await Assert.ThrowsAsync( + resendTransactionWebhookTask.AsTask); + + // then + actualTransactionsDependencyValidationException.Should().BeEquivalentTo( + expectedTransactionsDependencyValidationException); + + this.flutterWaveBrokerMock.Verify(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait), + Times.Once); + + this.flutterWaveBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldThrowDependencyValidationExceptionOnPostResendTransactionWebhookIfTooManyRequestsOccurredAsync() + { + // given + int someTransactionId = GetRandomNumber(); + int someWait = GetRandomNumber(); + + var httpResponseTooManyRequestsException = + new HttpResponseTooManyRequestsException(); + + var excessiveCallTransactionsException = + new ExcessiveCallTransactionsException( + httpResponseTooManyRequestsException); + + var expectedTransactionsDependencyValidationException = + new TransactionsDependencyValidationException( + excessiveCallTransactionsException); + + this.flutterWaveBrokerMock.Setup(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait)) + .ThrowsAsync(httpResponseTooManyRequestsException); + + // when + ValueTask resendTransactionWebhookTask = + this.transactionsService.PostResendTransactionWebhookAsync(someTransactionId, someWait); + + TransactionsDependencyValidationException actualTransactionsDependencyValidationException = + await Assert.ThrowsAsync( + resendTransactionWebhookTask.AsTask); + + // then + actualTransactionsDependencyValidationException.Should().BeEquivalentTo( + expectedTransactionsDependencyValidationException); + + this.flutterWaveBrokerMock.Verify(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait), + Times.Once); + + this.flutterWaveBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldThrowDependencyExceptionOnPostResendTransactionWebhookIfHttpResponseErrorOccurredAsync() + { + // given + int someTransactionId = GetRandomNumber(); + int someWait = GetRandomNumber(); + + var httpResponseException = + new HttpResponseException(); + + var failedServerTransactionsException = + new FailedServerTransactionsException( + httpResponseException); + + var expectedTransactionsDependencyException = + new TransactionsDependencyException( + failedServerTransactionsException); + + this.flutterWaveBrokerMock.Setup(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait)) + .ThrowsAsync(httpResponseException); + + // when + ValueTask resendTransactionWebhookTask = + this.transactionsService.PostResendTransactionWebhookAsync(someTransactionId, someWait); + + TransactionsDependencyException actualTransactionsDependencyException = + await Assert.ThrowsAsync( + resendTransactionWebhookTask.AsTask); + + // then + actualTransactionsDependencyException.Should().BeEquivalentTo( + expectedTransactionsDependencyException); + + this.flutterWaveBrokerMock.Verify(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait), + Times.Once); + + this.flutterWaveBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task ShouldThrowServiceExceptionOnPostResendTransactionWebhookIfServiceErrorOccurredAsync() + { + // given + int someTransactionId = GetRandomNumber(); + int someWait = GetRandomNumber(); + var serviceException = new Exception(); + + var failedTransactionsServiceException = + new FailedTransactionsServiceException(serviceException); + + var expectedTransactionsServiceException = + new TransactionsServiceException(failedTransactionsServiceException); + + this.flutterWaveBrokerMock.Setup(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait)) + .ThrowsAsync(serviceException); + + // when + ValueTask resendTransactionWebhookTask = + this.transactionsService.PostResendTransactionWebhookAsync(someTransactionId, someWait); + + TransactionsServiceException actualTransactionsServiceException = + await Assert.ThrowsAsync( + resendTransactionWebhookTask.AsTask); + + // then + actualTransactionsServiceException.Should().BeEquivalentTo( + expectedTransactionsServiceException); + + this.flutterWaveBrokerMock.Verify(broker => + broker.PostResendTransactionWebhookAsync(someTransactionId, someWait), + Times.Once); + + this.flutterWaveBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + } +} diff --git a/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Logic.PostResendTransactionWebhook.cs b/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Logic.PostResendTransactionWebhook.cs new file mode 100644 index 0000000..742cc7b --- /dev/null +++ b/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Logic.PostResendTransactionWebhook.cs @@ -0,0 +1,57 @@ +using FluentAssertions; +using FlutterWave.Core.Models.Services.Foundations.ExternalFlutterWave.ExternalTransactions; +using FlutterWave.Core.Models.Services.Foundations.FlutterWave.Transactions; +using Moq; + +namespace FlutterWave.Core.Tests.Unit.Services.Foundations.Transactions +{ + public partial class TransactionServiceTests + { + [Fact] + public async Task ShouldResendTransactionWebhookWithTransactionIdAsync() + { + // given + int inputTransactionId = GetRandomNumber(); + int inputWait = GetRandomNumber(); + + dynamic resendTransactionWebhookRandomProperties = + CreateRandomResendTransactionWebhookProperties(); + + var externalResendTransactionWebhookResponse = new ExternalResendTransactionWebhookResponse + { + Status = resendTransactionWebhookRandomProperties.Status, + Message = resendTransactionWebhookRandomProperties.Message, + Data = resendTransactionWebhookRandomProperties.Data, + }; + + var expectedResendTransactionWebhookResponse = new ResendTransactionWebhookResponse + { + Status = resendTransactionWebhookRandomProperties.Status, + Message = resendTransactionWebhookRandomProperties.Message, + Data = resendTransactionWebhookRandomProperties.Data, + }; + + var expectedResendTransactionWebhook = new ResendTransactionWebhook + { + Response = expectedResendTransactionWebhookResponse + }; + + this.flutterWaveBrokerMock.Setup(broker => + broker.PostResendTransactionWebhookAsync(inputTransactionId, inputWait)) + .ReturnsAsync(externalResendTransactionWebhookResponse); + + // when + ResendTransactionWebhook actualResendTransactionWebhook = + await this.transactionsService.PostResendTransactionWebhookAsync(inputTransactionId, inputWait); + + // then + actualResendTransactionWebhook.Should().BeEquivalentTo(expectedResendTransactionWebhook); + + this.flutterWaveBrokerMock.Verify(broker => + broker.PostResendTransactionWebhookAsync(inputTransactionId, inputWait), + Times.Once); + + this.flutterWaveBrokerMock.VerifyNoOtherCalls(); + } + } +} diff --git a/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Validations.PostResendTransactionWebhook.cs b/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Validations.PostResendTransactionWebhook.cs new file mode 100644 index 0000000..555a3b9 --- /dev/null +++ b/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.Validations.PostResendTransactionWebhook.cs @@ -0,0 +1,47 @@ +using FluentAssertions; +using FlutterWave.Core.Models.Services.Foundations.FlutterWave.Transactions; +using Moq; + +namespace FlutterWave.Core.Tests.Unit.Services.Foundations.Transactions +{ + public partial class TransactionServiceTests + { + [Theory] + [InlineData(0, 1)] + [InlineData(1, 0)] + public async Task ShouldThrowValidationExceptionOnPostResendTransactionWebhookIfIdOrWaitIsInvalidAsync( + int invalidTransactionId, int invalidWait) + { + // given + var invalidTransactionsException = + new InvalidTransactionsException(); + + invalidTransactionsException.AddData( + key: nameof(ResendTransactionWebhook), + values: "A valid number is required"); + + var expectedTransactionsValidationException = + new TransactionsValidationException(invalidTransactionsException); + + // when + ValueTask resendTransactionWebhookTask = + this.transactionsService.PostResendTransactionWebhookAsync(invalidTransactionId, invalidWait); + + TransactionsValidationException actualTransactionsValidationException = + await Assert.ThrowsAsync( + resendTransactionWebhookTask.AsTask); + + // then + actualTransactionsValidationException.Should() + .BeEquivalentTo(expectedTransactionsValidationException); + + this.flutterWaveBrokerMock.Verify(broker => + broker.PostResendTransactionWebhookAsync( + It.IsAny(), It.IsAny()), + Times.Never); + + this.flutterWaveBrokerMock.VerifyNoOtherCalls(); + this.dateTimeBrokerMock.VerifyNoOtherCalls(); + } + } +} diff --git a/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.cs b/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.cs index eebabb3..046790f 100644 --- a/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.cs +++ b/FlutterWave.Core.Tests.Unit/Services/Foundations/Transactions/TransactionServiceTests.cs @@ -400,6 +400,20 @@ private static List CreateRandomTransactionTimelineData() #endregion + #region ResendTransactionWebhookProperties + + private static dynamic CreateRandomResendTransactionWebhookProperties() + { + return new + { + Status = GetRandomString(), + Message = GetRandomString(), + Data = new object(), + }; + } + + #endregion + private static Filler CreateMultipleTransactionFiller() { var filler = new Filler(); diff --git a/FlutterWave.Core/Brokers/FlutterWave/FlutterWaveBroker.Transactions.cs b/FlutterWave.Core/Brokers/FlutterWave/FlutterWaveBroker.Transactions.cs index be4816f..f8fca0f 100644 --- a/FlutterWave.Core/Brokers/FlutterWave/FlutterWaveBroker.Transactions.cs +++ b/FlutterWave.Core/Brokers/FlutterWave/FlutterWaveBroker.Transactions.cs @@ -58,5 +58,12 @@ public async ValueTask PostVerifyTransactionA return await GetAsync( relativeUrl: $"v3/transactions/{transactionReference}/verify"); } + + public async ValueTask PostResendTransactionWebhookAsync( + int transactionId, int wait) + { + return await PostAsync( + relativeUrl: $"v3/transactions/{transactionId}/resend-hook?wait={wait}", content: null); + } } } diff --git a/FlutterWave.Core/Brokers/FlutterWave/IFlutterWaveBroker.Transactions.cs b/FlutterWave.Core/Brokers/FlutterWave/IFlutterWaveBroker.Transactions.cs index 54a6a9a..a76a646 100644 --- a/FlutterWave.Core/Brokers/FlutterWave/IFlutterWaveBroker.Transactions.cs +++ b/FlutterWave.Core/Brokers/FlutterWave/IFlutterWaveBroker.Transactions.cs @@ -26,6 +26,9 @@ ValueTask PostVerifyTransactionAsync( ValueTask PostVerifyTransactionAsync( string transactionReference); + + ValueTask PostResendTransactionWebhookAsync( + int transactionId, int wait); } diff --git a/FlutterWave.Core/Clients/Transactions/ITransactionsClient.cs b/FlutterWave.Core/Clients/Transactions/ITransactionsClient.cs index f75cc36..65932fe 100644 --- a/FlutterWave.Core/Clients/Transactions/ITransactionsClient.cs +++ b/FlutterWave.Core/Clients/Transactions/ITransactionsClient.cs @@ -29,5 +29,8 @@ ValueTask VerifyTransactionAsync( ValueTask VerifyTransactionAsync( string transactionReference); + + ValueTask ResendTransactionWebhookAsync( + int transactionId, int wait); } } diff --git a/FlutterWave.Core/Clients/Transactions/TransactionsClient.cs b/FlutterWave.Core/Clients/Transactions/TransactionsClient.cs index 60a7319..ab4904b 100644 --- a/FlutterWave.Core/Clients/Transactions/TransactionsClient.cs +++ b/FlutterWave.Core/Clients/Transactions/TransactionsClient.cs @@ -374,5 +374,50 @@ public async ValueTask VerifyTransactionAsync(string transact TransactionServiceException.InnerException as Xeption); } } + + public async ValueTask ResendTransactionWebhookAsync(int transactionId, int wait) + { + try + { + return await transactionsService.PostResendTransactionWebhookAsync(transactionId, wait); + } + catch (TransactionsValidationException TransactionValidationException) + { + throw new TransactionsClientValidationException( + TransactionValidationException.InnerException as Xeption); + } + catch (TransactionsDependencyValidationException TransactionDependencyValidationException) + { + + var message = TransactionDependencyValidationException.InnerException.InnerException.Message; + if (message != null) + { + ExternalErrorResponse bankBranches = + JsonSerializer.Deserialize(message); + + return new ResendTransactionWebhook + { + Response = new ResendTransactionWebhookResponse + { + Message = bankBranches.Message, + Status = bankBranches.Status, + } + }; + } + + throw new TransactionsClientValidationException( + TransactionDependencyValidationException.InnerException as Xeption); + } + catch (TransactionsDependencyException TransactionDependencyException) + { + throw new TransactionsClientDependencyException( + TransactionDependencyException.InnerException as Xeption); + } + catch (TransactionsServiceException TransactionServiceException) + { + throw new TransactionsClientServiceException( + TransactionServiceException.InnerException as Xeption); + } + } } } diff --git a/FlutterWave.Core/Models/Services/Foundations/ExternalFlutterWave/ExternalTransactions/ExternalResendTransactionWebhookResponse.cs b/FlutterWave.Core/Models/Services/Foundations/ExternalFlutterWave/ExternalTransactions/ExternalResendTransactionWebhookResponse.cs new file mode 100644 index 0000000..45bae81 --- /dev/null +++ b/FlutterWave.Core/Models/Services/Foundations/ExternalFlutterWave/ExternalTransactions/ExternalResendTransactionWebhookResponse.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace FlutterWave.Core.Models.Services.Foundations.ExternalFlutterWave.ExternalTransactions +{ + internal class ExternalResendTransactionWebhookResponse + { + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("message")] + public string Message { get; set; } + + [JsonProperty("data")] + public object Data { get; set; } + } +} diff --git a/FlutterWave.Core/Models/Services/Foundations/FlutterWave/Transactions/ResendTransactionWebhook.cs b/FlutterWave.Core/Models/Services/Foundations/FlutterWave/Transactions/ResendTransactionWebhook.cs new file mode 100644 index 0000000..bbdea4a --- /dev/null +++ b/FlutterWave.Core/Models/Services/Foundations/FlutterWave/Transactions/ResendTransactionWebhook.cs @@ -0,0 +1,7 @@ +namespace FlutterWave.Core.Models.Services.Foundations.FlutterWave.Transactions +{ + public class ResendTransactionWebhook + { + public ResendTransactionWebhookResponse Response { get; set; } + } +} diff --git a/FlutterWave.Core/Models/Services/Foundations/FlutterWave/Transactions/ResendTransactionWebhookResponse.cs b/FlutterWave.Core/Models/Services/Foundations/FlutterWave/Transactions/ResendTransactionWebhookResponse.cs new file mode 100644 index 0000000..de14851 --- /dev/null +++ b/FlutterWave.Core/Models/Services/Foundations/FlutterWave/Transactions/ResendTransactionWebhookResponse.cs @@ -0,0 +1,9 @@ +namespace FlutterWave.Core.Models.Services.Foundations.FlutterWave.Transactions +{ + public class ResendTransactionWebhookResponse + { + public string Status { get; set; } + public string Message { get; set; } + public object Data { get; set; } + } +} diff --git a/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/ITransactionsService.cs b/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/ITransactionsService.cs index a9f7329..38db040 100644 --- a/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/ITransactionsService.cs +++ b/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/ITransactionsService.cs @@ -26,5 +26,8 @@ ValueTask PostVerifyTransactionAsync( ValueTask PostVerifyTransactionAsync( string transactionReference); + + ValueTask PostResendTransactionWebhookAsync( + int transactionId, int wait); } } diff --git a/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.Exceptions.cs b/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.Exceptions.cs index e5fda83..6773158 100644 --- a/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.Exceptions.cs +++ b/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.Exceptions.cs @@ -21,6 +21,8 @@ internal partial class TransactionsService private delegate ValueTask ReturningRefundDetailsFunction(); + private delegate ValueTask ReturningResendTransactionWebhookFunction(); + private async ValueTask TryCatch( ReturningMultipleRefundTransactionsFunction returningMultipleRefundTransactionsFunction) @@ -527,6 +529,79 @@ private async ValueTask TryCatch( } } + private async ValueTask TryCatch( + ReturningResendTransactionWebhookFunction returningResendTransactionWebhookFunction) + { + try + { + return await returningResendTransactionWebhookFunction(); + } + catch (NullTransactionsException nullTransactionsException) + { + throw new TransactionsValidationException(nullTransactionsException); + } + catch (InvalidTransactionsException invalidTransactionsException) + { + throw new TransactionsValidationException(invalidTransactionsException); + } + catch (HttpResponseUrlNotFoundException httpResponseUrlNotFoundException) + { + var invalidConfigurationTransactionsException = + new InvalidConfigurationTransactionsException(httpResponseUrlNotFoundException); + + throw new TransactionsDependencyException(invalidConfigurationTransactionsException); + } + catch (HttpResponseUnauthorizedException httpResponseUnauthorizedException) + { + var unauthorizedTransactionsException = + new UnauthorizedTransactionsException(httpResponseUnauthorizedException); + + throw new TransactionsDependencyException(unauthorizedTransactionsException); + } + catch (HttpResponseForbiddenException httpResponseForbiddenException) + { + var unauthorizedTransactionsException = + new UnauthorizedTransactionsException(httpResponseForbiddenException); + + throw new TransactionsDependencyException(unauthorizedTransactionsException); + } + catch (HttpResponseNotFoundException httpResponseNotFoundException) + { + var notFoundTransactionsException = + new NotFoundTransactionsException(httpResponseNotFoundException); + + throw new TransactionsDependencyValidationException(notFoundTransactionsException); + } + catch (HttpResponseBadRequestException httpResponseBadRequestException) + { + var invalidTransactionsException = + new InvalidTransactionsException(httpResponseBadRequestException); + + throw new TransactionsDependencyValidationException(invalidTransactionsException); + } + catch (HttpResponseTooManyRequestsException httpResponseTooManyRequestsException) + { + var excessiveCallTransactionsException = + new ExcessiveCallTransactionsException(httpResponseTooManyRequestsException); + + throw new TransactionsDependencyValidationException(excessiveCallTransactionsException); + } + catch (HttpResponseException httpResponseException) + { + var failedServerTransactionsException = + new FailedServerTransactionsException(httpResponseException); + + throw new TransactionsDependencyException(failedServerTransactionsException); + } + catch (Exception exception) + { + var failedTransactionsServiceException = + new FailedTransactionsServiceException(exception); + + throw new TransactionsServiceException(failedTransactionsServiceException); + } + } + } } \ No newline at end of file diff --git a/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.Validations.cs b/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.Validations.cs index 0f4bb38..9fe9458 100644 --- a/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.Validations.cs +++ b/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.Validations.cs @@ -81,6 +81,11 @@ private static void ValidateTransactionsInt(int transactionsInt) => Validate( (Rule: IsInvalid(transactionsInt), Parameter: nameof(CreateRefund))); + private static void ValidateResendTransactionWebhook(int transactionId, int wait) => + Validate( + (Rule: IsInvalid(transactionId), Parameter: nameof(ResendTransactionWebhook)), + (Rule: IsInvalid(wait), Parameter: nameof(ResendTransactionWebhook))); + private static dynamic IsInvalid(string text) => new diff --git a/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.cs b/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.cs index 447bccf..fc9328c 100644 --- a/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.cs +++ b/FlutterWave.Core/Services/Foundations/FlutterWave/Transactions/TransactionsService.cs @@ -88,6 +88,15 @@ public ValueTask PostVerifyTransactionAsync(string transactio return ConvertToVerifyTransactionResponse(externalVerifyTransactionResponse); }); + public ValueTask PostResendTransactionWebhookAsync(int transactionId, int wait) => + TryCatch(async () => + { + ValidateResendTransactionWebhook(transactionId, wait); + ExternalResendTransactionWebhookResponse externalResendTransactionWebhookResponse = + await flutterWaveBroker.PostResendTransactionWebhookAsync(transactionId, wait); + return ConvertToResendTransactionWebhookResponse(externalResendTransactionWebhookResponse); + }); + private static CreateRefund ConvertToCreateRefundResponse(ExternalCreateRefundResponse externalCreateRefundResponse) { @@ -375,5 +384,19 @@ private static VerifyTransaction ConvertToVerifyTransactionResponse(ExternalVeri } + private static ResendTransactionWebhook ConvertToResendTransactionWebhookResponse( + ExternalResendTransactionWebhookResponse externalResendTransactionWebhookResponse) + { + return new ResendTransactionWebhook + { + Response = new ResendTransactionWebhookResponse + { + Status = externalResendTransactionWebhookResponse.Status, + Message = externalResendTransactionWebhookResponse.Message, + Data = externalResendTransactionWebhookResponse.Data + } + }; + } + } }