@clize/clize 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli.js +201 -18
- package/dist/cli.js.map +1 -1
- package/dist/core/domain-health.js +224 -0
- package/dist/core/domain-health.js.map +1 -0
- package/dist/core/domains.js +5 -2
- package/dist/core/domains.js.map +1 -1
- package/dist/core/email-tx.js +142 -0
- package/dist/core/email-tx.js.map +1 -0
- package/dist/core/email.js +4 -2
- package/dist/core/email.js.map +1 -1
- package/dist/core/shop.js +383 -0
- package/dist/core/shop.js.map +1 -0
- package/dist/core/sites.js +89 -2
- package/dist/core/sites.js.map +1 -1
- package/dist/index.js +40 -2
- package/dist/index.js.map +1 -1
- package/dist/providers/email/cloudflare-outbound.js +13 -2
- package/dist/providers/email/cloudflare-outbound.js.map +1 -1
- package/dist/providers/email/resend.js +1 -0
- package/dist/providers/email/resend.js.map +1 -1
- package/dist/providers/email/ses.js +5 -0
- package/dist/providers/email/ses.js.map +1 -1
- package/dist/remote.js +79 -2
- package/dist/remote.js.map +1 -1
- package/dist/state/file-store.js +34 -0
- package/dist/state/file-store.js.map +1 -1
- package/package.json +1 -1
- package/skills/clize/SKILL.md +3 -2
- package/skills/clize-site-build/SKILL.md +1 -1
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
// ── 状态机(六态 + created 预付态)─────────────────────────────
|
|
2
|
+
// created → paid(仅 webhook)→ sourced → shipped → delivered;任意付费态 → refund_pending → refunded。
|
|
3
|
+
export const ORDER_TRANSITIONS = {
|
|
4
|
+
created: ["paid"],
|
|
5
|
+
paid: ["sourced", "shipped", "refund_pending", "refunded"],
|
|
6
|
+
sourced: ["shipped", "refund_pending", "refunded"],
|
|
7
|
+
shipped: ["delivered", "refund_pending", "refunded"],
|
|
8
|
+
delivered: ["refund_pending", "refunded"],
|
|
9
|
+
refund_pending: ["refunded"],
|
|
10
|
+
refunded: [],
|
|
11
|
+
};
|
|
12
|
+
/** 状态可否从 from 转到 to(纯函数,便于测)。 */
|
|
13
|
+
export function canTransition(from, to) {
|
|
14
|
+
return ORDER_TRANSITIONS[from]?.includes(to) ?? false;
|
|
15
|
+
}
|
|
16
|
+
/** 校验状态转移;非法抛错(带可读原因)。 */
|
|
17
|
+
export function assertTransition(from, to) {
|
|
18
|
+
if (from === to)
|
|
19
|
+
return; // 幂等重入(如重复置 shipped)由调用方另判,这里不拦同态
|
|
20
|
+
if (!canTransition(from, to))
|
|
21
|
+
throw new Error(`订单状态不能从 ${from} → ${to}(允许:${(ORDER_TRANSITIONS[from] || []).join(" / ") || "无"})`);
|
|
22
|
+
}
|
|
23
|
+
// ── 行项:按目录核价,保留 sku(结账建单用)──────────────────
|
|
24
|
+
/** 按 _catalog.json 核购物车,返回带 sku 的可信行项(单价一律取目录,客户端传的价忽略)。
|
|
25
|
+
* 抛错 = 该单不该成(未知 sku / 价非正)。纯函数。 */
|
|
26
|
+
export function buildOrderLineItems(catalog, wanted) {
|
|
27
|
+
const bySku = new Map(catalog.products.map((p) => [p.sku, p]));
|
|
28
|
+
return wanted.map((it) => {
|
|
29
|
+
const p = it.sku ? bySku.get(it.sku) : undefined;
|
|
30
|
+
if (!p)
|
|
31
|
+
throw new Error(`无此商品:${it.sku ?? "(空)"}`);
|
|
32
|
+
if (!(Number(p.price) > 0))
|
|
33
|
+
throw new Error(`商品 ${p.sku} 价格无效`);
|
|
34
|
+
return {
|
|
35
|
+
sku: p.sku,
|
|
36
|
+
name: p.name,
|
|
37
|
+
qty: Math.max(1, Math.min(1000, Math.floor(Number(it.quantity) || 1))),
|
|
38
|
+
unitPrice: Number(p.price),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** 行项合计(美元,两位精度)。 */
|
|
43
|
+
export function orderTotalUsd(lineItems) {
|
|
44
|
+
return Math.round(lineItems.reduce((n, li) => n + li.unitPrice * li.qty, 0) * 100) / 100;
|
|
45
|
+
}
|
|
46
|
+
// ── todo / 物流归类(纯函数)──────────────────────────────────
|
|
47
|
+
/** 某订单进入 shipped 的时刻(取最后一条 to=shipped 事件;兜底 updatedAt/createdAt)。 */
|
|
48
|
+
export function shippedSince(order) {
|
|
49
|
+
const ev = [...(order.events || [])].reverse().find((e) => e.to === "shipped");
|
|
50
|
+
return ev?.at || order.updatedAt || order.createdAt;
|
|
51
|
+
}
|
|
52
|
+
function ageDays(iso, nowMs) {
|
|
53
|
+
const t = Date.parse(iso);
|
|
54
|
+
return Number.isFinite(t) ? (nowMs - t) / 86400_000 : 0;
|
|
55
|
+
}
|
|
56
|
+
/** 今日操作清单(纯归类,不做决策):待采购 / 待发信 / 在途超期。 */
|
|
57
|
+
export function categorizeTodo(orders, nowMs, staleDays = 5) {
|
|
58
|
+
const toSource = orders.filter((o) => o.status === "paid");
|
|
59
|
+
const toNotify = orders.filter((o) => o.status === "shipped" && !o.emailedAt);
|
|
60
|
+
const toFollowUp = orders.filter((o) => o.status === "shipped" && ageDays(shippedSince(o), nowMs) >= staleDays);
|
|
61
|
+
return { toSource, toNotify, toFollowUp };
|
|
62
|
+
}
|
|
63
|
+
/** 在途单(shipped)清单;stale=true 时只留轨迹停更(此处以 shipped 时长近似)≥staleDays 的报警项。 */
|
|
64
|
+
export function selectShipments(orders, nowMs, opts) {
|
|
65
|
+
const staleDays = opts?.staleDays ?? 5;
|
|
66
|
+
const inTransit = orders.filter((o) => o.status === "shipped");
|
|
67
|
+
if (!opts?.staleOnly)
|
|
68
|
+
return inTransit;
|
|
69
|
+
return inTransit.filter((o) => ageDays(shippedSince(o), nowMs) >= staleDays);
|
|
70
|
+
}
|
|
71
|
+
/** 17TRACK 公共门到门查询页(无需账户)。 */
|
|
72
|
+
export function trackingUrl(trackingNo) {
|
|
73
|
+
return `https://t.17track.net/en#nums=${encodeURIComponent(trackingNo)}`;
|
|
74
|
+
}
|
|
75
|
+
/** 店面自助查单页(3.7 提供;买家凭邮箱+订单号查)。带斜杠为规范式:
|
|
76
|
+
* serving worker 对 `/orders/` 走 index 归一 → 命中商家自建 /orders/index.html(有则优先)否则白送内置页。 */
|
|
77
|
+
export function trackPageUrl(shop) {
|
|
78
|
+
return `https://${shop.toLowerCase()}/orders/`;
|
|
79
|
+
}
|
|
80
|
+
function applyTemplate(tpl, vars) {
|
|
81
|
+
return tpl.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? "");
|
|
82
|
+
}
|
|
83
|
+
/** 生成发货通知邮件(草稿)。默认模板即可用;有 template 覆盖则套占位符。纯函数。 */
|
|
84
|
+
export function buildShipmentEmail(order, opts) {
|
|
85
|
+
const shopName = opts.shopName || opts.shop;
|
|
86
|
+
const tracking = order.trackingNo || "";
|
|
87
|
+
const trackUrl = tracking ? trackingUrl(tracking) : "";
|
|
88
|
+
const trackPage = trackPageUrl(opts.shop);
|
|
89
|
+
const vars = {
|
|
90
|
+
tracking,
|
|
91
|
+
trackUrl,
|
|
92
|
+
orderId: order.id,
|
|
93
|
+
shop: shopName,
|
|
94
|
+
trackPage,
|
|
95
|
+
};
|
|
96
|
+
if (opts.template?.subject || opts.template?.body) {
|
|
97
|
+
return {
|
|
98
|
+
subject: applyTemplate(opts.template.subject || `Your ${shopName} order has shipped`, vars),
|
|
99
|
+
text: applyTemplate(opts.template.body ||
|
|
100
|
+
`Your order is on its way.\nTracking: {{tracking}}\nTrack it: {{trackUrl}}`, vars),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const carrier = order.carrier ? ` via ${order.carrier}` : "";
|
|
104
|
+
const lines = [
|
|
105
|
+
`Hi,`,
|
|
106
|
+
``,
|
|
107
|
+
`Good news — your ${shopName} order has shipped${carrier}. 📦`,
|
|
108
|
+
``,
|
|
109
|
+
tracking ? `Tracking number: ${tracking}` : `It's on the way.`,
|
|
110
|
+
tracking ? `Track it door-to-door: ${trackUrl}` : ``,
|
|
111
|
+
``,
|
|
112
|
+
`You can also check your order status anytime here: ${trackPage}`,
|
|
113
|
+
`(enter your email + order number)`,
|
|
114
|
+
``,
|
|
115
|
+
`Questions? Just reply to this email — a real person reads every message.`,
|
|
116
|
+
``,
|
|
117
|
+
`— ${shopName}`,
|
|
118
|
+
].filter((l) => l !== "");
|
|
119
|
+
return {
|
|
120
|
+
subject: `Your ${shopName} order has shipped 📦`,
|
|
121
|
+
text: lines.join("\n"),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// ── 视图整形 ────────────────────────────────────────────────
|
|
125
|
+
/** 商家侧订单视图(自己看:含全部字段,merchantMeta 是商家自己的数据)。 */
|
|
126
|
+
export function merchantView(o) {
|
|
127
|
+
return {
|
|
128
|
+
id: o.id,
|
|
129
|
+
shop: o.shop,
|
|
130
|
+
status: o.status,
|
|
131
|
+
buyerEmail: o.buyerEmail,
|
|
132
|
+
amountUsd: o.amountUsd,
|
|
133
|
+
currency: o.currency,
|
|
134
|
+
lineItems: o.lineItems,
|
|
135
|
+
shippingAddress: o.shippingAddress,
|
|
136
|
+
mode: o.mode,
|
|
137
|
+
trackingNo: o.trackingNo,
|
|
138
|
+
carrier: o.carrier,
|
|
139
|
+
supplierOrderId: o.supplierOrderId,
|
|
140
|
+
refundedUsd: o.refundedUsd,
|
|
141
|
+
merchantMeta: o.merchantMeta,
|
|
142
|
+
emailedAt: o.emailedAt,
|
|
143
|
+
createdAt: o.createdAt,
|
|
144
|
+
updatedAt: o.updatedAt,
|
|
145
|
+
events: o.events,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** 买家自助查单视图(公开:只回状态 + 物流,绝不外泄成本/供应商/其他 PII)。 */
|
|
149
|
+
export function buyerView(o) {
|
|
150
|
+
return {
|
|
151
|
+
orderId: o.id,
|
|
152
|
+
status: o.status,
|
|
153
|
+
placedAt: o.createdAt,
|
|
154
|
+
amountUsd: o.amountUsd,
|
|
155
|
+
currency: o.currency,
|
|
156
|
+
items: o.lineItems.map((li) => ({ sku: li.sku, name: li.name, qty: li.qty })),
|
|
157
|
+
trackingNo: o.trackingNo || null,
|
|
158
|
+
carrier: o.carrier || null,
|
|
159
|
+
trackUrl: o.trackingNo ? trackingUrl(o.trackingNo) : null,
|
|
160
|
+
refundedUsd: o.refundedUsd ?? null,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// ── ctx 层:履约动作(状态机 + store 写;外呼由 worker 接力)──────
|
|
164
|
+
/** 列订单(all=跨全店;否则按 shop 过滤;默认排除 created 弃单)。 */
|
|
165
|
+
export async function listOrders(ctx, opts) {
|
|
166
|
+
const shop = opts.all ? undefined : opts.shop?.toLowerCase();
|
|
167
|
+
const rows = await ctx.store.listOrders(ctx.tenantId, {
|
|
168
|
+
shop,
|
|
169
|
+
statuses: opts.status ? [opts.status] : undefined,
|
|
170
|
+
excludeCreated: !opts.status,
|
|
171
|
+
limit: opts.limit,
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
orders: rows.map(merchantView),
|
|
175
|
+
scope: shop || "all-shops",
|
|
176
|
+
note: !shop && !opts.all ? "跨全部店面(未指定 --domain);--domain 收窄到单店" : undefined,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export async function getOrder(ctx, id) {
|
|
180
|
+
const o = await ctx.store.getOrder(ctx.tenantId, id);
|
|
181
|
+
if (!o)
|
|
182
|
+
throw new Error(`订单 ${id} 不存在或不属于你`);
|
|
183
|
+
return merchantView(o);
|
|
184
|
+
}
|
|
185
|
+
/** 今日操作清单(状态查询的语法糖,不做决策)。 */
|
|
186
|
+
export async function todo(ctx, opts) {
|
|
187
|
+
const shop = opts.all ? undefined : opts.shop?.toLowerCase();
|
|
188
|
+
const rows = await ctx.store.listOrders(ctx.tenantId, { shop, excludeCreated: true, limit: 500 });
|
|
189
|
+
const { toSource, toNotify, toFollowUp } = categorizeTodo(rows, opts.nowMs, opts.staleDays);
|
|
190
|
+
const brief = (o) => ({
|
|
191
|
+
id: o.id,
|
|
192
|
+
shop: o.shop,
|
|
193
|
+
buyerEmail: o.buyerEmail,
|
|
194
|
+
amountUsd: o.amountUsd,
|
|
195
|
+
trackingNo: o.trackingNo,
|
|
196
|
+
ageDays: Math.floor(ageDays(o.status === "shipped" ? shippedSince(o) : o.createdAt, opts.nowMs)),
|
|
197
|
+
});
|
|
198
|
+
return {
|
|
199
|
+
scope: shop || "all-shops",
|
|
200
|
+
toSource: { count: toSource.length, orders: toSource.map(brief) },
|
|
201
|
+
toNotify: { count: toNotify.length, orders: toNotify.map(brief) },
|
|
202
|
+
toFollowUp: { count: toFollowUp.length, orders: toFollowUp.map(brief) },
|
|
203
|
+
hint: "toSource=新 paid 待采购 · toNotify=有 tracking 未发信(clize shop notify <id> --confirm)· toFollowUp=在途超期待安抚。向谁采购/退不退仍是你的判断。",
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/** 在途单清单(shipments;stale=只列超期报警项)。 */
|
|
207
|
+
export async function shipments(ctx, opts) {
|
|
208
|
+
const shop = opts.all ? undefined : opts.shop?.toLowerCase();
|
|
209
|
+
const rows = await ctx.store.listOrders(ctx.tenantId, {
|
|
210
|
+
shop,
|
|
211
|
+
statuses: ["shipped"],
|
|
212
|
+
limit: 500,
|
|
213
|
+
});
|
|
214
|
+
const sel = selectShipments(rows, opts.nowMs, { staleOnly: opts.staleOnly, staleDays: opts.staleDays });
|
|
215
|
+
return {
|
|
216
|
+
scope: shop || "all-shops",
|
|
217
|
+
staleOnly: !!opts.staleOnly,
|
|
218
|
+
staleDays: opts.staleDays ?? 5,
|
|
219
|
+
count: sel.length,
|
|
220
|
+
shipments: sel.map((o) => ({
|
|
221
|
+
id: o.id,
|
|
222
|
+
shop: o.shop,
|
|
223
|
+
buyerEmail: o.buyerEmail,
|
|
224
|
+
trackingNo: o.trackingNo,
|
|
225
|
+
carrier: o.carrier,
|
|
226
|
+
trackUrl: o.trackingNo ? trackingUrl(o.trackingNo) : null,
|
|
227
|
+
shippedDaysAgo: Math.floor(ageDays(shippedSince(o), opts.nowMs)),
|
|
228
|
+
})),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** 履约回写:据 intent 计算目标状态、校验转移、落库(+事件)。外呼(17track/发信)由 worker 接力。 */
|
|
232
|
+
export async function fulfillOrder(ctx, id, intent) {
|
|
233
|
+
const o = await ctx.store.getOrder(ctx.tenantId, id);
|
|
234
|
+
if (!o)
|
|
235
|
+
throw new Error(`订单 ${id} 不存在或不属于你`);
|
|
236
|
+
// 目标状态:delivered > tracking(→shipped)> supplier(→sourced);够不到则只更字段。
|
|
237
|
+
let target;
|
|
238
|
+
if (intent.delivered)
|
|
239
|
+
target = "delivered";
|
|
240
|
+
else if (intent.trackingNo)
|
|
241
|
+
target = o.status === "paid" || o.status === "sourced" ? "shipped" : undefined;
|
|
242
|
+
else if (intent.supplierOrderId)
|
|
243
|
+
target = o.status === "paid" ? "sourced" : undefined;
|
|
244
|
+
const patch = {};
|
|
245
|
+
if (intent.supplierOrderId !== undefined)
|
|
246
|
+
patch.supplierOrderId = intent.supplierOrderId;
|
|
247
|
+
if (intent.trackingNo !== undefined)
|
|
248
|
+
patch.trackingNo = intent.trackingNo;
|
|
249
|
+
if (intent.carrier !== undefined)
|
|
250
|
+
patch.carrier = intent.carrier;
|
|
251
|
+
const hasFieldUpdate = Object.keys(patch).length > 0;
|
|
252
|
+
if (!target && !hasFieldUpdate)
|
|
253
|
+
throw new Error("无事可做:指定 --supplier-order / --tracking [--carrier] / --delivered 之一。");
|
|
254
|
+
let transitionedTo;
|
|
255
|
+
let event;
|
|
256
|
+
if (target && target !== o.status) {
|
|
257
|
+
assertTransition(o.status, target); // 非法转移(如 delivered 想回 shipped)在此抛错
|
|
258
|
+
patch.status = target;
|
|
259
|
+
transitionedTo = target;
|
|
260
|
+
const noteParts = [
|
|
261
|
+
intent.supplierOrderId ? `supplier=${intent.supplierOrderId}` : "",
|
|
262
|
+
intent.trackingNo ? `tracking=${intent.trackingNo}` : "",
|
|
263
|
+
].filter(Boolean);
|
|
264
|
+
event = {
|
|
265
|
+
at: new Date().toISOString(),
|
|
266
|
+
from: o.status,
|
|
267
|
+
to: target,
|
|
268
|
+
by: "merchant",
|
|
269
|
+
note: noteParts.join(" ") || undefined,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const order = await ctx.store.updateOrder(ctx.tenantId, id, patch, event);
|
|
273
|
+
return {
|
|
274
|
+
order,
|
|
275
|
+
transitionedTo,
|
|
276
|
+
registerTracking: transitionedTo === "shipped" && !!order.trackingNo,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/** 记发货通知已发出(notify --confirm 成功后回写 emailed_at + 事件)。 */
|
|
280
|
+
export async function markNotified(ctx, id, to) {
|
|
281
|
+
const o = await ctx.store.getOrder(ctx.tenantId, id);
|
|
282
|
+
if (!o)
|
|
283
|
+
throw new Error(`订单 ${id} 不存在或不属于你`);
|
|
284
|
+
return ctx.store.updateOrder(ctx.tenantId, id, { emailedAt: new Date().toISOString() }, { at: new Date().toISOString(), from: o.status, to: o.status, by: "merchant", note: `notified ${to}` });
|
|
285
|
+
}
|
|
286
|
+
/** 退款前置校验 + 报价(纯读;不动 Stripe/库)。抛错 = 不可退。 */
|
|
287
|
+
export function refundQuote(order, requestedUsd) {
|
|
288
|
+
if (order.status === "created")
|
|
289
|
+
throw new Error("该单尚未付款,无可退。");
|
|
290
|
+
if (order.status === "refunded")
|
|
291
|
+
throw new Error("该单已全额退款。");
|
|
292
|
+
const refundedSoFar = order.refundedUsd ?? 0;
|
|
293
|
+
const remaining = Math.round((order.amountUsd - refundedSoFar) * 100) / 100;
|
|
294
|
+
if (!(remaining > 0))
|
|
295
|
+
throw new Error("无可退余额(已退完)。");
|
|
296
|
+
const amount = requestedUsd != null ? Math.round(requestedUsd * 100) / 100 : remaining;
|
|
297
|
+
if (!(amount > 0))
|
|
298
|
+
throw new Error("退款金额需为正。");
|
|
299
|
+
if (amount > remaining)
|
|
300
|
+
throw new Error(`退款额 $${amount} 超过可退余额 $${remaining}。`);
|
|
301
|
+
if (!order.stripePaymentIntent)
|
|
302
|
+
throw new Error("该单缺 payment_intent(可能是旧单未捕获),无法自动退款;请到 Stripe 后台手动退。");
|
|
303
|
+
return {
|
|
304
|
+
orderId: order.id,
|
|
305
|
+
amountUsd: amount,
|
|
306
|
+
remainingUsd: remaining,
|
|
307
|
+
mode: order.mode,
|
|
308
|
+
paymentIntent: order.stripePaymentIntent,
|
|
309
|
+
connectAccountId: order.mode === "direct" ? order.connectAccountId : undefined,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/** 退款原因推荐码(选品反馈回路的数据源;非强制,允许自由文本)。 */
|
|
313
|
+
export const REFUND_REASON_CODES = [
|
|
314
|
+
"out_of_stock",
|
|
315
|
+
"damaged",
|
|
316
|
+
"not_as_described",
|
|
317
|
+
"buyer_remorse",
|
|
318
|
+
"fraud_suspect",
|
|
319
|
+
"other",
|
|
320
|
+
];
|
|
321
|
+
/** 归一退款原因:留原文,若首词命中推荐码则附标准化 code(纯函数)。 */
|
|
322
|
+
export function normalizeRefundReason(raw) {
|
|
323
|
+
const reason = (raw ?? "").trim() || undefined;
|
|
324
|
+
if (!reason)
|
|
325
|
+
return {};
|
|
326
|
+
const head = reason.toLowerCase().split(/[\s::]/)[0];
|
|
327
|
+
const code = REFUND_REASON_CODES.includes(head) ? head : undefined;
|
|
328
|
+
return { reason, code };
|
|
329
|
+
}
|
|
330
|
+
/** 退款执行后落库:refundedUsd 累计 + 全额则 status→refunded + balance 模式据实下扣余额 + audit。
|
|
331
|
+
* Stripe 退款调用由 worker 先完成,把 stripeRefundId 传进来;reason 落 events 与审计(选品反馈源)。 */
|
|
332
|
+
export async function applyRefund(ctx, id, refundedAmountUsd, meta) {
|
|
333
|
+
const o = await ctx.store.getOrder(ctx.tenantId, id);
|
|
334
|
+
if (!o)
|
|
335
|
+
throw new Error(`订单 ${id} 不存在或不属于你`);
|
|
336
|
+
const newRefunded = Math.round(((o.refundedUsd ?? 0) + refundedAmountUsd) * 100) / 100;
|
|
337
|
+
const full = newRefunded >= o.amountUsd - 0.005;
|
|
338
|
+
const targetStatus = full ? "refunded" : o.status;
|
|
339
|
+
if (full && o.status !== "refunded")
|
|
340
|
+
assertTransition(o.status, "refunded");
|
|
341
|
+
const { reason, code } = normalizeRefundReason(meta.reason);
|
|
342
|
+
const event = {
|
|
343
|
+
at: new Date().toISOString(),
|
|
344
|
+
from: o.status,
|
|
345
|
+
to: targetStatus,
|
|
346
|
+
by: "merchant",
|
|
347
|
+
note: `refund $${refundedAmountUsd.toFixed(2)}${meta.stripeRefundId ? ` ${meta.stripeRefundId}` : ""}${full ? " (full)" : " (partial)"}`,
|
|
348
|
+
reason,
|
|
349
|
+
};
|
|
350
|
+
const updated = await ctx.store.updateOrder(ctx.tenantId, id, { refundedUsd: newRefunded, status: targetStatus }, event);
|
|
351
|
+
// balance 模式:钱当初进了商家的 clize 余额,退款须据实下扣(可为负 = 商家欠 clize)。
|
|
352
|
+
if (o.mode === "balance")
|
|
353
|
+
await ctx.store.chargeBalanceAllowNegative(ctx.tenantId, refundedAmountUsd, {
|
|
354
|
+
action: "shop_refund",
|
|
355
|
+
detail: { orderId: id, stripeRefundId: meta.stripeRefundId, mode: "balance" },
|
|
356
|
+
});
|
|
357
|
+
// 资金动作一律审计(含退款原因 + 归一 code)。
|
|
358
|
+
await ctx.store.appendAudit(ctx.tenantId, {
|
|
359
|
+
action: "shop_refund",
|
|
360
|
+
detail: {
|
|
361
|
+
orderId: id,
|
|
362
|
+
shop: o.shop,
|
|
363
|
+
amountUsd: refundedAmountUsd,
|
|
364
|
+
mode: o.mode,
|
|
365
|
+
stripeRefundId: meta.stripeRefundId,
|
|
366
|
+
reason,
|
|
367
|
+
reasonCode: code,
|
|
368
|
+
full,
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
return updated;
|
|
372
|
+
}
|
|
373
|
+
/** 对外事件流回放(`shop events`;端点宕机可补拉)。all=跨全店;since=只看某时刻后。 */
|
|
374
|
+
export async function listEvents(ctx, opts) {
|
|
375
|
+
const shop = opts.all ? undefined : opts.shop?.toLowerCase();
|
|
376
|
+
const events = await ctx.store.listShopEvents(ctx.tenantId, {
|
|
377
|
+
shop,
|
|
378
|
+
sinceIso: opts.sinceIso,
|
|
379
|
+
limit: opts.limit,
|
|
380
|
+
});
|
|
381
|
+
return { scope: shop || "all-shops", count: events.length, events };
|
|
382
|
+
}
|
|
383
|
+
//# sourceMappingURL=shop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shop.js","sourceRoot":"","sources":["../../src/core/shop.ts"],"names":[],"mappings":"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"}
|
package/dist/core/sites.js
CHANGED
|
@@ -6,6 +6,7 @@ import fs from "node:fs";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { resolveDeployProject } from "./projects.js";
|
|
8
8
|
import { nameserversOnVercel } from "../lib/zone.js";
|
|
9
|
+
import { isPlatformHost, probeDelegation, probeHealthy, } from "./domain-health.js";
|
|
9
10
|
const SITE_WORKER = "clize-handle-site";
|
|
10
11
|
const SITE_KV_TITLE = "clize-sites";
|
|
11
12
|
const SITE_R2_BUCKET = "clize-site-assets"; // 大资产(≥128KB/媒体/>25MiB)存这;KV 只留热小文件
|
|
@@ -42,6 +43,8 @@ export default {
|
|
|
42
43
|
if (p === "/_clize/cart.js") return new Response(${JSON.stringify(SHOP_CART_JS)}, { headers: { "content-type": "text/javascript; charset=utf-8", "cache-control": "public, max-age=300" } });
|
|
43
44
|
if (p === "/_clize/checkout" && request.method === "POST") return fetch("https://api.clize.ai/v1/shop/checkout", { method: "POST", headers: { "content-type": "application/json", "x-clize-site": host }, body: await request.text() });
|
|
44
45
|
if (p.indexOf("/_clize/data/") === 0 && request.method === "POST") return fetch("https://api.clize.ai/v1/data/submit?collection=" + encodeURIComponent(p.slice(13)), { method: "POST", headers: { "content-type": "application/json", "x-clize-site": host }, body: await request.text() });
|
|
46
|
+
// 买家自助查单:同源转控制面(双因子 邮箱+订单号 在控制面校验,防枚举)。
|
|
47
|
+
if (p === "/_clize/track" && request.method === "POST") return fetch("https://api.clize.ai/v1/shop/track", { method: "POST", headers: { "content-type": "application/json", "x-clize-site": host }, body: await request.text() });
|
|
45
48
|
if (p.endsWith("/")) p += "index.html";
|
|
46
49
|
const hit = await env.SITES.getWithMetadata(host + p, { type: "arrayBuffer" });
|
|
47
50
|
if (hit && hit.value) {
|
|
@@ -83,6 +86,11 @@ export default {
|
|
|
83
86
|
if (!idx || !idx.value) {
|
|
84
87
|
return new Response(placeholder(host), { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
85
88
|
}
|
|
89
|
+
// 已部署但没自建 /orders → 白送买家自助查单页(商家自建 /orders/index.html 走上面 KV 命中、优先)。
|
|
90
|
+
// 同时认带斜杠(/orders/ → 上面已 += index.html)与裸 /orders(未过 index 归一)两种路径。
|
|
91
|
+
if (p === "/orders/index.html" || p === "/orders") {
|
|
92
|
+
return inject(new Response(ordersPage(host), { headers: { "content-type": "text/html; charset=utf-8" } }));
|
|
93
|
+
}
|
|
86
94
|
// 已部署:未知路径处理。显式配置 spa/404-page/none 优先;否则按有无 404.html 自动判定。
|
|
87
95
|
const cfg = await env.SITES.get("::cfg::" + host, { type: "json" });
|
|
88
96
|
const set = cfg && cfg.notFound;
|
|
@@ -102,6 +110,26 @@ export default {
|
|
|
102
110
|
return inject(new Response(notFound(host, p), { status: 404, headers: { "content-type": "text/html; charset=utf-8" } }));
|
|
103
111
|
}
|
|
104
112
|
};
|
|
113
|
+
function ordersPage(host) {
|
|
114
|
+
return '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex"><title>Track your order · '+host+'</title>'+
|
|
115
|
+
'<style>body{margin:0;min-height:100vh;display:grid;place-items:center;background:#050506;color:#EDEDEF;font-family:-apple-system,system-ui,sans-serif;padding:24px}'+
|
|
116
|
+
'.b{width:100%;max-width:420px}.h{font-size:13px;color:#8A8F98;font-family:ui-monospace,monospace}.t{margin:8px 0 20px;font-size:22px;font-weight:600}'+
|
|
117
|
+
'label{display:block;font-size:12px;color:#8A8F98;margin:12px 0 6px}input{width:100%;box-sizing:border-box;padding:11px 12px;border-radius:9px;border:1px solid #22242C;background:#0C0D11;color:#EDEDEF;font-size:14px}'+
|
|
118
|
+
'button{margin-top:18px;width:100%;padding:12px;border-radius:9px;border:0;background:#EDEDEF;color:#050506;font-size:14px;font-weight:600;cursor:pointer}button:disabled{opacity:.5}'+
|
|
119
|
+
'.r{margin-top:20px;font-size:14px;line-height:1.6}.r .k{color:#8A8F98;font-size:12px}.r .v{font-weight:600}.err{color:#F0806C;margin-top:16px;font-size:13px}a{color:#8AB4F8}'+
|
|
120
|
+
'.badge{display:inline-block;padding:2px 9px;border-radius:20px;background:#16311E;color:#6EE7A0;font-size:12px;font-weight:600;text-transform:capitalize}</style></head>'+
|
|
121
|
+
'<body><div class="b"><div class="h">'+host+'</div><div class="t">Track your order</div>'+
|
|
122
|
+
'<label>Email</label><input id="e" type="email" autocomplete="email" placeholder="you@example.com">'+
|
|
123
|
+
'<label>Order number</label><input id="o" placeholder="cs_...">'+
|
|
124
|
+
'<button id="go" onclick="look()">Look up order</button><div id="out"></div></div>'+
|
|
125
|
+
'<script>async function look(){var e=document.getElementById("e").value.trim(),o=document.getElementById("o").value.trim(),out=document.getElementById("out"),btn=document.getElementById("go");'+
|
|
126
|
+
'if(!e||!o){out.innerHTML="<div class=err>Enter both your email and order number.</div>";return}btn.disabled=true;out.innerHTML="";'+
|
|
127
|
+
'try{var r=await fetch("/_clize/track",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({email:e,orderId:o})});var d=await r.json();'+
|
|
128
|
+
'if(!r.ok||d.error){out.innerHTML="<div class=err>"+((d&&d.error)||"Order not found. Check your email and order number.")+"</div>"}'+
|
|
129
|
+
'else{var h="<div class=r><span class=badge>"+d.status+"</span>";if(d.trackingNo){h+="<div style=margin-top:14px><span class=k>Tracking</span><br><span class=v>"+d.trackingNo+"</span>";if(d.carrier)h+=" · "+d.carrier;if(d.trackUrl)h+="<br><a href="+d.trackUrl+" target=_blank rel=noopener>Track door-to-door \\u2197</a>"}h+="</div>";out.innerHTML=h}'+
|
|
130
|
+
'}catch(x){out.innerHTML="<div class=err>Something went wrong. Try again.</div>"}btn.disabled=false}'+
|
|
131
|
+
'document.addEventListener("keydown",function(ev){if(ev.key==="Enter")look()});</script></body></html>';
|
|
132
|
+
}
|
|
105
133
|
function placeholder(host) {
|
|
106
134
|
return '<!doctype html><html lang="zh"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>'+host+'</title>'+
|
|
107
135
|
'<style>body{margin:0;height:100vh;display:grid;place-items:center;background:#050506;color:#EDEDEF;font-family:-apple-system,system-ui,sans-serif}'+
|
|
@@ -152,7 +180,7 @@ const CT = {
|
|
|
152
180
|
".zip": "application/zip",
|
|
153
181
|
};
|
|
154
182
|
const ctOf = (f) => CT[path.extname(f).toLowerCase()] || "application/octet-stream";
|
|
155
|
-
async function kvNamespaceId(ctx) {
|
|
183
|
+
export async function kvNamespaceId(ctx) {
|
|
156
184
|
const list = await ctx.cf.request("GET", `/accounts/${ctx.cf.accountId}/storage/kv/namespaces?per_page=100`);
|
|
157
185
|
return list.find((n) => n.title === SITE_KV_TITLE)?.id;
|
|
158
186
|
}
|
|
@@ -257,6 +285,7 @@ export async function readCatalog(ctx, host) {
|
|
|
257
285
|
}
|
|
258
286
|
// 店面/表单的少量配置存 SITES KV 保留键(低写、按 host;clize 不存业务数据,只存这点接线)。
|
|
259
287
|
const shopHookKey = (host) => `::shop::hook::${host.toLowerCase()}`;
|
|
288
|
+
const shopNotifyKey = (host) => `::shop::notify::${host.toLowerCase()}`;
|
|
260
289
|
const dataSinkKey = (host, collection) => `::data::${host.toLowerCase()}/${collection}`;
|
|
261
290
|
async function kvGet(ctx, kvId, key) {
|
|
262
291
|
try {
|
|
@@ -283,6 +312,33 @@ export async function writeShopHook(ctx, host, url) {
|
|
|
283
312
|
else
|
|
284
313
|
await ctx.cf.request("DELETE", `/accounts/${ctx.cf.accountId}/storage/kv/namespaces/${kvId}/values/${encodeURIComponent(key)}`);
|
|
285
314
|
}
|
|
315
|
+
/** 读店面发货通知模板覆盖({subject?,body?};支持占位符 {{tracking}} 等);未配 → null。 */
|
|
316
|
+
export async function readShopNotifyTemplate(ctx, host) {
|
|
317
|
+
const kvId = await kvNamespaceId(ctx);
|
|
318
|
+
if (!kvId)
|
|
319
|
+
return null;
|
|
320
|
+
const raw = await kvGet(ctx, kvId, shopNotifyKey(host));
|
|
321
|
+
if (!raw)
|
|
322
|
+
return null;
|
|
323
|
+
try {
|
|
324
|
+
const t = JSON.parse(raw);
|
|
325
|
+
return t && (t.subject || t.body) ? t : null;
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/** 设/删店面发货通知模板(t=null 删除)。 */
|
|
332
|
+
export async function writeShopNotifyTemplate(ctx, host, t) {
|
|
333
|
+
const kvId = await ensureKv(ctx);
|
|
334
|
+
const key = shopNotifyKey(host);
|
|
335
|
+
if (t && (t.subject || t.body))
|
|
336
|
+
await ctx.cf.request("PUT", `/accounts/${ctx.cf.accountId}/storage/kv/namespaces/${kvId}/bulk`, [
|
|
337
|
+
{ key, value: JSON.stringify({ subject: t.subject, body: t.body }) },
|
|
338
|
+
]);
|
|
339
|
+
else
|
|
340
|
+
await ctx.cf.request("DELETE", `/accounts/${ctx.cf.accountId}/storage/kv/namespaces/${kvId}/values/${encodeURIComponent(key)}`);
|
|
341
|
+
}
|
|
286
342
|
/** 读某 collection 的表单接收端(webhook url);未配 → null。 */
|
|
287
343
|
export async function readDataSink(ctx, host, collection) {
|
|
288
344
|
const kvId = await kvNamespaceId(ctx);
|
|
@@ -295,6 +351,14 @@ export async function writeDataSink(ctx, host, collection, url) {
|
|
|
295
351
|
{ key: dataSinkKey(host, collection), value: url },
|
|
296
352
|
]);
|
|
297
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* 表单接收端能否被 `/v1/data/submit` 转发:提交是 `fetch(sink, {POST})`,只有 http(s) 会真发出。
|
|
356
|
+
* mailto:/tel: 等非 http scheme 会让 fetch 抛错、被 `.catch(() => {})` 吞掉 → 提交静默流失。
|
|
357
|
+
* 故配置时(writeDataSink 前)即拒非 http(s),把「不会投递」从静默失败提前成显式报错。
|
|
358
|
+
*/
|
|
359
|
+
export function isForwardableSink(url) {
|
|
360
|
+
return /^https?:\/\//i.test(url.trim());
|
|
361
|
+
}
|
|
298
362
|
/**
|
|
299
363
|
* 确保 host 的 apex zone 在 CF 且 active —— 绑 custom domain 到站点 worker 的前提。
|
|
300
364
|
* zone 不存在则建;pending(NS 没委派给 CF)且域名是 Vercel 注册的 → 自动把 NS 切到 CF + 催激活。
|
|
@@ -520,6 +584,26 @@ export async function deployFilesToHandle(ctx, handle, files, opts = {}) {
|
|
|
520
584
|
...(routed.rerouted ? { reroutedFrom: routed.checkout, project: routed.project } : {}),
|
|
521
585
|
},
|
|
522
586
|
});
|
|
587
|
+
// 部署后独立验证:公网权威视角(DoH)确认委派可达 —— deploy 成功 ≠ 域名活着。
|
|
588
|
+
// 2026-06 断链事故:注册局委派丢失、CF zone 假 active、deploy 全绿灯,静默断线近一周。
|
|
589
|
+
// 平台子域跳过(委派由平台 zone 保证);探测失败(DoH 网络问题)不拦部署、不误报。
|
|
590
|
+
let reachability;
|
|
591
|
+
let warning;
|
|
592
|
+
if (!isPlatformHost(host)) {
|
|
593
|
+
reachability = await probeDelegation(host);
|
|
594
|
+
if (reachability.delegation !== "error" && !probeHealthy(reachability)) {
|
|
595
|
+
const why = reachability.delegation === "elsewhere"
|
|
596
|
+
? `注册局 NS 委派指向 ${reachability.nameservers.join(", ")}(非 Cloudflare)`
|
|
597
|
+
: reachability.nxdomain
|
|
598
|
+
? "注册局对该域返回 NXDOMAIN(无委派记录)"
|
|
599
|
+
: reachability.delegation === "cloudflare"
|
|
600
|
+
? `NS 委派正常但 ${host} 解析不出记录`
|
|
601
|
+
: "权威侧查不到 NS 委派";
|
|
602
|
+
warning =
|
|
603
|
+
`⚠️ 文件已部署,但 ${host} 当前从公网无法访问:${why}。` +
|
|
604
|
+
`内容已在 KV 不会丢;若域名是刚接入(NS 传播中)可稍等重测,否则立即 \`clize domain check ${host}\` 分层排查。`;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
523
607
|
const has404 = files.some((f) => f.path.toLowerCase() === "/404.html");
|
|
524
608
|
return {
|
|
525
609
|
host,
|
|
@@ -531,7 +615,10 @@ export async function deployFilesToHandle(ctx, handle, files, opts = {}) {
|
|
|
531
615
|
? { rerouted: `已按域名所属项目 "${routed.project}" 部署(忽略陈旧检出 "${routed.checkout}")` }
|
|
532
616
|
: {}),
|
|
533
617
|
notFound: mode || (has404 ? "404-page(自动)" : "spa(自动)"),
|
|
534
|
-
|
|
618
|
+
// 公网可达性探测结论(权威 DoH 视角;delegation=cloudflare 且 resolves=true 为健康)。
|
|
619
|
+
...(reachability ? { reachability } : {}),
|
|
620
|
+
...(warning ? { warning } : {}),
|
|
621
|
+
message: `已上线 https://${host}(${files.length} 个文件;TLS 自动)${notFoundNote(mode, has404)}${warning ? " ⚠️ 但公网可达性异常,见 warning" : ""}`,
|
|
535
622
|
};
|
|
536
623
|
}
|
|
537
624
|
/** 把一个目录部署到 clize.app handle(本地:读 fs + 直接 CF)。 */
|