UML Sequence Diagrams - Sport Tech Club
Version: 1.0.0
Date: 2026-01-09
Status: Comprehensive Documentation
Table of Contents
- Booking Creation Flow
- Queue Management Flow (Ganha-Fica)
- Check-in Process
- Match Registration & Gamification
- Event Mode Activation
- Payment Processing
1. Booking Creation Flow
Overview
User searches for court availability, selects time slot, adds participants, processes payment (PIX/Card), receives confirmation and notification.
Actors
- User: Player making the booking
- Frontend: Vue.js application
- API Gateway: Entry point with auth
- Scheduling Service: Core domain for bookings
- Courts Service: Court availability management
- Payment Gateway: External payment processor (Stripe/Mercado Pago)
- Wallet Service: Internal wallet system
- Notification Service: Push/Email/SMS notifications
- Database: PostgreSQL with RLS
Key Decision Points
- Availability Check: Real-time validation prevents double-booking
- Pessimistic Locking:
SELECT FOR UPDATEprevents race conditions - Payment Methods: Multiple options with graceful fallback
- Webhook Reliability: Idempotency keys prevent duplicate processing
- Transaction Safety: ACID transactions ensure consistency
Error Handling
| Error Scenario | HTTP Status | User Message | System Action |
|---|---|---|---|
| Slot taken (race) | 409 Conflict | "Slot just taken, try another" | Rollback transaction |
| Payment failed | 402 Payment Required | "Payment declined. Try another card?" | Keep booking PENDING 15min |
| Insufficient wallet | 400 Bad Request | "Insufficient credits. Top up?" | Suggest top-up flow |
| Service timeout | 504 Gateway Timeout | "Taking longer than usual..." | Retry with exponential backoff |
2. Queue Management Flow (Ganha-Fica)
Overview
Players enter a dynamic queue, receive position assignments, get ETA calculations, are called when court is free, accept/decline, play match, register score. Winner stays, loser goes to end of queue.
Actors
- Player: User in queue or playing
- Frontend: Real-time Vue.js app
- Queue Service: Core allocation engine
- Match Service: Match management
- Courts Service: Court state management
- Notification Service: Real-time notifications via WebSocket
- Gamification Service: XP and ranking updates
Queue Allocation Algorithm
class AllocationEngine {
async allocateNextMatch(courtId: CourtId): Promise<AllocationResult> {
const players = await this.getNextPlayers(4);
// Score match quality
const matchQuality = this.calculateMatchQuality(players);
if (matchQuality.score < 0.7) {
// Wait for better match unless timeout
if (players[0].waitTime > 30 * 60) {
return this.allocate(players); // Force allocation
}
return AllocationResult.waitForBetterMatch();
}
// Form balanced teams
const teams = this.balanceTeams(players);
return AllocationResult.success(teams);
}
private calculateMatchQuality(players: Player[]): MatchQuality {
const skillBalance = this.checkSkillBalance(players);
const genderBalance = this.checkGenderBalance(players);
const preferenceMatch = this.checkPreferences(players);
return {
score: (skillBalance * 0.5) + (genderBalance * 0.3) + (preferenceMatch * 0.2),
factors: { skillBalance, genderBalance, preferenceMatch }
};
}
}Real-Time Updates
All queue state changes are broadcast via WebSocket:
// Client-side subscription
socket.on('queue:position_updated', (data) => {
updateQueuePosition(data.position, data.eta);
});
socket.on('queue:match_ready', (data) => {
showMatchAcceptanceModal(data);
startCountdown(60); // 60s to accept
});
socket.on('match:score_updated', (data) => {
updateLiveScore(data.score);
});3. Check-in Process
Overview
User arrives at arena, validates location (GPS/QR), system verifies booking, grants physical access, starts session, tracks game time, and handles check-out.
Actors
- User: Player with booking
- Mobile App: Vue.js PWA
- Access Control: Ziggy integration
- Presence Service: Check-in management
- IoT Devices: Tablets, beacons, QR codes
- Booking Service: Reservation validation
Access Control Integration (Ziggy)
class ZiggyAccessControlAdapter {
async grantAccess(userId: UserId, zoneId: ZoneId, validUntil: Date): Promise<void> {
const credential = await this.generateTemporaryCredential(userId);
await this.ziggyApi.post('/access/grant', {
credential_id: credential.id,
zone_id: zoneId,
valid_from: new Date(),
valid_until: validUntil,
access_level: 'TEMPORARY'
});
await this.ziggyApi.post('/devices/unlock', {
zone_id: zoneId,
duration_seconds: 5
});
}
async revokeAccess(credentialId: string): Promise<void> {
await this.ziggyApi.delete(`/access/${credentialId}`);
}
}Presence Session State Machine
enum PresenceStatus {
PENDING = 'PENDING', // Booking exists, not checked in
CHECKED_IN = 'CHECKED_IN', // User checked in, session active
IN_PROGRESS = 'IN_PROGRESS', // Game started
COMPLETED = 'COMPLETED', // Normal end
NO_SHOW = 'NO_SHOW', // Didn't check in
TIMEOUT = 'TIMEOUT', // Auto-ended
CANCELLED = 'CANCELLED' // Manually cancelled
}
class PresenceSession {
canCheckIn(): boolean {
const now = new Date();
const windowStart = subMinutes(this.booking.startTime, 15);
const windowEnd = addMinutes(this.booking.startTime, 5);
return now >= windowStart && now <= windowEnd;
}
calculateGameTime(): number {
const actualStart = this.checkInAt || this.booking.startTime;
const scheduledEnd = this.booking.endTime;
const lateness = differenceInMinutes(actualStart, this.booking.startTime);
return Math.max(0, differenceInMinutes(scheduledEnd, actualStart));
}
}4. Match Registration & Gamification
Overview
Match ends, players submit scores, system resolves conflicts, awards XP, checks for level-ups, unlocks achievements, and updates rankings.
Actors
- Players: Match participants
- Match Service: Score and result management
- Gamification Service: XP, levels, achievements
- Ranking Service: Leaderboard updates
- Wallet Service: Token rewards
- Analytics Service: Stats tracking
XP Calculation Engine
class XPCalculator {
calculate(match: Match, player: Player): XPAward {
const baseXP = 25; // Match completion
let bonusXP = 0;
const multipliers: number[] = [];
// Victory bonus
if (match.winner === player.team) {
bonusXP += 50;
} else {
bonusXP += 25; // Participation
}
// First game of day
if (this.isFirstGameToday(player)) {
multipliers.push(1.5); // +50%
}
// Streak bonus
if (player.streakDays >= 7) {
multipliers.push(1.25); // +25%
}
// Rank difference
const opponentRankDiff = this.calculateRankDifference(match, player);
if (opponentRankDiff > 100) {
multipliers.push(1.2); // Upset bonus
}
// Special achievements
if (match.mvp === player.id) {
bonusXP += 100;
}
// Apply multipliers
let totalXP = baseXP + bonusXP;
multipliers.forEach(m => totalXP *= m);
return {
total: Math.floor(totalXP),
breakdown: {
base: baseXP,
bonus: bonusXP,
multipliers
}
};
}
}Achievement System
const ACHIEVEMENTS = {
FIRST_MATCH: {
id: 'first_match',
name: 'First Steps',
criteria: (player) => player.matchesPlayed >= 1,
rarity: 'COMMON',
reward: { tokens: 50, xp: 25 }
},
WIN_STREAK_5: {
id: 'win_streak_5',
name: 'On Fire!',
criteria: (player) => player.consecutiveWins >= 5,
rarity: 'RARE',
reward: { tokens: 150, xp: 100 }
},
WEEKLY_WARRIOR: {
id: 'weekly_warrior',
name: 'Weekly Warrior',
criteria: (player) => player.matchesThisWeek >= 10,
rarity: 'EPIC',
reward: { tokens: 500, xp: 250 }
},
PERFECT_MONTH: {
id: 'perfect_month',
name: 'Perfect Attendance',
criteria: (player) => player.streakDays >= 30,
rarity: 'LEGENDARY',
reward: { tokens: 2000, xp: 1000 }
}
};5. Event Mode Activation
Overview
Arena manager creates special event, reserves courts, activates event mode with custom rules, participants check in, consumption is tracked separately, and event concludes.
Actors
- Manager: Arena operator
- Admin Console: Management interface
- Event Service: Event lifecycle management
- Courts Service: Court reservation
- Access Control: Special event access
- Participants: Event players
- Commerce Service: Separate billing
Event Modes
enum EventMode {
TOURNAMENT = 'TOURNAMENT', // Bracketed competition
CHAMPIONSHIP = 'CHAMPIONSHIP', // League format
FESTIVAL = 'FESTIVAL', // Social event
CLINIC = 'CLINIC', // Training/workshop
PRIVATE = 'PRIVATE' // Private rental
}
enum EventStatus {
DRAFT = 'DRAFT', // Being created
PUBLISHED = 'PUBLISHED', // Open for registration
REGISTRATION_CLOSED = 'REGISTRATION_CLOSED',
ACTIVE = 'ACTIVE', // Event in progress
COMPLETED = 'COMPLETED', // Finished, settling
CANCELLED = 'CANCELLED', // Cancelled
ARCHIVED = 'ARCHIVED' // Historical record
}Event Wallet Ledger
class EventWallet {
private ledger: Transaction[] = [];
recordEntryFee(userId: UserId, amount: Money): void {
this.ledger.push({
type: 'CREDIT',
source: 'ENTRY_FEE',
from: userId,
amount,
timestamp: new Date()
});
}
distributePrize(winnerId: UserId, prize: Money): void {
this.ledger.push({
type: 'DEBIT',
source: 'PRIZE_PAYOUT',
to: winnerId,
amount: prize,
timestamp: new Date()
});
}
getBalance(): Money {
return this.ledger.reduce((sum, tx) => {
return tx.type === 'CREDIT'
? sum + tx.amount
: sum - tx.amount;
}, 0);
}
auditTrail(): LedgerEntry[] {
// Immutable audit trail for compliance
return this.ledger.map(tx => Object.freeze(tx));
}
}6. Payment Processing
Overview
User creates payment for booking/event/consumption, system generates PIX QR code or tokenizes card, processes payment via gateway, receives webhook confirmation, updates wallet, and generates receipt.
Actors
- User: Paying customer
- Frontend: Payment UI
- Payment Service: Payment orchestration
- Payment Gateway: External processors (Stripe, Mercado Pago, PagSeguro)
- Wallet Service: Internal credits
- Booking Service: Related booking
- Notification Service: Receipts and confirmations
Payment Gateway Abstraction
interface PaymentGateway {
createPayment(params: CreatePaymentParams): Promise<PaymentResult>;
checkStatus(paymentId: string): Promise<PaymentStatus>;
refund(paymentId: string, amount: Money): Promise<RefundResult>;
validateWebhook(signature: string, payload: any): boolean;
}
class StripeGateway implements PaymentGateway {
async createPayment(params: CreatePaymentParams): Promise<PaymentResult> {
const charge = await this.stripe.charges.create({
amount: params.amount * 100, // Convert to cents
currency: 'brl',
source: params.cardToken,
description: params.description,
metadata: params.metadata
});
return {
providerPaymentId: charge.id,
status: this.mapStatus(charge.status),
paidAt: charge.paid ? new Date(charge.created * 1000) : null
};
}
validateWebhook(signature: string, payload: any): boolean {
return this.stripe.webhooks.constructEvent(
payload,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
}
}
class MercadoPagoGateway implements PaymentGateway {
async createPayment(params: CreatePaymentParams): Promise<PaymentResult> {
const payment = await this.mercadopago.payment.create({
transaction_amount: params.amount,
payment_method_id: 'pix',
payer: {
email: params.email,
identification: {
type: 'CPF',
number: params.cpf
}
}
});
return {
providerPaymentId: payment.body.id,
status: this.mapStatus(payment.body.status),
qrCode: payment.body.point_of_interaction?.transaction_data?.qr_code,
qrCodeString: payment.body.point_of_interaction?.transaction_data?.qr_code_base64
};
}
}
// Factory pattern for gateway selection
class PaymentGatewayFactory {
static create(method: PaymentMethod): PaymentGateway {
switch (method) {
case PaymentMethod.CREDIT_CARD:
case PaymentMethod.DEBIT_CARD:
return new StripeGateway();
case PaymentMethod.PIX:
case PaymentMethod.BOLETO:
return new MercadoPagoGateway();
default:
throw new Error(`Unsupported payment method: ${method}`);
}
}
}Webhook Security
class WebhookValidator {
validateMercadoPago(signature: string, payload: string): boolean {
const expectedSignature = crypto
.createHmac('sha256', process.env.MP_WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
validateStripe(signature: string, payload: string): boolean {
try {
stripe.webhooks.constructEvent(
payload,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
return true;
} catch (err) {
return false;
}
}
validateSource(ipAddress: string, provider: PaymentProvider): boolean {
const allowedIPs = {
MERCADO_PAGO: ['200.98.202.0/24', '52.67.0.0/16'],
STRIPE: ['3.18.12.0/22', '3.130.192.0/22']
};
return this.ipInRange(ipAddress, allowedIPs[provider]);
}
}Idempotency Implementation
class PaymentService {
async createPayment(params: CreatePaymentParams, idempotencyKey: string): Promise<Payment> {
// Check if payment already exists
const existing = await this.paymentRepo.findByIdempotencyKey(idempotencyKey);
if (existing) {
// Return existing payment (idempotent)
return existing;
}
// Create new payment
const payment = Payment.create({
...params,
idempotencyKey
});
await this.paymentRepo.save(payment);
return payment;
}
}
// Usage with retry logic
class PaymentClient {
async createPayment(params: CreatePaymentParams): Promise<Payment> {
const idempotencyKey = crypto.randomUUID();
let retries = 3;
while (retries > 0) {
try {
return await this.api.post('/payments', params, {
headers: {
'Idempotency-Key': idempotencyKey
}
});
} catch (error) {
if (error.status >= 500 && retries > 1) {
retries--;
await this.sleep(1000 * (4 - retries)); // Exponential backoff
} else {
throw error;
}
}
}
}
}Summary
These sequence diagrams provide comprehensive documentation of the six critical flows in the Sport Tech Club system:
- Booking Creation: Real-time availability → selection → payment → confirmation
- Queue Management (Ganha-Fica): Dynamic allocation → match formation → winner stays logic
- Check-in Process: Location validation → access control → session tracking
- Match & Gamification: Score resolution → XP award → level-up → achievements
- Event Mode: Creation → activation → separate billing → finalization
- Payment Processing: Multiple methods → gateway integration → webhooks → receipts
Key Architectural Patterns Used
- Pessimistic Locking: Prevent race conditions in bookings
- Event-Driven Architecture: Decoupled communication via events
- CQRS: Separate read/write models for performance
- Event Sourcing: Immutable audit trail (Wallet, Matches)
- Saga Pattern: Distributed transactions across services
- Idempotency: Safe retries for payment operations
- WebSocket: Real-time updates for queue and matches
- Anti-Corruption Layer: Clean integration with external systems
Error Handling Strategy
All flows include:
- Validation: Business rule validation before state changes
- Transactions: ACID guarantees for critical operations
- Rollback: Automatic rollback on failures
- Retry Logic: Exponential backoff for transient failures
- Graceful Degradation: Fallback mechanisms
- Audit Logging: Complete traceability
- User Feedback: Clear error messages
Version Control:
| Version | Date | Changes | Author |
|---|---|---|---|
| 1.0.0 | 2026-01-09 | Initial comprehensive diagrams | Atlas (Claude Opus 4.5) |
Related Documentation:
"The devil is in the details, but so is salvation." — Mies van der Rohe