CSA Integration

1. Overview

The Event Registration workflow integrates with Cycling South Africa (CSA) to validate rider membership and racing licenses. This validation is performed via three sequential FormField types during the registration question workflow:

StepCode FormField Class Purpose

CSI

CsaIdentificationFormField

Capture CSA membership number and identification details

CSM

CsaMembershipCheckFormField

Validate CSA membership status via CSA external API

CSL

CsaLicenseCheckFormField

Validate CSA racing license via CSA external API

These steps form a dependent chain: CSI captures the identifier, CSM validates membership using that identifier, and CSL validates the racing license.

2. ProcessData Flow

The CSA steps follow the standard ProcessData staging pattern. Each step reads data from prior steps and stores its result:

Step Reads From Stores

CSI

(user input)

CSA membership number, ID number, ID country

CSM

CSI ProcessData (CSA number or ID number)

Membership validation result, CSA member ID, membership status

CSL

CSI ProcessData (CSA number or ID number)

License validation result, membership status, license status

Both CSM and CSL gate form progression — the participant cannot advance to the next question if validation fails, unless an alternative action is chosen (day license, defer, exempt).

3. CSA API

The CSA API is hosted by Cycling South Africa at cyclingsa-events.co.za. It provides two endpoints for checking a rider’s membership and racing license status.

3.1. Authentication

Authentication is via an API key passed in the JSON request body (not as an HTTP header).

Parameter Description

key

A unique API key assigned to the organisation. Must be included in every request body.

The API key must be stored as an application property (csa.api.key), never hardcoded.

3.2. Base URL

https://www.cyclingsa-events.co.za/api/v1/exec.php

3.3. Endpoints

3.3.1. Check Membership Status

Checks whether a rider has an active CSA membership on a given date.

URL: ?c=Member&fn=checkMembershipStatus

Method: POST

Content-Type: application/json

Request body:

{
  "key": "<API_KEY>",
  "idNumber": "8501015009087",
  "date": "2026-06-15"
}
Field Type Description

key

String (required)

API authentication key

idNumber

String (conditional)

South African ID number. Much better suited for ZA citizens as it is a unique national identifier. Cannot be used for foreign riders.

csaId

String (conditional)

CSA’s internal member reference number. Can be used for any rider (local or foreign), but idNumber is preferred for ZA citizens.

date

String (optional)

Date to check in YYYY-MM-DD format. Defaults to current date if omitted.

Either idNumber or csaId must be provided. When the person’s ID country is ZA and an ID number is available, prefer idNumber as it is the stronger identifier. For foreign riders, csaId is the only option.

Response body:

{
  "ok": true,
  "memberFound": true,
  "active": true,
  "date": "2026-06-15"
}
Field Type Description

ok

Boolean

true if the API call executed successfully. false if an error occurred.

error

String

Error description. Only present when ok is false.

memberFound

Boolean

true if the rider was found on the CSA system.

active

Boolean

true if the rider has an active membership on the given date.

date

String

The date that was checked (YYYY-MM-DD).

3.3.2. Check Racing License Status

Checks the rider’s membership status AND racing license status in a single call. This is the more comprehensive endpoint.

URL: ?c=Member&fn=checkRacingLicenceStatus

Method: POST

Content-Type: application/json

Request body: Same structure as membership check.

{
  "key": "<API_KEY>",
  "idNumber": "8501015009087",
  "date": "2026-06-15"
}

Response body:

{
  "ok": true,
  "memberFound": true,
  "memberCsaId": "12345",
  "membershipStatus": "ACTIVE",
  "licenceStatus": "ACTIVE",
  "date": "2026-06-15"
}
Field Type Description

ok

Boolean

true if the API call executed successfully.

error

String

Error description. Only present when ok is false.

memberFound

Boolean

true if the rider was found on the CSA system.

memberCsaId

String

The CSA ID of the cyclist (returned by CSA, useful for cross-referencing).

membershipStatus

String

Membership status on the given date (see status codes below).

licenceStatus

String

Racing license status on the given date (see status codes below).

date

String

The date that was checked (YYYY-MM-DD).

3.4. Status Codes

3.4.1. Membership Status Values

Value Meaning

ACTIVE

