/** * BytesBuys.com - Pro Worker V2.16.8 * 1. EMAILS: Optimized Confirmation Email - Removed labels (Recipient, Company, Address, Phone). * 2. EMAILS: Moved Phone Number directly under the Recipient name. * 3. EMAILS: Preserved "Instructions:" label as requested. * 4. UI: Updated version to V2.16.8 in Dispatch Tool. * 5. LOGIC: 100% Locked Metadata & Inventory & Latest GAS URL. */ const GITHUB_OWNER = "Lemon-bh"; const GITHUB_REPO = "my-store"; const GITHUB_PATH = "products.json"; const PICKING_EMAIL = "liuxin642@gmail.com"; const ORDER_SHEET_URL = "https://script.google.com/macros/s/AKfycbxeHScvU1o5biG_BooXXk2qixp92d6oEckt4fd75LNHONcmOHye9ByCxHR4Khz_Gqj8_g/exec"; const SHEET_SECRET = "BytesBuys_2026_Secure"; export default { async fetch(request, env) { const url = new URL(request.url); const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, GET, OPTIONS", "Access-Control-Allow-Headers": "Content-Type" }; if (request.method === "OPTIONS") return new Response(null, { headers: corsHeaders }); if (url.pathname === "/scan") return new Response(SCANNER_HTML, { headers: { "Content-Type": "text/html;charset=UTF-8" } }); if (url.pathname === "/api/dispatch" && request.method === "POST") { try { const { order_id, tracking } = await request.json(); const gasRes = await fetch(ORDER_SHEET_URL, { method: "POST", body: JSON.stringify({ secret: SHEET_SECRET, action: "scan_ship", order_id, tracking }) }); const gasData = await gasRes.json(); if (gasData.status === "success") { await sendShippedResend(gasData, env); return new Response(JSON.stringify({ status: "success" }), { headers: corsHeaders }); } return new Response(JSON.stringify({ status: "error", msg: gasData.msg }), { headers: corsHeaders }); } catch (e) { return new Response(JSON.stringify({ status: "error", msg: e.message }), { headers: corsHeaders }); } } if (url.pathname === "/create-session" && request.method === "POST") { try { const { items, customer } = await request.json(); const ghRes = await fetch(`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${GITHUB_PATH}?t=${Date.now()}`, { headers: { "Authorization": `token ${env.GITHUB_TOKEN}`, "User-Agent": "Cloudflare-Worker" } }); const fileData = await ghRes.json(); const products = JSON.parse(decodeURIComponent(escape(atob(fileData.content.replace(/\s/g, ""))))); const params = new URLSearchParams(); params.append("mode", "payment"); params.append("success_url", `https://bytesbuys.com/success.html?oid=${customer.order_id}&em=${encodeURIComponent(customer.email)}`); params.append("cancel_url", "https://bytesbuys.com/cancel.html"); params.append("metadata[order_id]", customer.order_id); params.append("metadata[customer_name]", customer.name); params.append("metadata[customer_phone]", customer.phone); params.append("metadata[customer_company]", customer.company || "N/A"); params.append("metadata[customer_street]", customer.street); params.append("metadata[customer_street_2]", customer.street_2 || "N/A"); params.append("metadata[customer_suburb]", customer.suburb); params.append("metadata[customer_state]", customer.state); params.append("metadata[customer_postcode]", customer.postcode); params.append("metadata[instructions]", customer.instructions || "None"); params.append("payment_intent_data[description]", `Order Reference: #${customer.order_id}`); items.forEach((item, i) => { const p = products.find(x => String(x.id) === String(item.id)); if (p) { params.append(`line_items[${i}][price_data][currency]`, "aud"); params.append(`line_items[${i}][price_data][unit_amount]`, Math.round(p.price * 100)); params.append(`line_items[${i}][price_data][product_data][name]`, p.name); params.append(`line_items[${i}][price_data][product_data][metadata][loc]`, p.location || "N/A"); params.append(`line_items[${i}][price_data][product_data][metadata][sku]`, p.sku || "N/A"); params.append(`line_items[${i}][quantity]`, item.qty); } }); params.append("shipping_options[0][shipping_rate_data][display_name]", "Standard Delivery"); params.append("shipping_options[0][shipping_rate_data][type]", "fixed_amount"); params.append("shipping_options[0][shipping_rate_data][fixed_amount][amount]", "0"); params.append("shipping_options[0][shipping_rate_data][fixed_amount][currency]", "aud"); const sessionRes = await fetch("https://api.stripe.com/v1/checkout/sessions", { method: "POST", headers: { "Authorization": `Bearer ${env.STRIPE_SECRET_KEY}`, "Content-Type": "application/x-www-form-urlencoded" }, body: params }); const session = await sessionRes.json(); return new Response(JSON.stringify({ url: session.url }), { headers: corsHeaders }); } catch (e) { return new Response(JSON.stringify({ error: e.message }), { status: 500, headers: corsHeaders }); } } if (url.pathname === "/webhook" && request.method === "POST") { try { const body = await request.text(); const event = JSON.parse(body); if (event.type === "checkout.session.completed") { const session = event.data.object; const lineItemsRes = await fetch(`https://api.stripe.com/v1/checkout/sessions/${session.id}/line_items?expand[]=data.price.product`, { headers: { "Authorization": `Bearer ${env.STRIPE_SECRET_KEY}` } }); const lineItems = await lineItemsRes.json(); await updateInventory(lineItems.data, env); await syncOrderToSheet(session, lineItems.data); await sendPickingEmail(session, lineItems.data, env); await sendCustomerConfirmEmail(session, lineItems.data, env); } return new Response("OK", { status: 200 }); } catch (e) { return new Response("Webhook Error", { status: 500 }); } } return new Response(JSON.stringify({ status: "Active" }), { status: 200 }); } }; /** 🥇 数据同步逻辑 (BB_Orders) */ async function syncOrderToSheet(session, items) { const itemSummary = items.map(i => `${i.description} [LOC:${i.price.product.metadata?.loc || "N/A"}][SKU:${i.price.product.metadata?.sku || "N/A"}] (x${i.quantity})`).join(", "); const payload = { secret: SHEET_SECRET, action: "sync_order", order_id: session.metadata?.order_id, name: session.metadata?.customer_name, email: session.customer_details?.email, phone: session.metadata?.customer_phone, company: session.metadata?.customer_company, street: session.metadata?.customer_street, street_2: session.metadata?.customer_street_2 || "N/A", suburb: session.metadata?.customer_suburb, state: session.metadata?.customer_state, postcode: session.metadata?.customer_postcode, amount: (session.amount_total / 100).toFixed(2), items: itemSummary, instructions: session.metadata?.instructions || "None" }; await fetch(ORDER_SHEET_URL, { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(payload) }); } /** 🥇 库存扣减逻辑 */ async function updateInventory(paidItems, env) { const url = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${GITHUB_PATH}`; const res = await fetch(url, { headers: { "Authorization": `token ${env.GITHUB_TOKEN}`, "User-Agent": "Cloudflare-Worker" } }); const data = await res.json(); let products = JSON.parse(decodeURIComponent(escape(atob(data.content.replace(/\s/g, ""))))); paidItems.forEach(item => { const target = products.find(p => p.name === item.description); if (target) target.stock = Math.max(0, target.stock - item.quantity); }); await fetch(url, { method: "PUT", headers: { "Authorization": `token ${env.GITHUB_TOKEN}`, "Content-Type": "application/json", "User-Agent": "Cloudflare-Worker" }, body: JSON.stringify({ message: "Stock Update", content: btoa(unescape(encodeURIComponent(JSON.stringify(products, null, 2)))), sha: data.sha }) }); } async function sendPickingEmail(session, items, env) { const tableRows = items.map(x => `
| Loc | Item | SKU | Qty |
|---|
Recipient: ${session.metadata?.customer_name}
Address: ${session.metadata?.customer_street}, ${session.metadata?.customer_suburb} ${session.metadata?.customer_postcode}
Hi ${info.name},
Great news! Your order #${info.order_id} has been dispatched and is on its way via Australia Post.
Thank you for shopping with BytesBuys.com!
Hi ${session.metadata?.customer_name}, thanks for shopping with BytesBuys.com! Order received and currently processing. We will arrange delivery as soon as possible.
| Item Description | Qty | Price |
|---|