Skip to content

[Fact 06] Betaalwijze & SEPA-incassomachtiging #151

Description

@MiniMaxi-user

Onderdeel van epic #145 (Facturatie-module). Bouwt voort op Fact 01 (#146:
Invoice/InvoiceLine, OwnerBusinessProfile, enums), Fact 02 (#147:
autorisatie/inzage, assertCanManageInvoice), Fact 03/04 (#148/#149:
concept-factuur + voorvullen) en Fact 05 (#150: factuur-PDF, nummering,
bouwFactuurPdfData/FactuurPdfContext/FactuurPdfData in src/features/facturen/).
Fact 05 heeft "betaalwijze & SEPA-incassomachtiging op de PDF" expliciet naar deze
story (Fact 06) doorgeschoven.

User Story

Als staleigenaar of stalmedewerker (OWNER/STAFF)
wil ik per te factureren eigenaar de betaalwijze vastleggen (overboeking of doorlopende
SEPA-incasso) en bij incasso een SEPA-machtiging registreren (tenaamstelling, IBAN,
mandaatkenmerk en mandaatdatum), met IBAN-validatie

zodat de juiste betaalinstructie automatisch op de factuur-PDF verschijnt: een
overboekingsinstructie of de incasso-aankondiging met mandaatgegevens, en de boekhouding
klopt, zonder dat ik dit per factuur opnieuw hoef in te tikken.

Context