The cyclist has an active membership on the checked date.

EXPIRED

The cyclist’s membership has expired.

SUSPENDED

The cyclist’s membership is suspended.

NONE

The cyclist is not a CSA member.

NO MEMBERSHIP

The cyclist is not a CSA member (alternative phrasing of NONE).

3.4.2. License Status Values

Value Meaning

ACTIVE

The cyclist has an active racing license on the checked date.

INACTIVE

The license is inactive. This occurs when the cyclist has a racing license but their membership has expired — the license becomes inactive because it depends on active membership.

SUSPENDED

The cyclist’s racing license has been suspended.

NONE

The cyclist does not have a racing license.

NO LICENSE

The cyclist does not have a racing license (alternative phrasing of NONE).

The INACTIVE license status is distinct from EXPIRED. An INACTIVE license means the underlying membership has lapsed, making the license inactive even though the license itself has not expired. Renewing the membership would reactivate the license.

3.5. Key API Observation

The checkRacingLicenceStatus endpoint returns both membership and license status in a single response. The legacy implementation exploits this by calling only checkRacingLicenceStatus for both the membership check and the license check. This avoids making two separate API calls.

The new Java implementation should follow the same pattern: use checkRacingLicenceStatus as the primary endpoint, and only fall back to checkMembershipStatus if license information is not needed.

4. Java Implementation: CsaApiClient

The CsaApiClient Spring service in admin-service must implement the real API integration. The current implementation uses mock responses (csa.api.mock=true).

4.1. Configuration Properties

Property Default Description

csa.api.base-url

https://www.cyclingsa-events.co.za/api/v1/exec.php

CSA API base URL

csa.api.key

(none — required)

API authentication key

csa.api.timeout

10000

HTTP request timeout in milliseconds

csa.api.mock

true

Use mock responses for development/testing

4.2. Implementation Notes

  1. HTTP client: Use Spring RestTemplate or WebClient to POST JSON to the CSA API.

  2. Request construction: Build a JSON object with key, idNumber or csaId, and date.

  3. SSL: The legacy implementation disables SSL verification. The new implementation must NOT disable SSL verification. If certificate issues arise, add the CSA certificate to the Java truststore.

  4. Error handling:

    • cURL/HTTP errors → return CsaMembershipResponse.offline() or CsaLicenseResponse.offline()

    • ok=false in response → log the error message, return offline status

    • memberFound=false → return NOT_FOUND status

  5. Lookup preference: When the person’s ID country is ZA and an ID number is available, send idNumber (stronger identifier). For foreign riders, send csaId (the only option). Both can be used for any rider, but idNumber is preferred for ZA citizens.

4.3. Suggested CsaApiClient Method Signatures

/**
 * Check membership status using the checkMembershipStatus endpoint.
 * Use when only membership status is needed.
 */
public CsaMembershipResponse checkMembership(String idNumber, String csaId, LocalDate date);

/**
 * Check both membership and license status using checkRacingLicenceStatus.
 * Preferred method -- returns both statuses in one call.
 */
public CsaLicenseResponse checkLicense(String idNumber, String csaId, LocalDate date);

5. Validation Workflow

5.1. Business Rules Hierarchy

CSA requirements are determined at two levels, with category overriding event:

csa-requirement-hierarchy

5.1.1. Rule: License Requires Membership

When a racing license is required, CSA membership is always implicitly required. A rider cannot hold an active license without an active membership (CSA enforces this — an expired membership causes the license status to become INACTIVE).

5.1.2. Rule: Requirement Inheritance

Requirements cascade: Series → Event → Category. A category-level setting overrides the event-level setting. An event-level setting overrides the series-level setting. If no value is set at a level, it inherits from the parent.

5.1.3. Rule: Strict vs Lenient Registration

  • Strict (strictRegistration=true): The rider must have valid membership/license at the time of registration. The form blocks progression if validation fails.

  • Lenient (strictRegistration=false): The rider can register even without valid status. The compliance state is recorded and can be enforced closer to event day.

5.1.4. Rule: Day License

When a rider does not hold a valid license, they may purchase a day license — unless the event explicitly disallows it (dayLicenseDisallow=true).

5.2. Membership Validation (CSM)

