Skip to content

2025-11-25: VGBC Ticket Strategie - Update

Wijzigingen in Ticket Types

Oorspronkelijk Plan (24 nov)

  1. Early bird (manuele prijs aanpassing)
  2. Gewone kaarten
  3. Studenten kaarten (20% korting)
  4. Standhouders kaarten (quotum)
  5. Crew (niet via shop)

Nieuw Plan (25 nov) ✨

Definitief 4 soorten:

  1. Standaard Ticket (vervangt Early Bird)
  2. Prijs handmatig verhoogd via Commerce UI (geen datum-gebaseerde automatisering)
  3. Betaald via Mollie betaalflow
  4. Normale checkout flow

  5. Student Ticket

  6. prijs via field_beroep = student
  7. Betaald via Mollie betaalflow
  8. Normale checkout flow

  9. Standhouder Ticket

  10. Gekoppeld aan Partner entity met quotum
  11. Betaald via Mollie betaalflow
  12. Normale checkout flow

[!Note] Niet via cash!

  1. Custom/Admin Ticket ⭐ NIEUW
  2. Ad hoc aangemaakt door redactie/admin
  3. Voor uitzonderingen (VIPs, ter plekke cash, etc.)
  4. Geen Mollie betaling nodig - admin markeert als "betaald"
  5. Soortgelijk als "test payment" maar officieel ticket - hoeft niet via Mollie te lopen

[!Note] Nieuwe rol aanmaken 'Winkel'. Voor Commerce gerelateerde handelingen.


Custom/Admin Ticket Workflow

Scenario: Ter plekke Cash Betaling

Balie (Medewerker A)
├─ Customer betaalt cash €25
├─ Medewerker A gaat naar /admin/commerce/create-custom-ticket
├─ Form: Customer naam, email, ticket type
├─ Drupal genereert order (status: betaald)
├─ Drupal genereert License entity
├─ QR-code gegenereerd
├─ Print QR-code label (voor balie of ticket)
Deur (Medewerker B)
├─ Bezoeker scant QR-code
├─ Scan interface toont: GROEN ✓
├─ Accreditatiebewijs PDF gegenereerd
├─ Email naar customer (via MailChimp)
End Result: Bezoeker heeft bewijs van deelname

[!Note] Print een QR-code hoeft denk ik niet want kan handmatig als nodig vanaf gegenereerde PDF (invoice/bewijs) - moet toch gebeuren. Normaal gesproken gaat de QR-code naar E-mail en via telefoon kan QR worden gescand.


Implementatie: Custom Ticket Creation

Admin Interface

Locatie: /admin/commerce/custom-ticket (permission: create custom tickets)

Form velden: - Klant naam (required) - Klant email (required) - Ticket type (radio): Standaard | Student | Standhouder | Custom - Opmerkingen (textarea) - Betalingsmethode (radio): Mollie | Cash | Gratis

[!Note] Normale aanmeldingsprocedure voor Drupal, rol authenticated

Submit → Drupal doet: 1. Order aangemaakt (commerce_order) 2. Order state: paid (skip payment gateway) 3. Order email: sturen naar customer 4. License entity: gegenereerd 5. QR-code: gegenereerd (endroid/qr-code) 6. Redirect naar print page (of download label)

Order Status Handling

Standaard Ticket / Student / Standhouder: - Payment gateway: Mollie (user kiest in checkout) - Order state: pending → paid (via webhook)

Custom/Admin Ticket: - Payment gateway: none (admin kiest "Cash" of "Gratis") - Order state: direct paid (geen webhook) - Invoice: gegenereerd direct (zoals normal order)

[!Note] Rol admin of winkel kiest "Custom"

Database/Config Changes

Nieuwe field op commerce_order: - field_payment_method (select): Mollie | Cash | Gratis | Custom

[!Note] 'Mollie' en 'Custom' voldoen. Kan eventueel tekstveldje bij waarin medewerker (rol 'Winkel') een opmerking kan toevoegen

Nieuwe order state: - admin_created (optional, for tracking)

[!Note] Wellicht custom_created?

Order Type config: - Workflows unchanged - Invoice generation: still on order.paid


Technical: Custom Ticket Controller

