# E-Commerce Example: Sagas Implement the order fulfillment saga to coordinate order processing. ## OrderFulfillmentSaga The saga coordinates the entire order lifecycle: ```csharp public class OrderFulfillmentSaga : IWorkflow { private readonly IEventStreamStore _eventStore; private readonly IInventoryService _inventory; private readonly IPaymentService _payment; private readonly IShippingService _shipping; private readonly ILogger _logger; public async Task HandleAsync(OrderPlacedEvent @event, CancellationToken ct) { var streamName = $"order-{@event.OrderId}"; try { // Step 1: Reserve inventory _logger.LogInformation("Reserving inventory for order {OrderId}", @event.OrderId); var reservation = await _inventory.ReserveAsync( @event.OrderId, @event.Lines.Select(l => new InventoryItem { ProductId = l.ProductId, Quantity = l.Quantity }).ToList(), ct); if (!reservation.Success) { await _eventStore.AppendAsync(streamName, new InventoryUnavailableEvent { OrderId = @event.OrderId, UnavailableItems = reservation.UnavailableItems, NotifiedAt = DateTimeOffset.UtcNow }, ct); await _eventStore.AppendAsync(streamName, new OrderCancelledEvent { OrderId = @event.OrderId, CancelledBy = "System", Reason = "Inventory unavailable", RefundIssued = false, CancelledAt = DateTimeOffset.UtcNow }, ct); return; } await _eventStore.AppendAsync(streamName, new InventoryReservedEvent { OrderId = @event.OrderId, ReservationId = reservation.ReservationId, Items = @event.Lines.Select(l => new InventoryItemDto { ProductId = l.ProductId, Quantity = l.Quantity }).ToList(), ReservedAt = DateTimeOffset.UtcNow }, ct); // Step 2: Process payment _logger.LogInformation("Processing payment for order {OrderId}", @event.OrderId); var paymentResult = await _payment.ChargeAsync( @event.OrderId, @event.TotalAmount, ct); if (!paymentResult.Success) { // Compensation: Release inventory await _inventory.ReleaseAsync(reservation.ReservationId, ct); await _eventStore.AppendAsync(streamName, new PaymentFailedEvent { OrderId = @event.OrderId, Reason = paymentResult.ErrorMessage, FailedAt = DateTimeOffset.UtcNow }, ct); await _eventStore.AppendAsync(streamName, new OrderCancelledEvent { OrderId = @event.OrderId, CancelledBy = "System", Reason = "Payment failed", RefundIssued = false, CancelledAt = DateTimeOffset.UtcNow }, ct); return; } await _eventStore.AppendAsync(streamName, new PaymentProcessedEvent { PaymentId = paymentResult.PaymentId, OrderId = @event.OrderId, Amount = @event.TotalAmount, ProcessedAt = DateTimeOffset.UtcNow }, ct); await _eventStore.AppendAsync(streamName, new OrderPaidEvent { OrderId = @event.OrderId, PaymentId = paymentResult.PaymentId, PaymentMethod = "CreditCard", Amount = @event.TotalAmount, PaidAt = DateTimeOffset.UtcNow }, ct); _logger.LogInformation("Order {OrderId} fulfilled successfully", @event.OrderId); } catch (Exception ex) { _logger.LogError(ex, "Failed to fulfill order {OrderId}", @event.OrderId); await _eventStore.AppendAsync(streamName, new OrderFulfillmentFailedEvent { OrderId = @event.OrderId, ErrorMessage = ex.Message, FailedAt = DateTimeOffset.UtcNow }, ct); } } } ``` ## Saga Registration ```csharp // In Program.cs builder.Services.AddWorkflow(); ``` ## See Also - [07-http-api.md](07-http-api.md) - HTTP API endpoints - [Sagas](../../event-streaming/sagas/README.md)