Membersactivities framework basic example

12 Sep 2026 -



layout: post author: dirkvm ————–

<!DOCTYPE html>

MembersActivities Framework 1.1.0 — Basic Example Guide

MembersActivities Framework 1.1.0

Basic Example Application Guide
FrameworkMembersActivities 1.1.0
Controller Framework1.0.31
PurposeLearning and starting-point application
DatabaseMySQL / MariaDB

Contents

  1. Purpose of the Example
  2. Example Folder Structure
  3. Prerequisites
  4. Create the Application
  5. Install Dependencies
  6. Create the Database
  7. Create the First Administrator
  8. Configure the Application
  9. Understand index.php
  10. Understand Client Autoloading
  11. Understand controls.xml
  12. The Default Command
  13. Create Client Model Classes
  14. Activity, Member and Costitem Types
  15. Subscription Validation Strategies
  16. CommandDecorator Example
  17. Public Activity Example
  18. Seat-map and Reservation Example
  19. Payment Example
  20. Mollie Example
  21. Ticket Example
  22. Ticket Scanning Example
  23. Google Wallet Example
  24. Security Features
  25. Administration Example
  26. Run and Test the Example
  27. How to Turn the Example into Your Application
  28. Migrating an Existing 1.0.31 Application
  29. Basic Implementation Checklist

1. Purpose of the Example

The example directory included in MembersActivities Framework 1.1.0 is a complete starting-point client application. It demonstrates how the framework is intended to be integrated rather than merely showing isolated API calls.

The example contains:

Important: the example is intentionally application-specific. It contains sample values, sample texts and example business rules. Replace those with the rules and configuration of your own application.

Example Files

The complete example application is available as a ZIP archive, including the folder structure and all underlying example files: Download the Example Folder Structure and Files (ZIP).

The database setup is provided separately and is not included in the ZIP archive: Download DatabaseSetup.sql .

2. Example Folder Structure

The exact contents may evolve as the example application grows, but the important framework-specific structure is:

example/
│
├── index.php
├── composer.json
├── DatabaseSetup.sql
├── .htaccess
│
├── config/
│   ├── app_options.ini
│   └── [Your google wallet keyfile].json
│
├── MVCFramework/
│   ├── controls.xml
│   │
│   ├── commands/
│   │   ├── DefaultCommand.php
│   │   ├── admin/
│   │   ├── ajax/
│   │   ├── cron/
│   │   ├── downloads/
│   │   ├── mollie/
│   │   └── user/
│   │
│   ├── model/
│   │   ├── Activity.php
│   │   ├── ActivityMapper.php
│   │   ├── Activity_RGLR.php
│   │   ├── Activity_STMP.php
│   │   ├── Costitem.php
│   │   ├── CostitemMapper.php
│   │   ├── Member.php
│   │   ├── MemberMapper.php
│   │   ├── Member_RGLR.php
│   │   ├── Member_PRTN.php
│   │   ├── Payment.php
│   │   ├── PaymentMapper.php
│   │   ├── Payment_RGLR.php
│   │   ├── Payment_YRLY.php
│   │   ├── Subscription.php
│   │   ├── SubscriptionMapper.php
│   │   ├── Subscription_RGLR.php
│   │   ├── Ticket.php
│   │   ├── TicketMapper.php
│   │   ├── Ticket_RGLR.php
│   │   ├── TicketScan.php
│   │   ├── TicketScanMapper.php
│   │   ├── TicketScan_RGLR.php
│   │   └── SubscriptionValidation*.php
│   │
│   └── views/
│       ├── defaultView.php
│       ├── errorView.php
│       ├── admin/
│       ├── login/
│       └── user/
│
└── assets/
    ├── logging/
    ├── qrcodes/
    └── ...

3. Prerequisites

Before running the example, install:

The example requires MembersActivities Framework 1.1.0 or later. The framework in turn uses Controller Framework 1.0.31.

4. Create the Application

Step 1. Copy the complete example directory to a working application directory.
Step 2. Rename the directory if desired.
Step 3. Do not publish the configuration directory or credential files directly as downloadable web resources.
Step 4. Keep the application's framework-specific classes below MVCFramework/.

A suitable starting point is to keep the example structure intact until the application is understood, then progressively replace the example's client-specific code.

5. Install Dependencies

The example's composer.json should require MembersActivities Framework 1.1.0 or a compatible later release.

