Event Registration

1. Overview

The Event Registration workflow enables users to register participants for events such as races, tournaments, and competitions. It follows the Phase 2 Registration Design with implementation-specific details for event scenarios.

Key Features:

  • Multi-participant Registration - Register multiple people in a single transaction

  • LinkedPerson Integration - Select from existing linked persons

  • Order-based System - Creates Order entity for payment tracking

  • Payment Gateway Integration - Redirect to payment portal after registration

  • Cancellation Support - Cancel registrations with refund rules (Phase 2)

2. Event Concepts

2.1. Event Entities

event-entities

Event

Defines the overall competition or activity:

  • Name, description, dates

  • Location and venue

  • Organizer (Organisation)

  • Registration open/close dates

  • Status (upcoming, active, completed, cancelled)

Race

Specific category or distance within an event:

  • Name (e.g., "10km Run", "21km Half Marathon")

  • Distance, difficulty level

  • Age restrictions

  • Maximum participants

  • Price per participant

EventParticipant

Individual participant registration:

  • Links Person to Race

  • Stores participant-specific details

  • Tracks registration status

  • References Order for payment

  • Can include additional participant information (bib number, start time, etc.)

Order

Payment transaction container:

  • Groups multiple EventParticipants

  • Tracks payment status

  • Links to buyer (Person via userKey)

  • Total amount

  • Payment gateway reference

3. Registration Flow by Step

This section details each step from the Phase 2 Registration Design as it applies to event registration.

3.1. Step 1: Identity Selection

Phase 2 Enhancement: Identity Selection is a new capability being introduced. Current implementation uses URL parameters for user identification.

3.1.1. Current Implementation (URL-Based Access)

Route Pattern:

/register

Query Parameters:

Parameter Description Required Example

eventId

Event ID to register for

Yes

42

orgId

Organisation ID (organizer)

Yes

8

userKey

User key (security context)

Optional

abc123xyz

Example URLs:

https://app.example.com/register?eventId=42&orgId=8&userKey=abc123xyz
https://app.example.com/register?eventId=42&orgId=8

Session vs URL userKey:

  • If userKey provided in URL → Use URL parameter (external link scenario)

  • If no userKey in URL → Use session userKey (logged-in user scenario)

3.1.2. Phase 2: OIDC Authentication

When OIDC authentication is configured for the tenant, users will be presented with a choice:

  • Authenticate - Sign in via OIDC provider to access existing profile

  • Continue Anonymously - Proceed without authentication

See Identity Selection in the Phase 2 design for details.

3.2. Step 2: Registration Overview

The Registration Overview displays the event details and linked persons for selection.

Component: RegistrationComponent

File: src/main/webapp/app/entities/registration/list/registration.component.ts

3.2.1. Component Structure

export class RegistrationComponent implements OnInit, OnDestroy {
  persons: IPersonSelect[] = [];
  selectForm!: FormGroup;
  selectCount = 0;
  isLoading = false;
  isSaving = false;

  _eventId: number | undefined;
  event: IEvent | undefined;
  _organisationId: number | undefined;
  organisation: IOrganisation | undefined;
  _userKey: string | undefined;
  principal: IPerson | undefined;
  sessionUserId: boolean = true;
}

3.2.2. Initialization

ngOnInit(): void {
  this.selectForm = this.fb.group({
    selection: this.fb.array([]),
  });

  // Subscribe to checkbox changes for count
  this.selectionChangeSubscription = this.selectForm.valueChanges
    .subscribe(data => {
      const arr: boolean[] = data.selection;
      this.selectCount = arr.reduce((n, checkbox) =>
        (checkbox ? n + 1 : n), 0);
    });

  this.handleNavigation();
}

3.2.3. Navigation Handler

protected handleNavigation(): void {
  combineLatest([this.activatedRoute.data, this.activatedRoute.queryParamMap])
    .subscribe(([data, params]) => {
      const eventId = params.get('eventId');
      if (eventId) {
        this.eventId = +eventId;
      }

      const organisationId = params.get('orgId');
      if (organisationId) {
        this.organisationId = +organisationId;
      }

      const userKey = params.get('userKey');
      if (userKey) {
        this.userKey = userKey;
        this.sessionUserId = false;
      } else {
        this.userKey = this.sessionService.getUserKey();
        this.sessionUserId = true;
      }

      this.loadPage(pageNumber, true);
    });
}

