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:
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
- Admin creates a member in Membership (via UI or import)
- 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"
}
}
]
- My Factura returns the created consumer with its internal ID
- 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
- Look up local transaction by
transactionId(stored asexternalTransactionId) - Validate the status transition is valid (prevent replay/out-of-order updates)
- Update local transaction status according to the mapping table
- If status is
RETURNEDorREJECTED: create a dunning entry and schedule member notification - If status is
PAIDorSETTLED: update member's outstanding balance - 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:
- Query all local transactions in
PENDINGorSUBMITTEDstatus older than 24 hours - For each, call
GET /api/public/p2/v1/transaction/{externalTransactionId} - Compare returned status with local status
- 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:
- Member provides IBAN and signs SEPA mandate in Membership UI
- Membership validates IBAN (checksum + BIC lookup)
- Membership generates unique mandate reference:
MBR-{entityId}-{memberId}-{sequence} - 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
}
- Cash360 uses the mandate in subsequent SEPA XML exports
- 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):
- Contract scan: Find all active contracts where
nextBillingDate <= today - Lock:
SELECT ... FOR UPDATE SKIP LOCKEDto prevent duplicate billing in multi-instance deployments - Amount calculation: Calculate gross amount, net amount, and VAT per contract line item
- Local transaction creation: Create local Transaction records in PENDING status
- Batch submission: Submit all transactions to Cash360 via
POST /api/public/p2/v1/transaction - Link storage: Store returned Cash360 transaction IDs as
externalTransactionId - Date advancement: Update
nextBillingDateby addingbillingIntervalMonths - 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:
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:
- Member presents QR code / NFC card / barcode at terminal
- Terminal sends identifier to
POST /api/checkin/process - Server validates: active contract, not suspended, facility access included
- Server responds: GRANTED or DENIED (with reason)
- Terminal displays result (green/red), actuates door lock if applicable
- 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
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:
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:
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:
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.
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:
- Hardware detects passage → sends event to Membership API (webhook or MQTT)
- API updates
AccessZone.currentOccupancyatomically (UPDATE ... SET current_occupancy = current_occupancy + 1) - WebSocket push to all connected admin dashboards
- If occupancy exceeds
AccessZone.capacity, subsequent access requests are denied with "Facility at capacity" message - 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
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:
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:
- Lead status changes to WON
- System creates a new Member record, pre-filled from Lead data (name, email, phone)
- If a trial contract exists, it is upgraded to the selected full membership
- Activity history from the Lead is linked to the new Member for continuity
- Welcome email is sent via the Communication module
- 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
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
AccountingEntryentity includes astreamTypefield (THIRD_PARTYorOWN_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
AccountingEntrydata 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:
- Credential storage: API keys, OAuth tokens, and connection strings stored in encrypted secrets vault (not in application config files)
- Transport security: TLS 1.2+ for all external API calls, certificate pinning for Cash360 connection
- Rate limiting: Outbound API calls respect provider rate limits with exponential backoff
- Circuit breaker: Resilience4j circuit breakers on all external service calls, with fallback behavior (e.g., queue transactions locally if Cash360 is unavailable)
- Audit trail: All external API calls logged with request/response (sensitive fields redacted), stored for 90 days
- Webhook verification: Inbound webhooks authenticated via HMAC signature verification