{
    "require": {
        "setasign/fpdf": "^1.8",
        "blueimp/jquery-file-upload": "9.22.*",
        "tinymce/tinymce": "^8.0",
        "chillerlan/php-qrcode": "*",
        "samoscon/membersactivities-framework": "^1.1.0",
        "google/auth": "^1.53",
        "guzzlehttp/guzzle": "^7.10",
        "google/apiclient": "^2.19",
        "google/apiclient-services": "~0.350"
    }
}

Run:

composer install

The result is a vendor/ directory containing the framework and third-party dependencies.

Controller Framework: MembersActivities 1.1.0 is based on Controller Framework 1.0.31. The application should therefore keep the Controller Framework dependency compatible with that version.

6. Create the Database

The example provides DatabaseSetup.sql. Import it into a new MySQL/MariaDB database.

CREATE DATABASE your_database
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Then import the supplied SQL script using your preferred database administration tool.

The 1.1.0 database model includes the persistent ticketing model in addition to the existing activity, member, subscription and payment data.

For an existing application: do not simply import the reference schema over a production database. Back up the database and apply controlled schema changes.

7. Create the First Administrator

After importing DatabaseSetup.sql, the database contains the required tables, but the application still needs at least one member account that can log in as an administrator.

This is especially important for the example because the default configuration uses _MINLEVELTOLOGIN='A', which means that the login environment requires an administrator account.

Step 1. Generate a password hash using PHP.
php -r "echo password_hash('Demo123', PASSWORD_DEFAULT), PHP_EOL;"

Copy the generated hash and use it in the password field of the first member.

Step 2. Insert the first administrator into the member table.
INSERT INTO member
    (classification, name, lastname, email, role, password, ownpwd, active)
VALUES
    (
        'RGLR',
        'Demo',
        'Administrator',
        'admin@example.com',
        'A',
        '[PASTE THE PASSWORD HASH HERE]',
        1,
        1
    );
FieldValuePurpose
classification RGLR Uses the regular member type implementation supplied by the example.
role A Identifies the member as an administrator for the administrator login strategy.
password PHP password_hash() result Stores the password as a secure password hash rather than plain text.
ownpwd 1 Indicates that the member has their own password.
active 1 Activates the member account.
Security: Demo123 is only an example password. Generate your own password hash and use a strong, unique password for any real installation. Never store the plain-text password in the database.

8. Configure the Application

Edit config/app_options.ini.

_APPDIR=https://[(sub)domeinnaam]/
_HOMEPAGE=https://[your client homepage]
_ASSETDIR=https://[(sub)domeinnaam]/assets/

_SALTRAND=[long random secret]
_RAND=[random integer]

_DBUSER=[database username]
_DBPASSWORD="[database password]"
_DBNAME=[database name]
_DBHOST=localhost

8.1 Application identity

APP=BM_test
_LOGO=assets/[Your logo].jpg

Replace these with the identity of your own application.

8.2 Login policy

_MINLEVELTOLOGIN='A'

The example uses A to indicate an administrator-only login environment. The alternative U allows administrators and members to log in as users.

8.3 Payment policy

_WTALLOWED='Y'

This controls whether wire transfer is available as an alternative to online payment.

8.4 Mollie

_MOLLIECONFIG="test_[your mollie key]"

Use a test key during development and a live key only in a properly secured production configuration.

8.5 Google Wallet

_WALLETORIGIN=...
_WALLETCREDENTIALS=config/[your keyfile].json
_WALLETISSUERID=...

Google Wallet is optional. If it is not used, the corresponding configuration can remain disabled or empty according to the application's conventions.

AccessToken security: _SALTRAND is used as secret material for AccessToken generation. It must be a long, unpredictable secret and must not be exposed publicly.

9. Understand index.php

The example's entry point is deliberately small:

<?php

include __DIR__ . '/vendor/autoload.php';

spl_autoload_register(function ($class_name) {

    if (preg_match('/\\\\/', $class_name)) {
        $class_name = str_replace('\\', DIRECTORY_SEPARATOR, $class_name);
    }

    $file = __DIR__ . DIRECTORY_SEPARATOR .
            'MVCFramework' . DIRECTORY_SEPARATOR .
            $class_name . '.php';

    if (file_exists($file)) {
        require_once $file;
    }
});

