@umec/core 0.1.0-alpha.5 → 0.1.0-alpha.7
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/dist/checkout.d.ts +3 -1
- package/dist/checkout.js +1 -1
- package/dist/{chunk-XSJYDBH7.js → chunk-2EBXJT3M.js} +5 -1
- package/dist/chunk-2EBXJT3M.js.map +1 -0
- package/dist/{chunk-MUH43YWF.js → chunk-ZP7IGPJW.js} +20 -3
- package/dist/chunk-ZP7IGPJW.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/webhook.d.ts +8 -2
- package/dist/webhook.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-MUH43YWF.js.map +0 -1
- package/dist/chunk-XSJYDBH7.js.map +0 -1
package/dist/checkout.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ type CheckoutOffer = {
|
|
|
9
9
|
freeShipping: boolean;
|
|
10
10
|
customFields?: CheckoutCustomField[];
|
|
11
11
|
metadata?: Record<string, string>;
|
|
12
|
+
customerEmail?: string;
|
|
12
13
|
unitsRemaining?: Map<string, number>;
|
|
13
14
|
};
|
|
14
15
|
type CheckoutOfferUnavailable = {
|
|
@@ -22,12 +23,13 @@ type CheckoutHandlerOptions = {
|
|
|
22
23
|
shipping?: UmecConfig["shipping"];
|
|
23
24
|
customFields?: CheckoutCustomField[];
|
|
24
25
|
metadata?: Record<string, string>;
|
|
26
|
+
customerEmail?: string;
|
|
25
27
|
checkStock?: CheckStock;
|
|
26
28
|
checkOffer?: CheckOffer;
|
|
27
29
|
};
|
|
28
30
|
type CheckoutHandler = ({ request }: {
|
|
29
31
|
request: Request;
|
|
30
32
|
}) => Promise<Response>;
|
|
31
|
-
declare function createCheckoutHandler({ products, shipping, customFields: staticCustomFields, metadata: staticMetadata, checkStock, checkOffer, }: CheckoutHandlerOptions): CheckoutHandler;
|
|
33
|
+
declare function createCheckoutHandler({ products, shipping, customFields: staticCustomFields, metadata: staticMetadata, customerEmail: staticCustomerEmail, checkStock, checkOffer, }: CheckoutHandlerOptions): CheckoutHandler;
|
|
32
34
|
|
|
33
35
|
export { type CheckOffer, type CheckStock, type CheckoutCustomField, type CheckoutHandler, type CheckoutHandlerOptions, type CheckoutOffer, type CheckoutOfferUnavailable, type CheckoutProductsMap, createCheckoutHandler };
|
package/dist/checkout.js
CHANGED
|
@@ -44,6 +44,7 @@ function createCheckoutHandler({
|
|
|
44
44
|
shipping,
|
|
45
45
|
customFields: staticCustomFields = [],
|
|
46
46
|
metadata: staticMetadata = {},
|
|
47
|
+
customerEmail: staticCustomerEmail,
|
|
47
48
|
checkStock,
|
|
48
49
|
checkOffer
|
|
49
50
|
}) {
|
|
@@ -86,6 +87,7 @@ function createCheckoutHandler({
|
|
|
86
87
|
let freeShipping = false;
|
|
87
88
|
let offerCustomFields = [];
|
|
88
89
|
let offerMetadata = {};
|
|
90
|
+
let customerEmail = staticCustomerEmail;
|
|
89
91
|
if (typeof offerSlug === "string" && offerSlug.trim() !== "") {
|
|
90
92
|
if (!checkOffer) return jsonError("Offer not available", 503);
|
|
91
93
|
const offer = await checkOffer(offerSlug.trim());
|
|
@@ -104,6 +106,7 @@ function createCheckoutHandler({
|
|
|
104
106
|
freeShipping = offer.freeShipping;
|
|
105
107
|
offerCustomFields = offer.customFields ?? [];
|
|
106
108
|
offerMetadata = offer.metadata ?? {};
|
|
109
|
+
customerEmail = offer.customerEmail ?? staticCustomerEmail;
|
|
107
110
|
}
|
|
108
111
|
const shippingRate = shipping ? freeShipping ? { amount: 0, label: shipping.freeLabel } : calcShipping(checkoutItems, products, shipping) : void 0;
|
|
109
112
|
const stock = checkStock ? await checkStock(checkoutItems.map((item) => item.sku)) : /* @__PURE__ */ new Map();
|
|
@@ -142,6 +145,7 @@ function createCheckoutHandler({
|
|
|
142
145
|
items: JSON.stringify(checkoutItems),
|
|
143
146
|
...typeof offerSlug === "string" && offerSlug.trim() ? { offer_slug: offerSlug.trim() } : {}
|
|
144
147
|
},
|
|
148
|
+
...customerEmail ? { customer_email: customerEmail } : {},
|
|
145
149
|
phone_number_collection: { enabled: true },
|
|
146
150
|
custom_fields: checkoutCustomFields,
|
|
147
151
|
shipping_address_collection: { allowed_countries: ["JP"] },
|
|
@@ -185,4 +189,4 @@ function createCheckoutHandler({
|
|
|
185
189
|
export {
|
|
186
190
|
createCheckoutHandler
|
|
187
191
|
};
|
|
188
|
-
//# sourceMappingURL=chunk-
|
|
192
|
+
//# sourceMappingURL=chunk-2EBXJT3M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/checkout.ts"],"sourcesContent":["import Stripe from \"stripe\";\nimport type { UmecConfig, UmecProduct } from \"./config.js\";\nimport { getServerEnv } from \"./env.js\";\n\nconst STRIPE_API_VERSION = \"2026-05-27.dahlia\";\nconst MAX_ITEMS = 10;\nconst MAX_QUANTITY = 20;\n\ntype CheckoutItem = {\n sku: string;\n quantity: number;\n};\n\nexport type CheckoutProductsMap = Record<string, UmecProduct | undefined>;\n\nexport type CheckoutCustomField = Stripe.Checkout.SessionCreateParams.CustomField;\n\nexport type CheckoutOffer = {\n priceOverrides: Record<string, number>;\n freeShipping: boolean;\n customFields?: CheckoutCustomField[];\n metadata?: Record<string, string>;\n customerEmail?: string;\n unitsRemaining?: Map<string, number>;\n};\n\nexport type CheckoutOfferUnavailable = {\n error: \"Offer not found\" | \"Offer expired\";\n status: 404 | 410;\n};\n\nexport type CheckStock = (skus: string[]) => Promise<Map<string, number>>;\nexport type CheckOffer = (offerSlug: string) => Promise<CheckoutOffer | CheckoutOfferUnavailable | null>;\n\nexport type CheckoutHandlerOptions = {\n products: CheckoutProductsMap;\n shipping?: UmecConfig[\"shipping\"];\n customFields?: CheckoutCustomField[];\n metadata?: Record<string, string>;\n customerEmail?: string;\n checkStock?: CheckStock;\n checkOffer?: CheckOffer;\n};\n\nexport type CheckoutHandler = ({ request }: { request: Request }) => Promise<Response>;\n\nfunction jsonError(error: string, status: number) {\n return new Response(JSON.stringify({ error }), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction isCheckoutPayload(value: unknown): value is { items?: unknown; offerSlug?: unknown } {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction isValidQuantity(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1;\n}\n\nfunction isOfferUnavailable(value: CheckoutOffer | CheckoutOfferUnavailable): value is CheckoutOfferUnavailable {\n return \"error\" in value && \"status\" in value;\n}\n\nfunction omitReservedMetadata(metadata: Record<string, string>) {\n return Object.fromEntries(\n Object.entries(metadata).filter(([key]) => key !== \"items\" && key !== \"offer_slug\"),\n );\n}\n\nfunction calcShipping(\n items: CheckoutItem[],\n products: CheckoutProductsMap,\n shipping: NonNullable<UmecConfig[\"shipping\"]>,\n) {\n const totalUnits = items.reduce((sum, { sku, quantity }) => {\n return sum + (products[sku]?.shippingUnits ?? 1) * quantity;\n }, 0);\n\n const tier = shipping.tiers.find((shippingTier) => {\n return shippingTier.maxUnits === undefined || totalUnits <= shippingTier.maxUnits;\n }) ?? shipping.tiers[shipping.tiers.length - 1]!;\n\n return {\n amount: tier.amount,\n label: tier.label,\n };\n}\n\nexport function createCheckoutHandler({\n products,\n shipping,\n customFields: staticCustomFields = [],\n metadata: staticMetadata = {},\n customerEmail: staticCustomerEmail,\n checkStock,\n checkOffer,\n}: CheckoutHandlerOptions): CheckoutHandler {\n return async ({ request }) => {\n const { STRIPE_SECRET_KEY: stripeKey } = getServerEnv();\n if (!stripeKey) return jsonError(\"Stripe not configured\", 503);\n\n const stripe = new Stripe(stripeKey, {\n apiVersion: STRIPE_API_VERSION,\n });\n let payload: unknown;\n\n try {\n payload = await request.json();\n } catch {\n return jsonError(\"Invalid request body\", 400);\n }\n\n if (!isCheckoutPayload(payload)) return jsonError(\"Invalid request body\", 400);\n\n const { items } = payload;\n if (!Array.isArray(items) || items.length === 0) return jsonError(\"Invalid items\", 400);\n if (items.length > MAX_ITEMS) return jsonError(\"Invalid items\", 400);\n\n const rawItems: CheckoutItem[] = [];\n for (const item of items) {\n if (typeof item !== \"object\" || item === null) return jsonError(\"Invalid SKU\", 400);\n\n const { sku, quantity } = item as { sku?: unknown; quantity?: unknown };\n if (typeof sku !== \"string\" || !products[sku]) return jsonError(\"Invalid SKU\", 400);\n if (!isValidQuantity(quantity)) return jsonError(\"Invalid quantity\", 400);\n\n rawItems.push({ sku, quantity });\n }\n\n const quantityMap = new Map<string, number>();\n for (const { sku, quantity } of rawItems) {\n quantityMap.set(sku, (quantityMap.get(sku) ?? 0) + quantity);\n }\n\n for (const [, total] of quantityMap) {\n if (total > MAX_QUANTITY) return jsonError(\"Invalid quantity\", 400);\n }\n\n const checkoutItems: CheckoutItem[] = Array.from(quantityMap.entries()).map(\n ([sku, quantity]) => ({ sku, quantity }),\n );\n const { offerSlug } = payload as { items?: unknown; offerSlug?: unknown };\n let priceOverrides: Record<string, number> = {};\n let freeShipping = false;\n let offerCustomFields: CheckoutCustomField[] = [];\n let offerMetadata: Record<string, string> = {};\n let customerEmail = staticCustomerEmail;\n\n if (typeof offerSlug === \"string\" && offerSlug.trim() !== \"\") {\n if (!checkOffer) return jsonError(\"Offer not available\", 503);\n const offer = await checkOffer(offerSlug.trim());\n if (!offer) return jsonError(\"Offer not found\", 404);\n if (isOfferUnavailable(offer)) return jsonError(offer.error, offer.status);\n const offerUnits = offer.unitsRemaining;\n if (offerUnits && offerUnits.size > 0) {\n for (const { sku, quantity } of checkoutItems) {\n const remaining = offerUnits.get(sku);\n // TOCTOU race is acceptable for low-frequency sales. Move to Durable Objects if needed.\n if (remaining !== undefined && quantity > remaining) {\n return jsonError(`Offer unit limit reached: ${sku}`, 409);\n }\n }\n }\n priceOverrides = offer.priceOverrides;\n freeShipping = offer.freeShipping;\n offerCustomFields = offer.customFields ?? [];\n offerMetadata = offer.metadata ?? {};\n customerEmail = offer.customerEmail ?? staticCustomerEmail;\n }\n\n const shippingRate = shipping\n ? freeShipping\n ? { amount: 0, label: shipping.freeLabel }\n : calcShipping(checkoutItems, products, shipping)\n : undefined;\n const stock = checkStock ? await checkStock(checkoutItems.map((item) => item.sku)) : new Map<string, number>();\n for (const { sku, quantity } of checkoutItems) {\n const available = stock.get(sku);\n if (available !== undefined && quantity > available) {\n return jsonError(`Out of stock: ${sku}`, 409);\n }\n }\n\n const checkoutCustomFields: CheckoutCustomField[] = [\n ...staticCustomFields,\n ...offerCustomFields,\n {\n key: \"message\",\n label: { type: \"custom\", custom: \"メッセージ・ご要望(任意)\" },\n type: \"text\",\n optional: true,\n text: { maximum_length: 255 },\n },\n ];\n\n try {\n const sessionParams: Stripe.Checkout.SessionCreateParams = {\n ui_mode: \"embedded_page\",\n mode: \"payment\",\n line_items: checkoutItems.map(({ sku, quantity }) => ({\n price_data: {\n currency: \"jpy\",\n product_data: { name: products[sku]!.name },\n unit_amount: priceOverrides[sku] ?? products[sku]!.price,\n },\n quantity,\n })),\n metadata: {\n ...omitReservedMetadata(staticMetadata),\n ...omitReservedMetadata(offerMetadata),\n items: JSON.stringify(checkoutItems),\n ...(typeof offerSlug === \"string\" && offerSlug.trim() ? { offer_slug: offerSlug.trim() } : {}),\n },\n ...(customerEmail ? { customer_email: customerEmail } : {}),\n phone_number_collection: { enabled: true },\n custom_fields: checkoutCustomFields,\n shipping_address_collection: { allowed_countries: [\"JP\"] },\n ...(shippingRate\n ? {\n shipping_options: [\n {\n shipping_rate_data: {\n type: \"fixed_amount\",\n fixed_amount: { amount: shippingRate.amount, currency: \"jpy\" },\n display_name: shippingRate.label,\n delivery_estimate: {\n minimum: { unit: \"business_day\", value: 2 },\n maximum: { unit: \"business_day\", value: 5 },\n },\n },\n },\n ],\n }\n : {}),\n redirect_on_completion: \"never\",\n };\n\n const session = await stripe.checkout.sessions.create(sessionParams);\n\n return new Response(JSON.stringify({ clientSecret: session.client_secret }), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch (error) {\n const stripeError = error as {\n type?: string;\n code?: string;\n statusCode?: number;\n requestId?: string;\n };\n console.error(\"[checkout] stripe session create failed:\", {\n type: stripeError.type,\n code: stripeError.code,\n statusCode: stripeError.statusCode,\n requestId: stripeError.requestId,\n });\n\n if (stripeError.type === \"StripePermissionError\") {\n return jsonError(\"Stripe key does not have permission to create Checkout Sessions\", 503);\n }\n\n return jsonError(\"Checkout session could not be created\", 503);\n }\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -52,6 +52,7 @@ function createWebhookHandler({
|
|
|
52
52
|
recordRefund,
|
|
53
53
|
recordExpiry,
|
|
54
54
|
recordOrder,
|
|
55
|
+
recordEmailResult,
|
|
55
56
|
restoreStock,
|
|
56
57
|
decrementStock,
|
|
57
58
|
incrementOfferSold,
|
|
@@ -177,6 +178,7 @@ function createWebhookHandler({
|
|
|
177
178
|
shipping,
|
|
178
179
|
customFields: cf
|
|
179
180
|
});
|
|
181
|
+
let emailResult;
|
|
180
182
|
try {
|
|
181
183
|
const resend = new Resend(resendKey);
|
|
182
184
|
const { error } = await resend.emails.send({
|
|
@@ -188,11 +190,26 @@ function createWebhookHandler({
|
|
|
188
190
|
html
|
|
189
191
|
});
|
|
190
192
|
if (error) {
|
|
191
|
-
|
|
193
|
+
const details = providerErrorDetails(error);
|
|
194
|
+
console.error("[webhook] Resend error:", details);
|
|
195
|
+
emailResult = {
|
|
196
|
+
sessionId: session.id,
|
|
197
|
+
status: "failed",
|
|
198
|
+
...details.message ? { errorMessage: details.message } : {}
|
|
199
|
+
};
|
|
200
|
+
} else {
|
|
201
|
+
emailResult = { sessionId: session.id, status: "sent" };
|
|
192
202
|
}
|
|
193
203
|
} catch (e) {
|
|
194
|
-
|
|
204
|
+
const details = providerErrorDetails(e);
|
|
205
|
+
console.error("[webhook] Resend exception:", details);
|
|
206
|
+
emailResult = {
|
|
207
|
+
sessionId: session.id,
|
|
208
|
+
status: "failed",
|
|
209
|
+
...details.message ? { errorMessage: details.message } : {}
|
|
210
|
+
};
|
|
195
211
|
}
|
|
212
|
+
if (recordEmailResult) await recordEmailResult(emailResult);
|
|
196
213
|
return new Response("OK", { status: 200 });
|
|
197
214
|
};
|
|
198
215
|
}
|
|
@@ -201,4 +218,4 @@ export {
|
|
|
201
218
|
GET,
|
|
202
219
|
createWebhookHandler
|
|
203
220
|
};
|
|
204
|
-
//# sourceMappingURL=chunk-
|
|
221
|
+
//# sourceMappingURL=chunk-ZP7IGPJW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/webhook.ts"],"sourcesContent":["import { Resend } from \"resend\";\nimport Stripe from \"stripe\";\nimport { getServerEnv } from \"./env.js\";\n\nconst STRIPE_API_VERSION = \"2026-05-27.dahlia\";\n\ntype ShippingDetails = NonNullable<\n NonNullable<Stripe.Checkout.Session[\"collected_information\"]>[\"shipping_details\"]\n>;\ntype SessionWithLegacyShipping = Stripe.Checkout.Session & {\n shipping_details?: ShippingDetails | null;\n};\nexport type WebhookItem = {\n sku: string;\n quantity: number;\n};\nexport type SupportedWebhookEvent =\n | \"checkout.session.completed\"\n | \"checkout.session.expired\"\n | \"charge.refunded\";\n\nexport type OrderEmailLineItem = {\n description: string | null;\n quantity: number | null;\n amount: number | null;\n};\n\nexport type OrderEmailData = {\n name: string;\n email: string;\n phone: string | null;\n items: OrderEmailLineItem[];\n totalAmount: number;\n shippingCostAmount: number | null;\n shipping: ShippingDetails | null | undefined;\n customFields: Record<string, string>;\n};\n\nexport type RenderOrderEmail = (order: OrderEmailData) => { subject: string; html: string };\n\nexport type WebhookOrder = {\n id: string;\n email: string;\n name: string | null;\n phone: string | null;\n items: WebhookItem[];\n amount: number;\n shippingCost: number | null;\n shippingAddress: ShippingDetails | null | undefined;\n paymentIntent: string | null;\n offerSlug: string | null;\n customFields: Record<string, string> | null;\n metadata: Record<string, string> | null;\n createdAt: number;\n};\n\nexport type WebhookRecordedOrder = {\n items: WebhookItem[];\n offerSlug: string | null;\n};\n\nexport type WebhookEmailResult = {\n sessionId: string;\n status: \"sent\" | \"failed\";\n errorMessage?: string;\n};\n\nexport type WebhookHandlerOptions = {\n claimEvent?: (eventId: string) => Promise<boolean>;\n recordRefund?: (paymentIntentId: string) => Promise<WebhookRecordedOrder | null>;\n recordExpiry?: (sessionId: string) => Promise<WebhookRecordedOrder | null>;\n recordOrder?: (order: WebhookOrder) => Promise<void>;\n recordEmailResult?: (result: WebhookEmailResult) => Promise<void>;\n restoreStock?: (items: WebhookItem[]) => Promise<void>;\n decrementStock?: (items: WebhookItem[]) => Promise<void>;\n incrementOfferSold?: (offerSlug: string, items: WebhookItem[]) => Promise<void>;\n decrementOfferSold?: (offerSlug: string, items: WebhookItem[]) => Promise<void>;\n renderOrderEmail?: RenderOrderEmail;\n orderEmailFrom?: string;\n orderEmailBcc?: string | string[];\n orderEmailReplyTo?: string;\n};\n\nexport type WebhookHandler = ({ request }: { request: Request }) => Promise<Response>;\n\nfunction parseCheckoutItems(items: string | null | undefined): WebhookItem[] {\n if (!items) return [];\n\n try {\n const parsed: unknown = JSON.parse(items);\n if (!Array.isArray(parsed)) return [];\n\n return parsed.filter((item): item is WebhookItem => {\n if (typeof item !== \"object\" || item === null) return false;\n const { sku, quantity } = item as { sku?: unknown; quantity?: unknown };\n return typeof sku === \"string\" && typeof quantity === \"number\" && Number.isSafeInteger(quantity) && quantity > 0;\n });\n } catch {\n return [];\n }\n}\n\nfunction isSupportedWebhookEvent(type: string): type is SupportedWebhookEvent {\n return (\n type === \"checkout.session.completed\" ||\n type === \"checkout.session.expired\" ||\n type === \"charge.refunded\"\n );\n}\n\nfunction getPaymentIntentId(paymentIntent: string | Stripe.PaymentIntent | null) {\n if (typeof paymentIntent === \"string\") return paymentIntent;\n return paymentIntent?.id ?? null;\n}\n\nfunction providerErrorDetails(error: unknown) {\n if (typeof error !== \"object\" || error === null) return { message: String(error) };\n const value = error as {\n name?: unknown;\n type?: unknown;\n code?: unknown;\n statusCode?: unknown;\n requestId?: unknown;\n message?: unknown;\n };\n\n return {\n name: typeof value.name === \"string\" ? value.name : undefined,\n type: typeof value.type === \"string\" ? value.type : undefined,\n code: typeof value.code === \"string\" ? value.code : undefined,\n statusCode: typeof value.statusCode === \"number\" ? value.statusCode : undefined,\n requestId: typeof value.requestId === \"string\" ? value.requestId : undefined,\n message: typeof value.message === \"string\" ? value.message : undefined,\n };\n}\n\nasync function restoreRecordedOrder(\n order: WebhookRecordedOrder | null,\n restoreStock?: (items: WebhookItem[]) => Promise<void>,\n decrementOfferSold?: (offerSlug: string, items: WebhookItem[]) => Promise<void>,\n) {\n const items = order?.items ?? [];\n if (items.length > 0 && restoreStock) await restoreStock(items);\n if (order?.offerSlug && decrementOfferSold) {\n await decrementOfferSold(order.offerSlug, items);\n }\n}\n\nexport const GET: WebhookHandler = async () => new Response(\"OK\", { status: 200 });\n\nexport function createWebhookHandler({\n claimEvent,\n recordRefund,\n recordExpiry,\n recordOrder,\n recordEmailResult,\n restoreStock,\n decrementStock,\n incrementOfferSold,\n decrementOfferSold,\n renderOrderEmail,\n orderEmailFrom,\n orderEmailBcc,\n orderEmailReplyTo,\n}: WebhookHandlerOptions = {}): WebhookHandler {\n return async ({ request }) => {\n console.log(\"[webhook] POST received, method:\", request.method);\n\n const {\n STRIPE_SECRET_KEY: stripeKey,\n STRIPE_WEBHOOK_SECRET: webhookSecret,\n RESEND_API_KEY: resendKey,\n } = getServerEnv();\n\n if (!stripeKey || !webhookSecret) {\n console.error(\"[webhook] Stripe env not configured\");\n return new Response(\"Server error\", { status: 500 });\n }\n\n const sig = request.headers.get(\"stripe-signature\");\n if (!sig) return new Response(\"Bad request\", { status: 400 });\n\n const payload = await request.text();\n\n const stripe = new Stripe(stripeKey, { apiVersion: STRIPE_API_VERSION });\n\n let event: Stripe.Event;\n try {\n event = await stripe.webhooks.constructEventAsync(payload, sig, webhookSecret);\n } catch {\n return new Response(\"Invalid signature\", { status: 400 });\n }\n\n if (!isSupportedWebhookEvent(event.type)) {\n return new Response(\"OK\", { status: 200 });\n }\n\n if (claimEvent) {\n const claim = await claimEvent(event.id);\n if (!claim) {\n console.log(\"[webhook] duplicate event skipped:\", event.id);\n return new Response(\"OK\", { status: 200 });\n }\n }\n\n if (event.type === \"charge.refunded\") {\n const charge = event.data.object as Stripe.Charge;\n const paymentIntent = getPaymentIntentId(charge.payment_intent);\n\n if (paymentIntent && recordRefund) {\n const order = await recordRefund(paymentIntent);\n await restoreRecordedOrder(order, restoreStock, decrementOfferSold);\n }\n\n return new Response(\"OK\", { status: 200 });\n }\n\n const session = event.data.object as Stripe.Checkout.Session;\n\n if (event.type === \"checkout.session.expired\") {\n if (recordExpiry) {\n const order = await recordExpiry(session.id);\n await restoreRecordedOrder(order, restoreStock, decrementOfferSold);\n }\n\n return new Response(\"OK\", { status: 200 });\n }\n\n const email = session.customer_details?.email;\n const customerName = session.customer_details?.name ?? null;\n const name = customerName ?? \"お客様\";\n const phone = session.customer_details?.phone ?? null;\n const totalAmount = session.amount_total ?? 0;\n const shippingCostAmount = session.shipping_cost?.amount_total ?? null;\n const paymentIntent = getPaymentIntentId(session.payment_intent);\n const checkoutItems = parseCheckoutItems(session.metadata?.items);\n const cf: Record<string, string> = {};\n for (const f of (session.custom_fields ?? []) as Array<{\n key: string;\n type: string;\n text?: { value?: string | null };\n dropdown?: { value?: string | null };\n }>) {\n const v = f.type === \"dropdown\" ? f.dropdown?.value : f.text?.value;\n if (typeof v === \"string\" && v.trim() !== \"\") cf[f.key] = v.trim();\n }\n const customFields = Object.keys(cf).length > 0 ? cf : null;\n const { items: _items, offer_slug: _offerSlug, ...externalMetadata } = session.metadata ?? {};\n const metadata = Object.keys(externalMetadata).length > 0 ? externalMetadata : null;\n\n const shipping =\n session.collected_information?.shipping_details ??\n (session as SessionWithLegacyShipping).shipping_details;\n\n const offerSlugFromMeta = session.metadata?.offer_slug ?? null;\n\n if (recordOrder) {\n await recordOrder({\n id: session.id,\n email: email ?? \"\",\n name: customerName,\n phone,\n items: checkoutItems,\n amount: totalAmount,\n shippingCost: shippingCostAmount,\n shippingAddress: shipping,\n paymentIntent,\n offerSlug: offerSlugFromMeta,\n customFields,\n metadata,\n createdAt: Date.now(),\n });\n if (checkoutItems.length > 0 && decrementStock) await decrementStock(checkoutItems);\n if (offerSlugFromMeta && checkoutItems.length > 0 && incrementOfferSold) {\n await incrementOfferSold(offerSlugFromMeta, checkoutItems);\n }\n }\n\n let lineItems: Stripe.LineItem[] = [];\n try {\n const result = await stripe.checkout.sessions.listLineItems(session.id, { limit: 100 });\n lineItems = result.data;\n } catch (e) {\n console.error(\"[webhook] listLineItems failed:\", providerErrorDetails(e));\n }\n\n if (!email) {\n console.error(\"[webhook] no customer email in session:\", session.id);\n return new Response(\"OK\", { status: 200 });\n }\n\n if (!resendKey || !renderOrderEmail || !orderEmailFrom) {\n if (!resendKey) console.error(\"[webhook] RESEND_API_KEY not set\");\n return new Response(\"OK\", { status: 200 });\n }\n\n const { subject, html } = renderOrderEmail({\n name,\n email,\n phone,\n items: lineItems.map((item) => ({\n description: item.description,\n quantity: item.quantity,\n amount: item.amount_total,\n })),\n totalAmount,\n shippingCostAmount,\n shipping,\n customFields: cf,\n });\n\n let emailResult: WebhookEmailResult;\n try {\n const resend = new Resend(resendKey);\n const { error } = await resend.emails.send({\n from: orderEmailFrom,\n to: email,\n ...(orderEmailBcc ? { bcc: orderEmailBcc } : {}),\n ...(orderEmailReplyTo ? { reply_to: orderEmailReplyTo } : {}),\n subject,\n html,\n });\n if (error) {\n const details = providerErrorDetails(error);\n console.error(\"[webhook] Resend error:\", details);\n emailResult = {\n sessionId: session.id,\n status: \"failed\",\n ...(details.message ? { errorMessage: details.message } : {}),\n };\n } else {\n emailResult = { sessionId: session.id, status: \"sent\" };\n }\n } catch (e) {\n const details = providerErrorDetails(e);\n console.error(\"[webhook] Resend exception:\", details);\n emailResult = {\n sessionId: session.id,\n status: \"failed\",\n ...(details.message ? { errorMessage: details.message } : {}),\n };\n }\n\n if (recordEmailResult) await recordEmailResult(emailResult);\n\n return new Response(\"OK\", { status: 200 });\n };\n}\n"],"mappings":";;;;;AAAA,SAAS,cAAc;AACvB,OAAO,YAAY;AAGnB,IAAM,qBAAqB;AAiF3B,SAAS,mBAAmB,OAAiD;AAC3E,MAAI,CAAC,MAAO,QAAO,CAAC;AAEpB,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,KAAK;AACxC,QAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC;AAEpC,WAAO,OAAO,OAAO,CAAC,SAA8B;AAClD,UAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,YAAM,EAAE,KAAK,SAAS,IAAI;AAC1B,aAAO,OAAO,QAAQ,YAAY,OAAO,aAAa,YAAY,OAAO,cAAc,QAAQ,KAAK,WAAW;AAAA,IACjH,CAAC;AAAA,EACH,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,wBAAwB,MAA6C;AAC5E,SACE,SAAS,gCACT,SAAS,8BACT,SAAS;AAEb;AAEA,SAAS,mBAAmB,eAAqD;AAC/E,MAAI,OAAO,kBAAkB,SAAU,QAAO;AAC9C,SAAO,eAAe,MAAM;AAC9B;AAEA,SAAS,qBAAqB,OAAgB;AAC5C,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO,EAAE,SAAS,OAAO,KAAK,EAAE;AACjF,QAAM,QAAQ;AASd,SAAO;AAAA,IACL,MAAM,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;AAAA,IACpD,MAAM,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;AAAA,IACpD,MAAM,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;AAAA,IACpD,YAAY,OAAO,MAAM,eAAe,WAAW,MAAM,aAAa;AAAA,IACtE,WAAW,OAAO,MAAM,cAAc,WAAW,MAAM,YAAY;AAAA,IACnE,SAAS,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU;AAAA,EAC/D;AACF;AAEA,eAAe,qBACb,OACA,cACA,oBACA;AACA,QAAM,QAAQ,OAAO,SAAS,CAAC;AAC/B,MAAI,MAAM,SAAS,KAAK,aAAc,OAAM,aAAa,KAAK;AAC9D,MAAI,OAAO,aAAa,oBAAoB;AAC1C,UAAM,mBAAmB,MAAM,WAAW,KAAK;AAAA,EACjD;AACF;AAEO,IAAM,MAAsB,YAAY,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAE1E,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,IAA2B,CAAC,GAAmB;AAC7C,SAAO,OAAO,EAAE,QAAQ,MAAM;AAC5B,YAAQ,IAAI,oCAAoC,QAAQ,MAAM;AAE9D,UAAM;AAAA,MACJ,mBAAmB;AAAA,MACnB,uBAAuB;AAAA,MACvB,gBAAgB;AAAA,IAClB,IAAI,aAAa;AAEjB,QAAI,CAAC,aAAa,CAAC,eAAe;AAChC,cAAQ,MAAM,qCAAqC;AACnD,aAAO,IAAI,SAAS,gBAAgB,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrD;AAEA,UAAM,MAAM,QAAQ,QAAQ,IAAI,kBAAkB;AAClD,QAAI,CAAC,IAAK,QAAO,IAAI,SAAS,eAAe,EAAE,QAAQ,IAAI,CAAC;AAE5D,UAAM,UAAU,MAAM,QAAQ,KAAK;AAEnC,UAAM,SAAS,IAAI,OAAO,WAAW,EAAE,YAAY,mBAAmB,CAAC;AAEvE,QAAI;AACJ,QAAI;AACF,cAAQ,MAAM,OAAO,SAAS,oBAAoB,SAAS,KAAK,aAAa;AAAA,IAC/E,QAAQ;AACN,aAAO,IAAI,SAAS,qBAAqB,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1D;AAEA,QAAI,CAAC,wBAAwB,MAAM,IAAI,GAAG;AACxC,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AAEA,QAAI,YAAY;AACd,YAAM,QAAQ,MAAM,WAAW,MAAM,EAAE;AACvC,UAAI,CAAC,OAAO;AACV,gBAAQ,IAAI,sCAAsC,MAAM,EAAE;AAC1D,eAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC3C;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,mBAAmB;AACpC,YAAM,SAAS,MAAM,KAAK;AAC1B,YAAMA,iBAAgB,mBAAmB,OAAO,cAAc;AAE9D,UAAIA,kBAAiB,cAAc;AACjC,cAAM,QAAQ,MAAM,aAAaA,cAAa;AAC9C,cAAM,qBAAqB,OAAO,cAAc,kBAAkB;AAAA,MACpE;AAEA,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AAEA,UAAM,UAAU,MAAM,KAAK;AAE3B,QAAI,MAAM,SAAS,4BAA4B;AAC7C,UAAI,cAAc;AAChB,cAAM,QAAQ,MAAM,aAAa,QAAQ,EAAE;AAC3C,cAAM,qBAAqB,OAAO,cAAc,kBAAkB;AAAA,MACpE;AAEA,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AAEA,UAAM,QAAQ,QAAQ,kBAAkB;AACxC,UAAM,eAAe,QAAQ,kBAAkB,QAAQ;AACvD,UAAM,OAAO,gBAAgB;AAC7B,UAAM,QAAQ,QAAQ,kBAAkB,SAAS;AACjD,UAAM,cAAc,QAAQ,gBAAgB;AAC5C,UAAM,qBAAqB,QAAQ,eAAe,gBAAgB;AAClE,UAAM,gBAAgB,mBAAmB,QAAQ,cAAc;AAC/D,UAAM,gBAAgB,mBAAmB,QAAQ,UAAU,KAAK;AAChE,UAAM,KAA6B,CAAC;AACpC,eAAW,KAAM,QAAQ,iBAAiB,CAAC,GAKvC;AACF,YAAM,IAAI,EAAE,SAAS,aAAa,EAAE,UAAU,QAAQ,EAAE,MAAM;AAC9D,UAAI,OAAO,MAAM,YAAY,EAAE,KAAK,MAAM,GAAI,IAAG,EAAE,GAAG,IAAI,EAAE,KAAK;AAAA,IACnE;AACA,UAAM,eAAe,OAAO,KAAK,EAAE,EAAE,SAAS,IAAI,KAAK;AACvD,UAAM,EAAE,OAAO,QAAQ,YAAY,YAAY,GAAG,iBAAiB,IAAI,QAAQ,YAAY,CAAC;AAC5F,UAAM,WAAW,OAAO,KAAK,gBAAgB,EAAE,SAAS,IAAI,mBAAmB;AAE/E,UAAM,WACJ,QAAQ,uBAAuB,oBAC9B,QAAsC;AAEzC,UAAM,oBAAoB,QAAQ,UAAU,cAAc;AAE1D,QAAI,aAAa;AACf,YAAM,YAAY;AAAA,QAChB,IAAI,QAAQ;AAAA,QACZ,OAAO,SAAS;AAAA,QAChB,MAAM;AAAA,QACN;AAAA,QACA,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,iBAAiB;AAAA,QACjB;AAAA,QACA,WAAW;AAAA,QACX;AAAA,QACA;AAAA,QACA,WAAW,KAAK,IAAI;AAAA,MACtB,CAAC;AACD,UAAI,cAAc,SAAS,KAAK,eAAgB,OAAM,eAAe,aAAa;AAClF,UAAI,qBAAqB,cAAc,SAAS,KAAK,oBAAoB;AACvE,cAAM,mBAAmB,mBAAmB,aAAa;AAAA,MAC3D;AAAA,IACF;AAEA,QAAI,YAA+B,CAAC;AACpC,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,SAAS,SAAS,cAAc,QAAQ,IAAI,EAAE,OAAO,IAAI,CAAC;AACtF,kBAAY,OAAO;AAAA,IACrB,SAAS,GAAG;AACV,cAAQ,MAAM,mCAAmC,qBAAqB,CAAC,CAAC;AAAA,IAC1E;AAEA,QAAI,CAAC,OAAO;AACV,cAAQ,MAAM,2CAA2C,QAAQ,EAAE;AACnE,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AAEA,QAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,gBAAgB;AACtD,UAAI,CAAC,UAAW,SAAQ,MAAM,kCAAkC;AAChE,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AAEA,UAAM,EAAE,SAAS,KAAK,IAAI,iBAAiB;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,UAAU,IAAI,CAAC,UAAU;AAAA,QAC9B,aAAa,KAAK;AAAA,QAClB,UAAU,KAAK;AAAA,QACf,QAAQ,KAAK;AAAA,MACf,EAAE;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc;AAAA,IAChB,CAAC;AAED,QAAI;AACJ,QAAI;AACF,YAAM,SAAS,IAAI,OAAO,SAAS;AACnC,YAAM,EAAE,MAAM,IAAI,MAAM,OAAO,OAAO,KAAK;AAAA,QACzC,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,GAAI,gBAAgB,EAAE,KAAK,cAAc,IAAI,CAAC;AAAA,QAC9C,GAAI,oBAAoB,EAAE,UAAU,kBAAkB,IAAI,CAAC;AAAA,QAC3D;AAAA,QACA;AAAA,MACF,CAAC;AACD,UAAI,OAAO;AACT,cAAM,UAAU,qBAAqB,KAAK;AAC1C,gBAAQ,MAAM,2BAA2B,OAAO;AAChD,sBAAc;AAAA,UACZ,WAAW,QAAQ;AAAA,UACnB,QAAQ;AAAA,UACR,GAAI,QAAQ,UAAU,EAAE,cAAc,QAAQ,QAAQ,IAAI,CAAC;AAAA,QAC7D;AAAA,MACF,OAAO;AACL,sBAAc,EAAE,WAAW,QAAQ,IAAI,QAAQ,OAAO;AAAA,MACxD;AAAA,IACF,SAAS,GAAG;AACV,YAAM,UAAU,qBAAqB,CAAC;AACtC,cAAQ,MAAM,+BAA+B,OAAO;AACpD,oBAAc;AAAA,QACZ,WAAW,QAAQ;AAAA,QACnB,QAAQ;AAAA,QACR,GAAI,QAAQ,UAAU,EAAE,cAAc,QAAQ,QAAQ,IAAI,CAAC;AAAA,MAC7D;AAAA,IACF;AAEA,QAAI,kBAAmB,OAAM,kBAAkB,WAAW;AAE1D,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3C;AACF;","names":["paymentIntent"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { ServerEnv, getServerEnv } from './env.js';
|
|
|
5
5
|
export { UmecLimitedPage, UmecMiddleware, UmecMiddlewareContext, UmecMiddlewareNext, UmecMiddlewareOptions, createUmecMiddleware } from './middleware.js';
|
|
6
6
|
export { NotifyEnv, NotifyProduct, NotifyProductsMap, NotifyUnshippedOptions, UnshippedOrder, notifyUnshipped } from './notify.js';
|
|
7
7
|
export { SubscribeHandler, SubscribeHandlerOptions, createSubscribeHandler } from './subscribe.js';
|
|
8
|
-
export { OrderEmailData, OrderEmailLineItem, RenderOrderEmail, SupportedWebhookEvent, WebhookHandler, WebhookHandlerOptions, WebhookItem, WebhookOrder, WebhookRecordedOrder, createWebhookHandler, GET as webhookGET } from './webhook.js';
|
|
8
|
+
export { OrderEmailData, OrderEmailLineItem, RenderOrderEmail, SupportedWebhookEvent, WebhookEmailResult, WebhookHandler, WebhookHandlerOptions, WebhookItem, WebhookOrder, WebhookRecordedOrder, createWebhookHandler, GET as webhookGET } from './webhook.js';
|
|
9
9
|
import 'jose';
|
|
10
10
|
import 'stripe';
|
|
11
11
|
import 'zod';
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GET,
|
|
3
3
|
createWebhookHandler
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZP7IGPJW.js";
|
|
5
5
|
import {
|
|
6
6
|
createAccessGuard
|
|
7
7
|
} from "./chunk-2SNGXEIL.js";
|
|
8
8
|
import {
|
|
9
9
|
createCheckoutHandler
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2EBXJT3M.js";
|
|
11
11
|
import {
|
|
12
12
|
defineUmecConfig,
|
|
13
13
|
formatEmailFrom,
|
package/dist/webhook.d.ts
CHANGED
|
@@ -44,11 +44,17 @@ type WebhookRecordedOrder = {
|
|
|
44
44
|
items: WebhookItem[];
|
|
45
45
|
offerSlug: string | null;
|
|
46
46
|
};
|
|
47
|
+
type WebhookEmailResult = {
|
|
48
|
+
sessionId: string;
|
|
49
|
+
status: "sent" | "failed";
|
|
50
|
+
errorMessage?: string;
|
|
51
|
+
};
|
|
47
52
|
type WebhookHandlerOptions = {
|
|
48
53
|
claimEvent?: (eventId: string) => Promise<boolean>;
|
|
49
54
|
recordRefund?: (paymentIntentId: string) => Promise<WebhookRecordedOrder | null>;
|
|
50
55
|
recordExpiry?: (sessionId: string) => Promise<WebhookRecordedOrder | null>;
|
|
51
56
|
recordOrder?: (order: WebhookOrder) => Promise<void>;
|
|
57
|
+
recordEmailResult?: (result: WebhookEmailResult) => Promise<void>;
|
|
52
58
|
restoreStock?: (items: WebhookItem[]) => Promise<void>;
|
|
53
59
|
decrementStock?: (items: WebhookItem[]) => Promise<void>;
|
|
54
60
|
incrementOfferSold?: (offerSlug: string, items: WebhookItem[]) => Promise<void>;
|
|
@@ -62,6 +68,6 @@ type WebhookHandler = ({ request }: {
|
|
|
62
68
|
request: Request;
|
|
63
69
|
}) => Promise<Response>;
|
|
64
70
|
declare const GET: WebhookHandler;
|
|
65
|
-
declare function createWebhookHandler({ claimEvent, recordRefund, recordExpiry, recordOrder, restoreStock, decrementStock, incrementOfferSold, decrementOfferSold, renderOrderEmail, orderEmailFrom, orderEmailBcc, orderEmailReplyTo, }?: WebhookHandlerOptions): WebhookHandler;
|
|
71
|
+
declare function createWebhookHandler({ claimEvent, recordRefund, recordExpiry, recordOrder, recordEmailResult, restoreStock, decrementStock, incrementOfferSold, decrementOfferSold, renderOrderEmail, orderEmailFrom, orderEmailBcc, orderEmailReplyTo, }?: WebhookHandlerOptions): WebhookHandler;
|
|
66
72
|
|
|
67
|
-
export { GET, type OrderEmailData, type OrderEmailLineItem, type RenderOrderEmail, type SupportedWebhookEvent, type WebhookHandler, type WebhookHandlerOptions, type WebhookItem, type WebhookOrder, type WebhookRecordedOrder, createWebhookHandler };
|
|
73
|
+
export { GET, type OrderEmailData, type OrderEmailLineItem, type RenderOrderEmail, type SupportedWebhookEvent, type WebhookEmailResult, type WebhookHandler, type WebhookHandlerOptions, type WebhookItem, type WebhookOrder, type WebhookRecordedOrder, createWebhookHandler };
|
package/dist/webhook.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/webhook.ts"],"sourcesContent":["import { Resend } from \"resend\";\nimport Stripe from \"stripe\";\nimport { getServerEnv } from \"./env.js\";\n\nconst STRIPE_API_VERSION = \"2026-05-27.dahlia\";\n\ntype ShippingDetails = NonNullable<\n NonNullable<Stripe.Checkout.Session[\"collected_information\"]>[\"shipping_details\"]\n>;\ntype SessionWithLegacyShipping = Stripe.Checkout.Session & {\n shipping_details?: ShippingDetails | null;\n};\nexport type WebhookItem = {\n sku: string;\n quantity: number;\n};\nexport type SupportedWebhookEvent =\n | \"checkout.session.completed\"\n | \"checkout.session.expired\"\n | \"charge.refunded\";\n\nexport type OrderEmailLineItem = {\n description: string | null;\n quantity: number | null;\n amount: number | null;\n};\n\nexport type OrderEmailData = {\n name: string;\n email: string;\n phone: string | null;\n items: OrderEmailLineItem[];\n totalAmount: number;\n shippingCostAmount: number | null;\n shipping: ShippingDetails | null | undefined;\n customFields: Record<string, string>;\n};\n\nexport type RenderOrderEmail = (order: OrderEmailData) => { subject: string; html: string };\n\nexport type WebhookOrder = {\n id: string;\n email: string;\n name: string | null;\n phone: string | null;\n items: WebhookItem[];\n amount: number;\n shippingCost: number | null;\n shippingAddress: ShippingDetails | null | undefined;\n paymentIntent: string | null;\n offerSlug: string | null;\n customFields: Record<string, string> | null;\n metadata: Record<string, string> | null;\n createdAt: number;\n};\n\nexport type WebhookRecordedOrder = {\n items: WebhookItem[];\n offerSlug: string | null;\n};\n\nexport type WebhookHandlerOptions = {\n claimEvent?: (eventId: string) => Promise<boolean>;\n recordRefund?: (paymentIntentId: string) => Promise<WebhookRecordedOrder | null>;\n recordExpiry?: (sessionId: string) => Promise<WebhookRecordedOrder | null>;\n recordOrder?: (order: WebhookOrder) => Promise<void>;\n restoreStock?: (items: WebhookItem[]) => Promise<void>;\n decrementStock?: (items: WebhookItem[]) => Promise<void>;\n incrementOfferSold?: (offerSlug: string, items: WebhookItem[]) => Promise<void>;\n decrementOfferSold?: (offerSlug: string, items: WebhookItem[]) => Promise<void>;\n renderOrderEmail?: RenderOrderEmail;\n orderEmailFrom?: string;\n orderEmailBcc?: string | string[];\n orderEmailReplyTo?: string;\n};\n\nexport type WebhookHandler = ({ request }: { request: Request }) => Promise<Response>;\n\nfunction parseCheckoutItems(items: string | null | undefined): WebhookItem[] {\n if (!items) return [];\n\n try {\n const parsed: unknown = JSON.parse(items);\n if (!Array.isArray(parsed)) return [];\n\n return parsed.filter((item): item is WebhookItem => {\n if (typeof item !== \"object\" || item === null) return false;\n const { sku, quantity } = item as { sku?: unknown; quantity?: unknown };\n return typeof sku === \"string\" && typeof quantity === \"number\" && Number.isSafeInteger(quantity) && quantity > 0;\n });\n } catch {\n return [];\n }\n}\n\nfunction isSupportedWebhookEvent(type: string): type is SupportedWebhookEvent {\n return (\n type === \"checkout.session.completed\" ||\n type === \"checkout.session.expired\" ||\n type === \"charge.refunded\"\n );\n}\n\nfunction getPaymentIntentId(paymentIntent: string | Stripe.PaymentIntent | null) {\n if (typeof paymentIntent === \"string\") return paymentIntent;\n return paymentIntent?.id ?? null;\n}\n\nfunction providerErrorDetails(error: unknown) {\n if (typeof error !== \"object\" || error === null) return { message: String(error) };\n const value = error as {\n name?: unknown;\n type?: unknown;\n code?: unknown;\n statusCode?: unknown;\n requestId?: unknown;\n message?: unknown;\n };\n\n return {\n name: typeof value.name === \"string\" ? value.name : undefined,\n type: typeof value.type === \"string\" ? value.type : undefined,\n code: typeof value.code === \"string\" ? value.code : undefined,\n statusCode: typeof value.statusCode === \"number\" ? value.statusCode : undefined,\n requestId: typeof value.requestId === \"string\" ? value.requestId : undefined,\n message: typeof value.message === \"string\" ? value.message : undefined,\n };\n}\n\nasync function restoreRecordedOrder(\n order: WebhookRecordedOrder | null,\n restoreStock?: (items: WebhookItem[]) => Promise<void>,\n decrementOfferSold?: (offerSlug: string, items: WebhookItem[]) => Promise<void>,\n) {\n const items = order?.items ?? [];\n if (items.length > 0 && restoreStock) await restoreStock(items);\n if (order?.offerSlug && decrementOfferSold) {\n await decrementOfferSold(order.offerSlug, items);\n }\n}\n\nexport const GET: WebhookHandler = async () => new Response(\"OK\", { status: 200 });\n\nexport function createWebhookHandler({\n claimEvent,\n recordRefund,\n recordExpiry,\n recordOrder,\n restoreStock,\n decrementStock,\n incrementOfferSold,\n decrementOfferSold,\n renderOrderEmail,\n orderEmailFrom,\n orderEmailBcc,\n orderEmailReplyTo,\n}: WebhookHandlerOptions = {}): WebhookHandler {\n return async ({ request }) => {\n console.log(\"[webhook] POST received, method:\", request.method);\n\n const {\n STRIPE_SECRET_KEY: stripeKey,\n STRIPE_WEBHOOK_SECRET: webhookSecret,\n RESEND_API_KEY: resendKey,\n } = getServerEnv();\n\n if (!stripeKey || !webhookSecret) {\n console.error(\"[webhook] Stripe env not configured\");\n return new Response(\"Server error\", { status: 500 });\n }\n\n const sig = request.headers.get(\"stripe-signature\");\n if (!sig) return new Response(\"Bad request\", { status: 400 });\n\n const payload = await request.text();\n\n const stripe = new Stripe(stripeKey, { apiVersion: STRIPE_API_VERSION });\n\n let event: Stripe.Event;\n try {\n event = await stripe.webhooks.constructEventAsync(payload, sig, webhookSecret);\n } catch {\n return new Response(\"Invalid signature\", { status: 400 });\n }\n\n if (!isSupportedWebhookEvent(event.type)) {\n return new Response(\"OK\", { status: 200 });\n }\n\n if (claimEvent) {\n const claim = await claimEvent(event.id);\n if (!claim) {\n console.log(\"[webhook] duplicate event skipped:\", event.id);\n return new Response(\"OK\", { status: 200 });\n }\n }\n\n if (event.type === \"charge.refunded\") {\n const charge = event.data.object as Stripe.Charge;\n const paymentIntent = getPaymentIntentId(charge.payment_intent);\n\n if (paymentIntent && recordRefund) {\n const order = await recordRefund(paymentIntent);\n await restoreRecordedOrder(order, restoreStock, decrementOfferSold);\n }\n\n return new Response(\"OK\", { status: 200 });\n }\n\n const session = event.data.object as Stripe.Checkout.Session;\n\n if (event.type === \"checkout.session.expired\") {\n if (recordExpiry) {\n const order = await recordExpiry(session.id);\n await restoreRecordedOrder(order, restoreStock, decrementOfferSold);\n }\n\n return new Response(\"OK\", { status: 200 });\n }\n\n const email = session.customer_details?.email;\n const customerName = session.customer_details?.name ?? null;\n const name = customerName ?? \"お客様\";\n const phone = session.customer_details?.phone ?? null;\n const totalAmount = session.amount_total ?? 0;\n const shippingCostAmount = session.shipping_cost?.amount_total ?? null;\n const paymentIntent = getPaymentIntentId(session.payment_intent);\n const checkoutItems = parseCheckoutItems(session.metadata?.items);\n const cf: Record<string, string> = {};\n for (const f of (session.custom_fields ?? []) as Array<{\n key: string;\n type: string;\n text?: { value?: string | null };\n dropdown?: { value?: string | null };\n }>) {\n const v = f.type === \"dropdown\" ? f.dropdown?.value : f.text?.value;\n if (typeof v === \"string\" && v.trim() !== \"\") cf[f.key] = v.trim();\n }\n const customFields = Object.keys(cf).length > 0 ? cf : null;\n const { items: _items, offer_slug: _offerSlug, ...externalMetadata } = session.metadata ?? {};\n const metadata = Object.keys(externalMetadata).length > 0 ? externalMetadata : null;\n\n const shipping =\n session.collected_information?.shipping_details ??\n (session as SessionWithLegacyShipping).shipping_details;\n\n const offerSlugFromMeta = session.metadata?.offer_slug ?? null;\n\n if (recordOrder) {\n await recordOrder({\n id: session.id,\n email: email ?? \"\",\n name: customerName,\n phone,\n items: checkoutItems,\n amount: totalAmount,\n shippingCost: shippingCostAmount,\n shippingAddress: shipping,\n paymentIntent,\n offerSlug: offerSlugFromMeta,\n customFields,\n metadata,\n createdAt: Date.now(),\n });\n if (checkoutItems.length > 0 && decrementStock) await decrementStock(checkoutItems);\n if (offerSlugFromMeta && checkoutItems.length > 0 && incrementOfferSold) {\n await incrementOfferSold(offerSlugFromMeta, checkoutItems);\n }\n }\n\n let lineItems: Stripe.LineItem[] = [];\n try {\n const result = await stripe.checkout.sessions.listLineItems(session.id, { limit: 100 });\n lineItems = result.data;\n } catch (e) {\n console.error(\"[webhook] listLineItems failed:\", providerErrorDetails(e));\n }\n\n if (!email) {\n console.error(\"[webhook] no customer email in session:\", session.id);\n return new Response(\"OK\", { status: 200 });\n }\n\n if (!resendKey || !renderOrderEmail || !orderEmailFrom) {\n if (!resendKey) console.error(\"[webhook] RESEND_API_KEY not set\");\n return new Response(\"OK\", { status: 200 });\n }\n\n const { subject, html } = renderOrderEmail({\n name,\n email,\n phone,\n items: lineItems.map((item) => ({\n description: item.description,\n quantity: item.quantity,\n amount: item.amount_total,\n })),\n totalAmount,\n shippingCostAmount,\n shipping,\n customFields: cf,\n });\n\n try {\n const resend = new Resend(resendKey);\n const { error } = await resend.emails.send({\n from: orderEmailFrom,\n to: email,\n ...(orderEmailBcc ? { bcc: orderEmailBcc } : {}),\n ...(orderEmailReplyTo ? { reply_to: orderEmailReplyTo } : {}),\n subject,\n html,\n });\n if (error) {\n console.error(\"[webhook] Resend error:\", providerErrorDetails(error));\n }\n } catch (e) {\n console.error(\"[webhook] Resend exception:\", providerErrorDetails(e));\n }\n\n return new Response(\"OK\", { status: 200 });\n };\n}\n"],"mappings":"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","names":["paymentIntent"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/checkout.ts"],"sourcesContent":["import Stripe from \"stripe\";\nimport type { UmecConfig, UmecProduct } from \"./config.js\";\nimport { getServerEnv } from \"./env.js\";\n\nconst STRIPE_API_VERSION = \"2026-05-27.dahlia\";\nconst MAX_ITEMS = 10;\nconst MAX_QUANTITY = 20;\n\ntype CheckoutItem = {\n sku: string;\n quantity: number;\n};\n\nexport type CheckoutProductsMap = Record<string, UmecProduct | undefined>;\n\nexport type CheckoutCustomField = Stripe.Checkout.SessionCreateParams.CustomField;\n\nexport type CheckoutOffer = {\n priceOverrides: Record<string, number>;\n freeShipping: boolean;\n customFields?: CheckoutCustomField[];\n metadata?: Record<string, string>;\n unitsRemaining?: Map<string, number>;\n};\n\nexport type CheckoutOfferUnavailable = {\n error: \"Offer not found\" | \"Offer expired\";\n status: 404 | 410;\n};\n\nexport type CheckStock = (skus: string[]) => Promise<Map<string, number>>;\nexport type CheckOffer = (offerSlug: string) => Promise<CheckoutOffer | CheckoutOfferUnavailable | null>;\n\nexport type CheckoutHandlerOptions = {\n products: CheckoutProductsMap;\n shipping?: UmecConfig[\"shipping\"];\n customFields?: CheckoutCustomField[];\n metadata?: Record<string, string>;\n checkStock?: CheckStock;\n checkOffer?: CheckOffer;\n};\n\nexport type CheckoutHandler = ({ request }: { request: Request }) => Promise<Response>;\n\nfunction jsonError(error: string, status: number) {\n return new Response(JSON.stringify({ error }), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction isCheckoutPayload(value: unknown): value is { items?: unknown; offerSlug?: unknown } {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction isValidQuantity(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1;\n}\n\nfunction isOfferUnavailable(value: CheckoutOffer | CheckoutOfferUnavailable): value is CheckoutOfferUnavailable {\n return \"error\" in value && \"status\" in value;\n}\n\nfunction omitReservedMetadata(metadata: Record<string, string>) {\n return Object.fromEntries(\n Object.entries(metadata).filter(([key]) => key !== \"items\" && key !== \"offer_slug\"),\n );\n}\n\nfunction calcShipping(\n items: CheckoutItem[],\n products: CheckoutProductsMap,\n shipping: NonNullable<UmecConfig[\"shipping\"]>,\n) {\n const totalUnits = items.reduce((sum, { sku, quantity }) => {\n return sum + (products[sku]?.shippingUnits ?? 1) * quantity;\n }, 0);\n\n const tier = shipping.tiers.find((shippingTier) => {\n return shippingTier.maxUnits === undefined || totalUnits <= shippingTier.maxUnits;\n }) ?? shipping.tiers[shipping.tiers.length - 1]!;\n\n return {\n amount: tier.amount,\n label: tier.label,\n };\n}\n\nexport function createCheckoutHandler({\n products,\n shipping,\n customFields: staticCustomFields = [],\n metadata: staticMetadata = {},\n checkStock,\n checkOffer,\n}: CheckoutHandlerOptions): CheckoutHandler {\n return async ({ request }) => {\n const { STRIPE_SECRET_KEY: stripeKey } = getServerEnv();\n if (!stripeKey) return jsonError(\"Stripe not configured\", 503);\n\n const stripe = new Stripe(stripeKey, {\n apiVersion: STRIPE_API_VERSION,\n });\n let payload: unknown;\n\n try {\n payload = await request.json();\n } catch {\n return jsonError(\"Invalid request body\", 400);\n }\n\n if (!isCheckoutPayload(payload)) return jsonError(\"Invalid request body\", 400);\n\n const { items } = payload;\n if (!Array.isArray(items) || items.length === 0) return jsonError(\"Invalid items\", 400);\n if (items.length > MAX_ITEMS) return jsonError(\"Invalid items\", 400);\n\n const rawItems: CheckoutItem[] = [];\n for (const item of items) {\n if (typeof item !== \"object\" || item === null) return jsonError(\"Invalid SKU\", 400);\n\n const { sku, quantity } = item as { sku?: unknown; quantity?: unknown };\n if (typeof sku !== \"string\" || !products[sku]) return jsonError(\"Invalid SKU\", 400);\n if (!isValidQuantity(quantity)) return jsonError(\"Invalid quantity\", 400);\n\n rawItems.push({ sku, quantity });\n }\n\n const quantityMap = new Map<string, number>();\n for (const { sku, quantity } of rawItems) {\n quantityMap.set(sku, (quantityMap.get(sku) ?? 0) + quantity);\n }\n\n for (const [, total] of quantityMap) {\n if (total > MAX_QUANTITY) return jsonError(\"Invalid quantity\", 400);\n }\n\n const checkoutItems: CheckoutItem[] = Array.from(quantityMap.entries()).map(\n ([sku, quantity]) => ({ sku, quantity }),\n );\n const { offerSlug } = payload as { items?: unknown; offerSlug?: unknown };\n let priceOverrides: Record<string, number> = {};\n let freeShipping = false;\n let offerCustomFields: CheckoutCustomField[] = [];\n let offerMetadata: Record<string, string> = {};\n\n if (typeof offerSlug === \"string\" && offerSlug.trim() !== \"\") {\n if (!checkOffer) return jsonError(\"Offer not available\", 503);\n const offer = await checkOffer(offerSlug.trim());\n if (!offer) return jsonError(\"Offer not found\", 404);\n if (isOfferUnavailable(offer)) return jsonError(offer.error, offer.status);\n const offerUnits = offer.unitsRemaining;\n if (offerUnits && offerUnits.size > 0) {\n for (const { sku, quantity } of checkoutItems) {\n const remaining = offerUnits.get(sku);\n // TOCTOU race is acceptable for low-frequency sales. Move to Durable Objects if needed.\n if (remaining !== undefined && quantity > remaining) {\n return jsonError(`Offer unit limit reached: ${sku}`, 409);\n }\n }\n }\n priceOverrides = offer.priceOverrides;\n freeShipping = offer.freeShipping;\n offerCustomFields = offer.customFields ?? [];\n offerMetadata = offer.metadata ?? {};\n }\n\n const shippingRate = shipping\n ? freeShipping\n ? { amount: 0, label: shipping.freeLabel }\n : calcShipping(checkoutItems, products, shipping)\n : undefined;\n const stock = checkStock ? await checkStock(checkoutItems.map((item) => item.sku)) : new Map<string, number>();\n for (const { sku, quantity } of checkoutItems) {\n const available = stock.get(sku);\n if (available !== undefined && quantity > available) {\n return jsonError(`Out of stock: ${sku}`, 409);\n }\n }\n\n const checkoutCustomFields: CheckoutCustomField[] = [\n ...staticCustomFields,\n ...offerCustomFields,\n {\n key: \"message\",\n label: { type: \"custom\", custom: \"メッセージ・ご要望(任意)\" },\n type: \"text\",\n optional: true,\n text: { maximum_length: 255 },\n },\n ];\n\n try {\n const sessionParams: Stripe.Checkout.SessionCreateParams = {\n ui_mode: \"embedded_page\",\n mode: \"payment\",\n line_items: checkoutItems.map(({ sku, quantity }) => ({\n price_data: {\n currency: \"jpy\",\n product_data: { name: products[sku]!.name },\n unit_amount: priceOverrides[sku] ?? products[sku]!.price,\n },\n quantity,\n })),\n metadata: {\n ...omitReservedMetadata(staticMetadata),\n ...omitReservedMetadata(offerMetadata),\n items: JSON.stringify(checkoutItems),\n ...(typeof offerSlug === \"string\" && offerSlug.trim() ? { offer_slug: offerSlug.trim() } : {}),\n },\n phone_number_collection: { enabled: true },\n custom_fields: checkoutCustomFields,\n shipping_address_collection: { allowed_countries: [\"JP\"] },\n ...(shippingRate\n ? {\n shipping_options: [\n {\n shipping_rate_data: {\n type: \"fixed_amount\",\n fixed_amount: { amount: shippingRate.amount, currency: \"jpy\" },\n display_name: shippingRate.label,\n delivery_estimate: {\n minimum: { unit: \"business_day\", value: 2 },\n maximum: { unit: \"business_day\", value: 5 },\n },\n },\n },\n ],\n }\n : {}),\n redirect_on_completion: \"never\",\n };\n\n const session = await stripe.checkout.sessions.create(sessionParams);\n\n return new Response(JSON.stringify({ clientSecret: session.client_secret }), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch (error) {\n const stripeError = error as {\n type?: string;\n code?: string;\n statusCode?: number;\n requestId?: string;\n };\n console.error(\"[checkout] stripe session create failed:\", {\n type: stripeError.type,\n code: stripeError.code,\n statusCode: stripeError.statusCode,\n requestId: stripeError.requestId,\n });\n\n if (stripeError.type === \"StripePermissionError\") {\n return jsonError(\"Stripe key does not have permission to create Checkout Sessions\", 503);\n }\n\n return jsonError(\"Checkout session could not be created\", 503);\n }\n };\n}\n"],"mappings":";;;;;AAAA,OAAO,YAAY;AAInB,IAAM,qBAAqB;AAC3B,IAAM,YAAY;AAClB,IAAM,eAAe;AAsCrB,SAAS,UAAU,OAAe,QAAgB;AAChD,SAAO,IAAI,SAAS,KAAK,UAAU,EAAE,MAAM,CAAC,GAAG;AAAA,IAC7C;AAAA,IACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAEA,SAAS,kBAAkB,OAAmE;AAC5F,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,SAAS,gBAAgB,OAAiC;AACxD,SAAO,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,SAAS;AAC9E;AAEA,SAAS,mBAAmB,OAAoF;AAC9G,SAAO,WAAW,SAAS,YAAY;AACzC;AAEA,SAAS,qBAAqB,UAAkC;AAC9D,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,QAAQ,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,QAAQ,WAAW,QAAQ,YAAY;AAAA,EACpF;AACF;AAEA,SAAS,aACP,OACA,UACA,UACA;AACA,QAAM,aAAa,MAAM,OAAO,CAAC,KAAK,EAAE,KAAK,SAAS,MAAM;AAC1D,WAAO,OAAO,SAAS,GAAG,GAAG,iBAAiB,KAAK;AAAA,EACrD,GAAG,CAAC;AAEJ,QAAM,OAAO,SAAS,MAAM,KAAK,CAAC,iBAAiB;AACjD,WAAO,aAAa,aAAa,UAAa,cAAc,aAAa;AAAA,EAC3E,CAAC,KAAK,SAAS,MAAM,SAAS,MAAM,SAAS,CAAC;AAE9C,SAAO;AAAA,IACL,QAAQ,KAAK;AAAA,IACb,OAAO,KAAK;AAAA,EACd;AACF;AAEO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA,cAAc,qBAAqB,CAAC;AAAA,EACpC,UAAU,iBAAiB,CAAC;AAAA,EAC5B;AAAA,EACA;AACF,GAA4C;AAC1C,SAAO,OAAO,EAAE,QAAQ,MAAM;AAC5B,UAAM,EAAE,mBAAmB,UAAU,IAAI,aAAa;AACtD,QAAI,CAAC,UAAW,QAAO,UAAU,yBAAyB,GAAG;AAE7D,UAAM,SAAS,IAAI,OAAO,WAAW;AAAA,MACnC,YAAY;AAAA,IACd,CAAC;AACD,QAAI;AAEJ,QAAI;AACF,gBAAU,MAAM,QAAQ,KAAK;AAAA,IAC/B,QAAQ;AACN,aAAO,UAAU,wBAAwB,GAAG;AAAA,IAC9C;AAEA,QAAI,CAAC,kBAAkB,OAAO,EAAG,QAAO,UAAU,wBAAwB,GAAG;AAE7E,UAAM,EAAE,MAAM,IAAI;AAClB,QAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,EAAG,QAAO,UAAU,iBAAiB,GAAG;AACtF,QAAI,MAAM,SAAS,UAAW,QAAO,UAAU,iBAAiB,GAAG;AAEnE,UAAM,WAA2B,CAAC;AAClC,eAAW,QAAQ,OAAO;AACxB,UAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO,UAAU,eAAe,GAAG;AAElF,YAAM,EAAE,KAAK,SAAS,IAAI;AAC1B,UAAI,OAAO,QAAQ,YAAY,CAAC,SAAS,GAAG,EAAG,QAAO,UAAU,eAAe,GAAG;AAClF,UAAI,CAAC,gBAAgB,QAAQ,EAAG,QAAO,UAAU,oBAAoB,GAAG;AAExE,eAAS,KAAK,EAAE,KAAK,SAAS,CAAC;AAAA,IACjC;AAEA,UAAM,cAAc,oBAAI,IAAoB;AAC5C,eAAW,EAAE,KAAK,SAAS,KAAK,UAAU;AACxC,kBAAY,IAAI,MAAM,YAAY,IAAI,GAAG,KAAK,KAAK,QAAQ;AAAA,IAC7D;AAEA,eAAW,CAAC,EAAE,KAAK,KAAK,aAAa;AACnC,UAAI,QAAQ,aAAc,QAAO,UAAU,oBAAoB,GAAG;AAAA,IACpE;AAEA,UAAM,gBAAgC,MAAM,KAAK,YAAY,QAAQ,CAAC,EAAE;AAAA,MACtE,CAAC,CAAC,KAAK,QAAQ,OAAO,EAAE,KAAK,SAAS;AAAA,IACxC;AACA,UAAM,EAAE,UAAU,IAAI;AACtB,QAAI,iBAAyC,CAAC;AAC9C,QAAI,eAAe;AACnB,QAAI,oBAA2C,CAAC;AAChD,QAAI,gBAAwC,CAAC;AAE7C,QAAI,OAAO,cAAc,YAAY,UAAU,KAAK,MAAM,IAAI;AAC5D,UAAI,CAAC,WAAY,QAAO,UAAU,uBAAuB,GAAG;AAC5D,YAAM,QAAQ,MAAM,WAAW,UAAU,KAAK,CAAC;AAC/C,UAAI,CAAC,MAAO,QAAO,UAAU,mBAAmB,GAAG;AACnD,UAAI,mBAAmB,KAAK,EAAG,QAAO,UAAU,MAAM,OAAO,MAAM,MAAM;AACzE,YAAM,aAAa,MAAM;AACzB,UAAI,cAAc,WAAW,OAAO,GAAG;AACrC,mBAAW,EAAE,KAAK,SAAS,KAAK,eAAe;AAC7C,gBAAM,YAAY,WAAW,IAAI,GAAG;AAEpC,cAAI,cAAc,UAAa,WAAW,WAAW;AACnD,mBAAO,UAAU,6BAA6B,GAAG,IAAI,GAAG;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AACA,uBAAiB,MAAM;AACvB,qBAAe,MAAM;AACrB,0BAAoB,MAAM,gBAAgB,CAAC;AAC3C,sBAAgB,MAAM,YAAY,CAAC;AAAA,IACrC;AAEA,UAAM,eAAe,WACjB,eACE,EAAE,QAAQ,GAAG,OAAO,SAAS,UAAU,IACvC,aAAa,eAAe,UAAU,QAAQ,IAChD;AACJ,UAAM,QAAQ,aAAa,MAAM,WAAW,cAAc,IAAI,CAAC,SAAS,KAAK,GAAG,CAAC,IAAI,oBAAI,IAAoB;AAC7G,eAAW,EAAE,KAAK,SAAS,KAAK,eAAe;AAC7C,YAAM,YAAY,MAAM,IAAI,GAAG;AAC/B,UAAI,cAAc,UAAa,WAAW,WAAW;AACnD,eAAO,UAAU,iBAAiB,GAAG,IAAI,GAAG;AAAA,MAC9C;AAAA,IACF;AAEA,UAAM,uBAA8C;AAAA,MAClD,GAAG;AAAA,MACH,GAAG;AAAA,MACH;AAAA,QACE,KAAK;AAAA,QACL,OAAO,EAAE,MAAM,UAAU,QAAQ,iFAAgB;AAAA,QACjD,MAAM;AAAA,QACN,UAAU;AAAA,QACV,MAAM,EAAE,gBAAgB,IAAI;AAAA,MAC9B;AAAA,IACF;AAEA,QAAI;AACF,YAAM,gBAAqD;AAAA,QACzD,SAAS;AAAA,QACT,MAAM;AAAA,QACN,YAAY,cAAc,IAAI,CAAC,EAAE,KAAK,SAAS,OAAO;AAAA,UACpD,YAAY;AAAA,YACV,UAAU;AAAA,YACV,cAAc,EAAE,MAAM,SAAS,GAAG,EAAG,KAAK;AAAA,YAC1C,aAAa,eAAe,GAAG,KAAK,SAAS,GAAG,EAAG;AAAA,UACrD;AAAA,UACA;AAAA,QACF,EAAE;AAAA,QACF,UAAU;AAAA,UACR,GAAG,qBAAqB,cAAc;AAAA,UACtC,GAAG,qBAAqB,aAAa;AAAA,UACrC,OAAO,KAAK,UAAU,aAAa;AAAA,UACnC,GAAI,OAAO,cAAc,YAAY,UAAU,KAAK,IAAI,EAAE,YAAY,UAAU,KAAK,EAAE,IAAI,CAAC;AAAA,QAC9F;AAAA,QACA,yBAAyB,EAAE,SAAS,KAAK;AAAA,QACzC,eAAe;AAAA,QACf,6BAA6B,EAAE,mBAAmB,CAAC,IAAI,EAAE;AAAA,QACzD,GAAI,eACA;AAAA,UACE,kBAAkB;AAAA,YAChB;AAAA,cACE,oBAAoB;AAAA,gBAClB,MAAM;AAAA,gBACN,cAAc,EAAE,QAAQ,aAAa,QAAQ,UAAU,MAAM;AAAA,gBAC7D,cAAc,aAAa;AAAA,gBAC3B,mBAAmB;AAAA,kBACjB,SAAS,EAAE,MAAM,gBAAgB,OAAO,EAAE;AAAA,kBAC1C,SAAS,EAAE,MAAM,gBAAgB,OAAO,EAAE;AAAA,gBAC5C;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF,IACA,CAAC;AAAA,QACL,wBAAwB;AAAA,MAC1B;AAEA,YAAM,UAAU,MAAM,OAAO,SAAS,SAAS,OAAO,aAAa;AAEnE,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,cAAc,QAAQ,cAAc,CAAC,GAAG;AAAA,QAC3E,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,cAAc;AAMpB,cAAQ,MAAM,4CAA4C;AAAA,QACxD,MAAM,YAAY;AAAA,QAClB,MAAM,YAAY;AAAA,QAClB,YAAY,YAAY;AAAA,QACxB,WAAW,YAAY;AAAA,MACzB,CAAC;AAED,UAAI,YAAY,SAAS,yBAAyB;AAChD,eAAO,UAAU,mEAAmE,GAAG;AAAA,MACzF;AAEA,aAAO,UAAU,yCAAyC,GAAG;AAAA,IAC/D;AAAA,EACF;AACF;","names":[]}
|