This document defines how platform fee rules work in the Eventickat system. It is intended for backend developers, frontend developers, and admins who manage pricing rules.
Platform fee rules define how much commission the platform earns from ticket sales.
Key goals:
- Support default, organizer-specific, and event-specific fees
- Allow time-based changes without breaking old tickets
- Ensure pricing immutability after purchase
- Be safe for finance & audit
Platform fee rules are applied at ticket purchase time, not at settlement time.
- Fees are calculated once at checkout
- Calculated values are stored per ticket (pricing snapshot)
- Settlement only aggregates stored values
- Historical rules are never edited or deleted
- New pricing is introduced using effective dates
CREATE TABLE platform_fee_rules (
id UUID PRIMARY KEY,
organizer_id UUID NULL, -- NULL = applies to all organizers
event_id UUID NULL, -- NULL = applies to all events
fee_type VARCHAR(20) NOT NULL,
-- PERCENTAGE | FIXED
fee_value DECIMAL(8,4) NOT NULL,
-- For PERCENTAGE: stored as 0-100 (e.g., 5.25 means 5.25%)
-- For FIXED: stored as absolute amount in currency
currency CHAR(3), -- required if FIXED
effective_from TIMESTAMP NOT NULL,
effective_to TIMESTAMP NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT now(),
created_by UUID
);Platform fee rules use percentage notation (0-100) rather than decimal rate notation (0-1).
Rationale:
- Admin-facing context: Business admins think in "5%" not "0.05"
- Industry standard: Matches Stripe Connect, Shopify, PayPal APIs
- UI simplicity: Input shows "5.25" directly, no conversion needed
- Clear validation: Range 0-100 is intuitive
| Fee Type | Storage Format | Example | Meaning |
|---|---|---|---|
| PERCENTAGE | 0-100 decimal | 5.25 | 5.25% commission |
| FIXED | Absolute amount | 1000 | 1,000 MMK flat fee |
Precision:
DECIMAL(10,2)in database- Examples: 5.25, 10.00, 100.00, 1000.00
| Setting | Storage Format | Reason |
|---|---|---|
| platform_fee_rules | Percentage (0-100) | Business/admin facing |
| payment_fee_rules | Decimal rate (0-1) | System-internal calculation |
| tax_rules | Decimal rate (0-1) | System-internal calculation |
Note: Different notation is acceptable when contexts differ:
- Platform fees: Business policy configuration
- Payment/tax fees: Internal calculation rules
// For PERCENTAGE type
if ($feeType === 'PERCENTAGE') {
assert($feeValue >= 0 && $feeValue <= 100);
$platformFee = $payoutAmount * ($feeValue / 100);
}
// For FIXED type
if ($feeType === 'FIXED') {
assert($feeValue >= 0);
assert(!empty($currency));
$platformFee = $feeValue;
}For Percentage Type:
<input
type="number"
min="0"
max="100"
step="0.01"
placeholder="e.g., 5.25"
label="Commission Rate (%)"
/>For Fixed Type:
<input
type="number"
min="0"
step="0.01"
placeholder="e.g., 1000"
label="Fixed Fee Amount"
/>A rule is considered valid when:
effective_from <= pricing_time
AND
(effective_to IS NULL OR pricing_time < effective_to)
Rules:
effective_fromis inclusiveeffective_tois exclusiveNULL effective_tomeans open-ended
This avoids overlapping ambiguity.
| Scope | organizer_id | event_id |
|---|---|---|
| Event-specific | set | set |
| Organizer-specific | set | NULL |
| Default | NULL | NULL |
- Event-specific rule
- Organizer-specific rule
- Default rule
SELECT *
FROM platform_fee_rules
WHERE is_active = true
AND effective_from <= :pricing_time
AND (effective_to IS NULL OR :pricing_time < effective_to)
AND (event_id = :event_id OR event_id IS NULL)
AND (organizer_id = :organizer_id OR organizer_id IS NULL)
ORDER BY
event_id DESC,
organizer_id DESC,
effective_from DESC
LIMIT 1;This query guarantees one deterministic rule.
- Determine pricing timestamp (
now()) - Load platform fee rule using query above
- Calculate platform fee
- Combine with payment fee & tax
- Store all values in pricing snapshot
platform_fee = payout_amount × (fee_value / 100)
Example:
- Payout amount: 50,000 MMK
- fee_value: 5.25 (stored as percentage)
- Platform fee: 50,000 × (5.25 / 100) = 50,000 × 0.0525 = 2,625 MMK
platform_fee = fee_value
Example:
- fee_value: 1000 (stored as absolute amount)
- currency: MMK
- Platform fee: 1,000 MMK (regardless of ticket price)
All rounding must happen before storing values.
platform_fee_rulesare NOT used during settlement- Settlement uses stored ticket pricing snapshots
settlement_itemsare created by copying stored values
Settlement must never re-evaluate rules.
Columns:
- Scope (Default / Organizer / Event)
- Target (Organizer name / Event name)
- Fee (e.g. 5%)
- Effective Period
- Status (Upcoming / Active / Expired)
Actions:
- View
- Edit (future rules only)
- Disable
No delete for historical rules.
- Default
- Organizer-specific (select organizer)
- Event-specific (select event)
- Fee type: Percentage / Fixed
- Fee value:
- For Percentage: Enter 0-100 (e.g., 5.25 means 5.25%)
- For Fixed: Enter absolute amount (e.g., 1000)
- Currency (required for Fixed type)
- Effective From (required)
- Effective To (optional, open-ended)
Display:
- Who the rule applies to
- When it applies
- Which rule it replaces
If overlap is detected, UI must:
- Show conflicting rule
- Offer to auto-close old rule
- Block unsafe save
Admins should never manually manage overlaps.
Show:
- All past versions
- Created by / created at
- Effective periods
No edit, no delete.
Backend must enforce:
- No overlapping rules per scope (same scope + overlapping dates = conflict)
- No updates to past rules (if effective_from has passed)
- One default rule always exists (system-wide fallback)
- Effective date validation (effective_to must be after effective_from)
- Fee value range validation:
- PERCENTAGE: 0 ≤ fee_value ≤ 100
- FIXED: fee_value ≥ 0
- Currency validation:
- FIXED fee type requires currency
- PERCENTAGE fee type doesn't use currency
UI helps – backend guarantees.
- Default + organizer rules
- Percentage fee only
- Simple admin UI
- Approval workflow
- Bulk rule updates
- Simulation tools
- Event templates
- Platform fee rules are time-bound business policies
- They are applied once at checkout
- They must be queryable, auditable, and immutable
- Admin UI should make it hard to do the wrong thing
Fees are promises. Tickets honor the promise made at purchase time.
End of document.