Skip to content

Examples: Durable Refund Flow

A runnable refund pattern showing bounded semantic extraction, deterministic policy evaluation, and a stable capability identity. The deterministic model fixture keeps the example offline; replace it with a configured provider adapter in production. The account and payment clients remain application-owned.

ts
import { invariant } from "@invariant-tech/sdk";
import { sqlite } from "@invariant-tech/sqlite";
import { z } from "zod";

type RefundClassification = {
  reason: "DUPLICATE_CHARGE" | "SERVICE_FAILURE" | "OTHER";
  requestedAmount: number;
};

const accountStore = {
  load: async (userId: string) => ({
    userId,
    tier: "VIP" as const,
    refundableBalance: 100,
  }),
};

const refundModel = {
  provider: "fixture",
  defaultModel: "refund-fixture-v1",
  async generateReasoning() {
    return {
      result: { reason: "DUPLICATE_CHARGE", requestedAmount: 75 },
    };
  },
};

const app = invariant({
  storage: sqlite("./data/refunds.db"),
  models: { default: refundModel },
});

export const durableRefund = app.workflow("durable-refund", {
  inputSchema: z.object({
    userId: z.string(),
    request: z.string(),
  }),
})
  .capability("load-account", async ({ input }) => ({
    account: await accountStore.load(input.userId),
  }))
  .reason<RefundClassification>("classify-request", {
    instruction: "Extract the refund reason and requested amount from the customer request.",
    schema: {
      type: "object",
      properties: {
        reason: { type: "string", enum: ["DUPLICATE_CHARGE", "SERVICE_FAILURE", "OTHER"] },
        requestedAmount: { type: "number", minimum: 0 },
      },
      required: ["reason", "requestedAmount"],
      additionalProperties: false,
    },
  })
  .step("check-policy", ({ state }) => {
    const eligibleReason = state.reason !== "OTHER";
    const amount = Math.min(state.requestedAmount, state.account.refundableBalance);
    return { approved: eligibleReason && amount > 0, amount };
  })
  .capability("stripe-refund", {
    idempotencyKey: "refund:{{runId}}",
    handler: async ({ state }) => {
      if (!state.approved) return { status: "DENIED" as const };
      return { status: "SUCCESS" as const, amount: state.amount };
    },
  });

async function main() {
  const session = await app.sessions.loadOrCreateSession("sess_refund_1", "usr_refund_1");
  await session.startWorkflow(durableRefund, {
    userId: "usr_refund_1",
    request: "I was charged twice; refund $75.",
  });

  console.log(session.snapshot().projectedState);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Next Steps

Invariant Durable Execution Engine.