Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ACL-20) - implement payments auth flow #203

Merged
merged 17 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions examples/MvcExample/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ public async Task<IActionResult> Donate(DonateModel donateModel)
}

return apiResponse.Data.Match<IActionResult>(
authorizing =>
{
ViewData["Status"] = authorizing.Status;
return View("Success");
},
authorizationRequired =>
{
var hppLink = _truelayer.Payments.CreateHostedPaymentPageLink(authorizationRequired.Id,
Expand All @@ -97,7 +102,8 @@ public async Task<IActionResult> Donate(DonateModel donateModel)
}

[HttpGet]
public async Task<IActionResult> Complete([FromQuery(Name = "payment_id")]string paymentId)
[Obsolete]
public async Task<IActionResult> Complete([FromQuery(Name = "payment_id")] string paymentId)
{
if (string.IsNullOrWhiteSpace(paymentId))
return StatusCode((int)HttpStatusCode.BadRequest);
Expand Down Expand Up @@ -126,7 +132,7 @@ void SetProviderAndSchemeId(OneOf<PaymentMethod.BankTransfer, PaymentMethod.Mand
userSelected => (userSelected.ProviderId, userSelected.SchemeId),
preselected => (preselected.ProviderId, preselected.SchemeId)
),
mandate => ("unavailable", "unavailable")) ?? ("unavailable", "unavailable");
mandate => ("unavailable", "unavailable")) ?? ("unavailable", "unavailable");

ViewData["ProviderId"] = providerId;
ViewData["SchemeId"] = schemeId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using TrueLayer;
using System.Threading.Tasks;

namespace MvcExample.Controllers
{
Expand Down
8 changes: 4 additions & 4 deletions examples/MvcExample/Controllers/PayoutController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
Expand Down Expand Up @@ -35,21 +35,21 @@ public async Task<IActionResult> CreatePayout(PayoutModel payoutModel)
{
return View("Index");
}

var externalAccount = new Beneficiary.ExternalAccount(
"TrueLayer",
"truelayer-dotnet",
new AccountIdentifier.Iban(payoutModel.Iban),
dateOfBirth: new DateTime(1970, 12, 31),
address: new Address("London", "England", "EC1R 4RB", "GB", "1 Hardwick St")
);

var payoutRequest = new CreatePayoutRequest(
payoutModel.MerchantAccountId,
payoutModel.AmountInMajor.ToMinorCurrencyUnit(2),
Currencies.GBP,
externalAccount,
metadata: new() {{"a", "b"}});
metadata: new() { { "a", "b" } });

var apiResponse = await _truelayer.Payouts.CreatePayout(
payoutRequest,
Expand Down
2 changes: 1 addition & 1 deletion src/TrueLayer/ApiResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public ApiResponse(ProblemDetails problemDetails, HttpStatusCode statusCode, str
/// Gets the HTTP status code of the response
/// </summary>
public HttpStatusCode StatusCode { get; }

/// <summary>
/// Gets the TrueLayer trace identifier for the request
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/TrueLayer/Auth/GetAuthTokenRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public GetAuthTokenRequest(params string[]? scopes)
/// Gets the OAuth scope value
/// </summary>
public string? Scope { get; }

/// <summary>
/// Gets whether the access token request is scoped
/// </summary>
Expand Down
8 changes: 4 additions & 4 deletions src/TrueLayer/Common/Address.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,22 @@ public Address(
/// Gets full street address including house number and street name.
/// </summary>
public string AddressLine1 { get; }

/// <summary>
/// Gets details like building name, suite, apartment number, etc.
/// </summary>
public string? AddressLine2 { get; }

/// <summary>
/// Gets the name of the city / locality.
/// </summary>
public string City { get; }

/// <summary>
/// Gets the name of the country / stat.
/// </summary>
public string State { get; }

/// <summary>
/// Gets the zip code or postal code
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/TrueLayer/ITrueLayerClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using TrueLayer.Auth;
using TrueLayer.Payments;
using TrueLayer.MerchantAccounts;
using TrueLayer.Payments;
using TrueLayer.PaymentsProviders;
using TrueLayer.Payouts;

Expand Down
2 changes: 1 addition & 1 deletion src/TrueLayer/Mandates/IMandatesApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ 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>
/// Lists mandates for a user
Expand Down
2 changes: 1 addition & 1 deletion src/TrueLayer/Mandates/MandatesApi.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using TrueLayer.Auth;
using OneOf;
using TrueLayer.Auth;
using TrueLayer.Common;
using TrueLayer.Extensions;
using TrueLayer.Mandates.Model;
Expand Down
2 changes: 1 addition & 1 deletion src/TrueLayer/Mandates/Model/Mandate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace TrueLayer.Mandates.Model
{
using ProviderUnion = OneOf<Payments.Model.Provider.UserSelected, Provider.Preselected>;
using BeneficiaryUnion = OneOf<ExternalAccount, MerchantAccount>;
using ProviderUnion = OneOf<Payments.Model.Provider.UserSelected, Provider.Preselected>;

public static class Mandate
{
Expand Down
4 changes: 2 additions & 2 deletions src/TrueLayer/Mandates/Model/MandateDetail.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
using System;
using System.Collections.Generic;
using OneOf;
using TrueLayer.Models;
using TrueLayer.Payments.Model;
using TrueLayer.Serialization;
using static TrueLayer.Mandates.Model.Beneficiary;
using TrueLayer.Models;
using static TrueLayer.Mandates.Model.MandateDetail;

namespace TrueLayer.Mandates.Model
{
using ProviderUnion = OneOf<Payments.Model.Provider.UserSelected, Provider.Preselected>;
using BeneficiaryUnion = OneOf<ExternalAccount, MerchantAccount>;
using MandateDetailUnion = OneOf<AuthorizationRequiredMandateDetail, AuthorizingMandateDetail, AuthorizedMandateDetail, FailedMandateDetail, RevokedMandateDetail>;
using ProviderUnion = OneOf<Payments.Model.Provider.UserSelected, Provider.Preselected>;

public static class MandateDetail
{
Expand Down
2 changes: 1 addition & 1 deletion src/TrueLayer/Mandates/Model/MandateType.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace TrueLayer.Mandates.Model;
namespace TrueLayer.Mandates.Model;

using System;

Expand Down
2 changes: 1 addition & 1 deletion src/TrueLayer/MerchantAccounts/Model/MerchantAccount.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Collections.Generic;
using OneOf;
using AccountIdentifier = TrueLayer.Payments.Model.AccountIdentifier;
using System.Collections.Generic;

namespace TrueLayer.MerchantAccounts.Model
{
Expand Down
13 changes: 7 additions & 6 deletions src/TrueLayer/Models/AuthorizationFlow.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OneOf;

namespace TrueLayer.Models
{
using AuthorizationFlowActionUnion = OneOf<AuthorizationFlowAction.ProviderSelection, AuthorizationFlowAction.Consent, AuthorizationFlowAction.Form, AuthorizationFlowAction.WaitForOutcome, AuthorizationFlowAction.Redirect>;
using AuthorizationFlowActionUnion = OneOf<
AuthorizationFlowAction.ProviderSelection,
AuthorizationFlowAction.Consent,
AuthorizationFlowAction.Form,
AuthorizationFlowAction.WaitForOutcome,
AuthorizationFlowAction.Redirect
>;

/// <summary>
/// Contains information regarding the next action to be taken in the authorization flow.
Expand Down
7 changes: 0 additions & 7 deletions src/TrueLayer/Models/AuthorizationFlowAction.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using OneOf;
using TrueLayer.Serialization;
using static System.Net.Mime.MediaTypeNames;
using static TrueLayer.Models.Input;

namespace TrueLayer.Models
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ namespace TrueLayer.Models
/// </summary>
/// <param name="Actions">Contains the next action to be taken in the authorization flow.</param>
/// <param name="Configuration"></param>
public record AuthorizationFlowWithConfiguration (Actions Actions, Configuration? Configuration = null) : AuthorizationFlow (Actions);
public record AuthorizationFlowWithConfiguration(Actions Actions, Configuration? Configuration = null) : AuthorizationFlow(Actions);
}
10 changes: 6 additions & 4 deletions src/TrueLayer/Models/Input.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static class Input
/// </summary>
/// <param name="Key">A key that is intended to be used as a translation key to look up and render localised text. If a key is not provided, it indicates that this value cannot be localised and that the default value should always be preferred.</param>
/// <param name="DefaultValue">A value that can be used as a default to show to the user, if the key cannot be used to look up a relevant value.</param>
public record DisplayText([property: JsonPropertyName("default")]string DefaultValue, string? Key = null);
public record DisplayText([property: JsonPropertyName("default")] string DefaultValue, string? Key = null);

/// <summary>
/// A regex to validate the input against.
Expand All @@ -33,15 +33,17 @@ public record Regex([property: JsonPropertyName("regex")] string Value, DisplayT
/// <summary>
/// The type of text input that this represents.
/// </summary>
public enum Format {
public enum Format
{
Any = 0,
Numerical = 1,
Alphabetical = 2,
Alphanumerical = 3,
Email = 4,
SortCode = 5,
AccountNumber = 6,
Iban = 7 };
Iban = 7
};

public abstract record InputBase(
string Type,
Expand Down Expand Up @@ -151,7 +153,7 @@ public record TextWithImage(
/// <param name="Id">The identifier of the option, this is the value to be submitted back to the API.</param>
/// <param name="DisplayText">The value to show to the PSU in the UI select dropdown.</param>
/// <param name="SearchAliases">Alternative search terms for this option.</param>
public record Option (string Id, DisplayText DisplayText, List<string> SearchAliases);
public record Option(string Id, DisplayText DisplayText, List<string> SearchAliases);

/// <summary>
/// Select input object.
Expand Down
10 changes: 4 additions & 6 deletions src/TrueLayer/Models/Provider.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using TrueLayer.PaymentsProviders.Model;

namespace TrueLayer.Models
{
Expand All @@ -18,6 +14,7 @@ namespace TrueLayer.Models
/// <param name="Availability">Provider availability.</param>
/// <param name="CountryCode"></param>
/// <param name="SearchAliases">Alternative search terms that should be used to help users find this provider.</param>
/// <prama name="Schemes">List of schemes supported by the provider.</prama>
public record Provider(
string? Id = null,
string? DisplayName = null,
Expand All @@ -26,5 +23,6 @@ public record Provider(
string? BgColor = null,
ProviderAvailability? Availability = null,
CountryCode? CountryCode = null,
List<string>? SearchAliases = null);
List<string>? SearchAliases = null,
List<Scheme>? Schemes = null);
}
27 changes: 25 additions & 2 deletions src/TrueLayer/Payments/IPaymentsApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@
using System.Threading.Tasks;
using OneOf;
using TrueLayer.Payments.Model;
using TrueLayer.Payments.Model.AuthorizationFlow;

namespace TrueLayer.Payments
{
using AuthorizationResponseUnion = OneOf<
AuthorizationFlowResponse.AuthorizationFlowAuthorizing,
AuthorizationFlowResponse.AuthorizationFlowAuthorizationFailed
>;
using CreatePaymentUnion = OneOf<
CreatePaymentResponse.AuthorizationRequired,
CreatePaymentResponse.Authorized,
CreatePaymentResponse.Failed
CreatePaymentResponse.Failed,
CreatePaymentResponse.Authorizing
>;

using GetPaymentUnion = OneOf<
GetPaymentResponse.AuthorizationRequired,
GetPaymentResponse.Authorizing,
Expand Down Expand Up @@ -58,5 +63,23 @@ Task<ApiResponse<CreatePaymentUnion>> CreatePayment(
/// </param>
/// <returns>The HPP link you can redirect the end user to</returns>
string CreateHostedPaymentPageLink(string paymentId, string paymentToken, Uri returnUri);

/// <summary>
/// Start the authorization flow for a payment.
/// </summary>
/// <param name="paymentId">The payment identifier</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="request">The start authorization request details</param>
/// <param name="cancellationToken">The cancellation token to cancel the operation</param>
/// <returns></returns>
Task<ApiResponse<AuthorizationResponseUnion>> StartAuthorizationFlow(
string paymentId,
string idempotencyKey,
StartAuthorizationFlowRequest request,
CancellationToken cancellationToken = default);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using OneOf;

namespace TrueLayer.Payments.Model.AuthorizationFlow
{
using ProviderSelectionUnion = OneOf<Provider.UserSelected, Provider.Preselected>;
using SchemeSelectionUnion = OneOf<
SchemeSelection.UserSelected,
SchemeSelection.Preselected,
SchemeSelection.InstantOnly,
SchemeSelection.InstantPreferred
>;

/// <summary>
/// Can the UI redirect the end user to a third-party page? Configuration options are available to constrain if TrueLayer's Hosted Payment Page should be leveraged.
/// </summary>
/// <param name="ReturnUri">During the authorization flow the end user might be redirected to another page (e.g.bank website, TrueLayer's Hosted Payment Page). This URL determines where they will be redirected back once they completed the flow on the third-party's website. return_uri must be one of the allowed return_uris registered in TrueLayer's Console.</param>
/// <param name="DirectReturnUri">Only applicable if you're regulated and have a direct return URI registered with UK providers.
/// If you're regulated, you can specify a direct_return_uri to attempt the authorization flow via a direct redirect from the provider authorization page to your page without going via TrueLayer.
/// We recommend that your return_uri is a URI that can handle a non-direct return scenario. This ensures that if the direct_return_uri isn't registered with the user's chosen provider, the payment can still be authorized through a Truelayer domain.</param>
public record Redirect(Uri ReturnUri, Uri? DirectReturnUri = null);

public record UserAccountSelection();

/// <summary>
/// Start the authorization flow for a mandate.
/// </summary>
/// <param name="ProviderSelection">Can the UI render a provider selection screen?</param>
/// <param name="SchemeSelection">Can your UI render a scheme selection screen?
/// For payments where you set scheme_selection as user_selected, your UI must be able to render a screen where the user can select their payments scheme.
/// This field is required for payments with user_selected scheme selection. For other scheme selection types, it's optional</param>
/// <param name="Form">Can your UI render form inputs for the user to interact with?
/// Some providers require additional inputs, such as the remitter name and account details, to be provided before or during payment authorization.
/// To facilitate this, the API may return a form action as part of the authorization flow, which means your UI must be able to collect the required inputs.
/// This parameter states whether your UI supports the form action. If you omit this parameter, the API returns only providers that don't require additional inputs.
/// If the provider has been preselected and requires additional inputs, this field is required.</param>
/// <param name="Redirect">Can the UI redirect the end user to a third-party page? Configuration options are available to constrain if TrueLayer's Hosted Payment Page should be leveraged.</param>
/// <param name="Consent">Can the UI capture the user's consent? This field declares whether the UI supports the consent action, which is used to explicitly capture the end user's consent for initiating the payment. If it is omitted, the flow will continue without a consent action.</param>
/// <param name="UserAccountSelection">Can your UI render a user account selection screen?
/// If the user has previously consented to saving their bank account details with TrueLayer, they can choose from their saved accounts to speed up following payments.
/// This field states whether your UI can render a selection screen for these saved accounts. If you omit this, the user isn't presented with this option.</param>
public record StartAuthorizationFlowRequest(
ProviderSelectionUnion? ProviderSelection = null,
SchemeSelectionUnion? SchemeSelection = null,
Redirect? Redirect = null,
Form? Form = null,
Consent? Consent = null,
UserAccountSelection? UserAccountSelection = null);


}
Loading
Loading