Skip to content

Commit

Permalink
added idempotency key generation for mandates; added CreateHostedPaym…
Browse files Browse the repository at this point in the history
…entPageLink method
  • Loading branch information
tl-Roberto-Mancinelli committed Dec 2, 2024
1 parent f2a9503 commit 3214d0c
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 27 deletions.
63 changes: 54 additions & 9 deletions src/TrueLayer/Mandates/IMandatesApi.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using OneOf;
Expand Down Expand Up @@ -32,7 +33,9 @@ public interface IMandatesApi
/// <param name="cancellationToken">The cancellation token to cancel the operation</param>
/// <returns>An API response that includes details of the created mandate if successful, otherwise problem details</returns>
Task<ApiResponse<CreateMandateResponse>> CreateMandate(
CreateMandateRequest mandateRequest, string idempotencyKey, CancellationToken cancellationToken = default);
CreateMandateRequest mandateRequest,
string? idempotencyKey = null,
CancellationToken cancellationToken = default);

/// <summary>
/// Gets a mandate
Expand All @@ -42,7 +45,24 @@ Task<ApiResponse<CreateMandateResponse>> CreateMandate(
/// <param name="cancellationToken">The cancellation token to cancel the operation</param>
/// <returns>An API response that includes details of the mandate if successful, otherwise problem details</returns>
Task<ApiResponse<MandateDetailUnion>> GetMandate(
string mandateId, MandateType mandateType, CancellationToken cancellationToken = default);
string mandateId,
MandateType mandateType,
CancellationToken cancellationToken = default);

/// <summary>
/// Generates a link to the TrueLayer hosted payment page
/// </summary>
/// <param name="mandateId">The mandate identifier</param>
/// <param name="resourceToken">The resource token, returned from <see cref="CreateMandate"/></param>
/// <param name="returnUri">
/// 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.
/// </param>
/// <returns>The HPP link you can redirect the end user to</returns>
string CreateHostedPaymentPageLink(
string mandateId,
string resourceToken,
Uri returnUri);

/// <summary>
/// Lists mandates for a user
Expand All @@ -52,7 +72,9 @@ Task<ApiResponse<MandateDetailUnion>> GetMandate(
/// <param name="cancellationToken">The cancellation token to cancel the operation</param>
/// <returns>An API response that includes details of the mandate if successful, otherwise problem details</returns>
Task<ApiResponse<ResourceCollection<MandateDetailUnion>>> ListMandates(
ListMandatesQuery query, MandateType mandateType, CancellationToken cancellationToken = default);
ListMandatesQuery query,
MandateType mandateType,
CancellationToken cancellationToken = default);

/// <summary>
/// Start the authorization flow for a mandate.
Expand All @@ -67,7 +89,11 @@ Task<ApiResponse<ResourceCollection<MandateDetailUnion>>> ListMandates(
/// <param name="cancellationToken">The cancellation token to cancel the operation</param>
/// <returns>An API response that includes details of the mandate if successful, otherwise problem details</returns>
Task<ApiResponse<AuthorizationResponseUnion>> StartAuthorizationFlow(
string mandateId, StartAuthorizationFlowRequest request, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default);
string mandateId,
StartAuthorizationFlowRequest request,
string? idempotencyKey,
MandateType mandateType,
CancellationToken cancellationToken = default);

/// <summary>
/// Submit the provider details selected by the PSU.
Expand All @@ -82,7 +108,11 @@ Task<ApiResponse<AuthorizationResponseUnion>> StartAuthorizationFlow(
/// <param name="cancellationToken">The cancellation token to cancel the operation</param>
/// <returns>An API response that includes details of the mandate if successful, otherwise problem details</returns>
Task<ApiResponse<AuthorizationResponseUnion>> SubmitProviderSelection(
string mandateId, SubmitProviderSelectionRequest request, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default);
string mandateId,
SubmitProviderSelectionRequest request,
string? idempotencyKey,
MandateType mandateType,
CancellationToken cancellationToken = default);

