Skip to content

Commit 70b8a25

Browse files
authored
Implement Payment API
Implement Payment API. Issue #480
1 parent 9d51eeb commit 70b8a25

68 files changed

Lines changed: 2912 additions & 72 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/articles/bankid.md

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# ActiveLogin.Authentication.BankId
22

3-
ActiveLogin.Authentication enables an application to support Swedish BankID (svenskt BankID) authentication and signing in .NET.
3+
ActiveLogin.Authentication enables an application to support Swedish BankID (svenskt BankID) authentication, signing, payments, phone authentication, phone signing and digital ID card verification in .NET.
44

5-
The most common scenbario is to use Active Login for BankID auth/login, so most of the concepts will be described from that perspective. We've designed sign to follow the same patterns and amke sure we can share things like certificate handling etc.
5+
The most common scenario is to use Active Login for BankID auth/login, so most of the concepts will be described from that perspective. We've designed the other features to follow the same patterns and make sure we can share things like certificate handling etc.
66

77
## Table of contents
88

@@ -22,6 +22,7 @@ The most common scenbario is to use Active Login for BankID auth/login, so most
2222
+ [Production environment](#production-environment)
2323
+ [Full sample for production](#full-sample-for-production)
2424
* [Sign](#sign)
25+
* [Payment](#payment)
2526
* [Basic configuration samples](#basic-configuration-samples)
2627
+ [Using client certificate from Azure KeyVault](#using-client-certificate-from-azure-keyvault)
2728
+ [Using client certificate from custom source](#using-client-certificate-from-custom-source)
@@ -323,7 +324,7 @@ services
323324
Once that is done you will be able to use these services in your application, for example in your controller:
324325

325326
* `IBankIdSignConfigurationProvider` : List the registered configuraitons (SameDevice / Other Device)
326-
* `IBankIdSignService` : Initiate and resulve the result of sign flow
327+
* `IBankIdSignService` : Initiate and resolve the result of sign flow
327328

328329
Here is a minimal sample. See `Standalone.MvcSample` for more details.
329330

@@ -391,6 +392,113 @@ public class SignController : Controller
391392

392393
---
393394

395+
# Payment
396+
397+
Payment works very similar to sign and auth. You need to register both the common BankID logic (environment, cert etc) as well as the payment specific configration (devices).
398+
399+
```csharp
400+
// Add Active Login - BankID
401+
services
402+
.AddBankId(bankId =>
403+
{
404+
bankId.AddDebugEventListener();
405+
bankId.UseQrCoderQrCodeGenerator();
406+
bankId.UseUaParserDeviceDetection();
407+
bankId.UseSimulatedEnvironment();
408+
});
409+
410+
// Add Active Login - Payment
411+
services.AddBankIdPayment(bankId =>
412+
{
413+
bankId.AddSameDevice(BankIdPaymentDefaults.SameDeviceConfigKey, "BankID (SameDevice)", options => { });
414+
bankId.AddOtherDevice(BankIdPaymentDefaults.OtherDeviceConfigKey, "BankID (OtherDevice)", options => { });
415+
});
416+
```
417+
418+
Once that is done you will be able to use these services in your application, for example in your controller:
419+
420+
* `IBankIdPaymentConfigurationProvider` : List the registered configuraitons (Same Device / Other Device)
421+
* `IBankIdPaymentService` : Initiate and resolve the result of payment flow
422+
423+
Here is a minimal sample. See `Standalone.MvcSample` for more details.
424+
425+
```csharp
426+
[AllowAnonymous]
427+
public class PaymentController : Controller
428+
{
429+
private readonly IBankIdPaymentConfigurationProvider _bankIdPaymentConfigurationProvider;
430+
private readonly IBankIdPaymentService _bankIdPaymentService;
431+
432+
public PaymentController(IBankIdPaymentConfigurationProvider bankIdPaymentConfigurationProvider, IBankIdPaymentService bankIdPaymentService)
433+
{
434+
_bankIdPaymentConfigurationProvider = bankIdPaymentConfigurationProvider;
435+
_bankIdPaymentService = bankIdPaymentService;
436+
}
437+
438+
public async Task<IActionResult> Index()
439+
{
440+
var configurations = await _bankIdPaymentConfigurationProvider.GetAllConfigurationsAsync();
441+
var providers = configurations
442+
.Where(x => x.DisplayName != null)
443+
.Select(x => new ExternalProvider(x.DisplayName ?? x.Key, x.Key));
444+
var viewModel = new BankIdViewModel(providers, $"{Url.Action(nameof(Index))}");
445+
446+
return View(viewModel);
447+
}
448+
449+
[AllowAnonymous]
450+
[HttpPost("Payment")]
451+
public IActionResult Payment([FromQuery] string provider, [FromForm] PaymentRequestModel model)
452+
{
453+
ArgumentNullException.ThrowIfNull(model, nameof(model));
454+
455+
var recipientName = "Demo Merchant Name";
456+
var amount = "100,00";
457+
var currency = "SEK";
458+
var props = new BankIdPaymentProperties(TransactionType.card, recipientName)
459+
{
460+
Money = new(amount, currency),
461+
UserVisibleData = "Demo of Payment with Active Login.",
462+
Items =
463+
{
464+
{"scheme", provider},
465+
{"transactionType", nameof(TransactionType.card)},
466+
{"recipientName", recipientName},
467+
{"amount", amount},
468+
{"currency", currency}
469+
},
470+
};
471+
472+
var returnPath = $"{Url.Action(nameof(Callback))}?provider={provider}";
473+
return this.BankIdInitiatePayment(props, returnPath, provider);
474+
}
475+
476+
[AllowAnonymous]
477+
[HttpPost]
478+
public async Task<IActionResult> Callback(string provider)
479+
{
480+
var result = await _bankIdPaymentService.GetPaymentResultAsync(provider);
481+
if (result?.Succeeded != true || result.BankIdCompletionData == null)
482+
{
483+
throw new Exception("Payment error");
484+
}
485+
486+
return View("Result", new PaymentResultViewModel(
487+
result.BankIdCompletionData.User.PersonalIdentityNumber,
488+
result.BankIdCompletionData.User.Name,
489+
result.BankIdCompletionData.Device.IpAddress,
490+
result.Properties.Items["transactionType"] ?? string.Empty,
491+
result.Properties.Items["recipientName"] ?? string.Empty,
492+
result.Properties.Items["amount"] ?? null,
493+
result.Properties.Items["currency"] ?? null
494+
)
495+
);
496+
}
497+
}
498+
499+
```
500+
501+
---
394502

395503
## Basic configuration samples
396504

@@ -489,6 +597,7 @@ If you want to apply some options for all BankID schemes, you can do so by using
489597
```
490598

491599
Requirements can also be set dynamically for each authentication, see section [Resolve requirements on Auth request](#resolve-requirements-on-auth-request). To use dynamic requirements with signatures provide the requirements as part the `BankIdSignProperties`, see section [Sign](#sign).
600+
To use dynamic requirements with payments provide the requirements as part the `BankIdPaymentProperties`, see section [Payment](#payment).
492601

493602
---
494603

@@ -803,10 +912,11 @@ In this folder, you can then create any of the partials and MVC will then discov
803912
- `_Style.cshtml`
804913
- `_Spinner.cshtml`
805914

806-
If you want, you can override the UI for Auth and Sign with different templates. Do so by placing the files in one of these folders:
915+
If you want, you can override the UI for Auth, Sign and Payment with different templates. Do so by placing the files in one of these folders:
807916

808917
* `Areas/ActiveLogin/Views/BankIdUiAuth`
809918
* `Areas/ActiveLogin/Views/BankIdUiSign`
919+
* `Areas/ActiveLogin/Views/BankIdUiPayment`
810920

811921
See [the MVC sample](https://github.com/ActiveLogin/ActiveLogin.Authentication/tree/main/samples/Standalone.MvcSample) to see this in action, as demonstrated [here](https://github.com/ActiveLogin/ActiveLogin.Authentication/tree/main/samples/Standalone.MvcSample/Areas/ActiveLogin/Views/BankIdUiAuth/_Wrapper.cshtml).
812922
@@ -854,12 +964,15 @@ At the moment, we trigger the events listed below. They all have unique event pr
854964
- `BankIdAspNetChallengeSuccessEvent`
855965
- `BankIdAspNetAuthenticateSuccessEvent`
856966
- `BankIdAspNetAuthenticateFailureEvent`
857-
- Auth
858-
- `BankIdAuthSuccessEvent`
859-
- `BankIdAuthErrorEvent`
967+
- Initialize
968+
- `BankIdInitializeSuccessEvent`
969+
- `BankIdInitializeErrorEvent`
860970
- Sign
861971
- `BankIdSignSuccessEvent`
862-
- `BankIdSignErrorEvent`
972+
- `BankIdSignFailureEvent`
973+
- Payment
974+
- `BankIdPaymentSuccessEvent`
975+
- `BankIdPaymentFailureEvent`
863976
- Collect
864977
- `BankIdCollectPendingEvent`
865978
- `BankIdCollectCompletedEvent`
@@ -1436,6 +1549,7 @@ public class BankIdAppApiClient : IBankIdAppApiClient
14361549
{
14371550
public Task<AuthResponse> AuthAsync(AuthRequest request) { ... }
14381551
public Task<SignResponse> SignAsync(SignRequest request) { ... }
1552+
public Task<PaymentResponse> PaymentAsync(PaymentRequest request) { ... }
14391553
public Task<PhoneAuthResponse> PhoneAuthAsync(PhoneAuthRequest request) { ... }
14401554
public Task<PhoneSignResponse> PhoneSignAsync(PhoneSignRequest request) { ... }
14411555
public Task<CollectResponse> CollectAsync(CollectRequest request) { ... }
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using ActiveLogin.Authentication.BankId.AspNetCore.Payment;
2+
using ActiveLogin.Authentication.BankId.Core.Payment;
3+
4+
using Microsoft.AspNetCore.Authorization;
5+
using Microsoft.AspNetCore.Mvc;
6+
7+
using Standalone.MvcSample.Models;
8+
9+
namespace Standalone.MvcSample.Controllers;
10+
11+
//
12+
// DISCLAIMER
13+
//
14+
// These are samples on how to use Active Login in different situations
15+
// and might not represent optimal way of setting up
16+
// ASP.NET MVC or other components.
17+
//
18+
// Please see this as inspiration, not a complete template.
19+
//
20+
21+
[AllowAnonymous]
22+
public class PaymentController : Controller
23+
{
24+
private readonly IBankIdPaymentConfigurationProvider _bankIdPaymentConfigurationProvider;
25+
private readonly IBankIdPaymentService _bankIdPaymentService;
26+
27+
public PaymentController(IBankIdPaymentConfigurationProvider bankIdPaymentConfigurationProvider, IBankIdPaymentService bankIdPaymentService)
28+
{
29+
_bankIdPaymentConfigurationProvider = bankIdPaymentConfigurationProvider;
30+
_bankIdPaymentService = bankIdPaymentService;
31+
}
32+
33+
public async Task<IActionResult> Index()
34+
{
35+
var configurations = await _bankIdPaymentConfigurationProvider.GetAllConfigurationsAsync();
36+
var providers = configurations
37+
.Where(x => x.DisplayName != null)
38+
.Select(x => new ExternalProvider(x.DisplayName ?? x.Key, x.Key));
39+
var viewModel = new BankIdViewModel(providers, $"{Url.Action(nameof(Index))}");
40+
41+
return View(viewModel);
42+
}
43+
44+
[AllowAnonymous]
45+
[HttpPost("Payment")]
46+
public IActionResult Payment([FromQuery] string provider, [FromForm] PaymentRequestModel model)
47+
{
48+
ArgumentNullException.ThrowIfNull(model, nameof(model));
49+
50+
var recipientName = "Demo Merchant Name";
51+
var amount = "100,00";
52+
var currency = "SEK";
53+
var props = new BankIdPaymentProperties(TransactionType.card, recipientName)
54+
{
55+
Money = new(amount, currency),
56+
UserVisibleData = "Demo of Payment with Active Login.",
57+
Items =
58+
{
59+
{"scheme", provider},
60+
{"transactionType", nameof(TransactionType.card)},
61+
{"recipientName", recipientName},
62+
{"amount", amount},
63+
{"currency", currency}
64+
},
65+
};
66+
67+
var returnPath = $"{Url.Action(nameof(Callback))}?provider={provider}";
68+
return this.BankIdInitiatePayment(props, returnPath, provider);
69+
}
70+
71+
[AllowAnonymous]
72+
[HttpPost]
73+
public async Task<IActionResult> Callback(string provider)
74+
{
75+
var result = await _bankIdPaymentService.GetPaymentResultAsync(provider);
76+
if (result?.Succeeded != true || result.BankIdCompletionData == null)
77+
{
78+
throw new Exception("Payment error");
79+
}
80+
81+
return View("Result", new PaymentResultViewModel(
82+
result.BankIdCompletionData.User.PersonalIdentityNumber,
83+
result.BankIdCompletionData.User.Name,
84+
result.BankIdCompletionData.Device.IpAddress,
85+
result.Properties.Items["transactionType"] ?? string.Empty,
86+
result.Properties.Items["recipientName"] ?? string.Empty,
87+
result.Properties.Items["amount"] ?? null,
88+
result.Properties.Items["currency"] ?? null
89+
)
90+
);
91+
}
92+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Standalone.MvcSample.Models;
2+
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
3+
4+
public class PaymentRequestModel
5+
{
6+
public string ReturnUrl { get; set; }
7+
8+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace Standalone.MvcSample.Models;
2+
3+
public class PaymentResultViewModel
4+
{
5+
public PaymentResultViewModel(string personalIdentityNumber, string name, string ipAddress, string transactionType, string recipientName, string? amount = null, string? currency = null)
6+
{
7+
PersonalIdentityNumber = personalIdentityNumber;
8+
Name = name;
9+
IpAddress = ipAddress;
10+
TransactionType = transactionType;
11+
RecipientName = recipientName;
12+
Amount = amount;
13+
Currency = currency;
14+
}
15+
16+
public string PersonalIdentityNumber { get; }
17+
public string Name { get; }
18+
public string IpAddress { get; }
19+
public string TransactionType { get; }
20+
public string RecipientName { get; }
21+
public string? Amount { get; }
22+
public string? Currency { get; }
23+
}

samples/Standalone.MvcSample/Program.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using ActiveLogin.Authentication.BankId.AspNetCore;
44
using ActiveLogin.Authentication.BankId.AspNetCore.Auth;
55
using ActiveLogin.Authentication.BankId.AspNetCore.Sign;
6+
using ActiveLogin.Authentication.BankId.AspNetCore.Payment;
67
using ActiveLogin.Authentication.BankId.AspNetCore.UserContext.Device.Resolvers;
78
using ActiveLogin.Authentication.BankId.AzureKeyVault;
89
using ActiveLogin.Authentication.BankId.AzureMonitor;
@@ -109,6 +110,13 @@
109110
bankId.AddOtherDevice(BankIdSignDefaults.OtherDeviceConfigKey, "BankID (OtherDevice)", options => { });
110111
});
111112

113+
// Add Active Login - Payment
114+
services.AddBankIdPayment(bankId =>
115+
{
116+
bankId.AddSameDevice(BankIdPaymentDefaults.SameDeviceConfigKey, "BankID (SameDevice)", options => { });
117+
bankId.AddOtherDevice(BankIdPaymentDefaults.OtherDeviceConfigKey, "BankID (OtherDevice)", options => { });
118+
});
119+
112120
// Add Authorization
113121
builder.Services.AddAuthorization(options =>
114122
{
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
@model Standalone.MvcSample.Models.BankIdViewModel
2+
@{
3+
Layout = "_Layout";
4+
ViewData["Title"] = "Demo (Payment)";
5+
}
6+
7+
<style>
8+
fieldset:invalid {
9+
border: 2px solid red;
10+
}
11+
12+
fieldset:valid {
13+
border: 0;
14+
}
15+
</style>
16+
17+
@if (Model.ExternalProviders.Any())
18+
{
19+
<div class="choose-provider">
20+
<h1 class="mb-5 mt-2 font-weight-normal">Active Payment</h1>
21+
22+
<form enctype="multipart/form-data" method="post">
23+
<div class="d-grid gap-2">
24+
25+
<input type="hidden" asp-for="ReturnUrl" />
26+
27+
@foreach (var provider in Model.ExternalProviders)
28+
{
29+
<input type="submit" class="btn btn-primary btn-block btn-lg" asp-route-provider="@provider.BankIdScheme" value="@provider.DisplayName" />
30+
}
31+
</div>
32+
</form>
33+
</div>
34+
}
35+
36+
@if (!Model.ExternalProviders.Any())
37+
{
38+
<div class="alert alert-warning">
39+
<strong>Invalid payment request</strong>
40+
There are no payment schemes configured for this client.
41+
</div>
42+
}

0 commit comments

Comments
 (0)