Route: /admin/commerce/custom-ticket

Permission: create custom vgbc tickets

<?php
// src/Controller/CustomTicketController.php in vg_ticket_scanner module

namespace Drupal\vg_ticket_scanner\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\commerce_order\Entity\Order;
use Drupal\commerce_license\Entity\License;
use Endroid\QrCode\QrCode;

class CustomTicketController extends ControllerBase {

    public function createForm() {
        // Form builder
    }

    public function submitForm($form, $form_state) {
        $values = $form_state->getValues();

        // 1. Create order
        $order = Order::create([
            'type' => 'default',
            'store_id' => 1,
            'uid' => 0, // Anonymous or specific admin user
            'mail' => $values['customer_email'],
            'state' => 'completed', // Direct to completed (paid)
            'field_payment_method' => 'cash', // Custom field
        ]);
        $order->save();

        // 2. Create order item (dummy product)
        $order_item = OrderItem::create([
            'type' => 'default',
            'order_id' => $order->id(),
            'purchased_entity' => $product_variation, // Which variation?
            'quantity' => 1,
            'unit_price' => $price,
        ]);
        $order_item->save();
        $order->addItem($order_item);
        $order->save();

        // 3. Generate License
        $license = License::create([
            'type' => 'vgbc_ticket',
            'uid' => $order->getCustomerId(),
            'product_variation' => $product_variation->id(),
            'granted' => TRUE,
            'expiration' => NULL, // Or set to event date
        ]);
        $license->save();

        // 4. Generate QR-code
        $qr_content = "VGBC2026-USER{$order->getCustomerId()}-LICENSE{$license->id()}";
        $qr_code = new QrCode($qr_content);
        $qr_path = "public://qrcodes/{$license->id()}.png";
        file_put_contents($qr_path, $qr_code->getImage());

        // 5. Attach to license
        $license->field_qr_code->setValue([
            'target_id' => $file->id(),
        ]);
        $license->save();

        // 6. Trigger invoice generation
        // (via event: commerce_order.place.post_transition)

        // 7. Send email
        $mail_service = \Drupal::service('plugin.manager.mail');
        $mail_service->mail('commerce_order', 'order_confirmation', $order->getEmail(), 'en', [
            'order' => $order,
            'license' => $license,
        ]);

        return $this->redirect('entity.commerce_order.canonical', [
            'commerce_order' => $order->id(),
        ]);
    }
}

UI/UX Considerations

Admin Create Ticket Form

╔═══════════════════════════════════════╗
║  Custom Ticket Aanmaken (VGBC2026)    ║
╠═══════════════════════════════════════╣
║                                       ║
║  Klant Naam: [___________________]   ║
║  Klant Email: [__________________]   ║
║                                       ║
║  Ticket Type:                         ║
║  ○ Standaard (€25)                    ║
║  ○ Student (€20)                      ║
║  ○ Standhouder                        ║
║  ○ Custom (beschrijving)              ║
║                                       ║
║  Betalingsmethode:                    ║
║  ○ Mollie (online payment button)     ║
║  ○ Cash (direct marked paid)          ║
║  ○ Gratis (sponsor/crew)              ║
║                                       ║
║  Opmerkingen:                         ║
║  [_____________________________]     ║
║  [_____________________________]     ║
║                                       ║
║  [Ticket Aanmaken]  [Annuleer]        ║
║                                       ║
╚═══════════════════════════════════════╝

Result page:
┌─────────────────────────────────────────┐
│ ✓ Ticket aangemaakt!                   │
│                                         │
│ Order #VG-2025-042                     │
│ Customer: Jan Jansen                   │
│ Email: jan@example.com                 │
│                                         │
│ QR-code:                               │
│ [████████ QR IMAGE ████████]           │
│                                         │
│ [Print Label] [Download PDF]           │
│ [Email versturen] [View Order]         │
└─────────────────────────────────────────┘

Betalingsmethode Veld

On commerce_order entity:

field_payment_method:
  type: list_string
  settings:
    allowed_values:
      mollie: 'Online betaling (Mollie)'
      cash: 'Cash (ter plekke)'
      gratis: 'Gratis/Sponsor'
      custom: 'Custom (admin)'

