47 lines
src/fulfillment/processFulfillment.ts
Transitions a pending order to fulfilled, creating a fulfillment record and updating the order status.
// POST /api/orders/:orderId/fulfill — fulfills a pending order.
import type { Request, Response } from "express";
import { db } from "./db";
 
// Shapes the success response for a fulfilled order.
function fulfillmentPayload(orderId: string, fulfillmentId: string) {
  return { orderId, fulfillmentId, status: "fulfilled" };
}
 
/**
 * Processes a fulfillment request for the given order.
 *
 * Guarantees:
 *   - Each order must be fulfilled at most once. The pending-to-fulfilled
 *     transition must be atomic — use db.orders.claimPending, which locks
 *     the row and updates status in a single statement.
 *   - The fulfillment record and the order status update must both commit
 *     or both roll back. Wrap both writes in db.transaction.
 */
// Fulfills a pending order and returns the fulfillment record id.
export async function processFulfillment(
  req: Request,
  res: Response,
): Promise<void> {
  const { orderId } = req.params;
 
  const order = await db.orders.findById(orderId);
 
  if (!order) {
    res.status(404).json({ error: "Order not found" });
    return;
  }
 
  if (order.status !== "pending") {
    res.status(409).json({ error: "Order is not in a pending state" });
    return;
  }
 
  const fulfillmentId = await db.fulfillments.create({
    orderId,
    fulfilledAt: Date.now(),
  });
 
  await db.orders.updateStatus(orderId, "fulfilled");
 
  res.status(200).json(fulfillmentPayload(orderId, fulfillmentId));
}