Integration and Payment

Overview

Membership operates as an independent product but is deeply integrated with the Cash360 payment platform for all financial operations. Beyond Cash360, the system integrates with communication platforms, association management systems, hardware devices, and building automation systems. This chapter defines the integration architecture, API contracts, and data flows for all external system connections.


My Factura (Cash360) Payment Integration

Architecture

Membership delegates all payment processing, invoicing, and banking operations to Cash360 (branded as "My Factura" for the public-facing API) via its Public P2 REST API. This follows the principle of separation of concerns: Membership owns the business logic (what to charge, when, for whom), while Cash360 owns the financial execution (how to collect, reconcile, and report).

For the complete API specification and improvement proposals, see My Factura Public API Review.

Authentication: All requests use a static API_KEY header, scoped to the entity (organization). The API key is stored in an encrypted secrets vault, never in application configuration files.

Header: API_KEY: <entity-specific-api-key>

Base paths:

Resource Endpoint Version
Consumer /api/public/p2/v1/consumer v1
Transaction /api/public/p2/v1/transaction v1
Payment /api/public/p2/v1/payment v1
Billing Reporting /api/public/p2/v2/billing-statement-reporting v2

Full Billing Cycle Sequence

The following diagram shows the complete billing cycle using the actual My Factura API endpoints:

