Preskoči na sadržaj

E9-F8: Self-Service Ticket Cancellation

Epic: E9: Ticket Management & Delivery

Size: M (Medium)

Problem / Outcome

Allow ticket buyers to cancel their tickets directly from the mobile app without contacting customer support. Buyers can select specific tickets to cancel (partial cancellation) or cancel all tickets in an order (full cancellation). Refund is automatically processed to the original payment method.

Important: This feature is available ONLY to the buyer (person who paid) and only if more than 48 hours before match start.

Scope

In-Scope:

  • Ticket selection via checkboxes in Order Details
  • Partial cancellation (some tickets from order)
  • Full cancellation (all tickets from order)
  • Refund amount calculation and display
  • Reason selection for cancellation
  • Automatic refund processing via Stripe
  • Notifications to buyer and affected ticket holders
  • Order status update (Partially Refunded / Refunded)

Out-of-Scope:

  • Cancellation within 48 hours (requires support)
  • Cancellation of transferred tickets
  • Partial refund of individual ticket (all-or-nothing per ticket)
  • Cancellation fee deduction (service fee is refundable in self-service)

Business Rules

  1. 48-Hour Rule: Self-service cancellation only available more than 48 hours before match
  2. Buyer Only: Only the person who paid can cancel tickets
  3. Active Tickets Only: Cannot cancel tickets that are already transferred or cancelled
  4. Full Ticket Refund: Refund includes ticket price + service fee for cancelled tickets
  5. Immediate Processing: Cancellation takes effect immediately, refund initiated automatically
  6. Seat Release: Cancelled seats return to inventory for resale

Acceptance Criteria

  • AC1: Cancel button only visible to buyer in Order Details
  • AC2: Cancellation blocked if less than 48 hours before match
  • AC3: Buyer can select individual tickets via checkboxes
  • AC4: "Select All" toggle works correctly
  • AC5: Transferred/cancelled tickets have disabled checkboxes
  • AC6: Confirmation screen shows selected tickets and refund amount
  • AC7: Reason dropdown required before confirmation
  • AC8: After confirmation: tickets cancelled, seats released, refund initiated
  • AC9: Buyer receives confirmation email with refund details
  • AC10: Affected ticket holders notified of cancellation
  • AC11: Order status updates to "Partially Refunded" or "Refunded"
  • AC12: Refund document available for download

User Interface

Ticket Selection (in Order Details)

│  Tickets in this Order (4)                                   │
│  ─────────────────────────                                  │
│  [✓] 1. Ivan Horvat (ivan@email.com)        [View Ticket]   │
│  [✓] 2. Ana Horvat (ana@email.com)          [View Ticket]   │
│  [ ] 3. Marko Horvat (on your device)       [View Ticket]   │
│  [—] 4. Petra Horvat - TRANSFERRED          [View Ticket]   │
│                                                              │
│  [Select All]                                                │
│                                                              │
│  [Cancel Selected Tickets (2)]                               │

Cancellation Confirmation Screen

┌─────────────────────────────────────────────────────────────┐
│  Cancel Tickets                                              │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  You are about to cancel 2 of 4 tickets:                    │
│                                                              │
│  • Ivan Horvat - Zone A, Row 5, Seat 12                     │
│  • Ana Horvat - Zone A, Row 5, Seat 13                      │
│                                                              │
│  ─────────────────────────────────────────────────────────  │
│                                                              │
│  Refund Summary:                                             │
│  Ticket price (2 × €40.00)                         €80.00   │
│  Service fee (2 × €2.00)                            €4.00   │
│  ─────────────────────────────────────────────────────────  │
│  Refund Amount                                     €84.00   │
│                                                              │
│  Refund to: Visa •••• 4455                                  │
│  Processing time: 5-10 business days                        │
│                                                              │
│  ─────────────────────────────────────────────────────────  │
│                                                              │
│  Reason for cancellation:                                    │
│  [Cannot attend                                    ▼]       │
│                                                              │
│  ⚠ This action cannot be undone. Cancelled tickets          │
│    will be released for resale.                             │
│                                                              │
│  [Go Back]                        [Confirm Cancellation]    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Cancellation Reasons

Reason Value
Cannot attend cannot_attend
Schedule conflict schedule_conflict
Bought wrong tickets wrong_tickets
Other other

Data Model Impact

Refund table (existing, extended):
- Add: source (ENUM: self_service, support, system)
- Add: cancellation_reason (VARCHAR)

Ticket table:
- Update status to CANCELLED
- Add cancelled_at (TIMESTAMP)
- Add cancelled_by (UUID, FK to user)

Order table:
- Update refund_status (none → partial → full)
- Update refunded_amount

New CancellationRequest table (optional, for audit):
- id (UUID, PK)
- order_id (UUID, FK)
- ticket_ids (UUID[])
- reason (VARCHAR)
- refund_amount (DECIMAL)
- initiated_by (UUID, FK)
- created_at (TIMESTAMP)
- status (ENUM: pending, completed, failed)

API Endpoints

POST /orders/{id}/cancel-tickets

Request:

{
  "ticket_ids": ["uuid1", "uuid2"],
  "reason": "cannot_attend"
}

Response:

{
  "success": true,
  "cancelled_tickets": 2,
  "refund": {
    "id": "ref_123",
    "amount": 84.00,
    "currency": "EUR",
    "status": "pending",
    "estimated_arrival": "5-10 business days"
  },
  "order_status": "partially_refunded"
}

Error Responses: - 403: Not the buyer - 400: Less than 48 hours before match - 400: Tickets already cancelled/transferred - 400: No tickets selected

Permissions/Roles

  • Buyer: Can cancel their own tickets (>48h before match)
  • Recipient: Cannot cancel - must contact buyer
  • Support Agent: Can cancel any ticket (see E10-F3)

How to Verify

npm test -- --grep "self-service cancellation"

Test Scenarios: 1. Buyer cancels 2 of 4 tickets - partial refund 2. Buyer cancels all tickets - full refund 3. Buyer tries to cancel <48h - blocked with message 4. Recipient tries to cancel - no option visible 5. Select transferred ticket - checkbox disabled 6. Refund amount calculation correct 7. Notifications sent to all parties

Dependencies

Implementation Tasks

See E9: Ticket Management Tasks

Doc References


Last Updated: January 2026