257 lines
13 KiB
Markdown
257 lines
13 KiB
Markdown
# 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.
|
|
|
|
### De betaallink
|
|
|
|
`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.
|
|
|
|
```sql
|
|
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.
|