API Endpoints Called:

  • GET /api/events/{id} - Load event details

  • GET /api/organisations/{id} - Load organisation details

  • GET /api/people/userkey/{userkey} - Load or create principal person

3.2.4. Loading LinkedPersons

loadPage(page?: number, dontNavigate?: boolean): void {
  const pageToLoad: number = page ?? this.page ?? 1;

  if (this.organisationId && this.userKey) {
    this.isLoading = true;
    this.personService.getAllLinkedOrgUsersByPrincipal(
      this.userKey,
      this.organisationId
    ).subscribe(
      (res: EmbeddedLinkedPersonDTO[]) => {
        this.isLoading = false;
        this.persons = res;
        this.selectForm.setControl('selection', this.buildSelection());
      },
      () => {
        this.isLoading = false;
        this.onError();
      }
    );
  }
}

API Endpoint: GET /api/people/linked?userKey={key}&organisationId={orgid}

Response:

[
  {
    "id": 1,
    "firstName": "John",
    "lastName": "Smith",
    "dateOfBirth": "1985-03-15",
    "gender": "MALE",
    "email": "[email protected]",
    "contactNumber": "0821234567"
  },
  {
    "id": 3,
    "firstName": "Billy",
    "lastName": "Smith",
    "dateOfBirth": "2010-11-03",
    "gender": "MALE",
    "email": null,
    "contactNumber": null
  }
]

3.2.5. Person Selection UI

person-selection-ui

3.2.6. Existing Registration Status

Phase 2 Enhancement: The Registration Overview will clearly show if any person is already registered for this event. See Registration Overview in the Phase 2 design.

For persons already registered:

  • Display "Registered" status

  • Show bib number if assigned

  • If current user was the payer, show link to view order/payment/invoice details

3.2.7. Event Cancellation

Phase 2 Enhancement: Event cancellation with refund rules is a new capability. See Registration Overview in the Phase 2 design.

Outstanding Requirements:

  • REQ-001: Define cancellation windows and refund percentages

  • REQ-002: Specify processing fee handling

  • REQ-003: Define refund mechanism

  • REQ-004: Determine notification flow for cancellation

For events, users will be able to cancel existing registrations:

  1. Select registered person

  2. Request cancellation

  3. System applies configured refund rules

  4. Refund processed (if applicable)

  5. Registration status updated to cancelled

The Link Person step allows users to add participants to their profile.

See LinkedPerson Management for complete details on the person search, matching, and linking workflow.

Add Person Button:

<a routerLink="/linked-person/search">
  <button type="button" class="btn btn-primary">
    <fa-icon icon="plus"></fa-icon>
    Add Person
  </button>
</a>

Flow:

  1. User clicks "Add Person"

  2. Navigate to /linked-person/search

  3. User searches/creates person

  4. Person is linked to principal

  5. Return to /register with updated person list

3.3.1. Form Array for Selection

buildSelection(clear?: boolean): FormArray<FormControl<boolean | null>> {
  const arr = this.persons.map(person => {
    return this.fb.control(!clear ? !!person.selected : false);
  });
  return this.fb.array(arr);
}

get selectionControl(): FormArray<FormControl<boolean | null | undefined>> {
  return this.selectForm.get('selection') as FormArray;
}

Validation:

get canRegister(): boolean {
  return !!this.event &&
         this.selectForm.valid &&
         this.selectCount > 0 &&
         !this.isLoading &&
         !this.isSaving;
}

3.4. Step 4: Registration Details

Phase 2 Enhancement: Event registration will support process-driven questions similar to membership registration. Current implementation uses direct registration without questions.

3.4.1. Current Implementation (Direct Registration)

Event registration currently bypasses the question workflow and proceeds directly to registration submission.

3.4.2. Phase 2: Question Workflow

For Phase 2, event registration will support:

  • Category Selection - Choose race category for each participant

  • Personal Information - Emergency contact, medical conditions (if not already captured)

  • Event-Specific Questions - T-shirt size, transport requirements, etc.

  • Legal Acceptance - Event indemnity, terms and conditions

The question workflow will use the same ProcessDefinition system as membership registration. See Process Flow Integration for details.

3.5. Step 5: Order Creation

