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

[ACL-255] Add multi Client support #239

Merged
merged 23 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5e74ad9
Add multiple client_id support
tl-Roberto-Mancinelli Dec 19, 2024
42eff54
switched from ob-natwest-vrp-sandbox to mock-payments-gb-redirect for…
tl-Roberto-Mancinelli Dec 18, 2024
e18ca3c
fix tests
tl-Roberto-Mancinelli Dec 19, 2024
133abe4
add serviceKey to customise DI
tl-Roberto-Mancinelli Dec 19, 2024
70385f0
fix example
tl-Roberto-Mancinelli Dec 19, 2024
93f045a
refactor
tl-Roberto-Mancinelli Dec 19, 2024
10264de
minor
tl-Roberto-Mancinelli Dec 20, 2024
c98bb16
minor
tl-Roberto-Mancinelli Dec 20, 2024
348a37d
summary updates
tl-Roberto-Mancinelli Dec 20, 2024
806a31d
IOptionsFactory refactor
tl-Roberto-Mancinelli Dec 20, 2024
8fa7a2b
test improvement
tl-Roberto-Mancinelli Dec 20, 2024
a9ea66a
change cache to singleton
tl-Roberto-Mancinelli Dec 20, 2024
a0fb393
ClientFactory improvements; tests refactor
tl-Roberto-Mancinelli Dec 23, 2024
9d2ffb5
mandate tests refactor
tl-Roberto-Mancinelli Dec 23, 2024
994b559
fix fixture
tl-Roberto-Mancinelli Dec 23, 2024
7c52e78
minor changes to mandate tests
tl-Roberto-Mancinelli Dec 23, 2024
48d99ae
use TheoryData for mandate test cases
tl-Roberto-Mancinelli Dec 23, 2024
36fcc49
minor
tl-Roberto-Mancinelli Dec 23, 2024
0abcea9
fix disabled tests
tl-Roberto-Mancinelli Dec 23, 2024
1376533
use PrivateBeta for mandate tests
tl-Roberto-Mancinelli Dec 23, 2024
59b31ac
add missing guards; add runtime version
tl-Roberto-Mancinelli Dec 23, 2024
cf13ca7
add NotAUrl acceptance tests
tl-Roberto-Mancinelli Dec 23, 2024
68d1961
minor change to caching key
tl-Roberto-Mancinelli Jan 15, 2025
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
7 changes: 3 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ jobs:
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Setup .NET 3.1
uses: actions/setup-dotnet@v2
with:
dotnet-version: 3.1.x
- name: Restore tools
run: dotnet tool restore
- name: Run the build script
Expand All @@ -44,6 +40,9 @@ jobs:
TrueLayer__ClientId: ${{ secrets.TRUELAYER__CLIENTID }}
TrueLayer__ClientSecret: ${{ secrets.TRUELAYER__CLIENTSECRET }}
TrueLayer__Payments__SigningKey__KeyId: ${{ secrets.TRUELAYER__PAYMENTS__SIGNINGKEY__KEYID }}
TrueLayer2__ClientId: ${{ secrets.TRUELAYER__CLIENTID }}
tl-Roberto-Mancinelli marked this conversation as resolved.
Show resolved Hide resolved
TrueLayer2__ClientSecret: ${{ secrets.TRUELAYER__CLIENTSECRET }}
TrueLayer2__Payments__SigningKey__KeyId: ${{ secrets.TRUELAYER__PAYMENTS__SIGNINGKEY__KEYID }}
with:
cake-version: 5.0.0
target: CI
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ jobs:
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Setup .NET 3.1
uses: actions/setup-dotnet@v2
with:
dotnet-version: 3.1.x
- name: Restore tools
run: dotnet tool restore
- name: Run the build script
Expand All @@ -55,6 +51,9 @@ jobs:
TrueLayer__ClientId: ${{ secrets.TRUELAYER__CLIENTID }}
TrueLayer__ClientSecret: ${{ secrets.TRUELAYER__CLIENTSECRET }}
TrueLayer__Payments__SigningKey__KeyId: ${{ secrets.TRUELAYER__PAYMENTS__SIGNINGKEY__KEYID }}
TrueLayer2__ClientId: ${{ secrets.TRUELAYER__CLIENTID }}
TrueLayer2__ClientSecret: ${{ secrets.TRUELAYER__CLIENTSECRET }}
TrueLayer2__Payments__SigningKey__KeyId: ${{ secrets.TRUELAYER__PAYMENTS__SIGNINGKEY__KEYID }}
with:
cake-version: 5.0.0
target: ${{ github.event.inputs.target || 'Publish' }}
Expand Down
54 changes: 49 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Add your Client ID, Secret and Signing Key ID to `appsettings.json` or any other

