# Configuratie in WP-admin

De headless storefront leest twee soorten configuratie: waarden die bij de
backend horen (credentials, betaalproviders, routing) en waarden die bij de
webwinkel horen (vragen van de Peptidewijzer, teksten, loyalty-regels). De laatste
groep beheert de shop owner zelf in WordPress — niet in de frontend.

## Companion plugin

`wordpress/pw-companion/` is een kant-en-klare plugin (kopieer de map naar
`/wp-content/plugins/` en activeer). Onder **Peptidewinkel** in het admin-menu:

| Scherm | Wat je beheert |
| --- | --- |
| Peptidewijzer | Vragen en antwoordopties (NL/EN) + de mapping van antwoorden naar WooCommerce-producten of -tags |
| Loyalty | Spaarfactor, waarde per punt, minimum voor inwisselen, vervaldatum, bonuspunten en de tiers |
| Contactformulier | Inzendingen van het contactformulier (status, opnieuw mailen, verwijderen), de WPForms-koppeling en de geheime sleutel voor de site |
| SliceWP (affiliate) | De affiliate-koppeling: sleutel, wanneer commissies worden aangemaakt, zelfverwijzing, afwijzen bij annulering, rekenbasis en de laatste commissies |
| Wijzer-logboek | Welke antwoorden gekozen zijn en welke producten zijn aangeklikt |

De plugin serveert (openbaar, alleen-lezen):

* `GET /wp-json/pw/v1/config/peptidewijzer`
* `GET /wp-json/pw/v1/config/loyalty`
* `POST /wp-json/pw/v1/quiz/events` (loggen; max. één per seconde per IP)

En één schrijfroute die een geldige ondertekening vereist:

* `POST /wp-json/peptidewinkel/v1/contact` (contactformulier; HMAC-SHA256 over
  `tijdstempel.body`)
* `POST /wp-json/peptidewinkel/v1/affiliate-visit` (bezoek via een affiliate-link)
* `POST /wp-json/peptidewinkel/v1/affiliate-info` (slug, tarieven en cookieduur voor
  het affiliate-dashboard)

De storefront cached deze antwoorden vijf minuten per runtime en valt terug op de
ingebouwde configuratie als WordPress niet antwoordt of de plugin uit staat. Er
breekt dus nooit iets door een ontbrekende configuratie.

## Mapping van antwoorden naar producten

De mapping is een object met sleutels `"<vraag>:<antwoord>"`:

```json
{
  "goal:herstel": { "products": [123, 456], "tags": ["herstel"], "stack": [789],
                    "note": { "nl": "Herstelstack", "en": "Recovery stack" } },
  "frequency:wekelijks": { "products": [456], "tags": [], "stack": [], "note": { "nl": "", "en": "" } }
}
```

* `products` — vaste product-id's, in de volgorde waarin je ze wilt tonen.
* `stack` — een bundel; komt bovenaan als `prefer_stacks` aan staat.
* `tags` — gereserveerd voor productselectie op tag (nu nog niet uitgelezen).
* `note` — korte uitleg boven de resultaten, per taal.

Prijzen en voorraad komen altijd live uit WooCommerce; producten met status
`outofstock` worden niet aanbevolen zolang `hide_out_of_stock` aan staat. Laat de
mapping leeg en de wijzer gebruikt de ingebouwde scoring op de kennisbank.

## Contactformulier

Het formulier op de site gaat via de front-end runtime naar WordPress:

1. De bezoeker vult naam, e-mail en bericht in; de frontend controleert eerst de
   Turnstile-captcha.
2. De runtime tekent de JSON-body met HMAC-SHA256 over `tijdstempel.body` en stuurt
   hem naar `POST /wp-json/peptidewinkel/v1/contact` met de headers
   `X-PW-Timestamp` en `X-PW-Signature`.
3. De plugin controleert de ondertekening (maximaal vijf minuten oud), zet de
   inzending in WPForms als WPForms Pro actief is, mailt de melding en bewaart de
   inzending in zijn eigen tabel. Er is dus altijd een overzicht in WP-admin, ook
   op WPForms Lite (dat heeft geen entries).

Beheren doet u onder **Peptidewinkel → Contactformulier**:

* **Inzendingen** — filteren op status, zoeken, status wijzigen (nieuw, gelezen,
  afgehandeld, spam), de melding opnieuw sturen en verwijderen. Nieuwe inzendingen
  tellen op in het admin-menu.
* **Instellingen** — het WPForms-formulier, het meldingsadres, de veldkoppeling
  (standaard wordt op label gezocht: Naam, E-mail, Bericht) en de bewaartermijn
  (0 = nooit automatisch opruimen).
* **Koppeling** — het endpoint, de geheime sleutel en de testknop.

### Sleutel uitwisselen

De sleutel staat aan beide kanten en is het enige bewijs dat een inzending echt van
de site komt:

1. WP-admin → **Peptidewinkel → Contactformulier → Koppeling**: genereer of toon de
   sleutel.
2. Laravel-backend → **Instellingen → WordPress → Contactformulier secret**: vul
   dezelfde sleutel in.
