Wenche.MvaMelding (wenche v0.3.0)

Copy Markdown View Source

MVA-melding (VAT return) submission to Skatteetaten via Altinn 3.

Supports submitting and validating MVA-meldinger for Norwegian companies.

Authentication

Skatteetaten's MVA-melding API only supports ID-porten (end-user authentication). Maskinporten and system users are not supported — there is no Wenche helper to obtain a token here, and the systembruker flow cannot grant MVA rights.

Callers must obtain an Altinn platform token themselves by:

  1. Authenticating the end user via ID-porten
  2. Exchanging the resulting token for an Altinn token

The same Altinn token is used for both validation and submission. The scope tuples required from ID-porten are:

  • Validation: openid skatteetaten:mvameldingvalidering
  • Submission: openid altinn:instances.read altinn:instances.write

(The deprecated skatteetaten:mvameldinginnsending scope is being phased out during 2026 and replaced by the two Altinn instance scopes above.)

Pass the resulting Altinn token as :token to valider/2 or via the AltinnClient for send_inn/3.

MVA Data

The mva_data parameter is a map with these keys:

  • :org_nummer — organization number (string)
  • :termin — bi-monthly period 1-6
  • :year — tax year (integer)
  • :linjer — list of %{mva_kode: integer, grunnlag: number, sats: number, merverdiavgift: number}
  • :fastsatt_merverdiavgift — total MVA to pay (negative = refund)
  • :system_name — name of submitting system (default "Kontira")

Experimental

This module is experimental and untested in production.

Summary

Functions

Submits an MVA-melding to Skatteetaten via Altinn 3.

Validates an MVA-melding against Skatteetaten's validation API.

Functions

send_inn(mva_data, client, opts \\ [])

Submits an MVA-melding to Skatteetaten via Altinn 3.

Mirrors the Skatteetaten reference flow documented at https://skatteetaten.github.io/mva-meldingen/english/implementationguide/ (sequence diagram in github.com/Skatteetaten/mva-meldingen, docs/documentation/api/Mva-Melding-Innsending-Sekvensdiagram.txt):

  1. Generate konvolutt (envelope) and melding XML
  2. POST /instances — create Altinn instance
  3. PUT /data/{guid} (mvaMeldingInnsending) — upload konvolutt
  4. POST /data?dataType=mvamelding — upload melding
  5. PUT /process/next ("Fullfør Utfylling") — utfylling → bekreftelse
  6. PUT /process/next ("Fullfør Innsending") — bekreftelse → tilbakemelding

Both process/next calls return 200; the second one's response is what Skatteetaten's betalingsinformasjon.xml is generated from. We use fullfoor_instans/3 for both because Skatteetaten labels both transitions as "Fullfør" steps in their documentation — neste_prosesssteg/3 would work identically (same endpoint, same accepted status codes for this app).

To inspect the generated XML without submitting, call Wenche.MvaMeldingXml.generer_konvolutt_xml/1 and Wenche.MvaMeldingXml.generer_melding_xml/1 directly.

Returns {:ok, inbox_url} or {:error, reason}.

valider(mva_data, opts \\ [])

Validates an MVA-melding against Skatteetaten's validation API.

Options

  • :env"test" or "prod" (default: "prod")
  • :token — Altinn/Maskinporten token for authentication
  • :req_options — additional options merged into Req calls (e.g. for test stubs)

Skatteetaten's validation endpoint always responds with an XML <valideringsresultat> document — even when the melding is invalid the HTTP status is 200 and the payload describes the rule violations. This function parses that document into:

%{
  avvik_ved_meldingslevering: "ok" | "ugyldig skattemelding" | "advarsel" | nil,
  avvik: [
    %{
      sti_til_avvik: String.t() | nil,
      mva_kode: String.t() | nil,
      begrunnelse: String.t() | nil,
      avvikstype: String.t() | nil,
      avvik_kode: String.t() | nil,
      regel_definisjon: String.t() | nil
    }
  ],
  raw_xml: String.t()
}

Returns {:ok, validation_result} or {:error, reason}.