Upon completing Registration Details, the system commits the registration to the admin-service:

  1. EventParticipant records created (status: Pending Payment)

  2. Order created with line items:

    • Event entry fee per participant

    • Additional fees (if applicable)

  3. Order status set to Unpaid

The order details are then used to either display a reference number for manual payment or create a corresponding WooCommerce order for online payment.

See Order Creation in the Phase 2 design for complete details on the order creation process and payment callbacks.

3.6. Step 6: Payment

3.6.1. Registration Submission

register(value: boolean[]) {
  if (this.canRegister) {
    const participants: IParticipantCandidate[] = [];

    this.selectionControl.value.forEach((checkbox, i) => {
      if (!!checkbox) {
        const person: IPerson = this.persons[i];
        const participant: IParticipantCandidate = {
          id: null,
          person
        };
        participants.push(participant);
      }
    });

    const order: NewOrder = {
      id: null,
      organisation: this.organisation ?? null,
      buyer: { id: null, userKey: this.userKey ?? null },
    };

    const registration: IEventParticipantRegistration = {
      event: this.event!,
      participants,
      order,
    };

    this.isSaving = true;
    this.registrationService.register(registration).subscribe(
      (res: HttpResponse<IEventParticipantRegistration>) => {
        this.log.debug('Registration', res.body);
        this.clear();
        this.isSaving = false;
        if (res.body?.infoURL) {
          location.href = res.body.infoURL;
        }
      },
      () => {
        this.clear();
        this.isSaving = false;
      }
    );
  }
}

3.6.2. Registration Model

export interface IParticipantCandidate extends NewEventParticipant {
  questions?: IQuestion[];
  answer?: string;
  valid?: boolean;
  message?: string;
}

export interface IEventParticipantRegistration {
  event: IEvent;
  participants: IParticipantCandidate[];
  order?: IOrder | NewOrder;
  phase?: string;
  title?: string;
  message?: string;
  infoURL?: string;
  questionType?: string;
}

3.6.3. Request Payload Example

{
  "event": {
    "id": 42,
    "name": "Marathon 2024",
    "startDate": "2024-06-15T08:00:00Z"
  },
  "participants": [
    {
      "id": null,
      "person": {
        "id": 3,
        "firstName": "Billy",
        "lastName": "Smith",
        "dateOfBirth": "2010-11-03",
        "gender": "MALE"
      }
    }
  ],
  "order": {
    "id": null,
    "organisation": {
      "id": 8,
      "name": "Running Club"
    },
    "buyer": {
      "id": null,
      "userKey": "abc123xyz"
    }
  }
}

API Endpoint: POST /api/event-participants/register

3.6.4. Backend Processing

Backend Flow:

  1. Validate Registration

    • Check event registration is open

    • Verify person eligibility

    • Check age restrictions for race

    • Verify maximum participants not exceeded

  2. Create EventParticipant Records

    • One EventParticipant per selected person

    • Link to Race (derived from Event)

    • Assign bib numbers (if applicable)

    • Set status to PENDING_PAYMENT

  3. Create Order

    • Calculate total amount (participants × race price)

    • Generate order reference

    • Link to Organisation

    • Store buyer userKey

  4. Payment Integration

    • If amount > 0, generate payment URL

    • Include order reference and return URL

    • Return infoURL in response

  5. Free Registration

    • If amount = 0, mark participants as REGISTERED

    • Send confirmation email

    • No payment redirect needed

3.6.5. Registration Response

{
  "event": {
    "id": 42,
    "name": "Marathon 2024"
  },
  "participants": [
    {
      "id": 1523,
      "person": {
        "id": 3,
        "firstName": "Billy",
        "lastName": "Smith"
      },
      "bibNumber": "B1523",
      "status": "PENDING_PAYMENT"
    }
  ],
  "order": {
    "id": 892,
    "reference": "ORD-892-2024",
    "totalAmount": 350.00,
    "status": "PENDING"
  },
  "infoURL": "https://payment.gateway.com/pay?reference=ORD-892-2024&amount=350.00&return=https://app.example.com/registration/payment-return"
}

3.6.6. Online Payment (WooCommerce)

Payment Redirect:

if (res.body?.infoURL) {
  location.href = res.body.infoURL;
}

Payment Gateway URL Example:

https://payment.gateway.com/pay?
  reference=ORD-892-2024&
  amount=350.00&
  return=https://app.example.com/registration/payment-return&
  cancel=https://app.example.com/registration/payment-cancel