try {

    controllerframework\controllers\Controller::run();

} catch (\Throwable $e) {

    http_response_code(500);

    echo '<h1>Application initialization failed</h1>';
    echo '<p>' .
         htmlspecialchars(
             $e->getMessage(),
             ENT_QUOTES,
             'UTF-8'
         ) .
         '</p>';

    exit;
}

The sequence is:

index.php │ ├── Composer autoloader │ ├── client application autoloader │ └── Controller::run() │ ▼ framework request processing

10. Understand Client Autoloading

The example automatically maps namespaced client classes below MVCFramework/.

For example:

\model\Activity_RGLR

maps to:

MVCFramework/model/Activity_RGLR.php

and:

\commands\user\PublicActivityCommand

maps to:

MVCFramework/commands/user/PublicActivityCommand.php

This makes it possible for the client application to provide concrete classes expected by the abstract framework classes.

11. Understand controls.xml

MVCFramework/controls.xml connects URL paths to commands and views.

The root route is:

<command path="/" class="\commands\DefaultCommand">
    <view name="/defaultView" />
    ...
</command>

The activity route is:

<command path="/activity"
         class="\commands\user\PublicActivityCommand">
    <view name="/user/activity" />
    ...
</command>

The payment flow contains:

/createPayment │ ▼ /paymentToMollie │ ▼ Mollie │ ├── customer return │ └── webhook │ ▼ /webhookFromMollie │ ▼ Payment status update │ ▼ Payment_RGLR::statusReceived()

12. The Default Command

The example's DefaultCommand is a client CommandDecorator around the framework's default command.

class DefaultCommand
    extends \controllerframework\controllers\CommandDecorator
{
    public function doExecuteDecorator(
        \controllerframework\registry\Request $request
    ): ?int {

        $this->addResponses($request, [
            'title' => 'Inloggen'
        ]);

        return null;
    }

    public function initCommand(): void {
        $this->setCommand(
            new \membersactivities\commands\DefaultCommand()
        );
    }

    protected function getLevelOfLoginRequired(): void {
        $this->setLoginLevel(
            new \controllerframework\sessions\NoLoginRequired()
        );
    }
}

The client changes only what it needs: here, the page title.

13. Create Client Model Classes

The example supplies concrete client classes for the abstract MembersActivities models.

Activity

namespace model;

class Activity
    extends \membersactivities\model\activities\Activity
{
    // client-specific code
}

Costitem

namespace model;

class Costitem
    extends \membersactivities\model\activities\Costitem
{
    // client-specific code
}

Member

namespace model;

class Member
    extends \membersactivities\model\members\Member
{
    // client-specific code
}

Member supports the same parent-child Composite principle as Activity. A Member can therefore have child Members through the member hierarchy.

Subscription

namespace model;

class Subscription
    extends \membersactivities\model\subscriptions\Subscription
{
    // client-specific code
}

Payment

namespace model;

class Payment
    extends \membersactivities\model\subscriptions\Payment
{
    // client-specific code
}

Ticket

namespace model;

class Ticket
    extends \membersactivities\model\subscriptions\Ticket
{
    // client-specific code
}

TicketScan

namespace model;

class TicketScan
    extends \membersactivities\model\subscriptions\TicketScan
{
    // client-specific code
}

Most of these classes are intentionally almost empty. Their existence allows the framework's abstract model architecture to be specialized by the client.

14. Activity, Member and Costitem Types

The example demonstrates classification-specific type implementations.

Activity_RGLR

class Activity_RGLR
    extends \membersactivities\model\activities\ActivityTypeImplementation
{
}

This represents a normal activity without additional rules.

Activity_STMP

class Activity_STMP
    extends \membersactivities\model\activities\ActivityTypeImplementation
{
    public function seatmap(): bool {
        return true;
    }
}

This type tells the activity command/view that a seat map should be used.

Costitem_RGLR

class Costitem_RGLR
    extends \membersactivities\model\activities\CostitemTypeImplementation
{
}

Member types

The example contains:

Member_RGLR
Member_PRTN

These extend MemberTypeImplementation and demonstrate how client-specific member rules can be added.

For example, Member_RGLR implements a yearly participation fee, while Member_PRTN contains partner-specific logic.

Ticket types

Ticket_RGLR
TicketScan_RGLR

These provide the classification-specific implementation for tickets and ticket scans. The normal classification is RGLR.

15. Subscription Validation Strategies

The example contains three strategies:

SubscriptionValidationPublic
SubscriptionValidationUser
SubscriptionValidationAdmin

The user strategy checks:

