Skip to main content

Overview

The Ordering service manages the shopping cart and order lifecycle. It handles adding reserved seats to a cart, transitioning orders from draft to pending state, and preparing orders for payment processing. Port: 5003
Database Schema: bc_ordering
Dependencies: PostgreSQL, Redis, Kafka

Responsibilities

  • Manage shopping cart (draft orders)
  • Create and track orders through their lifecycle
  • Support both authenticated users and guest checkout (via guest tokens)
  • Transition orders from draftpendingpaidfulfilled
  • Link orders to seat reservations
  • Consume Kafka events from other services
  • Provide order details for fulfillment service

API Endpoints

Add to Cart

POST /cart/add
endpoint
Adds a reserved seat to the user’s cart. Creates a draft order if none exists.
Request Body:
ReservationId
Guid
required
The ID of the reservation
SeatId
Guid
required
The ID of the seat
Price
decimal
required
The price of the seat
UserId
string
The authenticated user ID (required if GuestToken not provided)
GuestToken
string
The guest token for anonymous checkout (required if UserId not provided)
~/workspace/source/services/ordering/src/Api/Controllers/CartController.cs
[HttpPost("add")]
public async Task<IActionResult> AddToCart([FromBody] AddToCartRequest request, CancellationToken cancellationToken = default)
{
    if (string.IsNullOrEmpty(request.UserId) && string.IsNullOrEmpty(request.GuestToken))
    {
        return BadRequest("Either UserId or GuestToken must be provided");
    }

    var command = new AddToCartCommand(
        request.ReservationId,
        request.SeatId,
        request.Price,
        request.UserId,
        request.GuestToken
    );

    var response = await _mediator.Send(command, cancellationToken);

    if (!response.Success)
    {
        return BadRequest(response.ErrorMessage);
    }

    return Ok(response.Order);
}
Response (200 OK):
Order
Order
The updated order (cart) object

Checkout Order

POST /orders/checkout
endpoint
Checks out an order, transitioning it from draft to pending state (ready for payment).
Request Body:
OrderId
Guid
required
The ID of the order to checkout
UserId
string
The authenticated user ID
GuestToken
string
The guest token for anonymous checkout
~/workspace/source/services/ordering/src/Api/Controllers/OrdersController.cs
[HttpPost("checkout")]
public async Task<IActionResult> Checkout([FromBody] CheckoutRequest request, CancellationToken cancellationToken = default)
{
    if (string.IsNullOrEmpty(request.UserId) && string.IsNullOrEmpty(request.GuestToken))
    {
        return BadRequest("Either UserId or GuestToken must be provided");
    }

    var command = new CheckoutOrderCommand(
        request.OrderId,
        request.UserId,
        request.GuestToken
    );

    var response = await _mediator.Send(command, cancellationToken);

    if (!response.Success)
    {
        return response.ErrorMessage switch
        {
            "Order not found" => NotFound(response.ErrorMessage),
            "Unauthorized" => Unauthorized(response.ErrorMessage),
            _ => BadRequest(response.ErrorMessage)
        };
    }

    return Ok(response.Order);
}
Response (200 OK):
Order
Order
The checked-out order in pending state
Error Responses:
  • 400 Bad Request: Validation errors
  • 401 Unauthorized: UserId/GuestToken mismatch
  • 404 Not Found: Order not found

Get Order Details

GET /orders/{id}
endpoint
Retrieves order details by ID. Used by the Fulfillment service for ticket enrichment.
id
Guid
required
The order ID
~/workspace/source/services/ordering/src/Api/Controllers/OrdersController.cs
[HttpGet("{id}")]
public async Task<IActionResult> GetOrderDetails(Guid id, CancellationToken cancellationToken = default)
{
    var result = await _mediator.Send(new GetOrderQuery(id), cancellationToken);
    
    if (result == null)
        return NotFound();

    return Ok(new
    {
        OrderId = result.Id,
        CustomerEmail = result.UserId ?? "[email protected]",
        EventId = Guid.Empty, // TODO: Need to trace back from SeatId
        EventName = "Event Details Not Implemented",
        SeatNumber = $"Seat-{firstItem.SeatId}",
        Price = result.TotalAmount,
        Currency = "USD"
    });
}

Domain Models

Order

Represents a customer order (cart).
Id
Guid
Unique order identifier
UserId
string?
Authenticated user ID (null for guest orders)
GuestToken
string?
Guest checkout token (null for authenticated orders)
TotalAmount
decimal
Total order amount
State
string
Order state: draft, pending, paid, fulfilled, or cancelled
CreatedAt
DateTime
When the order was created
PaidAt
DateTime?
When the order was paid (null if unpaid)
Items
ICollection<OrderItem>
Collection of order items (seats)
~/workspace/source/services/ordering/src/Domain/Entities/Order.cs
public class Order
{
    public Guid Id { get; set; }
    public string? UserId { get; set; }
    public string? GuestToken { get; set; }
    public decimal TotalAmount { get; set; }
    public string State { get; set; } = "draft"; // draft, pending, paid, fulfilled, cancelled
    public DateTime CreatedAt { get; set; }
    public DateTime? PaidAt { get; set; }
    public ICollection<OrderItem> Items { get; set; } = new List<OrderItem>();
}

OrderItem

Represents a single seat/ticket within an order.
Id
Guid
Unique order item identifier
OrderId
Guid
Reference to the parent order
SeatId
Guid
Reference to the seat
Price
decimal
Price for this item
~/workspace/source/services/ordering/src/Domain/Entities/Order.cs
public class OrderItem
{
    public Guid Id { get; set; }
    public Guid OrderId { get; set; }
    public Guid SeatId { get; set; }
    public decimal Price { get; set; }
    public Order Order { get; set; } = null!;
}

Configuration

Database Connections

appsettings.json
{
  "ConnectionStrings": {
    "Default": "Host=postgres;Port=5432;Database=ticketing;Username=postgres;Password=postgres;SearchPath=bc_ordering",
    "Redis": "redis:6379",
    "Kafka": "kafka:9092"
  }
}

JWT Authentication

{
  "Jwt": {
    "Key": "dev-secret-key-minimum-32-chars-required-for-security",
    "Issuer": "SpecKit.Identity",
    "Audience": "SpecKit.Services"
  }
}

Kafka Consumer

{
  "Kafka": {
    "BootstrapServers": "kafka:9092",
    "ConsumerGroupId": "ordering-service",
    "EnableConsumer": true
  }
}

Order State Machine

Orders progress through the following states:
  1. draft: Initial state when items are added to cart
  2. pending: After checkout, ready for payment
  3. paid: Payment successfully processed (updated by Payment service)
  4. fulfilled: Tickets generated and delivered (updated by Fulfillment service)
  5. cancelled: Order was cancelled or payment failed

Kafka Integration

The Ordering service consumes Kafka events to update order state:
  • payment-succeeded: Transitions order from pendingpaid
  • ticket-issued: Transitions order from paidfulfilled

Use Cases

  • AddToCart: Adds a reserved seat to the user’s cart (draft order)
  • CheckoutOrder: Validates and transitions order to pending state
  • GetOrder: Retrieves order details for internal service consumption

Architecture Notes

  • Uses MediatR for CQRS-style command/query handling
  • Supports both authenticated and guest checkout flows
  • Infrastructure services registered via AddInfrastructure() extension method
  • Listens to Kafka for order state updates from downstream services

Build docs developers (and LLMs) love