Onboarding (Backend)
De onboarding module beheert de zelfregistratie van een nieuwe apotheek. Dit is de enige plek in het platform waar nog geen tenant-omgeving bestaat: het proces draait buiten elke tenant en maakt aan het einde zelf het schema, het abonnementsprofiel en het eerste beheerderaccount aan.
Technisch Ontwerp
De registratie verloopt in twee API-stappen die de frontend direct na elkaar aanroept. initiate valideert de gegevens, maakt een provisionele Stripe-customer en bewaart een OnboardingSession. complete maakt vervolgens de tenant aan en start de proefperiode. Er is geen betaalverificatie tijdens de onboarding: een nieuwe apotheek start direct in de 14-daagse trial zonder creditcard. Het abonnement zelf wordt later in de billing-module geactiveerd.
De externe side-effects (Stripe finaliseren, standaardvragen seeden, uitnodigingsmail) staan bewust buiten de databasetransactie. Zo laat een rollback (bijvoorbeeld een uniciteitsconflict op het domein) nooit een actieve Stripe-customer achter zonder lokale tenant. Mislukt een side-effect na de commit, dan blijft de tenant bestaan en logt de service een remediation-trail.
flowchart TD
Start[POST /api/onboarding/initiate/] --> Turnstile[verify_turnstile_token]
Turnstile -->|bot/expired| Reject[400 Botcontrole mislukt]
Turnstile -->|ok| Validate[services.validate_kvk]
Validate -->|format/duplicaat/onbekend| Conflict[409 Conflict]
Validate -->|geldig| Cust[create_stripe_customer provisional]
Cust --> Session[OnboardingSession opslaan + token]
Session --> Complete[POST /api/onboarding/complete/]
Complete --> Lock[select_for_update op session, completed=False]
Lock -->|verlopen/gebruikt/geen terms| Bad[400 Bad Request]
Lock -->|geldig| Tx[transaction.atomic in public schema]
Tx --> Tenant[Apotheek + Domain tenant.remedice.nl]
Tenant --> Profile[BillingProfile + start_trial]
Profile --> Terms[TermsConsent general]
Terms --> Role[create_admin_role alle permissies]
Role --> Invite[create_invitation admin]
Invite --> Mark[OnboardingSession.completed=True]
Mark --> Post[Post-commit side-effects]
Post --> Final[finalize_stripe_customer]
Post --> Seed[seed_standaardvragen in tenant schema]
Post --> Mail[send_onboarding_invite_email]
Datamodel (ERD)
De module houdt slechts een tijdelijk record bij: de OnboardingSession. De definitieve entiteiten (Apotheek, Domain, BillingProfile, Role, Invitation) leven in andere apps en worden bij complete aangemaakt. Het telefoonnummer voor de eenmalige onboarding-SMS wordt versleuteld bewaard.
erDiagram
OnboardingSession {
bigint id PK
uuid token UK
string kvk_number
string apotheek_name
string billing_email
string general_email
string admin_email
string admin_first_name
string admin_last_name
string admin_phone "encrypted"
string straat_adres
string postcode
string stad
string terms_version
datetime terms_accepted_at
string stripe_customer_id
boolean completed
datetime created_at
datetime expires_at
}
API & Communicatie
De module stelt twee publieke endpoints beschikbaar onder /api/onboarding/:
-
POST /initiate/: valideert KVK-nummer, contactgegevens, adres en de bot-gate, maakt een provisionele Stripe-customer en slaat eenOnboardingSessionop. Antwoordt met{ session_token }(201). Throttling via scopeonboarding_initiate. -
POST /complete/: ontvangt{ session_token }, maakt de tenant (Apotheek+Domain), hetBillingProfile, de beheerderrol met alle permissies en de admin-uitnodiging aan, en start de trial. Antwoordt met{ tenant_name, admin_email }(201). Throttling via scopeonboarding_complete.
De beheerder ontvangt na complete een uitnodigingsmail (send_onboarding_invite_email) waarmee hij zijn account afrondt: wachtwoord en TOTP instellen via de auth-onboardingstroom. Er wordt tijdens de registratie niets afgeschreven.
Foutafhandeling & Statuscodes
201 Created: sessie aangemaakt (initiate) of registratie voltooid (complete).400 Bad Request: botcontrole mislukt (Turnstile), of bijcompleteeen verlopen, reeds gebruikte of niet-geaccepteerde sessie.409 Conflict: KVK-nummer ongeldig, al gekoppeld aan een bestaandBillingProfile, of niet aanwezig in het Handelsregister.502 Bad Gateway: onverwachte fout bij Stripe-communicatie of tenant-provisioning.
Bij een KVK-API-outage faalt de bestaanscontrole open (de registratie gaat door), maar de uniciteitscontrole op BillingProfile.kvk_number blijft altijd strikt.
Autorisatie & Beveiliging
De endpoints zijn publiek (authentication_classes = [], AllowAny) omdat de aanvrager nog geen account heeft. De drempels tegen misbruik zijn:
- Cloudflare Turnstile bot-gate op de voorwaardenstap (
turnstile.py). Actief wanneerTURNSTILE_REQUIREDaanstaat en een secret is geconfigureerd; in dev is de check een no-op. - Per-IP throttling via
ScopedRateThrottle(scopesonboarding_initiate/onboarding_complete). - Sessie-token als ondoorzichtige UUID met een geldigheid van 24 uur (
expires_at). - Idempotentie:
completeclaimt de sessie metselect_for_updateen zetcompleted=True; een tweede aanroep met hetzelfde token faalt met 400. - KVK-uniciteit op
BillingProfilevoorkomt dubbele omgevingen voor dezelfde organisatie.
Afgebroken sessies worden door de Celery-taak cleanup_abandoned_onboarding_sessions opgeruimd: verlopen, niet-voltooide sessies worden verwijderd en hun provisionele Stripe-customer wordt losgekoppeld.
Bestandsstructuur & Verantwoordelijkheden
models.py: hetOnboardingSessionmodel met versleuteld telefoonnummer en 24-uursexpires_at.services.py:initiate_onboarding,complete_onboarding,validate_kvk,create_admin_role,generate_schema_name. Bevat de tenant-provisioning en de scheiding tussen transactie en post-commit side-effects.views.py: de twee publiekeAPIView-endpoints met throttling en Turnstile-check.serializers.py: validatie van registratie- en voltooiingsgegevens, inclusief telefoonnummer-normalisatie.turnstile.py: server-side verificatie van het Cloudflare Turnstile-token.tasks.py:cleanup_abandoned_onboarding_sessions(opruimen van afgebroken sessies).
Belangrijke bestanden
backend/onboarding/services.pybackend/onboarding/models.pybackend/onboarding/views.pybackend/onboarding/turnstile.py