-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Document setting up payment in Altinn-3 apps (#1577)
* chore: add pages for documentation * fix: update wording in warning * feat: first version of payment docs * chore: fixed links and updated content * Add some more detail to backend payment docs. * Update payment DI doc * payment doc update * Document policy requirements for payment. * Add tags to payment docs. * use correct config for paymentDetails component * update receipt layout documentation --------- Co-authored-by: Adam Haeger <[email protected]> Co-authored-by: Bjørn Tore Gjerde <[email protected]>
- Loading branch information
1 parent
f5fc7e1
commit ebb5ff3
Showing
6 changed files
with
605 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
--- | ||
title: Payment | ||
linktitle: Payment | ||
description: Follow these steps to implement Payment in your App | ||
tags: [payment] | ||
weight: 10 | ||
--- | ||
|
||
1. Create NETS Easy agreement here: [payments.nets.eu](https://payments.nets.eu/nb-NO/checkout). | ||
2. [Backend configuration](/app/guides/payment/backend-configuration/). | ||
3. [Frontend configuration](/app/guides/payment/frontend-configuration/) | ||
|
||
{{<children />}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
--- | ||
title: Betaling | ||
linktitle: Betaling | ||
description: Følg disse grunnleggende stegene for å komme i gang med å integrere betaling i din Altinn App. | ||
tags: [betaling] | ||
weight: 10 | ||
--- | ||
|
||
### Før du starter | ||
|
||
Organisasjonen du lager appen for må ha en NETS Easy avtale. | ||
Du finner informasjon om hvordan du oppretter avtalen her: | ||
[payments.nets.eu](https://payments.nets.eu/nb-NO/checkout). | ||
|
||
{{<children />}} |
172 changes: 172 additions & 0 deletions
172
content/app/guides/payment/backend-configuration/_index.en.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
--- | ||
title: Backend configuration | ||
description: Set up your backend to handle payment. | ||
weight: 1 | ||
--- | ||
|
||
### 1. Create a data type to store payment information: | ||
|
||
This data type is used to store information and status about the payment. Put it in the `dataTypes` array in `App/config/applicationmetadata.json`. ID can be set to something else, but it must match the ID entered in `paymentDataType` in the process step, as shown in step 2. | ||
|
||
```json | ||
{ | ||
"id": "paymentInformation", | ||
"allowedContentTypes": [ | ||
"application/json" | ||
], | ||
"maxCount": 1, | ||
"minCount": 0, | ||
} | ||
``` | ||
|
||
|
||
### 2. Extend the app process with payment task: | ||
|
||
A process step and a gateway must be added to `App/config/process/process.bpmn`, as in the example below. | ||
|
||
Payment uses three user actions. If the Altinn user interface is used by the app, these will be called automatically when you are in the payment step. If only the API is used, these must be called manually via the `/actions` endpoint. | ||
- `pay`: Initiates the payment, often by making API calls to the payment processor. How to check which payment processor is used is described [here](#4-implement-the-iorderdetailscalculator-interface). Information and status about the initiated payment is stored in a JSON data type specified in the payment process step. | ||
- `confirm`: Called when payment has been completed to drive the process to the next step. | ||
- `reject`: If the end user sees something wrong with the order, the person concerned can press "Back" in the payment step. The payment is then canceled and information about the interrupted payment is deleted. Which process step you are then directed to is specified in a gateway, as exemplified below. | ||
|
||
```xml | ||
<bpmn:startEvent id="StartEvent_1"> | ||
<bpmn:outgoing>Flow_start_t1</bpmn:outgoing> | ||
</bpmn:startEvent> | ||
|
||
<bpmn:sequenceFlow id="Flow_start_t1" sourceRef="StartEvent_1" targetRef="Task_1" /> | ||
|
||
<bpmn:task id="Task_1" name="Utfylling"> | ||
<bpmn:incoming>Flow_start_t1</bpmn:incoming> | ||
<bpmn:incoming>Flow_g1_t1</bpmn:incoming> | ||
<bpmn:outgoing>Flow_t1_t2</bpmn:outgoing> | ||
<bpmn:extensionElements> | ||
<altinn:taskExtension> | ||
<altinn:taskType>data</altinn:taskType> | ||
</altinn:taskExtension> | ||
</bpmn:extensionElements> | ||
</bpmn:task> | ||
|
||
<bpmn:sequenceFlow id="Flow_t1_t2" sourceRef="Task_1" targetRef="Task_2" /> | ||
|
||
<bpmn:task id="Task_2" name="Betaling"> | ||
<bpmn:incoming>Flow_t1_t2</bpmn:incoming> | ||
<bpmn:outgoing>Flow_t2_g1</bpmn:outgoing> | ||
<bpmn:extensionElements> | ||
<altinn:taskExtension> | ||
<altinn:taskType>payment</altinn:taskType> | ||
<altinn:actions> | ||
<altinn:action>confirm</altinn:action> | ||
<altinn:action>pay</altinn:action> | ||
<altinn:action>reject</altinn:action> | ||
</altinn:actions> | ||
<altinn:paymentConfig> | ||
<altinn:paymentDataType>paymentInformation</altinn:paymentDataType> | ||
</altinn:paymentConfig> | ||
</altinn:taskExtension> | ||
</bpmn:extensionElements> | ||
</bpmn:task> | ||
|
||
<bpmn:sequenceFlow id="Flow_t2_g1" sourceRef="Task_2" targetRef="Gateway_1" /> | ||
|
||
<bpmn:exclusiveGateway id="Gateway_1"> | ||
<bpmn:incoming>Flow_t2_g1</bpmn:incoming> | ||
<bpmn:outgoing>Flow_g1_t1</bpmn:outgoing> | ||
<bpmn:outgoing>Flow_g1_end</bpmn:outgoing> | ||
</bpmn:exclusiveGateway> | ||
|
||
<bpmn:sequenceFlow id="Flow_g1_t1" sourceRef="Gateway_1" targetRef="Task_1"> | ||
<bpmn:conditionExpression>["equals", ["gatewayAction"], "reject"]</bpmn:conditionExpression> | ||
</bpmn:sequenceFlow> | ||
<bpmn:sequenceFlow id="Flow_g1_end" sourceRef="Gateway_1" targetRef="EndEvent_1"> | ||
<bpmn:conditionExpression>["equals", ["gatewayAction"], "confirm"]</bpmn:conditionExpression> | ||
</bpmn:sequenceFlow> | ||
|
||
<bpmn:endEvent id="EndEvent_1"> | ||
<bpmn:incoming>Flow_g1_end</bpmn:incoming> | ||
</bpmn:endEvent> | ||
``` | ||
The value of this node: `<altinn:paymentDataType>paymentInformation</altinn:paymentDataType>` must match the ID of the data type you configured in the previous step. | ||
|
||
### 3. Ensure correct authorization for payment process task: | ||
|
||
The user making the payment needs to have authorization for the `read`, `write`, `pay`, `confirm` and `reject` actions on the payment task. | ||
|
||
### 4. Implement the IOrderDetailsCalculator interface: | ||
|
||
Add a new class where you have your custom code, for example: `App/logic/OrderDetailsCalculator.cs`. | ||
|
||
Here you will implement your logic to calculate what the user will pay for. | ||
For example, you can add order lines based on form data, add mandatory fees, or add a fixed cost for the form. | ||
|
||
The return value from the `CalculateOrderDetails` method indicates: | ||
- Payment processor to be used for the order. These are made available by implementing the `IPaymentProcessor` interface and registering them as transient in program.cs. Fill in `Nets Easy' to use the default implementation for Nets Easy. | ||
- Currency | ||
- Order lines | ||
- Details of payment receiver. Used in receipt. | ||
|
||
In this example, the order lines are calculated based on form data: | ||
|
||
```c# | ||
public class OrderDetailsCalculator : IOrderDetailsCalculator | ||
{ | ||
private readonly IDataClient _dataClient; | ||
|
||
public OrderDetailsCalculator(IDataClient dataClient) | ||
{ | ||
_dataClient = dataClient; | ||
} | ||
|
||
public async Task<OrderDetails> CalculateOrderDetails(Instance instance, string? language) | ||
{ | ||
DataElement modelData = instance.Data.Single(x => x.DataType == "model"); | ||
InstanceIdentifier instanceIdentifier = new(instance); | ||
|
||
Form formData = (Form) await _dataClient.GetFormData(instanceIdentifier.InstanceGuid, typeof(Form), instance.Org, instance.AppId, | ||
instanceIdentifier.InstanceOwnerPartyId, new Guid(modelData.Id)); | ||
|
||
List<PaymentOrderLine> paymentOrderLines = formData.GoodsAndServicesProperties.Inventory.InventoryProperties | ||
.Where(x => !string.IsNullOrEmpty(x.NiceClassification) && !string.IsNullOrEmpty(x.GoodsAndServices)) | ||
.Select((x, index) => | ||
new PaymentOrderLine | ||
{ | ||
Id = index.ToString(), Name = $"{GetLocalizedName(x.Id, language)}", PriceExVat = GetPriceForInventoryItem(x), Quantity = 1, VatPercent = 0M | ||
}) | ||
.ToList(); | ||
|
||
return new OrderDetails { | ||
PaymentProcessorId = "Nets Easy", | ||
Currency = "NOK", | ||
OrderLines = paymentOrderLines, | ||
Receiver = GetReceiverDetails()}; | ||
} | ||
} | ||
|
||
``` | ||
|
||
Register IOrderDetailsCalculator implementation in program.cs: | ||
```c# | ||
void RegisterCustomAppServices(IServiceCollection services, IConfiguration config, IWebHostEnvironment env) | ||
{ | ||
// Register your apps custom service implementations here. | ||
services.AddTransient<IOrderDetailsCalculator, OrderDetailsCalculator>(); | ||
} | ||
``` | ||
|
||
|
||
### 5. Add config to appsettings.json: | ||
|
||
1. [Get your secret key from nets.](https://developer.nexigroup.com/nexi-checkout/en-EU/docs/access-your-integration-keys/). Make sure you use the test key during development. | ||
2. Add your secret key to keyvault, with the variable name: `NetsPaymentSettings--SecretApiKey`. This way it will override `SecretApiKey` in `appsettings.json`. | ||
3. Add `NetsPaymentSettings` to your `appsettings.json`. Remember to set the correct `baseUrl` in production. | ||
```json | ||
{ | ||
"NetsPaymentSettings": { | ||
"SecretApiKey": "In keyvault", | ||
"BaseUrl": "https://test.api.dibspayment.eu/", | ||
"TermsUrl": "https://www.yourwebsite.com/terms", | ||
"ShowOrderSummary": true, | ||
"ShowMerchantName": true | ||
} | ||
} | ||
``` |
170 changes: 170 additions & 0 deletions
170
content/app/guides/payment/backend-configuration/_index.nb.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
--- | ||
title: Backend konfigurasjon | ||
description: Sett opp din backend til å håndtere betaling. | ||
weight: 1 | ||
--- | ||
|
||
### 1. Opprett en datatype for å lagre betalingsinformasjon: | ||
|
||
Denne datatypen benyttes av betalingssteget for å lagre informasjon og status om betalingen. Legg den i `App/config/applicationmetadata.json` sin `dataTypes` array. ID kan settes til noe annet, men det må matche ID-en som legges inn i `paymentDataType` i prossessteget, som vist i punkt 2. | ||
|
||
```json | ||
{ | ||
"id": "paymentInformation", | ||
"allowedContentTypes": [ | ||
"application/json" | ||
], | ||
"maxCount": 1, | ||
"minCount": 0, | ||
} | ||
``` | ||
|
||
### 2. Utvid app prossesen med payment task: | ||
|
||
Det må legges til et prosessteg og en gateway i `App/config/process/process.bpmn`, som i eksemplet nedenfor. | ||
|
||
Betaling benytter tre user actions. Dersom Altinn brukergrensesnittet brukes av appen, så vil disse bli kalt automatisk når man står i betalingssteget. Om kun API benyttes så må disse kalles manuelt via `/actions` endepunktet. | ||
- `pay`: Setter i gang betalingen, ofte ved å gjøre API-kall til betalingsbehandler. Hvordan man kontrollerer hvilken betalingsbehandler som benyttes beskrives [her](#4-implementer-iorderdetailscalculator-interfacet-i-c). Informasjon og status om den igangsatte betalingen lagres i en JSON-datatype som angis i prosesssteget for betaling. | ||
- `confirm`: Kalles når betaling er ferdig gjennomført for å drive prosessen videre til neste steg. | ||
- `reject`: Dersom sluttbruker ser noe feil med ordren så kan vedkommede trykke "Tilbake" i betalingssteget. Da kanselleres betalingen og informasjon om den avbrutte betalingen slettes. Hvilket prosessteg man deretter ledes til angis i en gateway, som eksemplifisert nedenfor. | ||
|
||
```xml | ||
<bpmn:startEvent id="StartEvent_1"> | ||
<bpmn:outgoing>Flow_start_t1</bpmn:outgoing> | ||
</bpmn:startEvent> | ||
|
||
<bpmn:sequenceFlow id="Flow_start_t1" sourceRef="StartEvent_1" targetRef="Task_1" /> | ||
|
||
<bpmn:task id="Task_1" name="Utfylling"> | ||
<bpmn:incoming>Flow_start_t1</bpmn:incoming> | ||
<bpmn:incoming>Flow_g1_t1</bpmn:incoming> | ||
<bpmn:outgoing>Flow_t1_t2</bpmn:outgoing> | ||
<bpmn:extensionElements> | ||
<altinn:taskExtension> | ||
<altinn:taskType>data</altinn:taskType> | ||
</altinn:taskExtension> | ||
</bpmn:extensionElements> | ||
</bpmn:task> | ||
|
||
<bpmn:sequenceFlow id="Flow_t1_t2" sourceRef="Task_1" targetRef="Task_2" /> | ||
|
||
<bpmn:task id="Task_2" name="Betaling"> | ||
<bpmn:incoming>Flow_t1_t2</bpmn:incoming> | ||
<bpmn:outgoing>Flow_t2_g1</bpmn:outgoing> | ||
<bpmn:extensionElements> | ||
<altinn:taskExtension> | ||
<altinn:taskType>payment</altinn:taskType> | ||
<altinn:actions> | ||
<altinn:action>confirm</altinn:action> | ||
<altinn:action>pay</altinn:action> | ||
<altinn:action>reject</altinn:action> | ||
</altinn:actions> | ||
<altinn:paymentConfig> | ||
<altinn:paymentDataType>paymentInformation</altinn:paymentDataType> | ||
</altinn:paymentConfig> | ||
</altinn:taskExtension> | ||
</bpmn:extensionElements> | ||
</bpmn:task> | ||
|
||
<bpmn:sequenceFlow id="Flow_t2_g1" sourceRef="Task_2" targetRef="Gateway_1" /> | ||
|
||
<bpmn:exclusiveGateway id="Gateway_1"> | ||
<bpmn:incoming>Flow_t2_g1</bpmn:incoming> | ||
<bpmn:outgoing>Flow_g1_t1</bpmn:outgoing> | ||
<bpmn:outgoing>Flow_g1_end</bpmn:outgoing> | ||
</bpmn:exclusiveGateway> | ||
|
||
<bpmn:sequenceFlow id="Flow_g1_t1" sourceRef="Gateway_1" targetRef="Task_1"> | ||
<bpmn:conditionExpression>["equals", ["gatewayAction"], "reject"]</bpmn:conditionExpression> | ||
</bpmn:sequenceFlow> | ||
<bpmn:sequenceFlow id="Flow_g1_end" sourceRef="Gateway_1" targetRef="EndEvent_1"> | ||
<bpmn:conditionExpression>["equals", ["gatewayAction"], "confirm"]</bpmn:conditionExpression> | ||
</bpmn:sequenceFlow> | ||
|
||
<bpmn:endEvent id="EndEvent_1"> | ||
<bpmn:incoming>Flow_g1_end</bpmn:incoming> | ||
</bpmn:endEvent> | ||
``` | ||
NB: Verdien til denne noden: `<altinn:paymentDataType>paymentInformation</altinn:paymentDataType>` må matche ID-en til datatypen du konfigurerte i forrige steg. | ||
|
||
### 3. Gi tilganger til den som skal betale: | ||
|
||
Brukeren som skal betale må ha rettigheter til `read`, `write`, `pay`, `confirm` og `reject` handlingene på betalingprosessteget. | ||
|
||
|
||
### 4. Implementer IOrderDetailsCalculator interfacet i C#: | ||
|
||
Legg til en ny klasse der du har din custom kode, f.eks: `App/logic/OrderDetailsCalculator.cs`. | ||
|
||
Her vil du implementere din logikk for å kalkulere hva brukeren skal betale for. | ||
Du kan for eksempel aksessere skjemadata, legge til obligatoriske avgifter, eller kun legge til en fast kostnad for skjemaet. | ||
|
||
Returverdien fra `CalculateOrderDetails` metoden angir: | ||
- Betalingsbehandler som skal benyttes for ordren. Disse tilgjengeliggjøres ved å implementere interfacet `IPaymentProcessor` og registrere de som transient i program.cs. Fyll ut `Nets Easy` for å benytte standardimplementasjon for Nets Easy. | ||
- Valuta | ||
- Ordrelinjer | ||
- Detaljer om betalingsmottaker. Brukes i kvittering. | ||
|
||
I dette eksempelet regnes ordrelinjene ut basert på skjemadata: | ||
|
||
```c# | ||
public class OrderDetailsCalculator : IOrderDetailsCalculator | ||
{ | ||
private readonly IDataClient _dataClient; | ||
|
||
public OrderDetailsCalculator(IDataClient dataClient) | ||
{ | ||
_dataClient = dataClient; | ||
} | ||
|
||
public async Task<OrderDetails> CalculateOrderDetails(Instance instance, string? language) | ||
{ | ||
DataElement modelData = instance.Data.Single(x => x.DataType == "model"); | ||
InstanceIdentifier instanceIdentifier = new(instance); | ||
|
||
Form formData = (Form) await _dataClient.GetFormData(instanceIdentifier.InstanceGuid, typeof(Form), instance.Org, instance.AppId, | ||
instanceIdentifier.InstanceOwnerPartyId, new Guid(modelData.Id)); | ||
|
||
List<PaymentOrderLine> paymentOrderLines = formData.GoodsAndServicesProperties.Inventory.InventoryProperties | ||
.Where(x => !string.IsNullOrEmpty(x.NiceClassification) && !string.IsNullOrEmpty(x.GoodsAndServices)) | ||
.Select((x, index) => | ||
new PaymentOrderLine | ||
{ | ||
Id = index.ToString(), Name = $"{GetLocalizedName(x.Id, language)}", PriceExVat = GetPriceForInventoryItem(x), Quantity = 1, VatPercent = 0M | ||
}) | ||
.ToList(); | ||
|
||
return new OrderDetails { | ||
PaymentProcessorId = "Nets Easy", | ||
Currency = "NOK", | ||
OrderLines = paymentOrderLines, | ||
Receiver = GetReceiverDetails()}; | ||
} | ||
} | ||
``` | ||
|
||
Registrer IOrderDetailsCalculator implementasjonen i program.cs: | ||
```c# | ||
void RegisterCustomAppServices(IServiceCollection services, IConfiguration config, IWebHostEnvironment env) | ||
{ | ||
// Register your apps custom service implementations here. | ||
services.AddTransient<IOrderDetailsCalculator, OrderDetailsCalculator>(); | ||
} | ||
``` | ||
|
||
### 5. Legg til config i appSettings.json: | ||
|
||
1. [Hent din hemmelige nøkkel fra nets.](https://developer.nexigroup.com/nexi-checkout/en-EU/docs/access-your-integration-keys/). Pass på at du bruker testnøkkelen under utvikling. | ||
2. Legg til din hemmelige nøkkel i keyvault, med variabelnavnet: `NetsPaymentSettings--SecretApiKey`. På denne måten vil den overstyre `SecretApiKey` i `appsettings.json`. | ||
3. Legg til `NetsPaymentSettings` i din `appsettings.json`. Husk å sett riktig `baseUrl` i produksjon. | ||
```json | ||
{ | ||
"NetsPaymentSettings": { | ||
"SecretApiKey": "In keyvault", | ||
"BaseUrl": "https://test.api.dibspayment.eu/", | ||
"TermsUrl": "https://www.yourwebsite.com/terms", | ||
"ShowOrderSummary": true, | ||
"ShowMerchantName": true | ||
} | ||
} | ||
``` |
Oops, something went wrong.