diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 289ce7d7..bc2f4b86 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + filter: tree:0 - name: Setup .NET 9.0 uses: actions/setup-dotnet@v2 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9cce2025..b07ecd53 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,6 +23,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + filter: tree:0 - name: Setup .NET 9.0 uses: actions/setup-dotnet@v2 with: diff --git a/src/TrueLayer/Mandates/IMandatesApi.cs b/src/TrueLayer/Mandates/IMandatesApi.cs index 41d0c65a..4e8e1ab3 100644 --- a/src/TrueLayer/Mandates/IMandatesApi.cs +++ b/src/TrueLayer/Mandates/IMandatesApi.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Threading.Tasks; using OneOf; @@ -28,11 +29,15 @@ public interface IMandatesApi /// /// An idempotency key to allow safe retrying without the operation being performed multiple times. /// The value should be unique for each operation, e.g. a UUID, with the same key being sent on a retry of the same request. + /// If not provided an idempotency key is automatically generated. + /// + /// The cancellation token to cancel the operation /// - /// The cancellation token to cancel the operation /// An API response that includes details of the created mandate if successful, otherwise problem details Task> CreateMandate( - CreateMandateRequest mandateRequest, string idempotencyKey, CancellationToken cancellationToken = default); + CreateMandateRequest mandateRequest, + string? idempotencyKey = null, + CancellationToken cancellationToken = default); /// /// Gets a mandate @@ -42,7 +47,24 @@ Task> CreateMandate( /// The cancellation token to cancel the operation /// An API response that includes details of the mandate if successful, otherwise problem details Task> GetMandate( - string mandateId, MandateType mandateType, CancellationToken cancellationToken = default); + string mandateId, + MandateType mandateType, + CancellationToken cancellationToken = default); + + /// + /// Generates a link to the TrueLayer hosted payment page + /// + /// The mandate identifier + /// The resource token, returned from + /// + /// Your return URI to which the end user will be redirected after the mandate is completed. + /// Note this should be configured in the TrueLayer console under your application settings. + /// + /// The HPP link you can redirect the end user to + string CreateHostedPaymentPageLink( + string mandateId, + string resourceToken, + Uri returnUri); /// /// Lists mandates for a user @@ -52,7 +74,9 @@ Task> GetMandate( /// The cancellation token to cancel the operation /// An API response that includes details of the mandate if successful, otherwise problem details Task>> ListMandates( - ListMandatesQuery query, MandateType mandateType, CancellationToken cancellationToken = default); + ListMandatesQuery query, + MandateType mandateType, + CancellationToken cancellationToken = default); /// /// Start the authorization flow for a mandate. @@ -63,11 +87,16 @@ Task>> ListMandates( /// /// An idempotency key to allow safe retrying without the operation being performed multiple times. /// The value should be unique for each operation, e.g. a UUID, with the same key being sent on a retry of the same request. + /// If not provided an idempotency key is automatically generated. /// /// The cancellation token to cancel the operation /// An API response that includes details of the mandate if successful, otherwise problem details Task> StartAuthorizationFlow( - string mandateId, StartAuthorizationFlowRequest request, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default); + string mandateId, + StartAuthorizationFlowRequest request, + string? idempotencyKey, + MandateType mandateType, + CancellationToken cancellationToken = default); /// /// Submit the provider details selected by the PSU. @@ -78,11 +107,16 @@ Task> StartAuthorizationFlow( /// /// An idempotency key to allow safe retrying without the operation being performed multiple times. /// The value should be unique for each operation, e.g. a UUID, with the same key being sent on a retry of the same request. + /// If not provided an idempotency key is automatically generated. /// /// The cancellation token to cancel the operation /// An API response that includes details of the mandate if successful, otherwise problem details Task> SubmitProviderSelection( - string mandateId, SubmitProviderSelectionRequest request, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default); + string mandateId, + SubmitProviderSelectionRequest request, + string? idempotencyKey, + MandateType mandateType, + CancellationToken cancellationToken = default); /// /// Submit the consent given by the user @@ -95,7 +129,11 @@ Task> SubmitProviderSelection( /// /// The cancellation token to cancel the operation /// An API response that includes the authorization flow action details if successful, otherwise problem details - Task> SubmitConsent(string mandateId, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default); + Task> SubmitConsent( + string mandateId, + string? idempotencyKey, + MandateType mandateType, + CancellationToken cancellationToken = default); /// /// Get Confirmation Of Funds @@ -107,7 +145,11 @@ Task> SubmitProviderSelection( /// The cancellation token to cancel the operation /// An API response that includes details of the mandate if successful, otherwise problem details Task> GetConfirmationOfFunds( - string mandateId, int amountInMinor, string currency, MandateType mandateType, CancellationToken cancellationToken = default); + string mandateId, + int amountInMinor, + string currency, + MandateType mandateType, + CancellationToken cancellationToken = default); /// /// Gets a mandates constraints @@ -117,18 +159,26 @@ Task> GetConfirmationOfFunds( /// The cancellation token to cancel the operation /// An API response that includes details of the mandate if successful, otherwise problem details Task> GetMandateConstraints( - string mandateId, MandateType mandateType, CancellationToken cancellationToken = default); + string mandateId, + MandateType mandateType, + CancellationToken cancellationToken = default); /// /// Revoke mandate /// /// The id of the mandate + /// The type of the mandate. Either sweeping or commercial /// /// An idempotency key to allow safe retrying without the operation being performed multiple times. /// The value should be unique for each operation, e.g. a UUID, with the same key being sent on a retry of the same request. + /// If not provided an idempotency key is automatically generated. /// /// The cancellation token to cancel the operation /// An API response that includes the payment details if successful, otherwise problem details - Task RevokeMandate(string mandateId, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default); + Task RevokeMandate( + string mandateId, + string? idempotencyKey, + MandateType mandateType, + CancellationToken cancellationToken = default); } } diff --git a/src/TrueLayer/Mandates/MandatesApi.cs b/src/TrueLayer/Mandates/MandatesApi.cs index 505fe239..b79524be 100644 --- a/src/TrueLayer/Mandates/MandatesApi.cs +++ b/src/TrueLayer/Mandates/MandatesApi.cs @@ -6,6 +6,7 @@ using TrueLayer.Extensions; using TrueLayer.Mandates.Model; using TrueLayer.Models; +using TrueLayer.Payments; namespace TrueLayer.Mandates { @@ -25,12 +26,14 @@ internal class MandatesApi : IMandatesApi private readonly TrueLayerOptions _options; private readonly Uri _baseUri; private readonly IAuthApi _auth; + private readonly HppLinkBuilder _hppLinkBuilder; public MandatesApi(IApiClient apiClient, IAuthApi auth, TrueLayerOptions options) { _apiClient = apiClient.NotNull(nameof(apiClient)); _options = options.NotNull(nameof(options)); _auth = auth.NotNull(nameof(auth)); + _hppLinkBuilder = new HppLinkBuilder(options.Payments?.HppUri, options.UseSandbox ?? true); options.Payments.NotNull(nameof(options.Payments))!.Validate(); @@ -39,10 +42,13 @@ public MandatesApi(IApiClient apiClient, IAuthApi auth, TrueLayerOptions options } /// - public async Task> CreateMandate(CreateMandateRequest mandateRequest, string idempotencyKey, CancellationToken cancellationToken = default) + public async Task> CreateMandate( + CreateMandateRequest mandateRequest, + string? idempotencyKey = null, + CancellationToken cancellationToken = default) { mandateRequest.NotNull(nameof(mandateRequest)); - idempotencyKey.NotNullOrWhiteSpace(nameof(idempotencyKey)); + var mandateType = mandateRequest.Mandate.Match( vrpCommercial => vrpCommercial.Type, vrpSweeping => vrpSweeping.Type); @@ -56,7 +62,7 @@ public async Task> CreateMandate(CreateMandat return await _apiClient.PostAsync( _baseUri, mandateRequest, - idempotencyKey, + idempotencyKey ?? Guid.NewGuid().ToString(), authResponse.Data!.AccessToken, _options.Payments!.SigningKey, cancellationToken @@ -65,7 +71,10 @@ public async Task> CreateMandate(CreateMandat //TODO: is it correct that this method expects a mandate type? /// - public async Task> GetMandate(string mandateId, MandateType mandateType, CancellationToken cancellationToken = default) + public async Task> GetMandate( + string mandateId, + MandateType mandateType, + CancellationToken cancellationToken = default) { mandateId.NotNullOrWhiteSpace(nameof(mandateId)); mandateId.NotAUrl(nameof(mandateId)); @@ -85,7 +94,14 @@ public async Task> GetMandate(string mandateId, } /// - public async Task>> ListMandates(ListMandatesQuery query, MandateType mandateType, CancellationToken cancellationToken = default) + public string CreateHostedPaymentPageLink(string mandateId, string resourceToken, Uri returnUri) + => _hppLinkBuilder.Build(mandateId, resourceToken, returnUri); + + /// + public async Task>> ListMandates( + ListMandatesQuery query, + MandateType mandateType, + CancellationToken cancellationToken = default) { var authResponse = await _auth.GetAuthToken(new GetAuthTokenRequest($"recurring_payments:{mandateType.AsString()}"), cancellationToken); @@ -108,12 +124,17 @@ public async Task>> ListManda } /// - public async Task> StartAuthorizationFlow(string mandateId, StartAuthorizationFlowRequest request, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default) + public async Task> StartAuthorizationFlow( + string mandateId, + StartAuthorizationFlowRequest request, + string? idempotencyKey, + MandateType mandateType, + CancellationToken cancellationToken = default) { mandateId.NotNullOrWhiteSpace(nameof(mandateId)); mandateId.NotAUrl(nameof(mandateId)); request.NotNull(nameof(request)); - idempotencyKey.NotNullOrWhiteSpace(nameof(idempotencyKey)); + var authResponse = await _auth.GetAuthToken(new GetAuthTokenRequest($"recurring_payments:{mandateType.AsString()}"), cancellationToken); if (!authResponse.IsSuccessful) @@ -124,7 +145,7 @@ public async Task> StartAuthorizationFlo return await _apiClient.PostAsync( _baseUri.Append(mandateId).Append(MandatesEndpoints.AuthorizationFlow), request, - idempotencyKey, + idempotencyKey ?? Guid.NewGuid().ToString(), authResponse.Data!.AccessToken, _options.Payments!.SigningKey, cancellationToken @@ -132,12 +153,17 @@ public async Task> StartAuthorizationFlo } /// - public async Task> SubmitProviderSelection(string mandateId, SubmitProviderSelectionRequest request, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default) + public async Task> SubmitProviderSelection( + string mandateId, + SubmitProviderSelectionRequest request, + string? idempotencyKey, + MandateType mandateType, + CancellationToken cancellationToken = default) { mandateId.NotNullOrWhiteSpace(nameof(mandateId)); mandateId.NotAUrl(nameof(mandateId)); request.NotNull(nameof(request)); - idempotencyKey.NotNullOrWhiteSpace(nameof(idempotencyKey)); + var authResponse = await _auth.GetAuthToken(new GetAuthTokenRequest($"recurring_payments:{mandateType.AsString()}"), cancellationToken); if (!authResponse.IsSuccessful) @@ -148,18 +174,22 @@ public async Task> SubmitProviderSelecti return await _apiClient.PostAsync( _baseUri.Append(mandateId).Append(MandatesEndpoints.ProviderSelection), request, - idempotencyKey, + idempotencyKey ?? Guid.NewGuid().ToString(), authResponse.Data!.AccessToken, _options.Payments!.SigningKey, cancellationToken ); } - public async Task> SubmitConsent(string mandateId, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default) + public async Task> SubmitConsent( + string mandateId, + string? idempotencyKey, + MandateType mandateType, + CancellationToken cancellationToken = default) { mandateId.NotNullOrWhiteSpace(nameof(mandateId)); mandateId.NotAUrl(nameof(mandateId)); - idempotencyKey.NotNullOrWhiteSpace(nameof(idempotencyKey)); + var authResponse = await _auth.GetAuthToken(new GetAuthTokenRequest($"recurring_payments:{mandateType.AsString()}"), cancellationToken); if (!authResponse.IsSuccessful) @@ -170,7 +200,7 @@ public async Task> SubmitConsent(string return await _apiClient.PostAsync( _baseUri.Append(mandateId).Append(MandatesEndpoints.Consent), null, - idempotencyKey, + idempotencyKey ?? Guid.NewGuid().ToString(), authResponse.Data!.AccessToken, _options.Payments!.SigningKey, cancellationToken @@ -178,7 +208,12 @@ public async Task> SubmitConsent(string } /// - public async Task> GetConfirmationOfFunds(string mandateId, int amountInMinor, string currency, MandateType mandateType, CancellationToken cancellationToken = default) + public async Task> GetConfirmationOfFunds( + string mandateId, + int amountInMinor, + string currency, + MandateType mandateType, + CancellationToken cancellationToken = default) { mandateId.NotNullOrWhiteSpace(nameof(mandateId)); mandateId.NotAUrl(nameof(mandateId)); @@ -198,7 +233,10 @@ public async Task> GetConfirmationOf } /// - public async Task> GetMandateConstraints(string mandateId, MandateType mandateType, CancellationToken cancellationToken = default) + public async Task> GetMandateConstraints( + string mandateId, + MandateType mandateType, + CancellationToken cancellationToken = default) { mandateId.NotNullOrWhiteSpace(nameof(mandateId)); mandateId.NotAUrl(nameof(mandateId)); @@ -218,7 +256,11 @@ public async Task> GetMandateConstraints(str } /// - public async Task RevokeMandate(string mandateId, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default) + public async Task RevokeMandate( + string mandateId, + string? idempotencyKey, + MandateType mandateType, + CancellationToken cancellationToken = default) { mandateId.NotNullOrWhiteSpace(nameof(mandateId)); mandateId.NotAUrl(nameof(mandateId)); @@ -233,7 +275,7 @@ public async Task RevokeMandate(string mandateId, string idempotenc return await _apiClient.PostAsync( _baseUri.Append(mandateId).Append(MandatesEndpoints.Revoke), null, - idempotencyKey, + idempotencyKey ?? Guid.NewGuid().ToString(), authResponse.Data!.AccessToken, _options.Payments!.SigningKey, cancellationToken diff --git a/src/TrueLayer/Payments/IPaymentsApi.cs b/src/TrueLayer/Payments/IPaymentsApi.cs index a8ff9a59..e2acba80 100644 --- a/src/TrueLayer/Payments/IPaymentsApi.cs +++ b/src/TrueLayer/Payments/IPaymentsApi.cs @@ -42,11 +42,14 @@ public interface IPaymentsApi /// /// An idempotency key to allow safe retrying without the operation being performed multiple times. /// The value should be unique for each operation, e.g. a UUID, with the same key being sent on a retry of the same request. + /// If not provided an idempotency key is automatically generated. /// /// The cancellation token to cancel the operation /// An API response that includes details of the created payment if successful, otherwise problem details Task> CreatePayment( - CreatePaymentRequest paymentRequest, string idempotencyKey, CancellationToken cancellationToken = default); + CreatePaymentRequest paymentRequest, + string? idempotencyKey = null, + CancellationToken cancellationToken = default); /// /// Gets the details of an existing payment @@ -54,7 +57,9 @@ Task> CreatePayment( /// The payment identifier /// The cancellation token to cancel the operation /// An API response that includes the payment details if successful, otherwise problem details - Task> GetPayment(string id, CancellationToken cancellationToken = default); + Task> GetPayment( + string id, + CancellationToken cancellationToken = default); /// /// Generates a link to the TrueLayer hosted payment page @@ -66,7 +71,10 @@ Task> CreatePayment( /// Note this should be configured in the TrueLayer console under your application settings. /// /// The HPP link you can redirect the end user to - string CreateHostedPaymentPageLink(string paymentId, string paymentToken, Uri returnUri); + string CreateHostedPaymentPageLink( + string paymentId, + string paymentToken, + Uri returnUri); /// /// Start the authorization flow for a payment. @@ -75,13 +83,14 @@ Task> CreatePayment( /// /// An idempotency key to allow safe retrying without the operation being performed multiple times. /// The value should be unique for each operation, e.g. a UUID, with the same key being sent on a retry of the same request. + /// If not provided an idempotency key is automatically generated. /// /// The start authorization request details /// The cancellation token to cancel the operation /// Task> StartAuthorizationFlow( string paymentId, - string idempotencyKey, + string? idempotencyKey, StartAuthorizationFlowRequest request, CancellationToken cancellationToken = default); @@ -92,12 +101,14 @@ Task> StartAuthorizationFlow( /// /// An idempotency key to allow safe retrying without the operation being performed multiple times. /// The value should be unique for each operation, e.g. a UUID, with the same key being sent on a retry of the same request. + /// If not provided an idempotency key is automatically generated. /// /// The create payment refund request /// The cancellation token to cancel the operation /// The id of the created refund - Task> CreatePaymentRefund(string paymentId, - string idempotencyKey, + Task> CreatePaymentRefund( + string paymentId, + string? idempotencyKey, CreatePaymentRefundRequest request, CancellationToken cancellationToken = default); @@ -107,7 +118,8 @@ Task> CreatePaymentRefund(string paymen /// The payment identifier /// The cancellation token to cancel the operation /// The list of refunds for a payment. - Task> ListPaymentRefunds(string paymentId, + Task> ListPaymentRefunds( + string paymentId, CancellationToken cancellationToken = default); /// @@ -117,7 +129,8 @@ Task> ListPaymentRefunds(string paymentI /// The refund identifier /// The cancellation token to cancel the operation /// The details of the selected refund - Task> GetPaymentRefund(string paymentId, + Task> GetPaymentRefund( + string paymentId, string refundId, CancellationToken cancellationToken = default); @@ -128,9 +141,13 @@ Task> GetPaymentRefund(string paymentId, /// /// An idempotency key to allow safe retrying without the operation being performed multiple times. /// The value should be unique for each operation, e.g. a UUID, with the same key being sent on a retry of the same request. + /// If not provided an idempotency key is automatically generated. /// /// The cancellation token to cancel the operation /// HTTP 202 Accepted if successful, otherwise problem details. - Task CancelPayment(string paymentId, string idempotencyKey, CancellationToken cancellationToken = default); + Task CancelPayment( + string paymentId, + string? idempotencyKey = null, + CancellationToken cancellationToken = default); } } diff --git a/src/TrueLayer/Payments/Model/CreatePaymentRefundRequest.cs b/src/TrueLayer/Payments/Model/CreatePaymentRefundRequest.cs index b6140063..d3a84cab 100644 --- a/src/TrueLayer/Payments/Model/CreatePaymentRefundRequest.cs +++ b/src/TrueLayer/Payments/Model/CreatePaymentRefundRequest.cs @@ -2,6 +2,7 @@ namespace TrueLayer.Payments.Model; -public record CreatePaymentRefundRequest(string Reference, +public record CreatePaymentRefundRequest( + string Reference, uint? AmountInMinor = null, Dictionary? Metadata = null); diff --git a/src/TrueLayer/Payments/PaymentsApi.cs b/src/TrueLayer/Payments/PaymentsApi.cs index f53e0d85..9178f107 100644 --- a/src/TrueLayer/Payments/PaymentsApi.cs +++ b/src/TrueLayer/Payments/PaymentsApi.cs @@ -55,10 +55,9 @@ public PaymentsApi(IApiClient apiClient, IAuthApi auth, TrueLayerOptions options } /// - public async Task> CreatePayment(CreatePaymentRequest paymentRequest, string idempotencyKey, CancellationToken cancellationToken = default) + public async Task> CreatePayment(CreatePaymentRequest paymentRequest, string? idempotencyKey = null, CancellationToken cancellationToken = default) { paymentRequest.NotNull(nameof(paymentRequest)); - idempotencyKey.NotNullOrWhiteSpace(nameof(idempotencyKey)); var authResponse = await _auth.GetAuthToken(new GetAuthTokenRequest(AuthorizationScope.Payments), cancellationToken); @@ -70,7 +69,7 @@ public async Task> CreatePayment(CreatePaymentRe return await _apiClient.PostAsync( _baseUri, paymentRequest, - idempotencyKey, + idempotencyKey ?? Guid.NewGuid().ToString(), authResponse.Data!.AccessToken, _options.Payments!.SigningKey, cancellationToken @@ -105,13 +104,15 @@ public string CreateHostedPaymentPageLink(string paymentId, string paymentToken, /// public async Task> StartAuthorizationFlow( string paymentId, - string idempotencyKey, + string? idempotencyKey, StartAuthorizationFlowRequest request, CancellationToken cancellationToken = default) { paymentId.NotNullOrWhiteSpace(nameof(paymentId)); + paymentId.NotAUrl(nameof(paymentId)); idempotencyKey.NotNullOrWhiteSpace(nameof(idempotencyKey)); + request.NotNull(nameof(request)); var authResponse = await _auth.GetAuthToken( @@ -125,15 +126,18 @@ public async Task> StartAuthorizationFlo return await _apiClient.PostAsync( _baseUri.Append(paymentId).Append(PaymentsEndpoints.AuthorizationFlow), request, - idempotencyKey, + idempotencyKey ?? Guid.NewGuid().ToString(), authResponse.Data!.AccessToken, _options.Payments!.SigningKey, cancellationToken ); } - public async Task> CreatePaymentRefund(string paymentId, - string idempotencyKey, CreatePaymentRefundRequest request, CancellationToken cancellationToken = default) + public async Task> CreatePaymentRefund( + string paymentId, + string? idempotencyKey, + CreatePaymentRefundRequest request, + CancellationToken cancellationToken = default) { paymentId.NotNullOrWhiteSpace(nameof(paymentId)); paymentId.NotAUrl(nameof(paymentId)); @@ -150,7 +154,7 @@ public async Task> CreatePaymentRefund( return await _apiClient.PostAsync( _baseUri.Append(paymentId).Append(PaymentsEndpoints.Refunds), request, - idempotencyKey, + idempotencyKey ?? Guid.NewGuid().ToString(), authResponse.Data!.AccessToken, _options.Payments!.SigningKey, cancellationToken @@ -204,9 +208,13 @@ public async Task> GetPaymentRefund( ); } - public async Task CancelPayment(string paymentId, string idempotencyKey, CancellationToken cancellationToken = default) + public async Task CancelPayment( + string paymentId, + string? idempotencyKey = null, + CancellationToken cancellationToken = default) { paymentId.NotNullOrWhiteSpace(nameof(paymentId)); + paymentId.NotAUrl(nameof(paymentId)); idempotencyKey.NotNullOrWhiteSpace(nameof(idempotencyKey)); @@ -220,7 +228,7 @@ public async Task CancelPayment(string paymentId, string idempotenc return await _apiClient.PostAsync( _baseUri.Append(paymentId).Append(PaymentsEndpoints.Cancel), - idempotencyKey: idempotencyKey, + idempotencyKey: idempotencyKey ?? Guid.NewGuid().ToString(), accessToken: authResponse.Data!.AccessToken, signingKey: _options.Payments!.SigningKey, cancellationToken: cancellationToken); diff --git a/src/TrueLayer/Payouts/IPayoutsApi.cs b/src/TrueLayer/Payouts/IPayoutsApi.cs index d1266268..6ecb7021 100644 --- a/src/TrueLayer/Payouts/IPayoutsApi.cs +++ b/src/TrueLayer/Payouts/IPayoutsApi.cs @@ -25,11 +25,14 @@ public interface IPayoutsApi /// /// An idempotency key to allow safe retrying without the operation being performed multiple times. /// The value should be unique for each operation, e.g. a UUID, with the same key being sent on a retry of the same request. + /// If not provided an idempotency key is automatically generated. /// /// The cancellation token to cancel the operation /// An API response that includes details of the created payout if successful, otherwise problem details Task> CreatePayout( - CreatePayoutRequest payoutRequest, string idempotencyKey, CancellationToken cancellationToken = default); + CreatePayoutRequest payoutRequest, + string? idempotencyKey = null, + CancellationToken cancellationToken = default); /// /// Gets the details of an existing payment @@ -37,6 +40,8 @@ Task> CreatePayout( /// The payout identifier /// The cancellation token to cancel the operation /// An API response that includes the payout details if successful, otherwise problem details - Task> GetPayout(string id, CancellationToken cancellationToken = default); + Task> GetPayout( + string id, + CancellationToken cancellationToken = default); } } diff --git a/src/TrueLayer/Payouts/PayoutsApi.cs b/src/TrueLayer/Payouts/PayoutsApi.cs index 937a7041..a39232a4 100644 --- a/src/TrueLayer/Payouts/PayoutsApi.cs +++ b/src/TrueLayer/Payouts/PayoutsApi.cs @@ -37,10 +37,12 @@ public PayoutsApi(IApiClient apiClient, IAuthApi auth, TrueLayerOptions options) } /// - public async Task> CreatePayout(CreatePayoutRequest payoutRequest, string idempotencyKey, CancellationToken cancellationToken = default) + public async Task> CreatePayout( + CreatePayoutRequest payoutRequest, + string? idempotencyKey = null, + CancellationToken cancellationToken = default) { payoutRequest.NotNull(nameof(payoutRequest)); - idempotencyKey.NotNullOrWhiteSpace(nameof(idempotencyKey)); var authResponse = await _auth.GetAuthToken(new GetAuthTokenRequest(AuthorizationScope.Payments), cancellationToken); @@ -52,14 +54,16 @@ public async Task> CreatePayout(CreatePayoutRe return await _apiClient.PostAsync( _baseUri, payoutRequest, - idempotencyKey, + idempotencyKey ?? Guid.NewGuid().ToString(), authResponse.Data!.AccessToken, _options.Payments!.SigningKey, cancellationToken ); } - public async Task> GetPayout(string id, CancellationToken cancellationToken = default) + public async Task> GetPayout( + string id, + CancellationToken cancellationToken = default) { id.NotNullOrWhiteSpace(nameof(id)); id.NotAUrl(nameof(id)); diff --git a/test/TrueLayer.AcceptanceTests/MandatesTests.cs b/test/TrueLayer.AcceptanceTests/MandatesTests.cs index 6448de91..b1cc7448 100644 --- a/test/TrueLayer.AcceptanceTests/MandatesTests.cs +++ b/test/TrueLayer.AcceptanceTests/MandatesTests.cs @@ -38,8 +38,7 @@ public async Task Can_Get_Mandate(CreateMandateRequest mandateRequest) { // Arrange var client = _fixture.TlClients[0]; - var createResponse = await client.Mandates.CreateMandate( - mandateRequest, idempotencyKey: Guid.NewGuid().ToString()); + var createResponse = await client.Mandates.CreateMandate(mandateRequest); createResponse.StatusCode.Should().Be(HttpStatusCode.Created); var mandateId = createResponse.Data!.Id; var mandateType = mandateRequest.Mandate.Match( @@ -63,8 +62,7 @@ public async Task Can_List_Mandate(CreateMandateRequest mandateRequest) { // Arrange var client = _fixture.TlClients[0]; - var createResponse = await client.Mandates.CreateMandate( - mandateRequest, idempotencyKey: Guid.NewGuid().ToString()); + var createResponse = await client.Mandates.CreateMandate(mandateRequest); createResponse.StatusCode.Should().Be(HttpStatusCode.Created); // Act @@ -82,8 +80,7 @@ public async Task Can_Start_Preselected_Authorization(CreateMandateRequest manda { // Arrange var client = _fixture.TlClients[0]; - var createResponse = await client.Mandates.CreateMandate( - mandateRequest, idempotencyKey: Guid.NewGuid().ToString()); + var createResponse = await client.Mandates.CreateMandate(mandateRequest); var mandateId = createResponse.Data!.Id; StartAuthorizationFlowRequest authorizationRequest = new( new ProviderSelectionRequest(), @@ -119,8 +116,7 @@ public async Task Can_Complete_UserSelected_Full_Auth_Flow(CreateMandateRequest // Arrange var client = _fixture.TlClients[0]; const string providerId = "mock-payments-gb-redirect"; - var createResponse = await client.Mandates - .CreateMandate(mandateRequest, Guid.NewGuid().ToString()); + var createResponse = await client.Mandates.CreateMandate(mandateRequest); var mandateId = createResponse.Data!.Id; var mandateType = mandateRequest.Mandate.Match( @@ -210,9 +206,7 @@ public async Task Can_Create_Mandate_Payment(CreateMandateRequest mandateRequest var paymentRequest = RequestBuilders.CreateTestMandatePaymentRequest(mandateRequest, mandateId, false); // Act - var response = await _fixture.TlClients[0].Payments.CreatePayment( - paymentRequest, - idempotencyKey: Guid.NewGuid().ToString()); + var response = await _fixture.TlClients[0].Payments.CreatePayment(paymentRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); diff --git a/test/TrueLayer.AcceptanceTests/PaymentTests.cs b/test/TrueLayer.AcceptanceTests/PaymentTests.cs index d1740701..35d23259 100644 --- a/test/TrueLayer.AcceptanceTests/PaymentTests.cs +++ b/test/TrueLayer.AcceptanceTests/PaymentTests.cs @@ -54,8 +54,7 @@ public async Task Can_Create_External_Account_Payment(CreatePaymentRequest payme { foreach (var client in _fixture.TlClients) { - var response = await client.Payments.CreatePayment( - paymentRequest, idempotencyKey: Guid.NewGuid().ToString()); + var response = await client.Payments.CreatePayment(paymentRequest); response.StatusCode.Should().Be(HttpStatusCode.Created); var authorizationRequired = response.Data.AsT0; @@ -119,8 +118,7 @@ public async Task Can_Create_Merchant_Account_Gbp_Verification_Payment() Verification = new Verification.Automated { RemitterName = true } }); - var response = await _fixture.TlClients[0].Payments.CreatePayment( - paymentRequest, idempotencyKey: Guid.NewGuid().ToString()); + var response = await _fixture.TlClients[0].Payments.CreatePayment(paymentRequest); response.StatusCode.Should().Be(HttpStatusCode.Created); var authorizationRequired = response.Data.AsT0; @@ -150,8 +148,7 @@ public async Task Can_Create_Merchant_Account_Eur_Payment() new RelatedProducts(new SignupPlus()), new Beneficiary.MerchantAccount(_fixture.ClientMerchantAccounts[0].EurMerchantAccountId)); - var response = await _fixture.TlClients[0].Payments.CreatePayment( - paymentRequest, idempotencyKey: Guid.NewGuid().ToString()); + var response = await _fixture.TlClients[0].Payments.CreatePayment(paymentRequest); response.StatusCode.Should().Be(HttpStatusCode.Created); var authorizationRequired = response.Data.AsT0; @@ -180,8 +177,7 @@ public async Task Can_Create_Payment_With_Auth_Flow() providerSelection, sortCodeAccountNumber, initAuthorizationFlow: true); - var response = await _fixture.TlClients[0].Payments.CreatePayment( - paymentRequest, idempotencyKey: Guid.NewGuid().ToString()); + var response = await _fixture.TlClients[0].Payments.CreatePayment(paymentRequest); response.StatusCode.Should().Be(HttpStatusCode.Created); response.Data.IsT3.Should().BeTrue(); diff --git a/test/TrueLayer.AcceptanceTests/TrueLayer.AcceptanceTests.csproj b/test/TrueLayer.AcceptanceTests/TrueLayer.AcceptanceTests.csproj index b35f951e..663b6093 100644 --- a/test/TrueLayer.AcceptanceTests/TrueLayer.AcceptanceTests.csproj +++ b/test/TrueLayer.AcceptanceTests/TrueLayer.AcceptanceTests.csproj @@ -8,8 +8,8 @@ nullable; - - + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/TrueLayer.Tests/TrueLayer.Tests.csproj b/test/TrueLayer.Tests/TrueLayer.Tests.csproj index 9d8aadde..96a328af 100644 --- a/test/TrueLayer.Tests/TrueLayer.Tests.csproj +++ b/test/TrueLayer.Tests/TrueLayer.Tests.csproj @@ -17,7 +17,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - +