跳转到内容
搜索文档

支付购物车并发送发票

购物车结账并付款后发送发票

最后更新 查看 MarkdownAgent 设置

在本示例中,我们为电商网站实现 Workflow,每次创建购物车时触发。

Workflow 实例触发后,开始轮询 D1 数据库中的购物车 ID,直到结账完成。购物车结账后,我们通过 fetch POST 与外部提供商处理付款。最后,假设一切顺利,我们尝试使用 Email Workers 向客户发送带有发票的邮件。

如你所见,Workflows 处理各种服务响应和故障;它会重试 D1 直到购物车结账,在支付处理器失败时重试,在无法发送发票邮件时重试。开发者无需关心任何此类逻辑,workflow 可以运行数小时,处理所有可能的情况直到完成。

这是处理购物车的简化示例。在真实场景中,我们假设会有更多步骤和额外逻辑,但此示例让你了解 Workflows 能做什么。

import {
	WorkflowEntrypoint,
	WorkflowStep,
	WorkflowEvent,
} from "cloudflare:workers";
import { EmailMessage } from "cloudflare:email";
import { createMimeMessage } from "mimetext";

// We are using Email Routing to send emails out and D1 for our cart database
type Env = {
	CART_WORKFLOW: Workflow;
	SEND_EMAIL: any;
	DB: any;
};

// Workflow parameters: we expect a cartId
type Params = {
	cartId: string;
};

// Adjust this to your Cloudflare zone using Email Routing
const merchantEmail = "merchant@example.com";

// Uses mimetext npm to generate Email
const genEmail = (email: string, amount: number) => {
	const msg = createMimeMessage();
	msg.setSender({ name: "Pet shop", addr: merchantEmail });
	msg.setRecipient(email);
	msg.setSubject("You invoice");
	msg.addMessage({
		contentType: "text/plain",
		data: `Your invoice for ${amount} has been paid. Your products will be shipped shortly.`,
	});

	return new EmailMessage(merchantEmail, email, msg.asRaw());
};

// Workflow logic
export class cartInvoicesWorkflow extends WorkflowEntrypoint<Env, Params> {
	async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
		await step.sleep("sleep for a while", "10 seconds");

		// Retrieve the cart from the D1 database
		// if the cart hasn't been checked out yet retry every 2 minutes, 10 times, otherwise give up
		const cart = await step.do(
			"retrieve cart",
			{
				retries: {
					limit: 10,
					delay: 2000 * 60,
					backoff: "constant",
				},
				timeout: "30 seconds",
			},
			async () => {
				const { results } = await this.env.DB.prepare(
					`SELECT * FROM cart WHERE id = ?`,
				)
					.bind(event.payload.cartId)
					.run();
				// should return { checkedOut: true, amount: 250 , account: { email: "celsomartinho@gmail.com" }};
				if (results[0].checkedOut === false) {
					throw new Error("cart hasn't been checked out yet");
				}
				return results[0];
			},
		);

		// Proceed to payment, retry 10 times every minute or give up
		const payment = await step.do(
			"payment",
			{
				retries: {
					limit: 10,
					delay: 1000 * 60,
					backoff: "constant",
				},
				timeout: "30 seconds",
			},
			async () => {
				let resp = await fetch("https://payment-processor.example.com/", {
					method: "POST",
					headers: {
						"Content-Type": "application/json; charset=utf-8",
					},
					body: JSON.stringify({ amount: cart.amount }),
				});

				if (!resp.ok) {
					throw new Error("payment has failed");
				}

				return { success: true, amount: cart.amount };
			},
		);

		// Send invoice to the customer, retry 10 times every 5 minutes or give up
		// Requires that cart.account.email has previously been validated in Email Routing,
		// See https://developers.cloudflare.com/email-service/api/route-emails/email-handler/
		await step.do(
			"send invoice",
			{
				retries: {
					limit: 10,
					delay: 5000 * 60,
					backoff: "constant",
				},
				timeout: "30 seconds",
			},
			async () => {
				const message = genEmail(cart.account.email, payment.amount);
				try {
					await this.env.SEND_EMAIL.send(message);
				} catch (e) {
					throw new Error("failed to send invoice");
				}
			},
		);
	}
}

// Default page for admin
// Remove in production

export default {
	async fetch(req: Request, env: Env): Promise<Response> {
		let url = new URL(req.url);

		let id = new URL(req.url).searchParams.get("instanceId");

		// Get the status of an existing instance, if provided
		if (id) {
			let instance = await env.CART_WORKFLOW.get(id);
			return Response.json({
				status: await instance.status(),
			});
		}

		if (url.pathname.startsWith("/new")) {
			let instance = await env.CART_WORKFLOW.create({
				params: {
					cartId: "123",
				},
			});
			return Response.json({
				id: instance.id,
				details: await instance.status(),
			});
		}

		return new Response(
			`<html><body><a href="/new">new instance</a> or add ?instanceId=...</body></html>`,
			{
				headers: {
					"content-type": "text/html;charset=UTF-8",
				},
			},
		);
	},
};

以下是最小 package.json:

{
	"devDependencies": {
		"wrangler": "^3.83.0"
	},
	"dependencies": {
		"mimetext": "^3.0.24"
	}
}

最后是 Wrangler 配置文件

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "cart-invoices",
	"main": "src/index.ts",
	// Set this to today's date
	"compatibility_date": "2026-08-17",
	"compatibility_flags": [
		"nodejs_compat"
	],
	"workflows": [
		{
			"name": "cart-invoices-workflow",
			"binding": "CART_WORKFLOW",
			"class_name": "cartInvoicesWorkflow"
		}
	],
	"send_email": [
		{
			"name": "SEND_EMAIL"
		}
	]
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "cart-invoices"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-17"
compatibility_flags = [ "nodejs_compat" ]

[[workflows]]
name = "cart-invoices-workflow"
binding = "CART_WORKFLOW"
class_name = "cartInvoicesWorkflow"

[[send_email]]
name = "SEND_EMAIL"

如果你使用 TypeScript,每当你修改 Wrangler 配置文件时,请运行 wrangler types。这将根据你的绑定生成 env 对象的类型,以及运行时类型

这篇文档对您有帮助吗?