3.6.7. Manual Payment

Phase 2 Enhancement: Manual payment is a new capability being introduced. See Payment in the Phase 2 design.

Manual payment will be available for registration desk scenarios at events:

  1. System generates reference number with QR code

  2. User presents QR code to staff at registration desk

  3. Staff scans/enters reference on registration system

  4. Staff captures: amount, payment method, outcome

  5. Registration completes upon payment confirmation

3.6.8. Payment Return

Return URL: /registration/payment-return

Query Parameters:

Parameter Description Example

reference

Order reference

ORD-892-2024

status

Payment status

success, failed, cancelled

transactionId

Gateway transaction ID

TXN-12345678

Backend Processing on Return:

  1. Verify payment status with gateway

  2. Update Order status

  3. Update EventParticipant status

  4. Send confirmation email

  5. Display success/failure message

3.6.9. Payment States

payment-states

3.7. Step 7: Confirmation & Notifications

3.7.1. Immediate Confirmation

Upon successful payment:

  • EventParticipant records marked as REGISTERED

  • Bib numbers assigned (if applicable)

  • Confirmation displayed with registration details

  • Confirmation email sent

Confirmation Display:

Registration Successful!

Billy Smith - Bib Number: B1523

You will receive your race pack at:
Race Expo: March 14, 2024, 10:00-18:00
Centurion Mall

Race Start: March 15, 2024, 07:00

Good luck!

3.7.2. Scheduled Notifications

Phase 2 Enhancement: Scheduled notifications are managed by long-running processes. See Confirmation & Notifications in the Phase 2 design.

For events, scheduled notifications include:

  • Registration confirmation with event details

  • Event location and directions (closer to event date)

  • Program updates and changes

  • Pre-event reminders (day before, morning of)

  • Post-event results and certificates

4. Error Handling

4.1. Validation Errors

Client-Side:

  • No event loaded → Disable register button

  • No persons selected → Disable register button

  • Form invalid → Display validation messages

Server-Side:

{
  "phase": "VALIDATION_ERROR",
  "title": "Registration Error",
  "message": "One or more participants are not eligible for this event",
  "participants": [
    {
      "id": null,
      "person": { "id": 3, "firstName": "Billy", "lastName": "Smith" },
      "valid": false,
      "message": "Participant is under minimum age requirement (18 years)"
    }
  ]
}

Error Display:

<div *ngIf="registration.phase === 'VALIDATION_ERROR'" class="alert alert-danger">
  <h4>{{ registration.title }}</h4>
  <p>{{ registration.message }}</p>
  <ul>
    <li *ngFor="let p of registration.participants">
      <span *ngIf="!p.valid">
        {{ p.person.firstName }} {{ p.person.lastName }}: {{ p.message }}
      </span>
    </li>
  </ul>
</div>

4.2. Registration Failures

Common Failure Scenarios:

Scenario Cause Resolution

Event Full

Maximum participants reached

Show waitlist option

Registration Closed

Outside registration window

Display event information only

Duplicate Registration

Person already registered

Show existing registration

Payment Gateway Down

External service unavailable

Allow registration, mark as pending payment

Network Error

Connection timeout

Show retry button, preserve selection

4.3. Loading States

<div *ngIf="isLoading" class="text-center">
  <div class="spinner-border" role="status">
    <span class="sr-only">Loading persons...</span>
  </div>
</div>

<div *ngIf="isSaving" class="text-center">
  <div class="spinner-border" role="status">
    <span class="sr-only">Processing registration...</span>
  </div>
</div>

5. Integration Points

5.1. LinkedPerson Workflow

Event registration integrates with LinkedPerson management:

  1. User accesses registration URL

  2. System loads linked persons for user

  3. User can add more persons via LinkedPerson workflow

  4. User selects participants from linked persons

  5. Registration creates EventParticipant records

5.2. Order System

Event registration creates Order entities:

  • Order - Container for transaction

  • OrderLine - Individual line items (one per participant)

  • Payment - Payment record after gateway processing

See Financial Entities for details.

5.3. Email Notifications

Registration Confirmation:

  • Sent after successful registration (free events)

  • Sent after successful payment (paid events)

  • Includes participant details, event information

  • Includes payment receipt (if applicable)

Payment Receipt:

  • Sent after successful payment

  • Includes order reference, amount paid

  • Itemized list of participants