De facturatie-keten (Fact 01-05) levert een definitieve factuur met PDF, maar zegt nog
niet hoe de ontvanger betaalt. Uit het Stal Jasper-leasecontract (#142) was de
betaalwijze/incasso bewust geparkeerd naar de facturatie-epic; dit is die story.

Twee betaalwijzen zijn in MVP-scope:

  1. Overboeking - de factuur toont een overboekingsinstructie (graag voldoen binnen
    op rekening , met factuurnummer als kenmerk).
  2. Doorlopende SEPA-incasso - de stal incasseert automatisch. Hiervoor moet een
    SEPA-incassomachtiging (mandaat) geregistreerd zijn: tenaamstelling rekeninghouder,
    IBAN, een uniek mandaatkenmerk en de mandaatdatum (datum van ondertekening/akkoord).
    De factuur toont dan een incasso-aankondiging i.p.v. een overboekingsinstructie.

De betaalwijze + mandaatgegevens horen bij de ontvanger (de te factureren eigenaar),
omdat een eigenaar doorgaans op een manier betaalt voor al zijn paarden/contracten bij de
stal. De gegevens worden vastgelegd op OwnerBusinessProfile (al 1-1 aan User, al
gebruikt als factuurgegevens-bron door Fact 05) en bij het definitief maken als momentopname
op de factuur vastgelegd, zodat een latere mandaatwijziging een reeds uitgereikte factuur
niet verandert.

Voor de overboekingsinstructie is een IBAN + tenaamstelling van de stal (afzender)
nodig. Die staan nog niet op Stable; deze story voegt ze toe (Stable.iban,
Stable.accountHolder). Dit is bewust klein gehouden (twee tekstvelden, geen KvK/btw-
discussie uit Fact 05).

Geen externe betaalintegratie: conform CLAUDE.md (geen externe integraties in de MVP
tenzij expliciet) genereert deze story geen bank-/incassobestand (PAIN.008/SEPA-XML) en
legt geen koppeling met een bank of PSP. Het gaat puur om het vastleggen van de
betaalwijze/mandaat en het tonen op de PDF. Het feitelijk innen, betaalstatus-overgangen
en herinneringen vallen onder Fact 07.

Scope

Binnen scope:

  • Datamodel (Prisma + migratie):
    • Op OwnerBusinessProfile (ontvanger): een betaalwijze-enum en de mandaatvelden.
      • paymentMethod PaymentMethod @default(OVERBOEKING) met enum PaymentMethod { OVERBOEKING, SEPA_INCASSO }.
      • sepaAccountHolder String? (tenaamstelling rekeninghouder).
      • sepaIban String? (IBAN, genormaliseerd opgeslagen: zonder spaties, hoofdletters).
      • sepaMandateReference String? (uniek mandaatkenmerk).
      • sepaMandateDate DateTime? (datum ondertekening/akkoord van het mandaat).
    • Op Stable (afzender, voor overboekingsinstructie): iban String? en
      accountHolder String? (tenaamstelling). Twee tekstvelden, niets meer.
    • Op Invoice (momentopname bij definitief maken, zodat de PDF reproduceerbaar is en
      een latere mandaatwijziging een uitgereikte factuur niet wijzigt):
      paymentMethod PaymentMethod?, sepaAccountHolder String?, sepaIban String?,
      sepaMandateReference String?, sepaMandateDate DateTime?. Allemaal nullable; gevuld
      bij maakFactuurDefinitief vanuit het profiel van de ontvanger.
    • (Schemawijziging mag zonder vooraf overleg, project memory; strikt binnen deze scope.)
  • IBAN-validatie (server-side, gedeelde helper): een pure functie in
    src/features/facturen/ (bv. iban.ts) die een ingevoerde IBAN normaliseert (spaties weg,
    hoofdletters) en valideert volgens de standaard ISO 13616 / mod-97-10-check (lengte +
    landcode + 97-modulo gelijk aan 1). Test-vriendelijk met unit tests; geen externe lib nodig.
    Validatie wordt afgedwongen in de server action; ongeldige IBAN geeft een nette Nederlandse
    foutmelding. Tenaamstelling en mandaatkenmerk zijn verplicht zodra betaalwijze = SEPA-incasso.
  • Server actions (in src/features/facturen/actions.ts of een nabije module, OWNER/STAFF
    geautoriseerd via de bestaande Fact 02-helpers):
    • betaalwijze + mandaatgegevens van een eigenaar opslaan/wijzigen (op
      OwnerBusinessProfile); bij SEPA worden tenaamstelling, IBAN (geldig), mandaatkenmerk en
      mandaatdatum vereist; bij overboeking mogen de mandaatvelden leeg blijven.
    • stal-betaalgegevens (IBAN + tenaamstelling) opslaan op Stable.
    • maakFactuurDefinitief (Fact 05) uitbreiden: kopieer de actuele betaalwijze +
      (bij SEPA) mandaatgegevens van de ontvanger als momentopname naar de Invoice binnen
      dezelfde transactie als de nummertoekenning. Weiger definitief maken wanneer betaalwijze =
      SEPA maar er geen geldig mandaat is geregistreerd (nette NL-melding).
  • UI (Nederlands, design-tokens):
    • Betaalwijze + SEPA-mandaatvelden toevoegen aan het bestaande eigenaar-bewerkscherm
      (EigenaarBewerkenForm, admin) als eigen sectie "Betaalwijze". De mandaatvelden tonen
      voorwaardelijk wanneer "SEPA-incasso" gekozen is (zoals separateInvoiceAddress nu al
      voorwaardelijk velden toont). Het IBAN-veld toont een nette validatiemelding.
    • Stal-betaalgegevens (IBAN + tenaamstelling) in het bestaande stal-bewerkscherm.
    • Geen nieuw los scherm en geen nieuw sidebar-navigatie-item (overzicht + navigatie =
      Fact 07).
  • PDF (src/features/facturen/): het PDF-datamodel (FactuurPdfData /
    FactuurPdfContext / bouwFactuurPdfData) en FactuurPdfDocument.tsx uitbreiden met een
    betaalblok onderaan:
    • bij overboeking: instructie met stal-IBAN + tenaamstelling, vervaldatum/betaaltermijn
      en het factuurnummer als betalingskenmerk;
    • bij SEPA-incasso: aankondiging dat het bedrag automatisch wordt geincasseerd, met
      (leesbaar) IBAN, tenaamstelling, mandaatkenmerk en mandaatdatum.
      De bedragen/btw blijven uit berekenFactuurTotalen; dit blok voegt alleen de
      betaalinstructie toe. Huisstijl exact als de bestaande factuur-PDF (navy/cream/goud,
      Cormorant + Inter), geen nieuwe PDF-lib, geen tailwind.config.ts.

Buiten scope (latere/andere stories):

  • Daadwerkelijke incasso/bankkoppeling: genereren van SEPA-incassobestanden (PAIN.008/
    SEPA-XML), aanlevering bij de bank, PSP-/iDEAL-integratie. Expliciet niet in de MVP
    (CLAUDE.md). Deze story legt alleen vast en toont op de PDF.
  • Betaalstatus-overgangen (BETAALD/VERVALLEN), verzenden (e-mail/notificatie),
    herinneringen en het facturatie-overzicht + sidebar-navigatie
    behoren tot Fact 07.
  • Btw-/totalenlogica (Fact 03/04) en de nummering/PDF-stack zelf (Fact 05) worden
    hergebruikt, niet gewijzigd (behalve de PDF-uitbreiding met het betaalblok).
  • Mandaat-intrekking/-historie, meerdere mandaten per eigenaar, of een afzonderlijk
    mandaat-PDF/-formulier.
    Een actueel mandaat per eigenaar volstaat voor de MVP; de
    factuur legt het als momentopname vast.
  • Wijzigen van een reeds definitieve factuur (blijft read-only conform Fact 03/05).

Acceptatiecriteria

  • Als een OWNER/STAFF de betaalwijze van een eigenaar op overboeking zet, dan
    worden geen mandaatgegevens vereist en kan de factuur definitief gemaakt worden.
  • Als een OWNER/STAFF de betaalwijze op SEPA-incasso zet, dan zijn
    tenaamstelling, IBAN, mandaatkenmerk en mandaatdatum verplicht; ontbreekt er een,
    dan weigert het opslaan met een nette Nederlandse melding.
  • Als een ongeldige IBAN wordt ingevoerd (faalt op lengte/landcode/mod-97-check),
    dan wordt het opslaan geweigerd met een duidelijke Nederlandse foutmelding; een
    geldige IBAN wordt genormaliseerd opgeslagen (zonder spaties, in hoofdletters).
  • Wanneer een factuur definitief wordt gemaakt (Fact 05), dan worden de actuele
    betaalwijze en (bij SEPA) de mandaatgegevens als momentopname op de Invoice
    vastgelegd binnen dezelfde transactie als de nummertoekenning; een latere wijziging van
    het eigenaar-mandaat verandert die uitgereikte factuur niet.
  • Als de betaalwijze van de ontvanger SEPA-incasso is maar er geen geldig mandaat
    geregistreerd is, dan weigert maakFactuurDefinitief met een nette NL-melding.
  • Als de betaalwijze overboeking is, dan toont de factuur-PDF een
    overboekingsinstructie met de IBAN + tenaamstelling van de stal, de
    vervaldatum/betaaltermijn en het factuurnummer als betalingskenmerk.
  • Als de betaalwijze SEPA-incasso is, dan toont de factuur-PDF een
    incasso-aankondiging met IBAN, tenaamstelling, mandaatkenmerk en mandaatdatum
    (de momentopname-waarden van de factuur, niet het live profiel).
  • De betaalwijze- en mandaatvelden zijn te beheren in het bestaande
    eigenaar-bewerkscherm; de mandaatvelden verschijnen voorwaardelijk bij keuze
    SEPA-incasso. De stal-IBAN + tenaamstelling zijn te beheren in het bestaande
    stal-bewerkscherm. Er is geen nieuw scherm en geen nieuw navigatie-item.
  • Alle UI-teksten zijn Nederlands; de PDF gebruikt de bestaande huisstijl
    (navy/cream/goud, Cormorant + Inter); geen nieuwe PDF-lib, geen tailwind.config.ts.
  • npx prisma migrate dev draait schoon (alleen nieuwe velden/enum, bestaande data
    ongemoeid; paymentMethod default OVERBOEKING op bestaande profielen);
    npx prisma generate en npx tsc --noEmit slagen; de IBAN-validatie heeft unit tests
    (geldige + ongeldige IBANs) en de logica zit in src/features/facturen/.

Technische notities

  • IBAN-validatie zonder externe lib: normaliseer (verwijder spaties, uppercase), check
    landcode-lengte (bv. NL = 18 tekens) plus de generieke ISO 13616 mod-97-10: verplaats de
    eerste vier tekens naar achteren, vervang letters door cijfers (A=10 ... Z=35) en check dat
    de grote integer mod 97 gelijk aan 1. Reken de modulo string-gewijs om bigint-grenzen
    te vermijden. Pure functie + unit tests; geen IO. Plaats in src/features/facturen/iban.ts.
  • Momentopname op Invoice: spiegelt de gedenormaliseerde totalen-aanpak van Fact 01
    (waarden vastleggen op de factuur i.p.v. live joinen). Vul de Invoice-betaalvelden in
    maakFactuurDefinitief binnen de bestaande $transaction, na het bepalen van het nummer en
    voor de PDF-generatie (de PDF heeft de momentopname nodig).
  • PDF-datamodel: breid FactuurPdfData uit met een optioneel betaling-blok
    (wijze OVERBOEKING of SEPA_INCASSO; stalIban; stalTenaamstelling; iban; tenaamstelling;
    mandaatkenmerk; mandaatdatum) en pas bouwFactuurPdfData / FactuurPdfContext navenant aan;
    render een betaalblok in FactuurPdfDocument.tsx. Houd het pure/IO-vrije karakter van
    bouwFactuurPdfData intact (datum injecteerbaar, geen Prisma).
  • Stal-afzendergegevens: Stable.iban + Stable.accountHolder voeden de
    overboekingsinstructie; de Fact 05-open vraag over KvK/btw van de stal blijft buiten scope
    (alleen IBAN + tenaamstelling toevoegen, niets meer).
  • Autorisatie: hergebruik de Fact 02-helpers (assertCanManageInvoice /
    stalrol-controle). Het beheren van eigenaar-betaalgegevens loopt via de bestaande
    admin-/eigenaar-bewerkroute; borg dat alleen OWNER/STAFF (of platform-admin waar dat al
    geldt) deze gegevens kunnen muteren; geen nieuwe rol-uitzonderingen introduceren.
  • Geen circulaire afhankelijkheid met Fact 07: deze story raakt geen betaalstatus,
    verzending of herinneringen; ze vult alleen betaalwijze/mandaat + de PDF-instructie.
  • Prisma CLI draait via npx prisma in C:\Claude\velaro en leest .env.

Open vragen

Geen blokkerende open vragen. De keuzes hieronder zijn als onderbouwd voorstel verwerkt; de
bouwer mag bij sterke voorkeur afwijken zolang de acceptatiecriteria gehaald worden:

  • Betaalwijze + mandaat op OwnerBusinessProfile (per eigenaar), niet per contract.
    Een eigenaar betaalt doorgaans op een manier voor al zijn paarden/contracten bij de stal; dit
    voorkomt duplicatie en versnipperde invoer. Een per-contract-override is een latere
    uitbreiding (niet in deze story). De factuur legt het als momentopname vast, dus afwijken per
    factuur blijft mogelijk via het profiel op het moment van definitief maken.
  • Momentopname op de Invoice i.p.v. live joinen. Gekozen zodat een reeds uitgereikte
    factuur reproduceerbaar blijft en een latere mandaatwijziging hem niet verandert (consistent
    met de gedenormaliseerde totalen uit Fact 01).
  • Geen SEPA-XML/PAIN.008 en geen bankkoppeling in deze story. Conform CLAUDE.md (geen
    externe integraties in de MVP tenzij expliciet). De story legt vast en toont; het feitelijk
    innen is een bewuste, latere keuze (zou een aparte epic/story zijn).
  • Een actueel mandaat per eigenaar (geen mandaat-historie/-intrekking). Voldoende voor de
    MVP; intrekking/historie en meerdere mandaten zijn een latere uitbreiding.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions