Real World Example

Food Delivery Checkout (Swiggy / Zomato Style)

A food order touches restaurant, payment, delivery-partner and notification services. Payment providers such as Razorpay and Stripe accept an idempotency key, and the order flow is a saga:

App --POST /orders (Idempotency-Key)--> order-service
                                          | local tx: orders + outbox(OrderPlaced)
                                          v
   Kafka order-events --> restaurant-service : accept or reject (kitchen closed?)
                      --> payment-service    : capture UPI payment (idempotent on orderId)
                      --> delivery-service   : assign rider (timeout 3 min)

 Rider not found in 3 min  ->  orchestrator: RefundPayment, NotifyRestaurant(cancel),
                               order CANCELLED, push "Sorry, no delivery partner nearby"

Payment Consumer with All Three Ideas

@Component
@RequiredArgsConstructor
@Slf4j
public class PaymentCommandHandler {

    private final ProcessedEventRepository processed;
    private final PaymentRepository payments;
    private final RazorpayClient razorpay;
    private final OutboxRepository outbox;

    @KafkaListener(topics = "payment-commands", groupId = "payment-service")
    @Transactional
    public void handle(ChargePayment cmd) {
        if (processed.existsById(cmd.commandId())) {                         // 1. idempotent consumer
            log.info("Command {} already handled", cmd.commandId());
            return;
        }
        PaymentResult result = razorpay.capture(cmd.paymentToken(), cmd.amount(),
                "order-" + cmd.orderId());                                    // 2. idempotency key at the provider
        payments.save(Payment.of(cmd.orderId(), result));
        outbox.save(OutboxEvent.of(cmd.orderId(),                              // 3. reply via the outbox
                result.captured() ? new PaymentCompleted(cmd.orderId(), result.id())
                                  : new PaymentDeclined(cmd.orderId(), result.reason())));
        processed.save(new ProcessedEvent(cmd.commandId()));
    }
}
ProblemPattern that solves it
No transaction across order, payment, deliverySaga with compensations
Order saved but event lostTransactional outbox
Kafka delivers the command twiceIdempotent consumer (processed_events)
User double-taps "Pay"Idempotency-Key header + unique constraint