The CSM step calls the CSA API to check whether the rider’s membership is current and valid for the event.

Validation logic:

  1. Read the CSA ID (or ID number) from the CSI step’s ProcessData

  2. Call checkRacingLicenceStatus with the ID and the event start date as the check date

  3. Map the membershipStatus response to an internal status code

  4. Determine available actions based on the status

Status mapping:

CSA API membershipStatus Internal Status Available Actions

ACTIVE

ACTIVE

Proceed (auto-advance)

EXPIRED

EXPIRED

Renew membership, purchase day license (if allowed), defer, remove person

SUSPENDED

SUSPENDED

Remove person from registration

NONE / NO MEMBERSHIP

NOT_FOUND

Purchase day license (if allowed), defer, remove person

(API unreachable)

OFFLINE

Defer (allow registration, check later)

When status is ACTIVE: The CSM step auto-fills the answer as valid and can be configured to auto-advance (skip=true) so the user sees no interruption.

5.3. License Validation (CSL)

The CSL step calls the CSA API to check the rider’s racing license status.

Validation logic:

  1. Read the CSA ID from CSI ProcessData (same as CSM)

  2. Call checkRacingLicenceStatus with the ID and the event start date

  3. Map the licenceStatus response to an internal status code

  4. Determine available actions based on the status

Status mapping:

CSA API licenceStatus Internal Status Available Actions

ACTIVE

ACTIVE

Proceed (auto-advance)

INACTIVE

INACTIVE

Renew membership first (license becomes active when membership is renewed), defer, remove person

SUSPENDED

SUSPENDED

Remove person from registration

NONE / NO LICENSE

NOT_FOUND

Purchase day license (if allowed), defer, remove person

(API unreachable)

OFFLINE

Defer (allow registration, check later)

The INACTIVE license status specifically means the rider’s membership has expired, causing the license to become inactive. The resolution is to renew the membership, not the license. The UI should communicate this clearly.

5.4. User Actions on Validation Failure

When CSM or CSL validation fails, the user is presented with action options:

Action Description

PROCEED

Only available when status is ACTIVE. Advances to the next step.

RENEW

Directs the rider to renew their CSA membership. The rider must renew externally and then retry.

DAY_LICENSE

Adds a day license to the order. Only available if the event allows day licenses. A day license covers the rider for the specific event date only.

DEFER

Allows the registration to continue with a compliance status of DEFERRED. The rider must resolve the issue before the event. Useful when the API is offline or the rider intends to renew before event day.

REMOVE

Removes this person from the registration. Used when the rider cannot or will not resolve the compliance issue.

6. Caching Strategy

The legacy implementation uses a caching strategy to avoid redundant API calls:

  1. Active status: Cache the result until the event start date. An active membership/license checked for the event date will not change, so the result is stable.

  2. Non-active status: Cache for 5 minutes only. The rider may renew their membership at any time, so we retry after a short interval.

  3. Within 1 minute: Always return the cached result to prevent rapid repeated calls (e.g., page refreshes).

The new implementation should use Spring’s @Cacheable with a TTL-based eviction policy. Cache keys should include the CSA ID and the check date.

7. Admin-Service Wrapper API

The admin-service exposes its own REST endpoints that wrap the CSA API. Frontends (registration-portal, admin-ui) call the admin-service endpoints instead of calling CSA directly. This avoids CORS issues and keeps the API key server-side.

7.1. Endpoint: Check Membership and License

POST /api/csa/check

Request body:

{
  "idNumber": "8501015009087",
  "csaId": "12345",
  "date": "2026-06-15"
}
Field Type Description

idNumber

String (optional)

South African ID number. Preferred for ZA citizens.

csaId

String (optional)

CSA member ID. Used for foreign riders or when ID number is unavailable.

date

String (optional)

Date to check (YYYY-MM-DD). Defaults to current date if omitted.

At least one of idNumber or csaId must be provided.

Response body:

{
  "ok": true,
  "memberFound": true,
  "memberCsaId": "12345",
  "membershipStatus": "ACTIVE",
  "licenceStatus": "ACTIVE",
  "date": "2026-06-15"
}

