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 (vervangd Early Bird)
  2. Prijs handmatig verhoogd via Commerce UI (geen datum-gebaseerde automatisering)
  3. Betaald via Mollie of Cash
  4. Normale checkout flow

  5. Student Ticket

  6. 20% korting via field_beroep = student
  7. Betaald via Mollie of Cash
  8. Normale checkout flow

  9. Standhouder Ticket

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

  13. Custom/Admin Ticket ⭐ NIEUW

  14. Ad hoc aangemaakt door redactie/admin
  15. Voor uitzonderingen (VIPs, ter plekke cash, etc.)
  16. Geen Mollie betaling nodig - admin markeert als "betaald"
  17. Soortgelijk als "test payment" maar officieel ticket

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

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

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)

Database/Config Changes

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

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

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


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 → Mollie Payment → Order Completed → Invoice + QR

Ter Plekke Cash (Balie Medewerker)

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

VIP/Gratis (Redactie)

Admin Form → Gratis Selected → Order Completed (direct) → Invoice + QR Label Print

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)