### Initialize TrueLayer.NET

Register the TrueLayer client in `Startup.cs` or `Program.cs` (.NET 6.0):
Register the TrueLayer client in `Startup.cs` or `Program.cs` (.NET 9.0/.NET 8.0/.NET 6.0):

```c#
public IConfiguration Configuration { get; }
Expand All @@ -115,13 +115,57 @@ public void ConfigureServices(IServiceCollection services)
// For demo purposes only. Private key should be stored securely
options.Payments.SigningKey.PrivateKey = File.ReadAllText("ec512-private-key.pem");
}
})
// We advice to cache the auth token
.AddAuthTokenInMemoryCaching();
},
// For best performance and reliability we advice to cache the auth token
authCachingStrategy: AuthCachingStrategy.InMemory)

}
```

Alternatively you can create a class that implements `IConfigureOptions<TrueLayerOptions>` if you have more complex configuration requirements.
### Multiple TrueLayer Clients

Use keyed version of TrueLayer client (.NET 9.0/.NET 8.0):

```c#
.AddKeyedTrueLayer("TrueLayerGbp",
configuration,
options =>
{
// For demo purposes only. Private key should be stored securely
var privateKey = File.ReadAllText("ec512-private-key.pem");
if (options.Payments?.SigningKey != null)
{
options.Payments.SigningKey.PrivateKey = privateKey;
}
},
authTokenCachingStrategy: AuthTokenCachingStrategies.InMemory)
.AddKeyedTrueLayer("TrueLayerEur",
configuration,
options =>
{
// For demo purposes only. Private key should be stored securely
var privateKey = File.ReadAllText("ec512-private-key.pem");
if (options.Payments?.SigningKey != null)
{
options.Payments.SigningKey.PrivateKey = privateKey;
}
},
authTokenCachingStrategy: AuthTokenCachingStrategies.InMemory)
```

Use `[FromKeyedServices()]` attribute to retrieve keyed client

```c#
public GbpController([FromKeyedServices("TrueLayerGbp")]ITrueLayerClient trueLayerClient, ...
public EurController([FromKeyedServices("TrueLayerEur")]ITrueLayerClient trueLayerClient, ...
tl-Roberto-Mancinelli marked this conversation as resolved.
Show resolved Hide resolved
```

Or `GetRequiredKeyedService`

```c#
var GbpClient = ServiceProvider.GetRequiredKeyedService<ITrueLayerClient>("TrueLayerGbp");
var EurClient = ServiceProvider.GetRequiredKeyedService<ITrueLayerClient>("TrueLayerEur");
```

### Make a payment

