Files
2026-09-27 14:21:31 +00:00

13 KiB

Adverteren op MijnBesluit

Een balk op het overzichtsscherm met een korte tekst en een klein logo. Adverteerders boeken zelf een periode en een aantal vertoningen per uur, betalen via bunq, en na goedkeuring door de beheerder gaat de campagne draaien.

Dit stuk beschrijft hoe dat werkt en waarom. Lees het voordat je eraan begint te bouwen.

Waar de advertentie wél en niet komt

Wel: op het overzichtsscherm, op de website en in de app. Een brede tegel met een lichte achtergrond, bóven de vier tegels maar onder "wat er speelt" — die regel waarschuwt je dat er een overlijden op jouw naam is gemeld of dat er iets voor je klaarstaat, en die gaat altijd voor.

Niet: in de kluis, bij momenten, bij de nalatenschap, en nergens in de schermen rond een overlijdensmelding. Iemand die een bericht aan zijn kinderen klaarzet of net heeft gehoord dat er een overlijden op zijn naam is gemeld, hoort daar geen reclame naast te zien. Dat is niet preutsheid maar productbehoud: dat is precies het moment waarop mensen weglopen.

De tegel is laag — een afbeelding, een regel of twee tekst — en draagt zichtbaar het woord Advertentie. Een advertentie die zich voordoet als onderdeel van de applicatie kost je meer vertrouwen dan hij oplevert.

(Eerder stond hier dat de balk ónder de tegels hoort. Dat is op 16 september 2026 gewijzigd: hij staat nu bovenaan, maar wel achter "wat er speelt". De reden om hem laag en herkenbaar te houden blijft staan.)

Hoe wordt gekozen wie er getoond wordt

Per keer dat iemand het overzicht opent is er ruimte voor één advertentie. Er kunnen meerdere campagnes tegelijk lopen, elk met hun eigen aantal per uur. De keuze gaat zo:

  1. Zoek alle campagnes die nu mogen draaien: goedgekeurd, betaald, en binnen hun periode.
  2. Kijk per campagne hoe vaak hij dit klokuur al is getoond.
  3. Laat de campagne zien die het verst achterloopt op zijn eigen aantal - dus de laagste verhouding getoond gedeeld door geboekt.
  4. Zijn ze allemaal op hun aantal, dan verschijnt er niets. Geen opvulling, geen herhaling.
  5. Tel de vertoning op.

Zo krijgt wie meer boekt ook meer, zonder dat de een de ander verdringt, en zonder dat iemand over zijn ingekochte aantal heen gaat.

Let op wat dit betekent voor je voorwaarden. Het geboekte aantal is een maximum, geen belofte. Zijn er in een uur minder bezoekers dan er vertoningen zijn ingekocht, dan haalt niemand zijn aantal. De adverteerder moet daarom in zijn eigen scherm kunnen zien wat er werkelijk is getoond.

Wat er wordt opgeslagen

Adverteerder

Bedrijfsnaam, contactpersoon, e-mailadres, telefoonnummer, adres, postcode, plaats, land, KvK-nummer en btw-nummer. Dit zijn bedrijfsgegevens, geen persoonsgegevens van bezoekers, maar ze vallen wel onder je administratie: de belastingdienst wil ze zeven jaar kunnen zien.

Campagne

  • Wie hem heeft geboekt
  • De tekst, het logo en de link waar hij heen gaat
  • Begindatum en einddatum
  • Aantal vertoningen per uur
  • Het bedrag, het betaalkenmerk, en wanneer de betaling is gezien
  • De stand: concept, wacht-op-betaling, wacht-op-goedkeuring, loopt, afgewezen, gestopt - met bij een afwijzing de reden erbij, zodat de adverteerder weet waarom

Vertoningen

Eén regel per campagne per klokuur met een teller erin, niet één regel per vertoning. Bij een paar honderd bezoekers per uur scheelt dat een tabel die anders eindeloos volloopt, en voor het kiezen én voor de verantwoording is het genoeg.

Kliks worden apart geteld, ook per uur.

Het betaalpad

Bunq.me is een betaalverzoek, geen kassa. Er komt geen bericht terug naar MijnBesluit als er betaald is. De applicatie kan dus niet zelf vaststellen dat een campagne betaald is.

Daarom loopt het zo:

  1. De adverteerder stelt zijn campagne samen en ziet wat het kost.
  2. Hij krijgt de bunq.me-link, met het campagnenummer als omschrijving.
  3. De campagne komt op wacht-op-betaling.
  4. De beheerder ziet in zijn bunq dat er betaald is, met dat nummer erbij, en vinkt de campagne af als betaald.
  5. In hetzelfde scherm keurt hij de inhoud goed of af.
  6. Pas dan gaat hij draaien, vanaf de begindatum.

AdvertentieService.Betaallink zet hem in elkaar:

https://bunq.me/KwikBit?amount=30.00&description=MB-00012