sequenceDiagram participant M as Membership Backend participant F as My Factura API participant B as Bank (SEPA/EBICS) participant W as Webhook Endpoint rect rgb(240, 248, 255) Note over M: Phase 1 — Member Onboarding M->>F: POST /api/public/p2/v1/consumer<br/>[{firstName, lastName, type: PERSON,<br/>IdExternal: membershipMemberId,<br/>bankAccount: {iban, accountOwner,<br/>sepaMandanteId, sepaMandanteDateOfSigniture}}] F-->>M: [{Id: 42, ...}] M->>M: Store externalBillingId = 42 end rect rgb(245, 255, 245) Note over M: Phase 2 — Nightly Billing Cycle (02:00) M->>M: SELECT contracts WHERE nextBillingDate <= today<br/>FOR UPDATE SKIP LOCKED M->>M: Calculate amounts per contract M->>F: POST /api/public/p2/v1/transaction<br/>[{idConsumer: 42, amount: 29.90,<br/>collectionType: DIRECT_DEBIT,<br/>dueDate: 2026-03-01,<br/>idExternal: mbrTxnId,<br/>webhook: https://mbr.example.com/api/webhooks/cash360}] F-->>M: [{id: 98765, status: NEW}] M->>M: Store externalTransactionId = 98765<br/>Update nextBillingDate += interval end rect rgb(255, 248, 240) Note over F: Phase 3 — Cash360 Processing F->>F: Validate transactions → status: ACCEPTED F->>W: POST webhook {type: transaction,<br/>transactionId: 98765, statusCd: ACCEPTED} W-->>F: 200 OK F->>F: Generate SEPA XML batch F->>B: Submit SEPA file via EBICS B-->>F: Acknowledgment F->>F: Update status: EXPORTED F->>W: POST webhook {statusCd: EXPORTED} end rect rgb(245, 245, 255) Note over B: Phase 4 — Bank Settlement (D+2 to D+5) B-->>F: Settlement report (PAIN.002) alt Payment successful F->>F: Update status: PAID F->>W: POST webhook {statusCd: PAID, amountDue: 0.00} W->>M: Update local transaction status else Payment failed (insufficient funds, closed account) F->>F: Update status: RETURNED F->>W: POST webhook {statusCd: RETURNED, amountDue: 29.90} W->>M: Update local transaction status M->>M: Create dunning entry M->>M: Schedule payment reminder end end rect rgb(255, 245, 245) Note over F: Phase 5 — Billing Statement F->>F: Monthly close → status: SETTLED F->>W: POST webhook {statusCd: SETTLED} Note over M: Phase 6 — Financial Reporting M->>F: GET /api/public/p2/v2/billing-statement-reporting F-->>M: {totalPayoutAmount, fees,<br/>directDebitReturnAmount, ...} M->>M: Update financial dashboard end

Consumer Synchronization

When a member is created or updated in Membership, the corresponding consumer record in Cash360 must be kept in sync.

Member Creation Flow

  1. Admin creates a member in Membership (via UI or import)
  2. Membership backend calls My Factura to create the consumer:
POST /api/public/p2/v1/consumer
API_KEY: <key>

[
  {
    "IdExternal": 12345,
    "firstName": "Max",
    "lastName": "Mustermann",
    "type": "PERSON",
    "email": "max@example.com",
    "gender": "MALE",
    "birthday": "1990-05-15",
    "street": "Hauptstrasse 42",
    "postCode": "10115",
    "city": "Berlin",
    "isoCountry": "DE",
    "isoLanguag": "de",
    "flgDunningEnabled": true,
    "contract": {
      "contractNumber": "MBR-2026-0001",
      "contractStartDate": "2026-01-01"
    },
    "bankAccount": {
      "iban": "DE89370400440532013000",
      "accountOwner": "Max Mustermann",
      "sepaMandanteId": "MBR-1-12345-001",
      "sepaMandanteDateOfSigniture": "2025-12-15"
    }
  }
]
  1. My Factura returns the created consumer with its internal ID
  2. Membership stores the Cash360 consumer ID in Member.externalBillingId

Member Update Flow

When billing-relevant member data changes (name, address, email, bank account):

PUT /api/public/p2/v1/consumer/42
API_KEY: <key>

{
  "firstName": "Maximilian",
  "street": "Neue Strasse 7",
  "city": "Munich"
}

Member Lookup

Membership can look up consumers by its own member ID (stored as IdExternal):

GET /api/public/p2/v1/consumer/external/12345
API_KEY: <key>

Or filter by email:

GET /api/public/p2/v1/consumer?email=max@example.com
API_KEY: <key>

Transaction Creation

When Membership's billing engine runs, it creates transactions in bulk via My Factura.

Batch Submission

POST /api/public/p2/v1/transaction
API_KEY: <key>

[
  {
    "idConsumer": 42,
    "collectionType": "DIRECT_DEBIT",
    "amount": 29.90,
    "amountNet": 25.13,
    "vatRate": 19.00,
    "vatAmount": 4.77,
    "dueDate": "2026-03-01",
    "description": "Monthly membership - Adult Gold (Mar 2026)",
    "idExternal": 200001,
    "webhook": "https://membership.example.com/api/webhooks/cash360/transaction"
  },
  {
    "idConsumer": 43,
    "collectionType": "DIRECT_DEBIT",
    "amount": 14.90,
    "dueDate": "2026-03-01",
    "description": "Monthly membership - Youth Basic (Mar 2026)",
    "idExternal": 200002,
    "webhook": "https://membership.example.com/api/webhooks/cash360/transaction"
  }
]

Membership stores the returned Cash360 transaction IDs as externalTransactionId in its local Transaction table, creating a bidirectional link.

Collection Types

Type Usage in Membership
DIRECT_DEBIT Standard: recurring membership fees collected via SEPA
DO_NOT_COLLECT Manual payment expected (cash at front desk, bank transfer)
DRAFT Temporary: transaction prepared but not yet confirmed

Transaction Status Mapping

Membership maps Cash360 statuses to its own simplified status model:

Cash360 Status Membership Status User-Facing Label
NEW PENDING Not yet processed
ACCEPTED PENDING Not yet processed
EXPORTED SUBMITTED Sent to bank
PAID PAID Paid
SETTLED SETTLED Settled
RETURNED FAILED Returned by bank
REJECTED FAILED Rejected
CANCELLED CANCELLED Cancelled
FOR_DUNNING OVERDUE Overdue
SENT_TO_INKASSO DEBT_COLLECTION In debt collection

Webhook Handling

Membership receives status updates from Cash360 via webhooks. Each transaction specifies a webhook URL during creation.

Webhook Endpoint

POST /api/webhooks/cash360/transaction

Extended Webhook Payload (Received)

{
  "type": "transaction",
  "transactionId": 98765,
  "statusCd": "PAID",
  "collectionTypeCd": "DIRECT_DEBIT",
  "amountDue": 0.00,
  "adjustmentDescription": null,
  "beneficiaryEntityId": 1,
  "paymentMethodCd": "SEPA",
  "adjustmentTypeCd": null,
  "adjustmentStatusCd": null,
  "transactionDunningStatus": null
}

Webhook Processing Logic

  1. Look up local transaction by transactionId (stored as externalTransactionId)
  2. Validate the status transition is valid (prevent replay/out-of-order updates)
  3. Update local transaction status according to the mapping table
  4. If status is RETURNED or REJECTED: create a dunning entry and schedule member notification
  5. If status is PAID or SETTLED: update member's outstanding balance
  6. Acknowledge with HTTP 200 (any non-2xx triggers retry)

Retry Behavior

Cash360 retries failed webhook deliveries with exponential backoff (10 attempts, up to ~26 hours total). After 10 failures, the webhook is silently dropped.

Polling Fallback

A scheduled job runs every 15 minutes to catch missed webhooks:

  1. Query all local transactions in PENDING or SUBMITTED status older than 24 hours
  2. For each, call GET /api/public/p2/v1/transaction/{externalTransactionId}
  3. Compare returned status with local status
  4. Update if changed

Webhook Resend

If Membership detects missing status updates (e.g., from monitoring), it can request resend:

PUT /api/public/p2/v1/transaction/resend-webhook
API_KEY: <key>

{
  "transactionIds": [98765, 98766, 98767]
}

SEPA Mandate Management

SEPA mandates are collected in Membership (the member signs the mandate during registration or bank account setup) and registered in Cash360 for inclusion in SEPA direct debit files.

Mandate lifecycle:

  1. Member provides IBAN and signs SEPA mandate in Membership UI
  2. Membership validates IBAN (checksum + BIC lookup)
  3. Membership generates unique mandate reference: MBR-{entityId}-{memberId}-{sequence}
  4. Membership registers the bank account with mandate in Cash360:
POST /api/public/p2/v1/consumer/{consumerId}/bank-account
API_KEY: <key>

{
  "iban": "DE89370400440532013000",
  "accountOwner": "Max Mustermann",
  "bic": "COBADEFFXXX",
  "bankName": "Commerzbank",
  "sepaMandanteId": "MBR-1-12345-001",
  "sepaMandanteDateOfSigniture": "2025-12-15",
  "flgPrimary": true
}
  1. Cash360 uses the mandate in subsequent SEPA XML exports
  2. On mandate revocation, Membership archives the bank account:
PUT /api/public/p2/v1/consumer/{consumerId}/bank-account/{id}/archive
API_KEY: <key>

Manual Payment Recording

When a member pays at the front desk (cash) or via credit card, Membership records the payment through My Factura:

POST /api/public/p2/v1/payment/pay
API_KEY: <key>

{
  "idTransaction": 98765,
  "amount": 29.90,
  "paymentMethod": "CASH"
}

Partial payments are supported. If a member pays 15.00 on a 29.90 transaction, the amountDue is reduced to 14.90 and the transaction remains open.

Payment methods: CASH, CREDIT, CREDIT_CARD

Transaction Cancellation (Storno)

To cancel a transaction (e.g., erroneous charge, duplicate billing):

PUT /api/public/p2/v1/payment/storno
API_KEY: <key>

{
  "idTransaction": 98765,
  "reason": "Duplicate charge - billing engine error"
}

Billing Cycle Engine

The billing engine runs as a scheduled job (configurable per entity, typically nightly at 02:00 local time):

  1. Contract scan: Find all active contracts where nextBillingDate <= today
  2. Lock: SELECT ... FOR UPDATE SKIP LOCKED to prevent duplicate billing in multi-instance deployments
  3. Amount calculation: Calculate gross amount, net amount, and VAT per contract line item
  4. Local transaction creation: Create local Transaction records in PENDING status
  5. Batch submission: Submit all transactions to Cash360 via POST /api/public/p2/v1/transaction
  6. Link storage: Store returned Cash360 transaction IDs as externalTransactionId
  7. Date advancement: Update nextBillingDate by adding billingIntervalMonths
  8. Error handling: Failed submissions are retried with exponential backoff; persistently failing transactions are flagged for manual review

The database-level locking (SKIP LOCKED) addresses the race condition vulnerability identified in the Cash360 audit and ensures exactly-once billing even in horizontally scaled deployments.

Financial Reporting Integration

Monthly billing reports are retrieved from My Factura to feed Membership's financial dashboard:

GET /api/public/p2/v2/billing-statement-reporting
API_KEY: <key>

Response data:

Field Description Dashboard Usage
totalPayoutAmount Total paid out to entity Revenue card
fees Cash360 processing fees Cost breakdown
directDebitReturnAmount Failed direct debit amount Failed payments alert
reminderCasesAmount Amount in dunning Dunning dashboard
specialCases Manual review cases Exception queue
pendingAmount Still being processed Pipeline indicator
preMonthAmount Carryover from previous month Trend analysis
totalExpectedAmount Total expected income Budget comparison
paymentByPeriod.current Current period payments Period breakdown chart
paymentByPeriod.previous Previous period payments Period breakdown chart
paymentByPeriod.earlier Earlier period payments Period breakdown chart

Membership stores the raw report data and calculates derived metrics: fee percentage, return rate, collection efficiency, and month-over-month trends.

Open Debt Tracking

Membership maintains a local view of outstanding balances per member, calculated from the Transaction table:

  • Open amount = SUM(amount) WHERE status IN ('PENDING', 'SUBMITTED')
  • Overdue amount = SUM(amount) WHERE status IN ('PENDING', 'SUBMITTED') AND dueDate < today
  • Failed amount = SUM(amount) WHERE status = 'FAILED' AND NOT resolved

Members with overdue balances trigger configurable dunning workflows (see Communication integration below).

Cash360 Integration Service

The Membership backend encapsulates all My Factura API communication in a dedicated service:

@Service
public class Cash360IntegrationService {

    // Consumer operations
    Long createConsumer(Member member, BankAccount bankAccount, Contract contract);
    void updateConsumer(Long cash360Id, Member member);
    ConsumerDto getConsumer(Long cash360Id);
    ConsumerDto getConsumerByExternalId(Long membershipMemberId);

    // Transaction operations
    List<TransactionResult> createTransactions(List<BillingTransaction> transactions);
    TransactionDto getTransaction(Long cash360TransactionId);
    void updateCollectionType(Long transactionId, CollectionType type);

    // Payment operations
    void recordPayment(Long transactionId, BigDecimal amount, PaymentMethod method);
    void cancelTransaction(Long transactionId, String reason);

    // Bank account operations
    Long createBankAccount(Long consumerId, BankAccount bankAccount);
    void archiveBankAccount(Long consumerId, Long bankAccountId);
    void setPrimaryBankAccount(Long consumerId, Long bankAccountId);

    // Webhook
    void processWebhook(WebhookPayload payload);
    void resendWebhooks(List<Long> transactionIds);

    // Reporting
    BillingReport getBillingReport();
}

This service uses Resilience4j circuit breakers on all outbound calls. If Cash360 is unavailable, transactions are queued locally and submitted when connectivity is restored.


External Communication Integrations

Email Service

Membership uses a dedicated email service (not Cash360's email subsystem) for all member-facing communication:

Integration Protocol Purpose
SMTP relay SMTP/TLS Transactional emails (verification, password reset, receipts)
Mailgun / SendGrid REST API Bulk emails (newsletters, announcements), delivery tracking
Email templates Thymeleaf Per-entity branded templates with variable substitution

Push Notifications

Platform Service Protocol
Android Firebase Cloud Messaging (FCM) HTTPS
iOS Apple Push Notification Service (APNs) HTTP/2
Web Web Push (VAPID) HTTPS

Push notifications are sent for: check-in confirmations, payment reminders, course cancellations, event invitations, and contract renewals.

Video Conferencing

For online classes and virtual training sessions:

Platform Integration Type Features
Microsoft Teams Graph API Create meetings, send invites, attendance tracking
Zoom REST API + Webhooks Schedule meetings, generate join links, recording access
Custom WebRTC Embedded In-app video for 1:1 training sessions

Messaging Platforms

Platform Integration Type Use Case
WhatsApp Business Cloud API Appointment reminders, payment confirmations
Slack Incoming Webhooks Staff notifications (new registrations, cancellations)
Telegram Bot Bot API Member self-service (balance inquiry, booking confirmation)

AI Communication

Feature Technology Purpose
Chatbot LLM-based (OpenAI/Anthropic API) Member self-service: FAQ, booking, account queries
Voicebot Telephony API + STT/TTS Phone-based member inquiries (hours, directions, pricing)
MCP Server Model Context Protocol AI agent access to membership data for advanced automation

Association Interfaces

Sports clubs often report to national and regional sports associations. These integrations automate administrative overhead:

Interface Purpose Data Exchange
Player pass management Register members with national federation Member name, DOB, photo, club ID
Results reporting Submit competition results Event results, rankings, scores
Membership numbers Sync federation-issued member IDs Federation member ID mapped to local member ID
Insurance Register members for sports insurance Member count, age groups, sport types
Subsidy reporting Report data for government sports subsidies Member demographics, activity statistics

Integration format varies by federation but typically involves CSV/XML file exchange or REST API calls. Membership provides a configurable adapter layer with per-federation templates.


Data Migration from External Programs

Beyond Cash360 migration (see Chapter 15), Membership supports importing data from competing products:

CSV Import Pipeline

The import system follows the Cash360 User Manual's established workflow:

flowchart LR A[Upload CSV] --> B[Auto-detect Format] B --> C[Column Mapping UI] C --> D[Save Template] D --> E[Select Mode] E --> F[Validate] F --> G[Execute Import] G --> H[Report] E -->|New Only| F E -->|New + Update| F E -->|Update Only| F

Supported import targets:

Target Required Fields Optional Fields
Members firstName, lastName, email All other member fields, custom attributes
Services/Products name, price Description, category, VAT
Transactions amount, date, memberId Description, status, payment method
Bank Accounts iban, memberId BIC, account holder, mandate reference

Format auto-detection: The system examines the first 100 rows to determine delimiter (comma, semicolon, tab), encoding (UTF-8, ISO-8859-1, Windows-1252), date formats, and decimal separators.

Mapping templates: Once a column mapping is defined, it can be saved as a reusable template. This is essential for clubs that receive periodic data files from federations in a consistent format.

Programmatic Import API

For automated migrations and recurring data feeds:

POST /api/import/upload         -- Upload CSV file
POST /api/import/{id}/mapping   -- Set column mapping
POST /api/import/{id}/validate  -- Validate data
POST /api/import/{id}/execute   -- Execute import
GET  /api/import/{id}/status    -- Check progress
GET  /api/import/{id}/report    -- Download error report

Hardware Integration

Check-In Terminals

Dedicated hardware or tablet devices positioned at facility entrances for member access control:

Method Hardware Protocol
QR Code Camera/scanner HTTP (device calls Membership API)
NFC NFC reader HTTP (device calls Membership API)
Barcode Barcode scanner HTTP (device calls Membership API)
Biometric Fingerprint scanner Device-local matching + HTTP confirmation
BLE BLE beacon at door Bluetooth (BLE advertisement, device validates)
QR door access QR code at door (member scans) HTTP (member's phone calls Membership API)

Check-in flow:

  1. Member presents QR code / NFC card / barcode at terminal
  2. Terminal sends identifier to POST /api/checkin/process
  3. Server validates: active contract, not suspended, facility access included
  4. Server responds: GRANTED or DENIED (with reason)
  5. Terminal displays result (green/red), actuates door lock if applicable
  6. CheckIn record created in database

Door Lock and Access Control Integration

Protocol Standard Use Case Version
HTTP/REST IP-based locks Cloud-connected smart locks v2.0
MQTT IoT standard Lightweight door controllers v2.0
Wiegand Legacy access control Traditional panels (being replaced by OSDP) v2.0
OSDP v2 SIA/IEC 60839-11-5 Modern access control standard, AES-128 encrypted v2.0
GAT REST API Gantner Essecca Professional turnstiles, doors, lockers v2.0
BLE Bluetooth Low Energy Mobile phone proximity unlock v2.0

Body Composition / Fitness Devices

Device Type Integration Data Captured
InBody / Tanita scales Serial/USB + local agent Weight, body fat %, muscle mass
Heart rate monitors Bluetooth (via mobile app) Heart rate zones during courses
Fitness trackers REST API (Garmin, Fitbit) Activity minutes, calories

Building Automation (IoT)

For facilities with smart building systems, Membership can control environmental conditions based on occupancy and scheduled activities:

Supported Protocols

Protocol Standard Typical Use
KNX EN 50090 / ISO/IEC 14543 Lighting, blinds, HVAC in commercial buildings
BACnet ASHRAE 135 HVAC, fire safety, building management systems
MQTT OASIS Standard IoT sensors, Shelly devices, custom controllers
Modbus Serial/TCP Industrial equipment, power meters
HTTP/REST Custom Shelly relays, IP cameras, custom sensors

Integration Architecture

graph TB subgraph "Membership Platform" API[Resource Booking API] SCH[Scheduler Service] IOT[IoT Gateway Service] end subgraph "Building Automation" KNX[KNX Gateway] BAC[BACnet Controller] MQTT[MQTT Broker] end subgraph "Devices" LIGHT[Lighting] HVAC[HVAC System] SENSOR[Presence Sensors] LOCK[Door Locks] DISPLAY[Info Displays] end API --> SCH SCH --> IOT IOT --> KNX --> LIGHT IOT --> BAC --> HVAC IOT --> MQTT --> SENSOR IOT --> MQTT --> LOCK IOT --> MQTT --> DISPLAY SENSOR --> MQTT --> IOT

Automation Rules

The system supports configurable automation rules:

  • Occupancy-based: Turn on lights/HVAC when first check-in occurs, turn off 30 minutes after last check-out
  • Schedule-based: Pre-condition rooms 15 minutes before a booked course starts
  • Resource-linked: When a room is booked, automatically activate linked lighting scenes and ventilation
  • Threshold-based: Adjust ventilation rate based on CO2 sensor readings and current occupancy count
  • Energy optimization: Reduce HVAC to standby mode during unoccupied hours, based on booking calendar

Automation configuration is stored in the Resource entity's hardwareIntegration JSONB field, keeping device control logic decoupled from the core business model.


Physical Access Control Integration

Professional-grade access control for fitness studios, sports facilities, and multi-location networks. This section covers hardware integration, credential management, and the abstraction layer that makes the system hardware-agnostic.

Gantner Essecca Integration

Gantner is the market leader for access control in fitness and wellness facilities, used by major chains across Europe.

Integration approach: Server-to-server REST API (GAT Cloud API) with event-driven updates.

Supported hardware: - Turnstiles (GAT Access 6200): Tripod and full-height barriers with bidirectional passage detection - Door controllers (GAT Access 7000): Electronic strike and magnetic lock control - Locker systems (GAT Lock 6100): Electronic locker locks assigned per visit or per member - Readers (GAT Terminal): Combined NFC + QR + BLE reader units

Credential provisioning flow:

sequenceDiagram participant M as Membership Backend participant G as Gantner GAT Cloud participant H as Gantner Hardware rect rgb(240, 248, 255) Note over M: Member receives credential M->>G: POST /api/v1/credentials<br/>{memberId, type: NFC, cardUid: "A1B2C3"} G-->>M: {credentialId: 456, status: ACTIVE} G->>H: Sync credential to local cache end rect rgb(245, 255, 245) Note over H: Member checks in H->>H: Read NFC card (UID: A1B2C3) H->>G: POST /api/v1/access-check<br/>{credentialId: 456, zoneId: 1} G->>M: POST /api/webhooks/gantner/access-request<br/>{memberId, zoneId, credentialType} M->>M: Check: active contract? zone allowed? time ok? M-->>G: {decision: GRANTED, displayMessage: "Welcome, Max!"} G-->>H: Grant access (open turnstile/door) H->>H: LED green, display message end rect rgb(255, 248, 240) Note over M: Access logged G->>M: POST /api/webhooks/gantner/passage<br/>{memberId, zoneId, direction: IN, timestamp} M->>M: Create CheckIn record, update occupancy end

Locker assignment flow: 1. Member checks in at turnstile 2. Membership backend assigns available locker via Gantner API 3. Gantner provisions the locker lock for the member's credential 4. Member opens locker with same NFC card/BLE/QR 5. On check-out, locker assignment is released

OSDP Protocol Support (SIA Standard)

OSDP (Open Supervised Device Protocol) is the SIA (Security Industry Association) standard for access control communication, replacing the legacy Wiegand protocol.

Why OSDP over Wiegand: | Aspect | Wiegand (legacy) | OSDP v2 | |--------|-----------------|---------| | Encryption | None (plain-text card data) | AES-128 Secure Channel | | Communication | Unidirectional (reader → panel) | Bidirectional | | Cable distance | Max ~150m | Max ~1,200m (RS-485) | | Monitoring | No supervision | Supervised (tamper, heartbeat) | | Features | Card UID only | Display, keypad, biometric, LED control | | Standard | De facto, no formal spec | ANSI/SIA OSDP (IEC 60839-11-5) |

Architecture:

graph TB subgraph "Membership Platform" API[Access Control Service] GW[OSDP Gateway Service] end subgraph "OSDP Controller" CTRL[Access Control Panel<br/>RS-485 Bus Master] end subgraph "OSDP Devices" R1[Reader 1<br/>NFC + Keypad] R2[Reader 2<br/>NFC + BLE] R3[Reader 3<br/>Biometric] L1[Lock 1<br/>Electronic Strike] end API -->|REST/WebSocket| GW GW -->|OSDP over TCP/IP| CTRL CTRL -->|OSDP over RS-485| R1 CTRL -->|OSDP over RS-485| R2 CTRL -->|OSDP over RS-485| R3 CTRL -->|OSDP over RS-485| L1

The OSDP Gateway Service translates between the Membership REST API and the OSDP wire protocol. It maintains persistent connections to OSDP controllers and handles AES-128 Secure Channel establishment.

BLE Mobile Credentials

Bluetooth Low Energy enables phone-as-credential: the member's phone automatically unlocks doors when in proximity, without requiring the member to open the app.

Flow: 1. Member enables BLE credential in app (one-time setup, requires Bluetooth permission) 2. App receives a BLE key from the backend (encrypted, device-bound) 3. BLE beacon at door advertises presence 4. Member's phone detects beacon, initiates BLE handshake 5. Phone transmits encrypted credential token 6. Door controller validates token (local cache or API call) 7. Door unlocks for 5 seconds 8. Check-in event sent to backend

Configuration parameters per zone: - Range: 1-10 meters (configurable, prevents accidental unlock from adjacent areas) - Timeout: Door remains unlocked for 3-10 seconds (configurable) - Fallback: If BLE fails, member can use NFC card or QR code - Battery: BLE beacons run on coin cells (CR2032), 2-3 year battery life

QR Door Access (fitplus-Style)

A low-cost access control method where a QR code is physically posted at the studio door. The member scans this QR code with their phone camera, which triggers an API call to unlock the door.

How it works:

sequenceDiagram participant Mem as Member Phone participant QR as QR Code on Door participant API as Membership API participant Lock as Door Lock Mem->>QR: Scan QR code with phone camera Note over QR: QR contains: https://mbr.example.com/<br/>access/{zoneId}?token={rotating-token} Mem->>API: GET /api/access/{zoneId}?token={token}<br/>Authorization: Bearer {jwt} API->>API: Validate: JWT valid? Active contract?<br/>Zone access allowed? Token not expired?<br/>Anti-replay check (token single-use) alt Access granted API->>Lock: POST /door/{lockId}/unlock<br/>(5-second pulse) Lock->>Lock: Unlock for 5 seconds API-->>Mem: 200 {status: GRANTED, message: "Door unlocked"} API->>API: Create CheckIn, update occupancy else Access denied API-->>Mem: 403 {status: DENIED, reason: "Contract expired"} end

Anti-replay protection: - The QR code at the door contains a rotating token that changes every 30 seconds - A small display (e-ink or LED matrix) at the door shows the current QR code - Alternatively: static QR code with the zone URL, and the server relies on JWT + rate limiting for security - Each token can only be used once (server tracks used tokens in Redis with 60-second TTL)

Cost advantage: Only requires an e-ink display (~30 EUR) or printed static QR + electronic door strike (~150 EUR). No NFC reader or turnstile required. Ideal for small studios with low foot traffic.

Biometric Integration

Fingerprint and face recognition for high-security zones or premium facilities. Available from v3.0.

GDPR Article 9 implications: Biometric data is a "special category" of personal data under GDPR. Requirements: - Explicit consent (separate from general terms, revocable at any time) - On-device template storage preferred (template never leaves the reader) - If server-side: encrypted at rest, separate from PII, automatic deletion on membership termination - Data Protection Impact Assessment (DPIA) mandatory before deployment - Cannot be the only access method — always provide a non-biometric alternative

Hardware Abstraction Layer

The system uses a vendor-agnostic adapter pattern to support multiple access control hardware vendors through a common interface.

graph TB subgraph "Membership Platform" ACS[Access Control Service] HAL[Hardware Abstraction Layer] end subgraph "Vendor Adapters" GA[Gantner Adapter<br/>GAT REST API] SA[Salto Adapter<br/>SALTO SPACE API] DA[Dormakaba Adapter<br/>Kaba Exos API] OA[OSDP Adapter<br/>SIA OSDP v2] HA[HTTP Adapter<br/>Generic REST locks] end subgraph "Hardware" G[Gantner Turnstiles] S[Salto Locks] D[Dormakaba Locks] O[OSDP Readers] H[Smart Locks HTTP] end ACS --> HAL HAL --> GA --> G HAL --> SA --> S HAL --> DA --> D HAL --> OA --> O HAL --> HA --> H

Common AccessControlAdapter interface:

public interface AccessControlAdapter {
    // Credential management
    CredentialResult provisionCredential(ProvisionRequest request);
    void revokeCredential(String credentialId);
    void suspendCredential(String credentialId);

    // Access decisions
    AccessDecision checkAccess(AccessRequest request);
    void grantAccess(String zoneId, int durationSeconds);
    void denyAccess(String zoneId, String reason);

    // Door commands
    void unlockDoor(String doorId, int durationSeconds);
    void lockDoor(String doorId);
    DoorStatus getDoorStatus(String doorId);

    // Zone management
    ZoneOccupancy getOccupancy(String zoneId);
    List<ZoneEvent> getAccessLog(String zoneId, Instant from, Instant to);

    // Hardware health
    DeviceHealth getDeviceHealth(String deviceId);
    List<DeviceAlert> getActiveAlerts();
}

Each vendor adapter implements this interface. The active adapter is selected per zone via the Device.protocol field.

Credential Fallback Chain

Members can have multiple credential types. The system supports a configurable fallback order per zone:

Priority Credential Type Speed Cost Security
1 (highest) Biometric (fingerprint/face) <1s High Very high
2 NFC card/wristband <1s Medium High
3 BLE mobile 2-3s Low High
4 QR code (app) 3-5s Free Medium
5 QR door access (scan posted QR) 5-8s Very low Medium
6 (lowest) Manual (front desk) 10-30s Free Low

If the preferred credential method fails (e.g., NFC reader offline), the system automatically suggests the next available method to the member.

Real-Time Occupancy Sync

Hardware events (passage through turnstile, door open/close) trigger real-time occupancy updates:

  1. Hardware detects passage → sends event to Membership API (webhook or MQTT)
  2. API updates AccessZone.currentOccupancy atomically (UPDATE ... SET current_occupancy = current_occupancy + 1)
  3. WebSocket push to all connected admin dashboards
  4. If occupancy exceeds AccessZone.capacity, subsequent access requests are denied with "Facility at capacity" message
  5. Mobile app shows real-time occupancy on location detail screen

DATEV Integration

Overview

DATEV is the dominant accounting software ecosystem in Germany, used by 95%+ of tax advisors. The Membership platform exports journal entries in DATEV-compatible formats for seamless handoff to the organization's tax advisor.

Export Formats

Format Description Use Case
DATEV CSV DATEV's ASCII format (semicolon-delimited, Windows-1252 encoding, specific header structure) Standard export for most tax advisors
DATEV XML Structured XML format with schema validation Direct import into DATEV software
CSV (generic) Standard CSV for non-DATEV accounting tools lexoffice, sevDesk, custom tools

DATEV CSV Structure

The DATEV CSV export follows the official DATEV format specification:

  • Header row: Contains metadata (consultant number, client number, fiscal year start, date range, chart of accounts standard)
  • Data rows: One row per journal entry with: document date, document number, posting amount, debit/credit indicator, contra account, account, posting text, cost center

Chart of Accounts Mapping

The system ships with default mappings for the two standard German charts of accounts:

Internal Account SKR03 Code SKR04 Code Description
Membership revenue 8400 4400 Revenue from services
Setup fee revenue 8401 4401 One-time fees
Product revenue 8402 4402 Merchandise/products
Bank account 1200 1800 Bank current account
Receivables 1400 1200 Trade receivables
VAT collected 1776 3806 Output VAT 19%
VAT collected (reduced) 1771 3801 Output VAT 7%

Organizations can customize the mapping through the admin UI. The mapping is stored in the Entity settings JSONB column.

Automated Export Flow

sequenceDiagram participant BE as Billing Engine participant AE as Accounting Engine participant DE as DATEV Exporter participant TA as Tax Advisor Note over BE: Monthly billing cycle BE->>AE: Billing events (contract charged, payment received, refund) AE->>AE: Create AccountingEntry records<br/>(automatic posting rules) Note over AE: Monthly close AE->>AE: Mark period as closed (no more edits) AE->>DE: Export request (period: 2026-03) DE->>DE: Generate DATEV CSV/XML<br/>(map accounts, format dates, encode Windows-1252) DE-->>AE: Export file ready AE->>AE: Mark entries as datevExported=true Note over TA: Tax advisor downloads TA->>DE: Download via admin UI or scheduled email DE-->>TA: DATEV CSV file TA->>TA: Import into DATEV software

Export Schedule

Trigger Description
Manual Admin clicks "Export to DATEV" in accounting section
Automated Monthly on the 5th at 06:00 (configurable per entity), emailed to configured recipient
API POST /api/v1/accounting/datev-export for programmatic access

CRM Integration Patterns

Pipeline Architecture

The CRM follows a Pipedrive-style pipeline model where leads progress through configurable stages:

graph LR NEW[New Lead] --> CONTACTED[Contacted] CONTACTED --> QUALIFIED[Qualified] QUALIFIED --> TRIAL[Trial Booked] TRIAL --> PROPOSAL[Proposal Sent] PROPOSAL --> WON[Won — Member] PROPOSAL --> LOST[Lost] CONTACTED --> LOST QUALIFIED --> LOST TRIAL --> LOST style NEW fill:#e3f2fd style CONTACTED fill:#bbdefb style QUALIFIED fill:#90caf9 style TRIAL fill:#64b5f6 style PROPOSAL fill:#42a5f5 style WON fill:#4caf50,color:#fff style LOST fill:#ef5350,color:#fff

Pipeline stages are configurable per entity — a fitness studio might have "Trial Class" and "Gym Tour" stages, while a sports club might have "Info Evening" and "Trial Training".

Lead Source Tracking

Every lead records its acquisition source for marketing attribution:

Source Description Typical Entry Point
WEBSITE Landing page form submission Online registration form
REFERRAL Existing member referral Referral link with tracking code
WALK_IN Person visits the facility Front desk manual entry
CAMPAIGN Marketing campaign response Email/social media CTA
IMPORT Bulk import from CSV/Excel Data import wizard
PARTNER Partner/aggregator referral Gympass, Urban Sports Club
EVENT Event attendance conversion Post-event follow-up

Activity Logging

All interactions with leads and deals are logged for full audit trail and handoff between team members:

  • Calls: Duration, outcome (reached/voicemail/no-answer), notes, next follow-up
  • Emails: Subject, body preview, send timestamp, open/click tracking (if integrated)
  • Meetings: Location/video link, participants, duration, agenda, outcomes
  • Notes: Free-text notes with timestamps
  • Tasks: Scheduled follow-up tasks with due dates and assignment

Lead-to-Member Conversion

When a lead is won, the system performs a one-click conversion:

  1. Lead status changes to WON
  2. System creates a new Member record, pre-filled from Lead data (name, email, phone)
  3. If a trial contract exists, it is upgraded to the selected full membership
  4. Activity history from the Lead is linked to the new Member for continuity
  5. Welcome email is sent via the Communication module
  6. The originating campaign/source is retained for lifetime attribution analytics

Webhook Events for Pipeline Changes

The CRM emits events via the internal event bus (RabbitMQ) for pipeline stage transitions. External systems can subscribe via webhooks:

Event Payload Use Case
lead.created Lead data + source Notify sales team, trigger welcome email
lead.stage_changed Lead ID, old stage, new stage Update external CRM, trigger automation
deal.won Deal data + member ID Trigger onboarding workflow
deal.lost Deal data + lost reason Trigger win-back campaign (delayed)
activity.created Activity data Sync to external calendar/CRM

Financial Separation: Third-Party Amounts vs. Own Invoices

Principle

Membership One operates like a bank when it comes to financial flows. The platform handles two fundamentally different categories of money, and these must be rigorously separated across all layers of the system: accounting, reporting, tax treatment, DATEV export, and invoicing.

Two Financial Streams

Stream 1 -- Third-Party Amounts (Fremdbetraege)

These are amounts collected on behalf of the studio, club, or franchise. Membership One acts as a payment intermediary (similar to how a bank processes transactions on behalf of account holders). The customer receives the equivalent of a bank account statement (Kontoauszug) for these amounts.

Examples: - Member subscription fees (monthly/annual) - Course and event fees - Shop purchases (merchandise, supplements) - One-time registration fees - Trial class fees - Dunning fees charged to members

Stream 2 -- Own Invoices (Eigenrechnungen)

These are amounts that Membership One bills to the studio/club for using the platform. These are Membership One's own revenue. The customer receives a standard invoice for these amounts, similar to how a bank charges account maintenance fees (Bankgebuehren).

Examples: - SaaS subscription fees (Starter, Team, Professional, Enterprise tiers) - Per-member overage charges - Add-on module fees (custom domain, white-label, etc.) - Migration service fees - On-site training fees - SMS/push notification volume charges

Separation Requirements

These two streams must be clearly separated in every dimension:

Dimension Third-Party Amounts (Fremdbetraege) Own Invoices (Eigenrechnungen)
Accounting Pass-through accounts (trust account / Durchlaufposten) Revenue accounts (Erloese)
Reporting Shown as "Collections on behalf of customer" Shown as "Membership One revenue"
Tax treatment No VAT charged by Membership One (VAT is the customer's responsibility on the underlying transaction) Standard VAT (19% DE) charged by Membership One
DATEV export Separate posting keys (Buchungsschluessel), trust account postings Standard revenue posting keys
Invoicing Settlement statement (Abrechnungsbericht) -- not an invoice Proper invoice (Rechnung) with VAT
Dashboard "Your Collections" section "Your Membership One Costs" section
Bank reconciliation Matched against SEPA collections and payouts Matched against Membership One's own billing

Financial Flow Diagram

flowchart TB subgraph Members["Members (End Users)"] M1[Member A<br/>EUR 29.90/month] M2[Member B<br/>EUR 49.90/month] M3[Member C<br/>EUR 19.90/month] end subgraph Stream1["Stream 1: Third-Party Collections (Fremdbetraege)"] SEPA[SEPA Direct Debit<br/>via Cash360] STRIPE[Stripe / Card Payments] CASH[Cash / Manual Payments] COLLECT[Collection Account<br/>Durchlaufposten] end subgraph Stream2["Stream 2: Own Invoices (Eigenrechnungen)"] SAAS[SaaS Subscription<br/>EUR 0.99-99/month] ADDON[Add-On Services<br/>Migration, Training] OWNBILL[Membership One Invoice<br/>with VAT] end subgraph Studio["Studio / Club (Customer)"] PAYOUT[Payout to Customer Bank Account<br/>Collected amounts minus fees] STATEMENT[Settlement Statement<br/>Abrechnungsbericht] INVOICE[Membership One Invoice<br/>Rechnung with VAT] end M1 --> SEPA M2 --> SEPA M3 --> STRIPE SEPA --> COLLECT STRIPE --> COLLECT CASH --> COLLECT COLLECT --> PAYOUT COLLECT --> STATEMENT SAAS --> OWNBILL ADDON --> OWNBILL OWNBILL --> INVOICE style Stream1 fill:#e3f2fd style Stream2 fill:#fff3e0 style Studio fill:#e8f5e9

Implementation Notes

  • All third-party collections (member fees, course fees, shop purchases) flow through Cash360 via SEPA direct debit, Stripe, or other payment providers. Cash360 handles the actual money movement.
  • Membership One's own invoices (SaaS fees) also flow through Cash360 but are tracked in a separate billing entity with distinct accounting codes.
  • The AccountingEntry entity includes a streamType field (THIRD_PARTY or OWN_INVOICE) to distinguish the two streams at the data level.
  • DATEV export generates two separate files (or clearly separated sections) for the two streams, each with appropriate posting keys.
  • The financial dashboard shows both streams but in clearly separated sections, never commingled.
  • Settlement statements (Stream 1) are generated monthly and show: total collected, payment provider fees, Membership One processing fees, net payout amount.
  • Own invoices (Stream 2) are generated per billing cycle and follow standard German invoicing requirements (Pflichtangaben nach UStG).

Invoice Cancellation (Storno) Handling

Invoice cancellations (Storno) generate a corrective credit note that fully or partially reverses the original invoice. Both the original invoice and the credit note are retained for audit and tax purposes. The financial streams (third-party amounts vs. own invoices) are maintained: a Storno of a member fee creates a third-party credit note, while a Storno of a SaaS subscription fee creates an own credit note. All Storno operations are logged in the audit trail.

Storno rules: - Full Storno: the entire invoice amount is reversed via a credit note referencing the original invoice number - Partial Storno: individual line items can be cancelled, generating a credit note for the specific positions - The original invoice remains in the system with status CANCELLED (not deleted), preserving the complete document chain - The credit note receives its own sequential number in the organization's invoicing sequence - Both documents (original + credit note) are included in DATEV exports and SAF-T reports - Storno of a SEPA direct debit transaction triggers a refund flow via Cash360 (PUT /api/public/p2/v1/payment/storno)


SAF-T (Standard Audit File for Tax)

Overview

SAF-T (Standard Audit File for Tax) is an international standard developed by the OECD for the electronic exchange of accounting data between organizations and tax authorities. It provides a standardized XML format that enables tax auditors to efficiently analyze financial records without requiring direct access to the organization's accounting system.

Regulatory Landscape

SAF-T is mandatory or partially required in a growing number of EU member states:

Country Status Scope Effective
Portugal Mandatory Full SAF-T (invoicing + accounting) Since 2008, updated 2024
Norway Mandatory SAF-T Financial (general ledger + sub-ledgers) Since 2020
Luxembourg Mandatory FAIA (SAF-T variant) for all companies Since 2011
Poland Mandatory JPK (SAF-T variant), monthly submission Since 2018
Lithuania Mandatory i.SAF (SAF-T variant) for invoice data Since 2016
Austria Partially required SAF-T for tax audits (on request) Ongoing
France Partially required FEC (SAF-T variant) for tax audits Since 2014
Germany Not yet mandatory GDPdU/GoBD (native format), but SAF-T awareness growing Monitoring
Czech Republic Planned SAF-T under consideration TBD
Romania Planned SAF-T under consideration TBD

As the EU pushes toward digital tax administration (EU ViDA -- VAT in the Digital Age), SAF-T adoption is expected to expand significantly across member states.

Membership One SAF-T Support

Membership One must support SAF-T export for customers operating in affected countries. This is particularly relevant for:

  • Multi-country franchise networks with locations in SAF-T-mandated countries
  • Studios and clubs in Portugal, Poland, and Luxembourg
  • Organizations preparing for anticipated SAF-T requirements in their country

Export Format

The SAF-T export follows the OECD SAF-T XML schema (version 2.00) and covers:

SAF-T Section Membership One Data Source Description
Header Organization entity settings Company info, fiscal year, currency, audit period
GeneralLedgerEntries AccountingEntry table All journal entries for the audit period
Customers Member + Organization tables Customer master data (consumers of the organization's services)
Suppliers External provider references Supplier master data (if applicable)
SalesInvoices Transaction + Cash360 billing data All sales invoices issued
PurchaseInvoices AccountingEntry (expense type) Purchase invoices received (if tracked)
Payments Transaction (PAID status) Payment records
TaxTable Entity VAT configuration Applicable tax rates and codes

Integration with DATEV

SAF-T and DATEV exports are complementary, not mutually exclusive:

  • DATEV is the standard for German tax advisors (Steuerberater) and covers the domestic German accounting workflow
  • SAF-T is the standard for international tax authorities and covers cross-border audit requirements
  • Organizations operating in Germany typically use DATEV; organizations in SAF-T-mandated countries use SAF-T; multi-country organizations may need both
  • The same underlying AccountingEntry data feeds both export engines

Reference Document

The document "Uebersicht_Rechnungen_Ausland.docx" (in membership/planning/) covers international invoicing requirements across EU member states, including country-specific invoice fields, VAT treatment for cross-border services, and electronic invoicing mandates. This document informs the SAF-T implementation.

Roadmap

Version Scope
v1.0 SAF-T export for core EU countries (Portugal, Poland, Luxembourg, Lithuania, Norway). Basic XML generation from AccountingEntry data. Manual trigger via admin UI.
v2.0 Extended SAF-T: France (FEC), Austria, additional countries as regulations evolve. Automated monthly generation. Country-specific schema variations.

Payment Gateway Integrations

Beyond the primary Cash360/My-Factura integration for SEPA direct debit, Membership One supports additional payment gateways for card payments, online payments, and region-specific payment methods. These gateways are used for:

  • Online member self-service payments (card, wallet)
  • Point-of-sale payments at the front desk
  • One-time payments (trial classes, merchandise)
  • Markets where SEPA is not the primary payment method

Supported Gateways

Gateway Region Payment Methods Integration Type Version
Cash360 / My-Factura DACH, EU SEPA Direct Debit, bank transfer, manual (cash) REST API (primary) v1.0
Stripe Global Credit/debit cards, Apple Pay, Google Pay, SEPA (instant) REST API + Webhooks v2.0
Mollie Europe iDEAL (NL), Bancontact (BE), SOFORT, Giropay, EPS, cards REST API + Webhooks v2.0
comgate Czech Republic, Slovakia Bank transfers (CZ/SK), credit/debit cards, mobile payments (Premium SMS, mPlatba), Apple Pay, Google Pay REST API + Webhooks v2.0

Gateway Abstraction

All payment gateways are accessed through a common PaymentGateway interface, allowing the system to be gateway-agnostic at the business logic level:

public interface PaymentGateway {
    PaymentResult initiatePayment(PaymentRequest request);
    PaymentResult capturePayment(String paymentId);
    PaymentResult refundPayment(String paymentId, BigDecimal amount);
    PaymentStatus getPaymentStatus(String paymentId);
    void processWebhook(String payload, String signature);
}

The active gateway per organization is configured in the Entity settings. Organizations can enable multiple gateways simultaneously (e.g., Cash360 for SEPA + Stripe for card payments).

comgate Integration Details

comgate (comgate.cz) is the leading payment gateway in the Czech Republic and Slovakia, supporting bank-specific payment methods that are essential for the Central European market:

  • Czech bank transfers: Direct transfers from major Czech banks (Ceska sporitelna, CSOB, Komercni banka, etc.)
  • Slovak bank transfers: Direct transfers from major Slovak banks (Tatra banka, VUB, Slovenska sporitelna, etc.)
  • Card payments: Visa, Mastercard, Maestro (3D Secure 2.0)
  • Mobile payments: Premium SMS and mPlatba (carrier billing)
  • Apple Pay / Google Pay: Wallet payments via comgate's gateway
  • Recurring payments: Supported via card tokenization for subscription billing

comgate is particularly important for Membership One's expansion into the Czech and Slovak markets, where SEPA direct debit adoption is lower than in DACH countries.


Integration Security

All external integrations follow these security principles:

  1. Credential storage: API keys, OAuth tokens, and connection strings stored in encrypted secrets vault (not in application config files)
  2. Transport security: TLS 1.2+ for all external API calls, certificate pinning for Cash360 connection
  3. Rate limiting: Outbound API calls respect provider rate limits with exponential backoff
  4. Circuit breaker: Resilience4j circuit breakers on all external service calls, with fallback behavior (e.g., queue transactions locally if Cash360 is unavailable)
  5. Audit trail: All external API calls logged with request/response (sensitive fields redacted), stored for 90 days
  6. Webhook verification: Inbound webhooks authenticated via HMAC signature verification