Expand Down
10 changes: 5 additions & 5 deletions examples/MvcExample/Controllers/MerchantAccountsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ namespace MvcExample.Controllers
{
public class MerchantAccountsController : Controller
{
private readonly ITrueLayerClient _truelayer;
private readonly ITrueLayerClient _trueLayerClient;
private readonly ILogger<MerchantAccountsController> _logger;

public MerchantAccountsController(ITrueLayerClient truelayer, ILogger<MerchantAccountsController> logger)
public MerchantAccountsController(ITrueLayerClient trueLayerClient, ILogger<MerchantAccountsController> logger)
{
_truelayer = truelayer;
_trueLayerClient = trueLayerClient;
_logger = logger;
}

public async Task<IActionResult> Index()
{
var apiResponse = await _truelayer.MerchantAccounts.ListMerchantAccounts();
var apiResponse = await _trueLayerClient.MerchantAccounts.ListMerchantAccounts();

if (apiResponse.IsSuccessful)
{
Expand All @@ -42,7 +42,7 @@ public async Task<IActionResult> Index()

public async Task<IActionResult> Details(string id)
{
var apiResponse = await _truelayer.MerchantAccounts.GetMerchantAccount(id);
var apiResponse = await _trueLayerClient.MerchantAccounts.GetMerchantAccount(id);

if (apiResponse.IsSuccessful)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@

namespace MvcExample.Controllers
{
public class HomeController : Controller
public class PaymentsController : Controller
{
private readonly ITrueLayerClient _truelayer;
private readonly ILogger<HomeController> _logger;
private readonly ITrueLayerClient _trueLayerClient;
private readonly ILogger<PaymentsController> _logger;

public HomeController(ITrueLayerClient truelayer, ILogger<HomeController> logger)
public PaymentsController(ITrueLayerClient trueLayerClient, ILogger<PaymentsController> logger)
{
_truelayer = truelayer;
_trueLayerClient = trueLayerClient;
_logger = logger;
}

Expand All @@ -39,7 +39,9 @@
}

OneOf<Provider.UserSelected, Provider.Preselected> providerSelection = donateModel.UserPreSelectedFilter
? new Provider.Preselected(providerId: "mock-payments-gb-redirect", schemeSelection: new SchemeSelection.Preselected { SchemeId = "faster_payments_service"})
? new Provider.Preselected(
providerId: "mock-payments-gb-redirect",
schemeSelection: new SchemeSelection.Preselected { SchemeId = "faster_payments_service"})
: new Provider.UserSelected();

var paymentRequest = new CreatePaymentRequest(
Expand All @@ -56,7 +58,7 @@
null
);

var apiResponse = await _truelayer.Payments.CreatePayment(
var apiResponse = await _trueLayerClient.Payments.CreatePayment(
paymentRequest,
idempotencyKey: Guid.NewGuid().ToString()
);
Expand All @@ -78,26 +80,31 @@
}

return apiResponse.Data.Match<IActionResult>(
authorizing =>
{
ViewData["Status"] = authorizing.Status;
return View("Success");
},
authorizationRequired =>
{
var hppLink = _truelayer.Payments.CreateHostedPaymentPageLink(authorizationRequired.Id,
authorizationRequired.ResourceToken, new Uri(Url.ActionLink("Complete")));
// Return Uri must be whitelisted in TrueLayer console
var returnUri = new Uri(Url.ActionLink("Success"));

var hppLink = _trueLayerClient.Payments.CreateHostedPaymentPageLink(
authorizationRequired.Id,
authorizationRequired.ResourceToken,
returnUri);
return Redirect(hppLink);
},
authorized =>
{
ViewData["Status"] = authorized.Status;
return View("Success");
return View("Pending");
},
failed =>
{
ViewData["Status"] = failed.Status;
return View("Failed");
},
authorizing =>
{
ViewData["Status"] = authorizing.Status;
return View("Pending");
});
}

Expand All @@ -107,15 +114,20 @@
if (string.IsNullOrWhiteSpace(paymentId))
return StatusCode((int)HttpStatusCode.BadRequest);

var apiResponse = await _truelayer.Payments.GetPayment(paymentId);
var apiResponse = await _trueLayerClient.Payments.GetPayment(paymentId);

IActionResult Failed(string status, OneOf<PaymentMethod.BankTransfer, PaymentMethod.Mandate>? paymentMethod)
{
ViewData["Status"] = status;
if (!apiResponse.IsSuccessful)
return Failed(apiResponse.StatusCode.ToString(), null!);

SetProviderAndSchemeId(paymentMethod);
return View("Failed");
}
return apiResponse.Data.Match(
authRequired => Failed(authRequired.Status, authRequired.PaymentMethod),
SuccessOrPending,
SuccessOrPending,
SuccessOrPending,
SuccessOrPending,
failed => Failed(failed.Status, failed.PaymentMethod),
attemptFailed => Failed(attemptFailed.Status, attemptFailed.PaymentMethod)
);

IActionResult SuccessOrPending(PaymentDetails payment)
{
Expand All @@ -128,8 +140,8 @@
{
(string providerId, string schemeId) = paymentMethod?.Match(
bankTransfer => bankTransfer.ProviderSelection.Match(
userSelected => (userSelected.ProviderId, userSelected.SchemeId),

Check warning on line 143 in examples/MvcExample/Controllers/PaymentsController.cs

View workflow job for this annotation

GitHub Actions / build

'Provider.UserSelected.SchemeId' is obsolete: 'The field will be removed soon. Please start using the new <see cref="SchemeSelection"/> field.'
preselected => (preselected.ProviderId, preselected.SchemeId)

Check warning on line 144 in examples/MvcExample/Controllers/PaymentsController.cs

View workflow job for this annotation

GitHub Actions / build

'Provider.Preselected.SchemeId' is obsolete: 'The field will be removed soon. Please start using the new <see cref="SchemeSelection"/> field.'
),
mandate => ("unavailable", "unavailable")) ?? ("unavailable", "unavailable");

Expand All @@ -137,18 +149,13 @@
ViewData["SchemeId"] = schemeId;
}

if (!apiResponse.IsSuccessful)
return Failed(apiResponse.StatusCode.ToString(), null!);
IActionResult Failed(string status, OneOf<PaymentMethod.BankTransfer, PaymentMethod.Mandate>? paymentMethod)
{
ViewData["Status"] = status;

return apiResponse.Data.Match(
authRequired => Failed(authRequired.Status, authRequired.PaymentMethod),
SuccessOrPending,
SuccessOrPending,
SuccessOrPending,
SuccessOrPending,
failed => Failed(failed.Status, failed.PaymentMethod),
attemptFailed => Failed(attemptFailed.Status, attemptFailed.PaymentMethod)
);
SetProviderAndSchemeId(paymentMethod);
return View("Failed");
}
}

public IActionResult Privacy()
Expand Down
10 changes: 5 additions & 5 deletions examples/MvcExample/Controllers/PayoutController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ namespace MvcExample.Controllers
{
public class PayoutController : Controller
{
private readonly ITrueLayerClient _truelayer;
private readonly ITrueLayerClient _trueLayerClient;
private readonly ILogger<PayoutController> _logger;

public PayoutController(ITrueLayerClient truelayer, ILogger<PayoutController> logger)
public PayoutController(ITrueLayerClient trueLayerClient, ILogger<PayoutController> logger)
{
_truelayer = truelayer;
_trueLayerClient = trueLayerClient;
_logger = logger;
}

Expand Down Expand Up @@ -51,7 +51,7 @@ public async Task<IActionResult> CreatePayout(PayoutModel payoutModel)
externalAccount,
metadata: new() { { "a", "b" } });

var apiResponse = await _truelayer.Payouts.CreatePayout(
var apiResponse = await _trueLayerClient.Payouts.CreatePayout(
payoutRequest,
idempotencyKey: Guid.NewGuid().ToString()
);
Expand Down Expand Up @@ -88,7 +88,7 @@ public async Task<IActionResult> Complete(string payoutId)
return View();
}

var apiResponse = await _truelayer.Payouts.GetPayout(payoutId);
var apiResponse = await _trueLayerClient.Payouts.GetPayout(payoutId);

IActionResult Failed(string status)
{
Expand Down
28 changes: 14 additions & 14 deletions examples/MvcExample/Controllers/ProvidersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ namespace MvcExample.Controllers
{
public class ProvidersController : Controller
{
private readonly ITrueLayerClient _truelayer;
private readonly ITrueLayerClient _trueLayerClient;

public ProvidersController(ITrueLayerClient truelayer)
public ProvidersController(ITrueLayerClient trueLayerClient)
{
_truelayer = truelayer;
_trueLayerClient = trueLayerClient;
}

public IActionResult Index()
Expand All @@ -29,15 +29,11 @@ public async Task<IActionResult> GetProvider([FromQuery(Name = "id")] string pro
return StatusCode((int)HttpStatusCode.BadRequest);
}

var apiResponse = await _truelayer.PaymentsProviders.GetPaymentsProvider(providerId);
var apiResponse = await _trueLayerClient.PaymentsProviders.GetPaymentsProvider(providerId);

IActionResult Failed(string status)
{
ViewData["Status"] = status;
ViewData["ProviderId"] = providerId;

return View("Failed");
}
return apiResponse.IsSuccessful
? Success(apiResponse.Data)
: Failed(apiResponse.StatusCode.ToString());

IActionResult Success(PaymentsProvider provider)
{
Expand All @@ -46,9 +42,13 @@ IActionResult Success(PaymentsProvider provider)
return View("Success");
}

return apiResponse.IsSuccessful
? Success(apiResponse.Data)
: Failed(apiResponse.StatusCode.ToString());
IActionResult Failed(string status)
{
ViewData["Status"] = status;
ViewData["ProviderId"] = providerId;

return View("Failed");
}
}
}
}
2 changes: 1 addition & 1 deletion examples/MvcExample/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"applicationUrl": "https://localhost:3001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
Expand Down
Loading
Loading