if($subscribableitem->activity->subscriptionPeriodOver()) {
    return $this->errorcode(
        100,
        'Inschrijving of annuleren is jammer genoeg niet langer mogelijk.'
    );
}

if(!$member->active) {
    return $this->errorcode(
        200,
        'Je lidmaatschap is gedeactiveerd.'
    );
}

if($member->subscriptionuntil <
   $subscribableitem->activity->date) {

    return $this->errorcode(
        201,
        'Gelieve eerst je lidgeld voor volgend jaar te betalen'
    );
}

return $this->errorcode(0);

The admin strategy extends the user strategy and deliberately applies different rules.

16. CommandDecorator Example

The example's PublicActivityCommand illustrates the recommended extension pattern.

class PublicActivityCommand
    extends \controllerframework\controllers\CommandDecorator
{
    public function doExecuteDecorator(
        \controllerframework\registry\Request $request
    ): ?int {

        $id = filter_var(
            $request->get('id'),
            FILTER_VALIDATE_INT
        );

        if(!$id) {
            $request->set('errorcode', 'wrongID');
            $request->addFeedback("Wrong ID");
            return self::CMD_ERROR;
        }

        $activity = \model\Activity::find($id);

        $seatmap =
            $activity->activitytypeimplementation->seatmap();

        $this->addResponses($request, [
            'seatmap' => $seatmap
        ]);

        return null;
    }

    public function initCommand(): void {
        $this->setCommand(
            new \membersactivities\commands\user\PublicActivityCommand
        );
    }
}

The decorator performs client-specific work and then delegates the generic activity operation to the MembersActivities command.

17. Public Activity Example

The route is:

/activity?id=123

The client decorator validates the ID, retrieves the activity and determines whether the activity's type requires a seat map.

The generic framework command then prepares the activity, member and cost-item information for the view.

/activity?id=123 │ ▼ commands\user\PublicActivityCommand │ ├── validate ID ├── load Activity └── determine seatmap │ ▼ membersactivities\commands\user\PublicActivityCommand │ ▼ views/user/activity.php

The view renders activity information, cost items and, when enabled, the seat-map interface.

18. Seat-map and Reservation Example

A seat map is a user-interface mechanism for selecting seats. It is not the authoritative source for determining whether a seat is still available.

The application must perform the final availability check on the server immediately before the subscription and ticket data is persisted.

User selects seats │ ▼ Seat-map interface │ ▼ Submit reservation │ ▼ Server checks current database state │ ├── seat available │ │ │ ▼ │ persist reservation │ └── seat already reserved │ ▼ return conflict
Important: Never rely solely on the JavaScript seat map to prevent double booking. Two users can have the same seat displayed as available at the same time. The server-side database check is authoritative.

For applications with concurrent reservations, the check and persistence should be handled as one consistent transaction or through equivalent database-level protection.

19. Payment Example

The example uses a separate CreatePaymentCommand decorator. It currently delegates without adding extra logic.

public function doExecuteDecorator(
    \controllerframework\registry\Request $request
): ?int {
    return null;
}

The wrapped framework command performs the generic payment creation.

This is a useful template: client code can remain empty until application-specific behaviour is actually required.

20. Mollie Example

The example's PaymentToMollieCommand extends the framework Mollie command.

class PaymentToMollieCommand
    extends \membersactivities\commands\mollie\PaymentToMollieCommand
{
    public function doExecuteDecorator(
        \controllerframework\registry\Request $request
    ): ?int {

        $id = filter_var(
            $request->get('id'),
            FILTER_VALIDATE_INT
        );

        $request->set(
            'paymentConfirmation',
            'paymentConfirmation'
        );

        $request->set(
            'orderDescription',
            APP . " orderid=" . $id
        );

        return null;
    }
}

This demonstrates an important separation:

Client PaymentToMollieCommand │ ├── determines paymentConfirmation route └── determines orderDescription │ ▼ Framework OrderToMollieCommand │ ├── retrieves server-side Payment ├── uses server-side amount └── creates Mollie payment

The order description is therefore client-specific and travels through the Request. The payment amount must remain server-side.

20.1 Payment status processing

The example's Payment_RGLR implements statusReceived().

When the payment status becomes paid, the client-specific implementation can:

Payment state: payment status changes should use the dedicated payment status mechanism, such as statusReceived(), rather than treating payment status as an unrestricted generic property update.

21. Ticket Example