Impact op invoice/confirmation: - Mollie: "Betaling via Mollie: verwerkt" - Cash: "Betaling ter plekke: contant €25" - Gratis: "Gratis kaart" - Custom: "Admin aangemaakt"


QR-code Label Print

Option 1: Browser Print

<div class="qr-label">
  <h3>VGBC 2026</h3>
  <img src="/path/to/qr.png" />
  <p>{{ ticket_type }}</p>
  <p>{{ customer_name }}</p>
</div>

Option 2: PDF Download - Maak label PDF (wkhtmltopdf) - Download + print op balie


Invoice Aanpassingen

Invoice template moet toonen: - Order #VG-2025-042 - Customer (if known) - Ticket type - QR-code (klein formaat, rechtsboven) - Betalingsmethode: "Cash" of "Mollie" of "Gratis" - Amount: €0 als gratis, €25 als betaald

[!Note] Customer is altijd bekend. Eerst aanmelden aan systeem dan kun je pas in de winkel wat kopen, geldt ook voor tickets.

[!note] Kan ik de ticket nog vormgeven? We moeten sowieso nog een rondje vormgeven doen rond Invoice -> CSS connectie met PDF maker.


Workflow Samenvatting

Normale Koper (Mollie)

Online → Checkout → Mollie Payment → Order Completed → Invoice + QR

Student (Mollie met Korting)

Online → Checkout → Student Discount → Mollie Payment → Order Completed → Invoice + QR

Standhouder (Mollie)

Online → Checkout → Quotum Check → Order Completed (direct) → Invoice + QR

[!Note] Hoeft niet via Mollie, kaarten zijn gratis

### Ter Plekke Cash (Balie Medewerker)

Admin Form → Cash Selected → Order Completed (direct) → Invoice + QR

[!note] Moet dit via admin form, kan ook via normale weg: aanmelden (nieuwe) user of toewijzen betaalde user. Dan order completed (direct) -> Invoice (€0) + QR. Geen label print, gewoon via Email en telefoon laten zien voor scan.

### VIP/Gratis (Redactie)
Admin Form → Gratis Selected → Order Completed (direct) → Invoice + QR Label Print

[!Note] Gaat buiten Drupal Commerce om met handmatige gastenlijst

```


Open Vragen

  1. Mollie Payment Skip - Werkt state = completed zonder webhook trigger?
  2. Antwoord: Ja, maar test eerst!
  3. Event commerce_order.place.post_transition triggert invoice generation

  4. Anonymous vs Admin User - Order uid bij cash ticket?

  5. Optie A: uid = 1 (admin)
  6. Optie B: uid = 0 (anonymous)
  7. Optie C: uid = customer (als reeds ingelogd)

  8. QR Label Format - Wat voor label bij balie printen?

  9. Suggestie: 10cm x 10cm sticker of kaartje
  10. Handmatig inplakken in physical ticket?
  11. Of direct op invoice afdrukken?

  12. Email naar Customer - Wat zeggen?

  13. "Bedankt! Uw ticket is hier gedownload: [link]"
  14. "Scan de QR-code bij de deur voor accreditatiebewijs"

  15. Quotum voor Cash Tickets - Tellen mee naar standhouder quotum?

  16. Waarschijnlijk JA (ter plekke standhouder ook quotum hebben)
  17. Dus: check field_vgbc2026_tickets ook voor admin tickets

Implementation Checklist

  • Mollie skip test (order.state = completed direct)
  • Event subscriber: invoice generation op completed (niet alleen paid)
  • Custom Ticket form builder
  • Permission: create custom vgbc tickets
  • Payment method field on order
  • QR label printer (Symfony console command of form)
  • Invoice template update (show payment method)
  • Email template update (show payment method)
  • UI buttons op order page (print label, resend email, etc.)
  • Quotum validation ook voor admin tickets
  • Testing: E2E flow cash → scan → accreditatiebewijs

Status

Planning: Volgende sessie starten met custom ticket form

Priority: Hoog (blocking for testing ter plekke scenario)

Modules affected: - vg_commerce (order handling) - vg_ticket_scanner (new: custom ticket form + controller)