Sist endret: 2. okt. 2024

Hvordan konfigurere betaling i din Altinn-app

Følg disse grunnleggende stegene for å komme i gang med å integrere betaling i din Altinn App.

1. 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.

2. Legg til en betalingsoppgave i appens prosess, med tilhørende konfigurasjon

    Oppsettet i denne seksjonen gjøres fra Prosess-siden i appens arbeidsflate. Naviger dit ved å trykke på “Prosess” i toppmenyen fra appens arbeidsflate.

    • Legg til en betalingsoppgave i prosessen ved å velge betalingsoppgave fra menyen til venstre i prosess-editoren og dra den inn i prosessen. Følgende konfigurasjoner oppdateres automatisk:
      • Det legges til 2 datatyper knyttet til betaling (en for data om betaling, en for betalingskvittering).
      • Det legges til en ny sidegruppe tilknyttet betaling, med ferdig en ferdig oppsatt side.
      • Det legges til en ny regel for tilgangsstyring knyttet til betalingsoppgaven - denne må konfigureres senere.
    • Legg til en Gateway etter betalingssteget
      • Gatewayen bør ha 2 sekvensflyter ut:
        • En som peker videre i prosessen.
        • En som peker tilbake til forrige oppgave i prosessen (før betaling).
      • Legg til regler som bestemmer når sekvensflytene som skal brukes:
        • Klikk på sekvensflyten som går videre i prosessen.
          • I panelet til høyre, klikk på “Legg til en ny logikkregel”. Denne settes da opp med aksjonen “Avvise” (reject) som utgangspunkt.
          • Klikk på “Endre” og endre fra “Avvise” til “Bekreft”. Klikk på “Lagre og lukk” for å lagre regelen.
        • Klikk på sekvensflyten som går tilbake i prosessen. Gjenta stegene over, men denne gangen trenger du ikke å endre det oppsettet som legges til - aksjon skal være “Avvise”.

    Eksempel på en prosess med utfylling etterfulgt av betaling
    Eksempel på en prosess med utfylling etterfulgt av betaling

    Her følger en beskrivelse for hvordan man setter opp en betalingsoppgave i prosessen. Dette involverer flere steg, i flere konfigurasjonsfiler. Om du bruker Altinn Studio Designer, så blir all denne konfigurasjonen satt opp automatisk når du legger til betalingsoppgaven i prosessen.

    Opprett to datatyper for å lagre betalingsinformasjon:

    Den første datatypen benyttes av betalingssteget for å lagre informasjon og status om betalingen. Legg den i App/config/applicationmetadata.json sin dataTypes-array.

    {
        "id": "paymentInformation",
        "allowedContentTypes": [
            "application/json"
        ],
        "maxCount": 1,
        "minCount": 0,
    }
    

    Den andre datatypen benyttes for å lagre PDF-kvittering for betalingen. Legg den inn samme sted.

    {
        "id": "paymentReceiptPdf",
        "allowedContentTypes": [
            "application/pdf"
        ],
        "maxCount": 1,
        "minCount": 0,
    }
    

    ID-ene kan settes til noe annet, men det må matche ID-ene som legges inn i paymentDataType og paymentReceiptPdfDataType i prossessteget, som vist i punktet under.

    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-et benyttes, så må disse kalles manuelt via /actions-endepunktet.

    • pay: Setter i gang betalingen, ofte ved å gjøre API-kall til betalingsbehandler. 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 vedkommende 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.
        <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:paymentReceiptPdfDataType>paymentReceiptPdf</altinn:paymentReceiptPdfDataType>
              </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 noden <altinn:paymentDataType>paymentInformation</altinn:paymentDataType> må samsvare med ID-en til datatypen du konfigurerte i forrige steg. Det samme gjelder datatypen for pdf-kvittering.

    Legg til sidegruppe for betaling

    • Legg til en ny mappe under App/ui for betalingsoppgaven din. Kall den f.eks. “payment”.

    • Oppdater filen App/ui/layout-sets.json med ny sidegruppe, som har samme id som mappen du nettopp opprettet. Din oppdaterte layout-sets.json kan se slik ut:

      {
        "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout-sets.schema.v1.json",
        "sets": [
          {
            "id": "form",
            "dataType": "model",
            "tasks": [
              "Task_1"
            ]
          },
          {
            "id": "payment",
            "dataType": "model",
            "tasks": [
              "Task_2"
            ]
          }
        ]
      }
      
    • I din payment layoutSet mappe, legg til en ny fil, payment.json, med følgende layout:

      {
        "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
        "data": {
          "layout": [
            {
              "id": "payment-test-component",
              "type": "Payment",
              "textResourceBindings": {
                "title": "Oppsummering"
              }
            }
          ]
        }
      }
      

      Dette er nødvendig for at betaling skal fungere. Uten dette vil betalingssteget ditt bare vise en hvit side.

    3. Gi tilganger til den som skal betale

      Dette steget foregår i appens prosessverktøy. Naviger til appens arbeidsflate, og velg “Prosess” fra toppmenyen.

      • Velg betalingsoppgaven i prosessverktøyet ved å klikke på den.
        • Se at konfigurasjonspanelet på høyre side oppdateres med detajer fra oppgaven.
      • Klikk på “Tilganger”-seksjonen for å ekspandere den, og velg “Gå til Tilganger”.
      • Scroll ned til bunnen av modalen, og se at det er en regel som er markert i rødt.
      • Legg til rollen(e) som skal ha tilgang til betalingsoppgaven i regelen som er markert i rødt.
        • Når rolle er lagt til, er regelen gyldig og markeres ikke lenger i rødt.
      • Lukk Innstillinger-modalen ved å klikke på X-en øverst til høyre.
      Brukeren som skal betale må ha rettigheter til handlingene read, write, pay, confirm og reject på betalingsprosessteget.

      4. Konfigurer visning av betalingsinformasjon

        Oppsettet i denne seksjonen gjøres fra Lage-siden i appens arbeidsflate. Naviger dit ved å trykke på “Lage” i toppmenyen fra appens arbeidsflate.

        Hvordan vise betalingsinformasjon i skjemaet

        Dette steget skal gjøres i sidegruppen som er tilknyttet selve skjemaet. Du kan bytte mellom sidegrupper i nedtrekkslisten øverst til venstre på Lage-siden. Skjemaoppgaven som følger med appen når den opprettes er tilknyttet en sidegruppe som heter “form”. Dersom du har lagt til andre skjemaoppgaver i prosessen, har sidegruppen samme ID som oppgaven i prosessen.

        • Dra komponenten “Betalingsdetaljer” inn i skjemaet. Denne komponenten viser en tabell som viser elementene brukeren må betale for.

          • Komponenten ligger nederst i “Avansert” i komponentkolonnen til venstre på siden.

          Du kan plassere denne komponenten hvor som helst i skjemaet ditt, men vi anbefaler å i det minste sette det på den siste siden før brukeren blir bedt om å betale.

        • For å få oppdatert ordrelinjene etter hvert som data som brukes til å beregne ordrelinjer endres, må du legge til en mapping til datafeltene som brukes til å beregne ordrelinjene. Dette gjøres foreløpig manuelt, direkte i layout-filene:

        {
          "id": "paymentDetails",
          "type": "PaymentDetails",
          "textResourceBindings": {
            "title": "Oversikt over betaling",
            "description": "Her er en oversikt over hva du skal betale for."
          },
          "mapping": {
            "GoodsAndServicesProperties.Inventory.InventoryProperties": "paymentDetails"
          }
        }
        

        Hvordan vise betalingsinformasjon i betalingsssteget

        Dette er satt opp automatisk dersom du brukte Altinn Studio Designer og prosessverktøyet til å sette opp betalingssteget.

        Hvordan sette opp egen visning for kvittering for betaling (valgfritt)

        Dette steget er valgfritt. Dersom man ikke gjennomfører dette steget, vil oppsettet fra betalingssteget benyttes.

        1. Påse at det er utforming for betalingssteget som vises på Utforming-siden.
        2. Klikk på “Legg til ny side”.
        3. I konfigurasjonspanelet for siden, åpne “PDF” og klikk på “Gjør om siden til PDF”.
        4. Legg til komponenten “Betaling”, og ev. andre tekster og komponenter du ønsker på PDF-siden.
        5. Forhåndsvis PDF-visning ved å klikke på “utviklerverktøy” knappen nederst til høyre i forhåndsvisningen

          Utviklerverktøy

          • Klikk på knappen “Forhåndsvis PDF”

        Legg til OrderDetails-komponenten i skjemaet ditt

        Dette vil vise en tabell som viser elementene brukeren må betale for. Du kan plassere dette hvor som helst i appen din, men vi anbefaler å i det minste sette det på den siste siden før brukeren blir bedt om å betale.

        For å få oppdatert ordrelinjene etter hvert som data som brukes til å beregne ordrelinjer endres, må du legge til en mapping til datafeltene som brukes til å beregne ordrelinjene.

        {
          "id": "paymentDetails",
          "type": "PaymentDetails",
          "textResourceBindings": {
            "title": "Oversikt over betaling",
            "description": "Her er en oversikt over hva du skal betale for."
          },
          "mapping": {
            "GoodsAndServicesProperties.Inventory.InventoryProperties": "paymentDetails"
          }
        }
        

        Legg til layout for kvitteringen (Valgfritt)

        Hvis du vil vise mer informasjon på kvittering som vises til kunden, må du legge til en egen tilpasset layout for den.

        Legg til en layout fil, f.eks. receiptLayout.json.

        Her er et minimalt eksempel:

        {
          "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
          "data": {
            "layout": [
              {
                "id": "test",
                "type": "Payment",
                "renderAsSummary": true
              }
            ]
          }
        }
        

        Oppdater din layoutSet-settings.json-fil, og spesifiser din kvitteringslayout i pdfLayoutName feltet:

        {
          "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layoutSettings.schema.v1.json",
          "pages": {
            "order": [
              "payment"
            ],
            "pdfLayoutName": "paymentReceipt", 
            "showProgress": true,
            "showLanguageSelector": true
          }
        }
        

        Dette er alt du trenger for å vise en gyldig kvittering, men du kan også tilpasse receiptLayout.json ved å legge til ytterligere komponenter, for eksempel en paragrafkomponent hvis du ønsker å legge til ytterligere informasjon.

        5. Beregn hva som skal betales

          Dette steget må gjøres i kode. Se “Manuelt oppsett”-fanen for denne seksjonen for veiledning.

          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 å regne ut 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 metoden CalculateOrderDetails angir:

          • Betalingsbehandler som skal benyttes for ordren. Disse tilgjengeliggjøres ved å implementere interfacet IPaymentProcessor og registrere dem som transient i program.cs. Fyll ut Nets Easy for å benytte standardimplementasjon for Nets Easy.
          • Valuta
          • Ordrelinjer
          • Detaljer om betalingsmottaker. Brukes i kvittering.
          • Detaljer om betaler (valgfritt), dersom du ønsker å forhåndsutfylle denne informasjonen hos Nets Easy. Kan brukes i kombinasjon med Nets Easy sitt flagg MerchantHandlesConsumerData, som vi har eksponert via appsettings.json NetsPaymentSettings.MerchantHandlesConsumerData. Om den er satt til true så må detaljer om betaler sendes med, ellers feiler det.

          I dette eksempelet regnes ordrelinjene ut basert på skjemadata:

          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(),
                    Payer = GetPayerDetails()};
              }
          }
          

          Registrer IOrderDetailsCalculator-implementasjonen i program.cs:

          void RegisterCustomAppServices(IServiceCollection services, IConfiguration config, IWebHostEnvironment env)
          {
              // Register your apps custom service implementations here.
              services.AddTransient<IOrderDetailsCalculator, OrderDetailsCalculator>(); 
          }
          

          6. Koble appen til NETS Easy avtalen

            Dette steget må gjøres manuelt. Støtte for konfigurasjon i Altinn Studio kommer i løpet av høsten 2024. Se “Manuelt oppsett”-fanen for denne seksjonen for veiledning.

            Legg til config i appSettings.json:

            1. Hent din hemmelige nøkkel fra Nets.. Pass på at du bruker testnøkkelen under utvikling.
            2. Gjør appen din klar for bruk av Azure Key Vault som konfigurasjonkilde, om dette ikke allerede er gjort tidligere. Se relevant dokumentasjon.
            3. Legg til din hemmelige nøkkel i Key Vault, med variabelnavnet NetsPaymentSettings--SecretApiKey. På denne måten vil den overstyre SecretApiKey i appsettings.json.
            4. Legg til NetsPaymentSettings i din appsettings.json.
              {
                "NetsPaymentSettings": 
                {
                  "SecretApiKey": "In keyvault",
                  "BaseUrl": "https://test.api.dibspayment.eu/",
                  "TermsUrl": "https://www.yourwebsite.com/terms",
                  "ShowOrderSummary": true,
                  "ShowMerchantName": true,
                  "MerchantHandlesConsumerData": true,
                  "PaymentMethodsConfiguration": [
                    { "Name": "Visa", "Enabled": true },
                    { "Name": "MasterCard", "Enabled": true }
                  ]
                }
              }
              
              • Husk å sette riktig baseUrl i appsettings.Production.json. Eksempelet ovenfor inneholder URL til test API-et.
              • Feltet MerchantHandlesConsumerData er frivillig, og styrer hvorvidt du skal sende med detaljer om betaler, eller om det skal fylles ut i Nets Easy sin betalingsside.
              • PaymentMethodsConfiguration fungerer likt som beskrevet i Nets sin dokumentasjon her.
            5. Lokal mocking av SecretApiKey kan gjøres ved hjelp av user secrets.
              dotnet user-secrets init
              dotnet user-secrets set "NetsPaymentSettings:SecretApiKey" "test-secret-key-used-for-documentation"