Het adres staat in de instellingen onder Adverteren:BunqMe; een rekening wisselt eerder dan de code. Laat je die instelling leeg, dan komt er geen knop en staan het bedrag en het kenmerk er gewoon om met de hand over te maken - een knop naar de verkeerde rekening is erger dan geen knop.

Twee dingen die makkelijk misgaan en daarom in een zelfcontrole vastliggen:

  • Het bedrag krijgt een punt, wat de taalinstelling van de server ook is. Zonder dat maakt een Nederlandse Windows er 30,00 van, en dan leest bunq een ander bedrag of helemaal geen.
  • Het kenmerk gaat gecodeerd mee. Het is MB-00012: kort genoeg om over te typen als iemand de link niet gebruikt.

Op de knop staat wat je doet en voor hoeveel ("Betaal € 30,00 via bunq"), niet waar de link heen gaat. Dezelfde link staat in de bevestigingsmail, want niet iedereen komt terug in het scherm.

Stap 4 en 5 zijn twee vinkjes in één scherm. Wil je dit later automatisch, dan kan dat met de bunq-API in plaats van een bunq.me-link; dat is een aparte verbouwing en vraagt sleutels en een terugmeldadres.

Waarom goedkeuring vooraf

Zonder goedkeuring kan iedereen die betaalt zijn advertentie naast jouw tekst over uitvaarten krijgen. Je zou het achteraf kunnen weghalen, maar dan is het al gezien. De vertraging voor de adverteerder is klein - je kijkt er dezelfde dag naar - en het verschil in wat er mis kan gaan is groot.

De prijs

bedrag = dagen x vertoningen per uur x tarief

met een bodem eronder. Begin- en einddag tellen allebei mee. Het tarief en de bodem staan in de instellingen (Adverteren:TariefCenten, Adverteren:MinimumCenten) en niet in de code: het is het soort getal dat je na drie maanden wilt bijstellen. Standaard 20 cent en een bodem van 10 euro.

Een voorbeeld: 30 dagen, 5 keer per uur = 150 x 20 cent = 30 euro.

Hoe kom je aan zo'n getal? Reken terug naar wat de adverteerder hoogstens kan krijgen. Vijf keer per uur, 24 uur, 30 dagen is 3600 vertoningen; 30 euro is dan ruim 8 euro per duizend. Aan de lage kant voor een klein, goed omschreven publiek — en dat hoort ook, want dat maximum haalt hij nooit: 's nachts opent niemand het overzicht.

Bij een campagne wordt het gebruikte tarief mee opgeslagen (rate_cents). Verandert het tarief later, dan verandert een al geboekte campagne niet mee — anders valt een oude factuur niet meer na te rekenen.

Wat er nog beslist moet worden

  • Facturen. Moet MijnBesluit ze maken, of doe je dat zelf? Met btw erop hoort er een factuur met een doorlopend nummer bij.
  • Terugbetalen. Wat als een campagne door een storing niet heeft gedraaid?
  • Algemene voorwaarden. Daarin hoort in elk geval te staan dat het aantal een maximum is en dat je een advertentie mag weigeren.

Hoe het in de code zit

Wat Waar
Tabellen db/13_adverteren.sql: adverteerders, campagnes, campagne_uren
Gegevens MijnBesluit.Domain/Entities/Adverteren.cs
Regels MijnBesluit.Application/Services/AdvertentieService.cs
Opslag AdvertentieRepository in Infrastructure
Controles tests/MijnBesluit.SelfCheck/AdvertentieChecks.cs

Een adverteerder is een eigen tabel en geen regel in users. Hij is een bedrijf, geen persoon met een kluis, familie of nalatenschap. Hem bij de gebruikers zetten zou betekenen dat elke vraag over gebruikers hem moet uitzonderen, en één vergeten uitzondering is er één te veel in een applicatie die over nalatenschappen gaat. Inloggen gaat wel op dezelfde manier: een code van zes cijfers per e-mail, met een eigen doel-nummer in login_codes.

De afbeelding staat als blob bij de campagne en niet bij vault_files. Die tabel is voor spullen van gebruikers en is versleuteld met sleutels per gebruiker; een advertentie is juist openbaar. Hij wordt bij het opslaan verkleind.

Wat er bewust niet in zit: iets wat een advertentie koppelt aan wie hem ziet. Er wordt per campagne per uur geteld, meer niet. Geen profielen, geen kenmerken, geen bezoeker die door de applicatie heen te volgen is. Dit is een applicatie waarin mensen hun wachtwoorden en hun laatste woorden bewaren; daar hoort geen advertentienetwerk in te zitten dat meekijkt.

De beheerder krijgt een e-mail zodra er een campagne is ingediend. Vanaf dat moment ligt het bij hem: betaling afvinken en de inhoud beoordelen. Goedkeuren kán niet voordat de betaling is afgevinkt — anders draait er iets waarvoor niet betaald is, en dat merk je pas als het voorbij is.