MembersActivities 1.1.0 introduces a persistent Ticket domain model. A subscription can therefore result in individual ticket records rather than relying only on a subscription quantity.

21.1 Ticket model

A Ticket contains information such as:

PropertyPurpose
idUnique ticket identifier.
descriptionDomain description.
classificationTicket type classification, normally RGLR.
subscription_idSubscription from which the ticket originated.
tokenUnique ticket token used for ticket identification.
statusCurrent ticket status.
usedIndicates whether the ticket has already been used.
used_atTimestamp of ticket usage.
created_atCreation timestamp.

21.2 Ticket creation

Tickets should normally be created when the payment has reached the appropriate successful state. The exact business rule remains client-specific.

Subscription │ ├── Ticket 1 ├── Ticket 2 ├── Ticket 3 └── ...

This provides a stable domain object for ticket PDFs, QR codes, scanning, Google Wallet and later ticket-related operations.

22. Ticket Scanning Example

The example supports ticket scanning through the persistent Ticket and TicketScan models.

22.1 TicketScan model

PropertyPurpose
idUnique scan record.
ticket_idTicket that was scanned.
scannerScanner or scanner identity.
scanned_atTimestamp of the scan.
resultResult of the scan.

Typical result values are:

valid
already_used
cancelled
invalid
wrong_activity

22.2 Scanner flow

Ticket QR code │ ▼ Scanner application │ ▼ Validate ticket_scan AccessToken │ ▼ Find Ticket by token │ ▼ Verify activity │ ▼ Verify ticket status │ ▼ Atomically claim Ticket │ ├── accepted │ └── rejected │ ▼ create TicketScan

The scan operation must be protected against two scanners accepting the same ticket simultaneously.

22.3 Atomic ticket claim

A conceptual database operation is:

UPDATE ticket
SET
    used = 1,
    used_at = CURRENT_TIMESTAMP
WHERE
    id = :ticket_id
    AND status = 'valid'
    AND used = 0;

The application should verify that exactly one row was changed before considering the ticket accepted.

23. Google Wallet Example

Google Wallet integration in 1.1.0 is based on persistent Ticket records. The application should therefore not derive the number of Wallet tickets solely from the historical subscription.quantity field.

Payment becomes paid │ ▼ Create persistent Tickets │ ├── Ticket 1 ├── Ticket 2 └── ... │ ▼ Create Google Wallet representation for each Ticket

Each Wallet pass can therefore correspond to an actual Ticket domain object with its own identifier and token.

The Wallet integration remains optional and can be disabled for applications that do not use it.

24. Security Features

MembersActivities 1.1.0 is designed to be used together with the security features of Controller Framework 1.0.31.

24.1 CSRF protection

State-changing requests should use POST and CSRF protection. Client commands can obtain and validate the CSRF token through the Controller Framework command support.

Typical operations include:

24.2 AccessToken

AccessToken is intended for protected URLs and flows where a normal authenticated session is not the appropriate mechanism.

Examples include:

AccessToken::generate(
    'mollie-order',
    (string) $paymentId
);

AccessToken::generate(
    'ticket_scan',
    (string) $activityId
);

The purpose and identifier are part of the token generation process. The secret salt is configured through _SALTRAND.

24.3 Mollie amount protection

The amount submitted to Mollie must be derived from the server-side Payment object. It must not be trusted from a client request.

24.4 Administrative commands

Commands exposing administrative data or performing administrative changes should use the AdminLogin strategy.

24.5 Dynamic class instantiation

Client request data must not be allowed to select arbitrary PHP classes. Use controlled type implementations and CommandDecorators instead.

25. Administration Example

The example contains administrative commands for:

A typical client decorator explicitly requires administrator login:

protected function getLevelOfLoginRequired(): void {
    $this->setLoginLevel(
        new \controllerframework\sessions\AdminLogin()
    );
}

This should be the normal pattern for commands exposing administrative data or state-changing administrative operations.

26. Run and Test the Example

26.1 Development server

For a local PHP development environment, the application's public entry point should be index.php. Depending on the Controller Framework's routing requirements and your web-server setup, configure the document root and rewrite rules according to the supplied .htaccess.

26.2 First test

Open the configured application homepage:

https://your-domain.example/

The root command forwards to the configured login/activity/admin flow depending on the returned command status.

26.3 Test an activity

https://your-domain.example/activity?id=1

Verify that the activity, cost items and appropriate seat-map behaviour are displayed.

26.4 Test seat reservation

