// Generated by N2C Compiler
// Workflow: "Refund approval" (ID: "refund_approval")
// Target: Cloudflare Workflow
import { WorkflowEntrypoint } from "cloudflare:workers";
function __ccRpcResult(id, result) { return Response.json({ jsonrpc: "2.0", id, result }); }
function __ccRpcError(id, code, message, status) {
return Response.json({ jsonrpc: "2.0", id, error: { code, message } }, { status: status ?? 200 });
}
async function __ccToolResult(value) {
let unwrapped = value === undefined ? null : value;
if (unwrapped instanceof Response) {
const body = await unwrapped.clone().text();
try { unwrapped = JSON.parse(body); } catch { unwrapped = body; }
}
const text = typeof unwrapped === "string" ? unwrapped : (JSON.stringify(unwrapped) ?? "null");
const structured =
unwrapped !== null && typeof unwrapped === "object" && !Array.isArray(unwrapped)
? { structuredContent: unwrapped }
: {};
return { content: [{ type: "text", text }], ...structured };
}
export class N2CWorkflow extends WorkflowEntrypoint {
async run(event, step) {
const env = this.env;
const ctx = {
input: event.payload,
output: {},
nodes: {},
env,
execution: {
id: crypto.randomUUID(),
startedAt: new Date().toISOString(),
durationMs: 0,
retryCount: 0,
currentStep: "",
debugMode: false,
traceId: crypto.randomUUID(),
logger: { log: (msg) => console.log(msg), error: (msg) => console.error(msg) },
},
workflow: {
id: "refund_approval",
name: "Refund approval",
version: 1,
triggerType: "mcp",
settings: {},
},
node: {},
};
// === Node: tool (mcp_trigger) ===
ctx.nodes["tool"] = ctx.input;
// === Node: over (if) ===
if ((ctx.input?.amount_cents > 50000)) {
// === Node: ask (slack) ===
ctx.nodes["ask"] = await step.do("ask", async () => {
const ask_resRes = await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: {
"Authorization": "Bearer " + env.CREDENTIAL_ASK,
"Content-Type": "application/json"
},
body: JSON.stringify({ channel: "#billing", text: `Refund $${((__v) => __v !== null && typeof __v === "object" ? JSON.stringify(__v) : __v)((ctx.input?.amount_cents / 100))} on ${((__v) => __v !== null && typeof __v === "object" ? JSON.stringify(__v) : __v)(ctx.input?.payment_intent)}? ${((__v) => __v !== null && typeof __v === "object" ? JSON.stringify(__v) : __v)(ctx.input?.reason)}. Tell your agent yes or no within 24 hours.` }),
});
if (!ask_resRes.ok) {
throw new Error("Slack API error " + ask_resRes.status + ": " + (await ask_resRes.text()));
}
const ask_res_text = await ask_resRes.text();
let ask_res = {};
if (ask_res_text.trim() !== "") {
try { ask_res = JSON.parse(ask_res_text); } catch { ask_res = ask_res_text; }
}
return ask_res;
});
// === Node: approval (wait_for_event) ===
ctx.nodes["approval"] = { ...(await step.waitForEvent("approval", { type: "refund_decision", timeout: "24 hours" })), timedOut: false };
// === Node: approved (if) ===
if (ctx.nodes["approval"]?.payload?.approved) {
// === Node: refund_approved (http_request) ===
ctx.nodes["refund_approved"] = await step.do("refund_approved", async () => {
const refund_approved_res = await fetch("https://api.stripe.com/v1/refunds", { method: "POST", signal: AbortSignal.timeout(30000), headers: { "Idempotency-Key": `refund-${((__v) => __v !== null && typeof __v === "object" ? JSON.stringify(__v) : __v)(ctx.input?.payment_intent)}-${((__v) => __v !== null && typeof __v === "object" ? JSON.stringify(__v) : __v)(ctx.input?.amount_cents)}`, "Authorization": "Bearer " + String(env.CREDENTIAL_REFUND_APPROVED), "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ "payment_intent": ctx.input?.payment_intent, "amount": ctx.input?.amount_cents }).toString() });
const refund_approved_body = await refund_approved_res.text();
let refund_approved_json;
try { refund_approved_json = JSON.parse(refund_approved_body); } catch (__e) { refund_approved_json = undefined; }
if (!refund_approved_res.ok) throw new Error("HTTP " + refund_approved_res.status + ": " + refund_approved_body.slice(0, 200));
return { status: refund_approved_res.status, body: refund_approved_body, json: refund_approved_json };
});
} else {
}
} else {
// === Node: refund_now (http_request) ===
ctx.nodes["refund_now"] = await step.do("refund_now", async () => {
const refund_now_res = await fetch("https://api.stripe.com/v1/refunds", { method: "POST", signal: AbortSignal.timeout(30000), headers: { "Idempotency-Key": `refund-${((__v) => __v !== null && typeof __v === "object" ? JSON.stringify(__v) : __v)(ctx.input?.payment_intent)}-${((__v) => __v !== null && typeof __v === "object" ? JSON.stringify(__v) : __v)(ctx.input?.amount_cents)}`, "Authorization": "Bearer " + String(env.CREDENTIAL_REFUND_NOW), "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ "payment_intent": ctx.input?.payment_intent, "amount": ctx.input?.amount_cents }).toString() });
const refund_now_body = await refund_now_res.text();
let refund_now_json;
try { refund_now_json = JSON.parse(refund_now_body); } catch (__e) { refund_now_json = undefined; }
if (!refund_now_res.ok) throw new Error("HTTP " + refund_now_res.status + ": " + refund_now_body.slice(0, 200));
return { status: refund_now_res.status, body: refund_now_body, json: refund_now_json };
});
}
}
}
export default {
async fetch(request, env, executionCtx) {
if (request.method === "POST" && new URL(request.url).pathname === "/__resume/approval") {
{
const legacyProvided = request.headers.get("X-CC-Webhook-Secret") ?? "";
const expected = env.CC_RESUME_SECRET_APPROVAL ?? "";
const legacyMaxLen = Math.max(legacyProvided.length, expected.length);
let legacyMismatch = legacyProvided.length === expected.length ? 0 : 1;
for (let i = 0; i < legacyMaxLen; i++) {
legacyMismatch |= (legacyProvided.charCodeAt(i) || 0) ^ (expected.charCodeAt(i) || 0);
}
let authOk = legacyMismatch === 0 && expected !== "";
const signatureHeader = request.headers.get("X-CC-Signature");
if (!authOk && signatureHeader && expected !== "") {
const sigMatch = /^t=(\d+),v1=([0-9a-f]+)$/.exec(signatureHeader);
if (sigMatch) {
const signedTimestamp = Number(sigMatch[1]);
const ageSeconds = Math.abs(Date.now() / 1000 - signedTimestamp);
if (Number.isFinite(ageSeconds) && ageSeconds <= 300) {
const rawBody = await request.clone().text();
const hmacKey = await crypto.subtle.importKey("raw", new TextEncoder().encode(expected), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const signatureBytes = await crypto.subtle.sign("HMAC", hmacKey, new TextEncoder().encode(`${signedTimestamp}.${rawBody}`));
const computedSignature = [...new Uint8Array(signatureBytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
const providedSignature = sigMatch[2];
const sigMaxLen = Math.max(providedSignature.length, computedSignature.length);
let sigMismatch = providedSignature.length === computedSignature.length ? 0 : 1;
for (let i = 0; i < sigMaxLen; i++) {
sigMismatch |= (providedSignature.charCodeAt(i) || 0) ^ (computedSignature.charCodeAt(i) || 0);
}
authOk = sigMismatch === 0;
}
}
}
if (!authOk) {
return new Response("Unauthorized", { status: 401 });
}
}
const resumeBody = await request.json().catch(() => ({}));
const instanceId = typeof resumeBody.instanceId === "string" ? resumeBody.instanceId : "";
if (instanceId === "") {
return Response.json({ error: "instanceId is required" }, { status: 400 });
}
let instance;
try {
instance = await env.WORKFLOW.get(instanceId);
} catch {
return Response.json({ error: "Unknown workflow instance" }, { status: 404 });
}
try {
await instance.sendEvent({ type: "refund_decision", payload: resumeBody.payload });
} catch (resumeErr) {
return Response.json({ error: String((resumeErr && resumeErr.message) || resumeErr) }, { status: 409 });
}
return Response.json({ ok: true, instanceId, type: "refund_decision" });
}
{
const expected = "Bearer " + "replace-with-a-long-random-token";
const provided = request.headers.get("Authorization") ?? "";
const maxLen = Math.max(provided.length, expected.length);
let mismatch = provided.length === expected.length ? 0 : 1;
for (let i = 0; i < maxLen; i++) {
mismatch |= (provided.charCodeAt(i) || 0) ^ (expected.charCodeAt(i) || 0);
}
if (mismatch !== 0 || "replace-with-a-long-random-token" === "") {
return new Response("Unauthorized", { status: 401, headers: { "WWW-Authenticate": "Bearer" } });
}
}
const __ccTool = {"name":"refund_approval","description":"Starts a Stripe refund for a payment. Refunds over $500 first ask in Slack and wait up to 24 hours for a decision, so the call answers with the run id, not the refund. To answer, send the run a refund_decision event with {\"approved\": true} or {\"approved\": false}.","parameters":{"type":"object","properties":{"payment_intent":{"type":"string","description":"The Stripe PaymentIntent to refund (pi_…)"},"amount_cents":{"type":"integer","description":"How much to refund, in cents"},"reason":{"type":"string","description":"Why, shown to whoever approves it"}},"required":["payment_intent","amount_cents","reason"]}};
const __ccVersions = ["2025-11-25","2025-06-18","2025-03-26","2024-11-05"];
// MCP's HTTP transport uses POST for every request; GET is only for the
// server-initiated SSE stream, which a stateless server has nothing to put
// on — so it is refused rather than left to fall through to the dispatch.
if (request.method !== "POST") {
return new Response("Method Not Allowed", { status: 405, headers: { Allow: "POST" } });
}
const __ccBody = await request.json().catch(() => null);
if (__ccBody === null || typeof __ccBody !== "object") {
return __ccRpcError(null, -32700, "Parse error", 400);
}
// JSON-RPC batching was removed from MCP in revision 2025-06-18 and this
// server has never accepted it. Refused explicitly so a client that tries
// gets told, rather than having its first element silently executed.
if (Array.isArray(__ccBody)) {
return __ccRpcError(null, -32600, "Batched requests are not supported", 400);
}
const __ccId = __ccBody.id === undefined ? null : __ccBody.id;
const __ccMethod = typeof __ccBody.method === "string" ? __ccBody.method : "";
const __ccParams = __ccBody.params !== null && typeof __ccBody.params === "object" ? __ccBody.params : {};
// A notification carries no id and MUST get no response body (JSON-RPC 2.0),
// so every `notifications/*` — `initialized` above all — is acknowledged with
// a bare 202 instead of an envelope the client would have nothing to match.
if (__ccMethod.startsWith("notifications/")) {
return new Response(null, { status: 202 });
}
if (__ccMethod === "initialize") {
const __ccAsked = String(__ccParams.protocolVersion ?? "");
const __ccVersion = __ccVersions.includes(__ccAsked) ? __ccAsked : __ccVersions[0];
return __ccRpcResult(__ccId, {
protocolVersion: __ccVersion,
capabilities: { tools: { listChanged: false } },
serverInfo: { name: "Refund approval", version: "1.0.0" },
});
}
if (__ccMethod === "ping") {
return __ccRpcResult(__ccId, {});
}
if (__ccMethod === "tools/list") {
return __ccRpcResult(__ccId, {
tools: [
{
name: __ccTool.name,
description: __ccTool.description,
inputSchema: __ccTool.parameters,
},
],
});
}
if (__ccMethod === "tools/call") {
if (__ccParams.name !== __ccTool.name) {
return __ccRpcError(__ccId, -32602, "Unknown tool: " + String(__ccParams.name));
}
const __ccArgs =
__ccParams.arguments !== null && typeof __ccParams.arguments === "object"
? __ccParams.arguments
: {};
try {
const __ccInstance = await env.WORKFLOW.create({ params: __ccArgs });
const __ccValue = {
instanceId: __ccInstance.id,
status: "started",
note: "This workflow runs durably and may take longer than one request. It was started successfully; this response is an acknowledgement, not its result.",
};
return __ccRpcResult(__ccId, await __ccToolResult(__ccValue));
} catch (err) {
// A tool-EXECUTION failure is reported inside the result with
// `isError: true`, not as a JSON-RPC error — the model is meant to see
// it and decide what to do next, and a protocol-level error is invisible
// to it (MCP spec, Tools: error handling). Protocol errors above stay
// protocol errors; only the workflow's own failure lands here.
return __ccRpcResult(__ccId, {
isError: true,
content: [{ type: "text", text: "Workflow failed: " + String((err && err.message) || err) }],
});
}
}
return __ccRpcError(__ccId, -32601, "Method not found: " + __ccMethod);
},
};