3. Op de server: `php artisan settings export --env-file=.dev.vars` en herstart de
   front-end runtime, zodat `WPFORMS_CONTACT_SECRET` meegaat.
4. Gebruikte u hiervoor een Code Snippets-snippet dat dezelfde route registreerde?
   Verwijder dat: de plugin doet hetzelfde, maar dan beheerbaar. Zet u de sleutel
   liever in `wp-config.php`, dan wint de constante `PW_CONTACT_SECRET` van de
   sleutel in de plugin-instellingen.

Met **Test de ondertekening en het endpoint** op het Koppeling-tabblad controleert u
de hele keten in één klik: de plugin stuurt een ondertekend testverzoek door zijn
eigen route (zonder inzending of mail) en toont of de sleutel, WPForms en de
velden kloppen.

## SliceWP-koppeling (affiliate)

De affiliate-flow van de site bestaat uit drie stukken, en de plugin doet ze alle
drie:

1. **Bezoeken** — een influencer deelt `https://peptidewinkel.com/?aff=<id of slug>`.
   De storefront zet de cookie en meldt het bezoek aan
   `POST /wp-json/peptidewinkel/v1/affiliate-visit`, ondertekend met dezelfde
   HMAC-sleutel als het contactformulier. De plugin zoekt de affiliate op id, op de
   eigen SliceWP-slug of op gebruikersnaam en registreert de visit in SliceWP.
2. **Commissies** — de storefront zet `_pw_affiliate` op de bestelling. Zodra die
   betaald is (standaard `processing` of `completed`) maakt de plugin een
   SliceWP-commissie aan met het tarief uit SliceWP (per affiliate of shopbreed), op
   het subtotaal na korting en zonder verzendkosten. Bij annulering, terugbetaling of
   mislukking wordt een nog niet uitbetaalde commissie afgewezen; een uitbetaalde
   blijft staan. Zelfverwijzing (de affiliate bestelt via de eigen link) wordt
   genegeerd. Elke stap komt als ordernotitie in WooCommerce.
3. **Dashboard** — `POST /wp-json/peptidewinkel/v1/affiliate-info` geeft het
   klantportaal de eigen slug, de tarieven en de cookieduur. De overige
   affiliategegevens leest de backend rechtstreeks uit de SliceWP REST API met de
   consumer key/secret.

Beheren doet u onder **Peptidewinkel → SliceWP (affiliate)**:

* **Koppeling** — de sleutel (tonen, genereren, plakken via Instellingen) en de twee
  endpoints, met een testknop die een ondertekend verzoek door de eigen route stuurt
  zonder een bezoek te registreren.
* **Instellingen** — commissies automatisch aan/uit, wanneer (bij betaling of pas bij
  `completed`), de rekenbasis (subtotaal of volledig bedrag), zelfverwijzing
  blokkeren, afwijzen bij annulering en of bezoeken worden geregistreerd.
* **Status** — of SliceWP en WooCommerce actief zijn, de cookieduur en het
  standaardtarief uit SliceWP, waar de sleutel vandaan komt en de laatste
  commissies.

De sleutel is dezelfde waarde als `SLICEWP_BRIDGE_SECRET` in de runtime-omgeving van
de site: zet hem in Laravel bij **Instellingen → SliceWP → "SliceWP bridge secret"**
en draai daarna `php artisan settings export --env-file=.dev.vars`. Laat u hem liever
in `wp-config.php` staan (`PW_SLICEWP_SECRET`), dan wint die constante van de
plugin-instellingen. Gebruik een sleutel met alleen ASCII-tekens: de site leest hem
als latin1-bytes, dus een niet-ASCII teken zou nooit matchen.

Had u de losse plugin **Peptidewinkel – SliceWP bridge** actief? Verwijder die na het
activeren van deze plugin: de routes en de commissielogica zitten nu hier, en twee
plugins die hetzelfde doen maakt het alleen maar lastiger om te zien wat er gebeurt.

## Wat waar hoort

| Soort configuratie | Waar |
| --- | --- |
| WooCommerce/WP/SliceWP-credentials, betaalproviders, gateway-routing | Laravel-backend: `/admin/settings` of `php artisan settings set` |
| Peptidewijzer-vragen, -teksten en productmapping | WP-admin: Peptidewinkel → Peptidewijzer |
| Loyalty-regels (tiers, spaarfactor, bonussen, vervaldatum) | WP-admin: Peptidewinkel → Loyalty |
| Contactformulier (formulier-id, meldingsadres, veldkoppeling, sleutel) | WP-admin: Peptidewinkel → Contactformulier |
| Dezelfde contact-sleutel voor de runtime | Laravel-backend: Instellingen → WordPress → Contactformulier secret |
| SliceWP-koppeling (commissieregels, sleutel) | WP-admin: Peptidewinkel → SliceWP (affiliate) |
| Dezelfde affiliate-sleutel voor de runtime | Laravel-backend: Instellingen → SliceWP → SliceWP bridge secret |
| Commissietarieven en cookieduur | SliceWP zelf (Instellingen → Commissies) |
| Productteksten, blogposts, categorieën (NL/EN) | WPML in WordPress |