/// <summary>
/// Submit the consent given by the user
Expand All @@ -95,7 +125,11 @@ Task<ApiResponse<AuthorizationResponseUnion>> SubmitProviderSelection(
/// </param>
/// <param name="cancellationToken">The cancellation token to cancel the operation</param>
/// <returns>An API response that includes the authorization flow action details if successful, otherwise problem details</returns>
Task<ApiResponse<AuthorizationResponseUnion>> SubmitConsent(string mandateId, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default);
Task<ApiResponse<AuthorizationResponseUnion>> SubmitConsent(
string mandateId,
string? idempotencyKey,
MandateType mandateType,
CancellationToken cancellationToken = default);

/// <summary>
/// Get Confirmation Of Funds
Expand All @@ -107,7 +141,11 @@ Task<ApiResponse<AuthorizationResponseUnion>> SubmitProviderSelection(
/// <param name="cancellationToken">The cancellation token to cancel the operation</param>
/// <returns>An API response that includes details of the mandate if successful, otherwise problem details</returns>
Task<ApiResponse<GetConfirmationOfFundsResponse>> GetConfirmationOfFunds(
string mandateId, int amountInMinor, string currency, MandateType mandateType, CancellationToken cancellationToken = default);
string mandateId,
int amountInMinor,
string currency,
MandateType mandateType,
CancellationToken cancellationToken = default);

/// <summary>
/// Gets a mandates constraints
Expand All @@ -117,18 +155,25 @@ Task<ApiResponse<GetConfirmationOfFundsResponse>> GetConfirmationOfFunds(
/// <param name="cancellationToken">The cancellation token to cancel the operation</param>
/// <returns>An API response that includes details of the mandate if successful, otherwise problem details</returns>
Task<ApiResponse<GetConstraintsResponse>> GetMandateConstraints(
string mandateId, MandateType mandateType, CancellationToken cancellationToken = default);
string mandateId,
MandateType mandateType,
CancellationToken cancellationToken = default);

/// <summary>
/// Revoke mandate
/// </summary>
/// <param name="mandateId">The id of the mandate</param>
/// <param name="mandateType">The type of the mandate. Either sweeping or commercial</param>
/// <param name="idempotencyKey">
/// 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.
/// </param>
/// <param name="cancellationToken">The cancellation token to cancel the operation</param>
/// <returns>An API response that includes the payment details if successful, otherwise problem details</returns>
Task<ApiResponse> RevokeMandate(string mandateId, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default);
Task<ApiResponse> RevokeMandate(
string mandateId,
string? idempotencyKey,
MandateType mandateType,
CancellationToken cancellationToken = default);
}
}
78 changes: 60 additions & 18 deletions src/TrueLayer/Mandates/MandatesApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using TrueLayer.Extensions;
using TrueLayer.Mandates.Model;
using TrueLayer.Models;
using TrueLayer.Payments;