In welke volgorde te bouwen

  1. Adverteerder en campagne vastleggen, met een beheerscherm om te betalen-af-te-vinken en goed te keuren. Klaar. /beheer/adverteren, bereikbaar vanaf /beheer.
  2. De balk op het overzicht, met de keuzeregels en de tellers. Klaar. Op de website rechtstreeks vanuit AdvertentieService; in de app via de socketactie home.advertentie.
  3. Het scherm waarin een adverteerder zichzelf aanmeldt en een campagne samenstelt, met de bunq.me-link erin. Klaar. /adverteren en /adverteren/nieuw.
  4. Het scherm waarin hij ziet wat er werkelijk is getoond. Klaar. /adverteren/campagnes.

Zo staat het werkende deel er eerst, en kun je met één handmatig ingevoerde campagne al zien of die balk doet wat je wilt voordat er een adverteerder bij komt.

Het adverteerdersgedeelte

Drie pagina's, allemaal statisch weergegeven en niet interactief:

  • /adverteren - wat het is, wat het kost, en inloggen met een code per e-mail. De ingang staat op het inlogscherm, onder "Ik ben nieuw en wil een account aanmaken".
  • /adverteren/nieuw - de campagne samenstellen.
  • /adverteren/campagnes - zijn campagnes, de stand, de betaalregel, en wat er is getoond.

Een adverteerder logt in met een eigen cookie en een eigen schema (Adverteerderslogin), los van de gebruikers van MijnBesluit, en dat cookie gaat alleen mee naar /adverteren. Dat is geen netheid. Met één cookie en een claim erin hangt het verschil tussen een adverteerder en iemand met een kluis af van de vraag of elk scherm aan die ene controle denkt; met twee schema's kan dat niet misgaan. Om dezelfde reden zijn die schermen niet interactief: het nummer van de adverteerder komt bij elk verzoek vers uit dat cookie en hoeft dus nergens te blijven staan.

De code die hij per e-mail krijgt heeft een eigen doel (LoginCodePurpose.Adverteerder). Een adverteerderscode geeft dus nooit toegang tot een kluis, en een gewone inlogcode werkt niet in het adverteerdersgedeelte. Dat ligt vast in de zelfcontrole "een code voor adverteerders is geen inlogcode".

Wie nieuw is vult zijn bedrijfsnaam in vóórdat hij de code aanvraagt. Die naam wordt bij de code bewaard en pas gebruikt zodra de code klopt - net als de voornaam bij het registreren van een gebruiker. Zo kan niemand zich een bedrijfsnaam aanmeten zonder eerst bij dat e-mailadres te kunnen.

De prijs loopt mee terwijl je invult, met een klein stukje javascript in de pagina zelf. Dat is een gemak en geen bedrag: wat het kost rekent de server bij het indienen zelf uit.

Een campagne met de hand invoeren om het te proberen

Er is nog geen scherm waarin een adverteerder zichzelf aanmeldt, dus voor een proef zet je er één met de hand in. Daarna zie je hem op /beheer/adverteren staan, vink je de betaling af, keur je hem goed, en staat hij op het overzicht.

INSERT INTO adverteerders (company_name, contact_name, email, created_at)
VALUES ('Uitvaartzorg Noord', 'Piet', 'piet@voorbeeld.nl', UTC_TIMESTAMP(6));

INSERT INTO campagnes (advertiser_id, text, link, starts_on, ends_on, per_hour,
                       amount_cents, rate_cents, payment_reference, state, created_at)
VALUES (LAST_INSERT_ID(), 'Persoonlijk afscheid, dichtbij', 'https://voorbeeld.nl',
        CURDATE(), CURDATE() + INTERVAL 30 DAY, 5, 3000, 20, 'MB-HAND', 1, UTC_TIMESTAMP(6));

state 1 is wacht op betaling; de rest gaat via het beheerscherm.

Het klikpad

Adres Wat het doet
/advertentie/{id}/afbeelding De afbeelding. Openbaar, en een jaar in de browsercache
/advertentie/{id}/uit Telt de klik en stuurt door naar de adverteerder

Allebei zonder inlog: een advertentie is openbaar, en een plaatje dat alleen met een sessie te laden is werkt niet in de app.

De tegel linkt naar /uit en niet rechtstreeks naar de adverteerder. Met javascript tellen zou ook kunnen, maar dan telt hij niet mee als er iets misgaat onderweg. Dit is één plek en één telling. De doorverwijzing is bewust niet permanent: die blijft anders in de browser hangen en dan telt een tweede klik niet meer mee.

In de app opent de tegel in de browser van de telefoon (IBuitenlink), niet in de app zelf. Zou hij binnen de webview openen, dan wordt MijnBesluit een browser en staat iemand op de site van een adverteerder zonder weg terug naar zijn eigen kluis.