The response DTO mirrors the CSA checkRacingLicenceStatus response. For now the admin-service wrapper shares the same DTO structure as the upstream CSA API, keeping the integration simple. If the wrapper needs to diverge (e.g., enriching with internal compliance data), the DTOs can be separated later.

7.2. Endpoint: Check Membership Only

POST /api/csa/check-membership

Same request body. Returns the simpler membership-only response (mirrors checkMembershipStatus).

{
  "ok": true,
  "memberFound": true,
  "active": true,
  "date": "2026-06-15"
}

7.3. Design Rationale

  • No CORS: Frontends call admin-service on the same origin. Admin-service calls CSA server-side.

  • API key security: The CSA API key is never exposed to the browser.

  • Caching: Admin-service can cache CSA responses (see Caching Strategy) to reduce external API load.

  • Resilience: Admin-service handles timeouts, retries, and circuit breaking. Frontends get a consistent error response regardless of CSA API behaviour.

  • Shared DTOs (for now): The admin-service response mirrors the CSA API response structure. This avoids unnecessary mapping layers while the integration is straightforward. If admin-service needs to enrich or transform the response in future, the DTOs can be separated without breaking the upstream CSA contract.

8. ComplianceStatus on EventParticipant

The EventParticipant entity has a complianceStatus field (enum ComplianceStatus) that records the outcome of CSA validation:

Value Meaning

VALID

Membership and license checks passed with ACTIVE status.

EXEMPT

The rider is exempt from CSA requirements (manually set by admin).

DAY_LICENSE

The rider purchased a day license for this event.

DEFERRED

The rider deferred resolution. Must be resolved before event day.

PENDING

Checks have not yet been completed (initial state).

This field is set by the CSM and CSL FormFields when saving their ProcessData, and is also available for onDone() to read when creating the final EventParticipant entity.

9. Configuration

Events that require CSA validation include CSI, CSM, and CSL steps in their ProcessDefinition. Events that do not require CSA validation simply omit these steps — the ProcessDefinition is configurable per event.

The CSA requirement flags on Event and EventCategory entities control the business rules (see Business Rules Hierarchy).

10. Source Code

10.1. Legacy WordPress Implementation (Reference)

File Purpose

wpca-members/controllers/CSAController.php

API client — HTTP calls to CSA endpoints via cURL

wpca-members/controllers/CSAEventEntryController.php

Business logic — requirement hierarchy, status mapping, caching, validation rules

wpca-members/controllers/CSACheckMembershipStatusResponse.php

DTO for checkMembershipStatus response

wpca-members/controllers/CSACheckRacingLicenseStatusResponse.php

DTO for checkRacingLicenceStatus response

wpca-members/public/templates/EventCSACheck.php

UI for individual rider CSA status and day license purchase

wpca-members/public/templates/EventEntryReviewCSA.php

Review page showing all entrants' CSA status

10.2. New Java Implementation

File Purpose

admin-service/…​/csa/CsaApiClient.java

Spring service — API client (currently mock, real implementation pending)

admin-service/…​/csa/CsaCheckStatus.java

Enum: ACTIVE, EXPIRED, OFFLINE, NOT_FOUND, SUSPENDED

admin-service/…​/csa/CsaMembershipResponse.java

DTO for membership check response

admin-service/…​/csa/CsaLicenseResponse.java

DTO for license check response

admin-service/…​/csa/CsaApiException.java

RuntimeException for API failures

admin-service/…​/form/CsaIdentificationFormField.java

CSI step — captures CSA ID and UCI ID

admin-service/…​/form/CsaMembershipCheckFormField.java

CSM step — validates membership, presents action options

admin-service/…​/form/CsaLicenseCheckFormField.java

CSL step — validates license, presents action options

admin-service/…​/form/answer/CsaIdentificationAnswer.java

JSON answer for CSI step

admin-service/…​/form/answer/ComplianceAnswer.java

JSON answer for CSM/CSL steps

database/…​/enumeration/ComplianceStatus.java

Enum: VALID, EXEMPT, DAY_LICENSE, DEFERRED, PENDING

database/…​/domain/EventParticipant.java

Entity with complianceStatus field

database/…​/age/CSAAgeCalculator.java

Age calculator per CSA regulation (age on 31 December of current year)