Ga naar inhoud

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 een OnboardingSession op. Antwoordt met { session_token } (201). Throttling via scope onboarding_initiate.

  • POST /complete/: ontvangt { session_token }, maakt de tenant (Apotheek + Domain), het BillingProfile, de beheerderrol met alle permissies en de admin-uitnodiging aan, en start de trial. Antwoordt met { tenant_name, admin_email } (201). Throttling via scope onboarding_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 bij complete een verlopen, reeds gebruikte of niet-geaccepteerde sessie.
  • 409 Conflict: KVK-nummer ongeldig, al gekoppeld aan een bestaand BillingProfile, 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 wanneer TURNSTILE_REQUIRED aanstaat en een secret is geconfigureerd; in dev is de check een no-op.
  • Per-IP throttling via ScopedRateThrottle (scopes onboarding_initiate / onboarding_complete).
  • Sessie-token als ondoorzichtige UUID met een geldigheid van 24 uur (expires_at).
  • Idempotentie: complete claimt de sessie met select_for_update en zet completed=True; een tweede aanroep met hetzelfde token faalt met 400.
  • KVK-uniciteit op BillingProfile voorkomt 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: het OnboardingSession model met versleuteld telefoonnummer en 24-uurs expires_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 publieke APIView-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.py
  • backend/onboarding/models.py
  • backend/onboarding/views.py
  • backend/onboarding/turnstile.py

API & Communicatie (Swagger)