For a seat-map activity, select one or more seats and complete the reservation. Then test the same seat with a second browser or user session. The second reservation must be rejected when the seat has already been reserved.

26.5 Test payment

Use the test Mollie configuration. Verify the complete sequence:

Activity │ ▼ Create Payment │ ▼ Mollie │ ├── customer return │ └── webhook │ ▼ Payment status update │ ▼ Payment_RGLR::statusReceived() │ ▼ Persistent Ticket creation

26.6 Test ticket scanning

Generate or use a ticket containing its QR/token information and scan it with the scanner application.

Verify at least these cases:

26.7 Test administration

Log in as an administrator and verify activity, cost item, member, payment, ticket and export management as applicable to the example.

27. How to Turn the Example into Your Application

The recommended approach is incremental.

1. Keep the framework dependency compatible.
Start with MembersActivities Framework 1.1.0.
2. Replace configuration.
Set application name, domain, database, mail and integration credentials.
3. Replace the client models.
Adapt Member, Activity, Costitem, Subscription, Payment, Ticket and TicketScan to your domain.
4. Define classifications.
Create type implementations such as Activity_RGLR, Ticket_RGLR or your own application-specific types.
5. Define validation strategies.
Implement the rules for public users, members and administrators.
6. Adapt views.
Replace example branding, texts and layout.
7. Add decorators.
Use CommandDecorators for client-specific behaviour around framework commands.
8. Configure payments.
Keep amounts server-side and use the Request mechanism for client-specific payment descriptions.
9. Configure ticket handling.
Create persistent Ticket records after the appropriate successful payment state and use those records for PDFs, QR codes, scanning and Wallet integration.
10. Configure seat maps.
Use the seat map for presentation and selection, but perform the final availability check on the server before persistence.
11. Remove unused integrations.
If you do not use Google Wallet, seat maps, ticket scanning or another optional feature, do not retain unnecessary configuration or code.
12. Test before production.
Test authentication, CSRF, AccessTokens, subscriptions, seat conflicts, payments, webhooks, tickets, scanning, exports and error paths.

28. Migrating an Existing 1.0.31 Application

When upgrading an existing application from MembersActivities 1.0.31 to 1.1.0, the application should be upgraded deliberately rather than treating the framework package update as the only required change.

28.1 Composer

"samoscon/membersactivities-framework": "^1.1.0"

Run:

composer update samoscon/membersactivities-framework

28.2 Database

Apply the database changes required by the 1.1.0 schema, including the persistent ticket model and ticket scan model.

Where the existing application used subscription quantity to represent multiple tickets, determine how those existing subscriptions should be represented as individual Ticket records.

28.3 Google Wallet

Applications using Google Wallet should adapt the Wallet creation logic to use actual persistent Ticket records.

28.4 Seat reservations

Applications using seat maps should ensure that final seat availability is checked server-side before the reservation is persisted.

28.5 Payment handling

Review custom payment implementations and make sure that successful payment processing uses the dedicated status mechanism such as statusReceived().

28.6 Ticket scanning

Applications introducing ticket scanning should create the required TicketScan records and protect scanner access with the appropriate ticket_scan AccessToken flow.

29. Basic Implementation Checklist

AreaCheck
Composer samoscon/membersactivities-framework is set to ^1.1.0.
Controller Framework Controller Framework 1.0.31 is installed and compatible.
Configuration Application, database and integration settings are replaced with client values.
Secrets _SALTRAND, database passwords and API credentials are protected.
Database Reference schema is imported only for a new application or migrated deliberately.
Models Required client model classes exist, including Ticket and TicketScan where used.
Composite Activity and Member parent-child relationships are handled correctly where required.
Type implementations Every classification used by the database has a corresponding client type class.
Validation Public/user/admin subscription rules are explicitly defined.
Routing All required commands and views are registered in controls.xml.
Security POST/CSRF, login levels and AccessToken flows are tested.
Payments Payment amounts come from server-side Payment objects.
Mollie Test webhook and payment confirmation flow works before live deployment.
Tickets Successful payments create the required persistent Ticket records.
Ticket scanning Ticket validation and atomic ticket usage are tested.
Seat map Server-side seat conflict detection is tested.
Google Wallet Wallet passes are generated from persistent Ticket records when enabled.
Mail SMTP settings and mail queue processing are tested.
Cron Absolute paths are used.
Production HTTPS, protected configuration and production credentials are configured.