diff --git a/README.md b/README.md index 674448f..ea93646 100644 --- a/README.md +++ b/README.md @@ -109,11 +109,14 @@ CodFlow v1.1.0 — here's what works today: - ✅ Partial returns with automatic inventory restock ### Growth Engine -- ✅ Meta Conversions API (CAPI) fires `Purchase` only at confirmed delivery -- ✅ 7-day attribution window compliance -- ✅ Advanced data hashing (phone, city, IP, User-Agent) +- ✅ Meta Pixel (browser) + Conversions API (server) dual setup with event deduplication +- ✅ Merchant-chosen conversion event: `Lead` at order placement or `Purchase` at confirmed delivery +- ✅ Test Mode toggle routes CAPI events to Meta's test stream (`test_event_code`) +- ✅ Graph API v26.0 with 7-day attribution window compliance +- ✅ PII hashed per Meta spec (phone, names, city, zip, country, external_id); IP, User-Agent, `fbp`, `fbc` sent unhashed as required - ✅ `fbp` and `fbc` attribution preservation -- ✅ Durable retry with Cloudflare Workflows +- ✅ Durable retry with Cloudflare Workflows (network + Meta 5xx, exponential backoff) +- ✅ CAPI event audit log (`capi_event_log`) for every send attempt ### AI & Agentic (MCP) - ✅ RFC 9728 OAuth Protected Resource Discovery with dynamic client registration diff --git a/cod-astro/theme01/src/core/actions/index.ts b/cod-astro/theme01/src/core/actions/index.ts index ec0f3d4..9a2cae3 100644 --- a/cod-astro/theme01/src/core/actions/index.ts +++ b/cod-astro/theme01/src/core/actions/index.ts @@ -66,7 +66,19 @@ export const server = { z.string().min(10).max(1024).optional() ), }), - handler: async (input) => { + handler: async (input, context) => { + // Forward the shopper's attribution headers so cod-server records the + // visitor, not this worker — same mechanism as core/endpoints/abandoned.ts. + const forwardedHeaders: Record = {}; + const userAgent = context.request.headers.get("User-Agent"); + if (userAgent) forwardedHeaders["User-Agent"] = userAgent; + const clientIp = + context.request.headers.get("CF-Connecting-IP") ?? + context.request.headers.get("X-Forwarded-For")?.split(",")[0]?.trim(); + if (clientIp) forwardedHeaders["X-Forwarded-For"] = clientIp; + const referer = context.request.headers.get("Referer"); + if (referer) forwardedHeaders["Referer"] = referer; + const result = await placeOrder({ customerName: input.customerName, phone: input.phone, @@ -86,7 +98,7 @@ export const server = { fbc: input.fbc, fbp: input.fbp, otpToken: input.otpToken, - }); + }, forwardedHeaders); if (!result.success) { throw new Error(result.error); diff --git a/cod-astro/theme01/src/core/api/client.ts b/cod-astro/theme01/src/core/api/client.ts index ca53eb5..c00a726 100644 --- a/cod-astro/theme01/src/core/api/client.ts +++ b/cod-astro/theme01/src/core/api/client.ts @@ -141,12 +141,13 @@ export async function submitReview( } export async function placeOrder( - body: Record + body: Record, + forwardedHeaders?: Record ): Promise<{ success: true; data: { orderNumber: string; orderId: string; total: number } } | { success: false; error: string }> { try { const res = await fetch(`${COD_SERVER_URL}/store/orders`, { method: "POST", - headers: storeHeaders(), + headers: { ...storeHeaders(), ...forwardedHeaders }, body: JSON.stringify(body), }); const json = (await res.json()) as any; diff --git a/cod-astro/theme01/src/pages/thank-you.astro b/cod-astro/theme01/src/pages/thank-you.astro index 5b58fb1..577ed63 100644 --- a/cod-astro/theme01/src/pages/thank-you.astro +++ b/cod-astro/theme01/src/pages/thank-you.astro @@ -112,7 +112,10 @@ const steps = [