Payment Flow Example
This example demonstrates a resilient payment flow with stock reservation, payment processing, and compensation.
The Scenario
- Reserve Stock: Call an inventory service to hold the items.
- Process Payment: Call a payment gateway (Stripe/PayPal).
- Confirm Order: Update the database status.
- Notify User: Send a confirmation email.
If the payment fails, we must release the reserved stock.
The Code
ts
import { create } from "@eddiecbrl/orchestrix";
import { z } from "zod";
// 1. Define input schema
const PaymentSchema = z.object({
orderId: z.string(),
items: z.array(z.object({ sku: z.string(), qty: z.number() })),
amount: z.number(),
userId: z.string(),
});
type PaymentInput = z.infer<typeof PaymentSchema>;
// 2. Create the flow
const paymentFlow = create<PaymentInput>("payment-saga", { schema: PaymentSchema })
// Step 1: Reserve Stock
.step("reserve-stock", async (ctx) => {
const { items } = ctx.input;
const reservationIds = await inventoryService.reserve(items);
ctx.set("reservationIds", reservationIds);
}, {
compensate: async (ctx) => {
const ids = ctx.get<string[]>("reservationIds");
await inventoryService.release(ids);
}
})
// Step 2: Process Payment
.step("process-payment", async (ctx) => {
const { amount, userId, orderId } = ctx.input;
const transactionId = await paymentGateway.charge({
amount,
userId,
idempotencyKey: `pay_${orderId}`
});
ctx.set("transactionId", transactionId);
}, {
retries: 3,
retryDelayMs: 2000,
backoffFactor: 'exponential'
})
// Step 3: Confirm Order
.step("confirm-order", async (ctx) => {
const { orderId } = ctx.input;
const transactionId = ctx.get<string>("transactionId");
await db.orders.update(orderId, { status: 'paid', transactionId });
})
// Step 4: Notify
.step("send-email", async (ctx) => {
await emailService.send(ctx.input.userId, "Order Confirmed!");
}, {
retries: 5 // We really want to send this email
});
// 3. Run the flow
const result = await paymentFlow.run({
orderId: "ORD-123",
items: [{ sku: "LAPTOP-01", qty: 1 }],
amount: 1200,
userId: "user_99"
});
if (result.status === 'completed') {
console.log("Payment successful!");
} else {
console.error("Payment failed. Compensations were executed.");
}Why this is professional
- Type Safety: Uses Zod for runtime validation and TypeScript for compile-time safety.
- Resilience: Retries on the payment gateway and email service handle transient network issues.
- Consistency: The
compensatefunction ensures we don't leave items reserved if the payment fails. - Idempotency: We pass an
idempotencyKeyto the payment gateway (derived from our order ID).