namespace TrueLayer.Mandates
{
Expand All @@ -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();

Expand All @@ -39,10 +42,13 @@ public MandatesApi(IApiClient apiClient, IAuthApi auth, TrueLayerOptions options
}

/// <inheritdoc />
public async Task<ApiResponse<CreateMandateResponse>> CreateMandate(CreateMandateRequest mandateRequest, string idempotencyKey, CancellationToken cancellationToken = default)
public async Task<ApiResponse<CreateMandateResponse>> CreateMandate(
CreateMandateRequest mandateRequest,
string? idempotencyKey = null,
CancellationToken cancellationToken = default)
{
mandateRequest.NotNull(nameof(mandateRequest));
idempotencyKey.NotNullOrWhiteSpace(nameof(idempotencyKey));

var type = mandateRequest.Mandate.Match(t0 => t0.Type, t1 => t1.Type);
var authResponse = await _auth.GetAuthToken(new GetAuthTokenRequest($"recurring_payments:{type}"), cancellationToken);

Expand All @@ -54,7 +60,7 @@ public async Task<ApiResponse<CreateMandateResponse>> CreateMandate(CreateMandat
return await _apiClient.PostAsync<CreateMandateResponse>(
_baseUri,
mandateRequest,
idempotencyKey,
idempotencyKey ?? Guid.NewGuid().ToString(),
authResponse.Data!.AccessToken,
_options.Payments!.SigningKey,
cancellationToken
Expand All @@ -63,7 +69,10 @@ public async Task<ApiResponse<CreateMandateResponse>> CreateMandate(CreateMandat

//TODO: is it correct that this method expects a mandate type?
/// <inheritdoc />
public async Task<ApiResponse<MandateDetailUnion>> GetMandate(string mandateId, MandateType mandateType, CancellationToken cancellationToken = default)
public async Task<ApiResponse<MandateDetailUnion>> GetMandate(
string mandateId,
MandateType mandateType,
CancellationToken cancellationToken = default)
{
mandateId.NotNullOrWhiteSpace(nameof(mandateId));
mandateId.NotAUrl(nameof(mandateId));
Expand All @@ -83,7 +92,14 @@ public async Task<ApiResponse<MandateDetailUnion>> GetMandate(string mandateId,
}

/// <inheritdoc />
public async Task<ApiResponse<ResourceCollection<MandateDetailUnion>>> ListMandates(ListMandatesQuery query, MandateType mandateType, CancellationToken cancellationToken = default)
public string CreateHostedPaymentPageLink(string mandateId, string resourceToken, Uri returnUri)
=> _hppLinkBuilder.Build(mandateId, resourceToken, returnUri);

/// <inheritdoc />
public async Task<ApiResponse<ResourceCollection<MandateDetailUnion>>> ListMandates(
ListMandatesQuery query,
MandateType mandateType,
CancellationToken cancellationToken = default)
{
var authResponse = await _auth.GetAuthToken(new GetAuthTokenRequest($"recurring_payments:{mandateType.AsString()}"), cancellationToken);

Expand All @@ -106,12 +122,17 @@ public async Task<ApiResponse<ResourceCollection<MandateDetailUnion>>> ListManda
}

/// <inheritdoc />
public async Task<ApiResponse<AuthorizationResponseUnion>> StartAuthorizationFlow(string mandateId, StartAuthorizationFlowRequest request, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default)
public async Task<ApiResponse<AuthorizationResponseUnion>> 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)
Expand All @@ -122,20 +143,25 @@ public async Task<ApiResponse<AuthorizationResponseUnion>> StartAuthorizationFlo
return await _apiClient.PostAsync<AuthorizationResponseUnion>(
_baseUri.Append(mandateId).Append(MandatesEndpoints.AuthorizationFlow),
request,
idempotencyKey,
idempotencyKey ?? Guid.NewGuid().ToString(),
authResponse.Data!.AccessToken,
_options.Payments!.SigningKey,
cancellationToken
);
}

/// <inheritdoc />
public async Task<ApiResponse<AuthorizationResponseUnion>> SubmitProviderSelection(string mandateId, SubmitProviderSelectionRequest request, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default)
public async Task<ApiResponse<AuthorizationResponseUnion>> 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)
Expand All @@ -146,18 +172,22 @@ public async Task<ApiResponse<AuthorizationResponseUnion>> SubmitProviderSelecti
return await _apiClient.PostAsync<AuthorizationResponseUnion>(
_baseUri.Append(mandateId).Append(MandatesEndpoints.ProviderSelection),
request,
idempotencyKey,
idempotencyKey ?? Guid.NewGuid().ToString(),
authResponse.Data!.AccessToken,
_options.Payments!.SigningKey,
cancellationToken
);
}

public async Task<ApiResponse<AuthorizationResponseUnion>> SubmitConsent(string mandateId, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default)
public async Task<ApiResponse<AuthorizationResponseUnion>> 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)
Expand All @@ -168,15 +198,20 @@ public async Task<ApiResponse<AuthorizationResponseUnion>> SubmitConsent(string
return await _apiClient.PostAsync<AuthorizationResponseUnion>(
_baseUri.Append(mandateId).Append(MandatesEndpoints.Consent),
null,
idempotencyKey,
idempotencyKey ?? Guid.NewGuid().ToString(),
authResponse.Data!.AccessToken,
_options.Payments!.SigningKey,
cancellationToken
);
}

/// <inheritdoc />
public async Task<ApiResponse<GetConfirmationOfFundsResponse>> GetConfirmationOfFunds(string mandateId, int amountInMinor, string currency, MandateType mandateType, CancellationToken cancellationToken = default)
public async Task<ApiResponse<GetConfirmationOfFundsResponse>> GetConfirmationOfFunds(
string mandateId,
int amountInMinor,
string currency,
MandateType mandateType,
CancellationToken cancellationToken = default)
{
mandateId.NotNullOrWhiteSpace(nameof(mandateId));
mandateId.NotAUrl(nameof(mandateId));
Expand All @@ -196,7 +231,10 @@ public async Task<ApiResponse<GetConfirmationOfFundsResponse>> GetConfirmationOf
}

/// <inheritdoc />
public async Task<ApiResponse<GetConstraintsResponse>> GetMandateConstraints(string mandateId, MandateType mandateType, CancellationToken cancellationToken = default)
public async Task<ApiResponse<GetConstraintsResponse>> GetMandateConstraints(
string mandateId,
MandateType mandateType,
CancellationToken cancellationToken = default)
{
mandateId.NotNullOrWhiteSpace(nameof(mandateId));
mandateId.NotAUrl(nameof(mandateId));
Expand All @@ -216,7 +254,11 @@ public async Task<ApiResponse<GetConstraintsResponse>> GetMandateConstraints(str
}

/// <inheritdoc />
public async Task<ApiResponse> RevokeMandate(string mandateId, string idempotencyKey, MandateType mandateType, CancellationToken cancellationToken = default)
public async Task<ApiResponse> RevokeMandate(
string mandateId,
string? idempotencyKey,
MandateType mandateType,
CancellationToken cancellationToken = default)
{
mandateId.NotNullOrWhiteSpace(nameof(mandateId));
mandateId.NotAUrl(nameof(mandateId));
Expand All @@ -231,7 +273,7 @@ public async Task<ApiResponse> 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
Expand Down

0 comments on commit 3214d0c

Please sign in to comment.