@umec/core 0.1.0-alpha.0 → 0.1.0-alpha.3

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.
@@ -1,11 +1,13 @@
1
+ import Stripe from 'stripe';
1
2
  import { UmecProduct, UmecConfig } from './config.js';
2
3
  import 'zod';
3
4
 
4
5
  type CheckoutProductsMap = Record<string, UmecProduct | undefined>;
6
+ type CheckoutCustomField = Stripe.Checkout.SessionCreateParams.CustomField;
5
7
  type CheckoutOffer = {
6
8
  priceOverrides: Record<string, number>;
7
9
  freeShipping: boolean;
8
- recipientInfo: boolean;
10
+ customFields?: CheckoutCustomField[];
9
11
  unitsRemaining?: Map<string, number>;
10
12
  };
11
13
  type CheckoutOfferUnavailable = {
@@ -17,12 +19,13 @@ type CheckOffer = (offerSlug: string) => Promise<CheckoutOffer | CheckoutOfferUn
17
19
  type CheckoutHandlerOptions = {
18
20
  products: CheckoutProductsMap;
19
21
  shipping?: UmecConfig["shipping"];
22
+ customFields?: CheckoutCustomField[];
20
23
  checkStock?: CheckStock;
21
24
  checkOffer?: CheckOffer;
22
25
  };
23
26
  type CheckoutHandler = ({ request }: {
24
27
  request: Request;
25
28
  }) => Promise<Response>;
26
- declare function createCheckoutHandler({ products, shipping, checkStock, checkOffer, }: CheckoutHandlerOptions): CheckoutHandler;
29
+ declare function createCheckoutHandler({ products, shipping, customFields: staticCustomFields, checkStock, checkOffer, }: CheckoutHandlerOptions): CheckoutHandler;
27
30
 
28
- export { type CheckOffer, type CheckStock, type CheckoutHandler, type CheckoutHandlerOptions, type CheckoutOffer, type CheckoutOfferUnavailable, type CheckoutProductsMap, createCheckoutHandler };
31
+ export { type CheckOffer, type CheckStock, type CheckoutCustomField, type CheckoutHandler, type CheckoutHandlerOptions, type CheckoutOffer, type CheckoutOfferUnavailable, type CheckoutProductsMap, createCheckoutHandler };
package/dist/checkout.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createCheckoutHandler
3
- } from "./chunk-2NYFCO5V.js";
3
+ } from "./chunk-23EI6CWP.js";
4
4
  import "./chunk-7TJANK6S.js";
5
5
  export {
6
6
  createCheckoutHandler
@@ -37,6 +37,7 @@ function calcShipping(items, products, shipping) {
37
37
  function createCheckoutHandler({
38
38
  products,
39
39
  shipping,
40
+ customFields: staticCustomFields = [],
40
41
  checkStock,
41
42
  checkOffer
42
43
  }) {
@@ -77,13 +78,12 @@ function createCheckoutHandler({
77
78
  const { offerSlug } = payload;
78
79
  let priceOverrides = {};
79
80
  let freeShipping = false;
80
- let recipientInfo = false;
81
+ let offerCustomFields = [];
81
82
  if (typeof offerSlug === "string" && offerSlug.trim() !== "") {
82
83
  if (!checkOffer) return jsonError("Offer not available", 503);
83
84
  const offer = await checkOffer(offerSlug.trim());
84
85
  if (!offer) return jsonError("Offer not found", 404);
85
86
  if (isOfferUnavailable(offer)) return jsonError(offer.error, offer.status);
86
- recipientInfo = offer.recipientInfo;
87
87
  const offerUnits = offer.unitsRemaining;
88
88
  if (offerUnits && offerUnits.size > 0) {
89
89
  for (const { sku, quantity } of checkoutItems) {
@@ -95,6 +95,7 @@ function createCheckoutHandler({
95
95
  }
96
96
  priceOverrides = offer.priceOverrides;
97
97
  freeShipping = offer.freeShipping;
98
+ offerCustomFields = offer.customFields ?? [];
98
99
  }
99
100
  const shippingRate = shipping ? freeShipping ? { amount: 0, label: shipping.freeLabel } : calcShipping(checkoutItems, products, shipping) : void 0;
100
101
  const stock = checkStock ? await checkStock(checkoutItems.map((item) => item.sku)) : /* @__PURE__ */ new Map();
@@ -104,32 +105,17 @@ function createCheckoutHandler({
104
105
  return jsonError(`Out of stock: ${sku}`, 409);
105
106
  }
106
107
  }
107
- const customFields = [];
108
- if (recipientInfo) {
109
- customFields.push(
110
- {
111
- key: "class_name",
112
- label: { type: "custom", custom: "\u30AF\u30E9\u30B9\u540D" },
113
- type: "text",
114
- optional: false,
115
- text: { maximum_length: 50 }
116
- },
117
- {
118
- key: "child_name",
119
- label: { type: "custom", custom: "\u304A\u5B50\u3055\u3093\u306E\u304A\u540D\u524D" },
120
- type: "text",
121
- optional: false,
122
- text: { maximum_length: 50 }
123
- }
124
- );
125
- }
126
- customFields.push({
127
- key: "message",
128
- label: { type: "custom", custom: "\u30E1\u30C3\u30BB\u30FC\u30B8\u30FB\u3054\u8981\u671B\uFF08\u4EFB\u610F\uFF09" },
129
- type: "text",
130
- optional: true,
131
- text: { maximum_length: 255 }
132
- });
108
+ const checkoutCustomFields = [
109
+ ...staticCustomFields,
110
+ ...offerCustomFields,
111
+ {
112
+ key: "message",
113
+ label: { type: "custom", custom: "\u30E1\u30C3\u30BB\u30FC\u30B8\u30FB\u3054\u8981\u671B\uFF08\u4EFB\u610F\uFF09" },
114
+ type: "text",
115
+ optional: true,
116
+ text: { maximum_length: 255 }
117
+ }
118
+ ];
133
119
  try {
134
120
  const sessionParams = {
135
121
  ui_mode: "embedded_page",
@@ -147,7 +133,7 @@ function createCheckoutHandler({
147
133
  ...typeof offerSlug === "string" && offerSlug.trim() ? { offer_slug: offerSlug.trim() } : {}
148
134
  },
149
135
  phone_number_collection: { enabled: true },
150
- custom_fields: customFields,
136
+ custom_fields: checkoutCustomFields,
151
137
  shipping_address_collection: { allowed_countries: ["JP"] },
152
138
  ...shippingRate ? {
153
139
  shipping_options: [
@@ -189,4 +175,4 @@ function createCheckoutHandler({
189
175
  export {
190
176
  createCheckoutHandler
191
177
  };
192
- //# sourceMappingURL=chunk-2NYFCO5V.js.map
178
+ //# sourceMappingURL=chunk-23EI6CWP.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 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 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 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 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\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 }\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 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":"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","names":[]}
@@ -5,12 +5,6 @@ var SECURITY_HEADERS = {
5
5
  "X-Frame-Options": "SAMEORIGIN",
6
6
  "Referrer-Policy": "strict-origin-when-cross-origin"
7
7
  };
8
- var LIMITED_DOCS = [
9
- {
10
- pathPrefix: "/docs/asahiko2026special",
11
- expiresAt: Date.parse("2026-08-12T14:59:59.999Z")
12
- }
13
- ];
14
8
  function withSecurityHeaders(response) {
15
9
  const securedResponse = new Response(response.body, response);
16
10
  for (const [name, value] of Object.entries(SECURITY_HEADERS)) {
@@ -18,11 +12,11 @@ function withSecurityHeaders(response) {
18
12
  }
19
13
  return securedResponse;
20
14
  }
21
- function createUmecMiddleware({ maintenanceMode }) {
15
+ function createUmecMiddleware({ maintenanceMode, limitedPages = [] }) {
22
16
  return async (context, next) => {
23
17
  const path = context.url.pathname;
24
- const limitedDoc = LIMITED_DOCS.find(({ pathPrefix }) => path === pathPrefix || path.startsWith(`${pathPrefix}/`));
25
- if (limitedDoc && Date.now() > limitedDoc.expiresAt) {
18
+ const limitedPage = limitedPages.find(({ pathPrefix }) => path === pathPrefix || path.startsWith(`${pathPrefix}/`));
19
+ if (limitedPage && Date.now() > limitedPage.expiresAt) {
26
20
  return withSecurityHeaders(
27
21
  new Response("Not found", {
28
22
  status: 404,
@@ -44,4 +38,4 @@ function createUmecMiddleware({ maintenanceMode }) {
44
38
  export {
45
39
  createUmecMiddleware
46
40
  };
47
- //# sourceMappingURL=chunk-WZFM6R5J.js.map
41
+ //# sourceMappingURL=chunk-4V4GYE7E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["const SECURITY_HEADERS = {\n \"Strict-Transport-Security\": \"max-age=63072000; includeSubDomains\",\n \"X-Content-Type-Options\": \"nosniff\",\n \"X-Frame-Options\": \"SAMEORIGIN\",\n \"Referrer-Policy\": \"strict-origin-when-cross-origin\",\n};\n\n// Astro の ValidRedirectStatus と同一のユニオンに揃える。\n// number に広げると Astro の APIContext が strict モードで代入不能になる(TS2345)\nexport type UmecRedirectStatus = 300 | 301 | 302 | 303 | 304 | 307 | 308;\n\nexport type UmecMiddlewareContext = {\n url: URL;\n redirect: (path: string, status?: UmecRedirectStatus) => Response;\n};\n\nexport type UmecMiddlewareNext = () => Response | Promise<Response>;\n\nexport type UmecMiddleware = (context: UmecMiddlewareContext, next: UmecMiddlewareNext) => Promise<Response>;\n\nexport type UmecLimitedPage = {\n pathPrefix: string;\n expiresAt: number;\n};\n\nexport type UmecMiddlewareOptions = {\n maintenanceMode: boolean;\n limitedPages?: UmecLimitedPage[];\n};\n\nfunction withSecurityHeaders(response: Response) {\n const securedResponse = new Response(response.body, response);\n\n for (const [name, value] of Object.entries(SECURITY_HEADERS)) {\n securedResponse.headers.set(name, value);\n }\n\n return securedResponse;\n}\n\nexport function createUmecMiddleware({ maintenanceMode, limitedPages = [] }: UmecMiddlewareOptions): UmecMiddleware {\n return async (context, next) => {\n const path = context.url.pathname;\n\n const limitedPage = limitedPages.find(({ pathPrefix }) => path === pathPrefix || path.startsWith(`${pathPrefix}/`));\n if (limitedPage && Date.now() > limitedPage.expiresAt) {\n return withSecurityHeaders(\n new Response(\"Not found\", {\n status: 404,\n headers: {\n \"Cache-Control\": \"no-store\",\n },\n }),\n );\n }\n\n if (maintenanceMode && path !== \"/maintenance\" && !path.startsWith(\"/api/\")) {\n const response = context.redirect(\"/maintenance\", 302);\n response.headers.set(\"Cache-Control\", \"no-store\");\n return response;\n }\n\n return withSecurityHeaders(await next());\n };\n}\n"],"mappings":";AAAA,IAAM,mBAAmB;AAAA,EACvB,6BAA6B;AAAA,EAC7B,0BAA0B;AAAA,EAC1B,mBAAmB;AAAA,EACnB,mBAAmB;AACrB;AAyBA,SAAS,oBAAoB,UAAoB;AAC/C,QAAM,kBAAkB,IAAI,SAAS,SAAS,MAAM,QAAQ;AAE5D,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AAC5D,oBAAgB,QAAQ,IAAI,MAAM,KAAK;AAAA,EACzC;AAEA,SAAO;AACT;AAEO,SAAS,qBAAqB,EAAE,iBAAiB,eAAe,CAAC,EAAE,GAA0C;AAClH,SAAO,OAAO,SAAS,SAAS;AAC9B,UAAM,OAAO,QAAQ,IAAI;AAEzB,UAAM,cAAc,aAAa,KAAK,CAAC,EAAE,WAAW,MAAM,SAAS,cAAc,KAAK,WAAW,GAAG,UAAU,GAAG,CAAC;AAClH,QAAI,eAAe,KAAK,IAAI,IAAI,YAAY,WAAW;AACrD,aAAO;AAAA,QACL,IAAI,SAAS,aAAa;AAAA,UACxB,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,iBAAiB;AAAA,UACnB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI,mBAAmB,SAAS,kBAAkB,CAAC,KAAK,WAAW,OAAO,GAAG;AAC3E,YAAM,WAAW,QAAQ,SAAS,gBAAgB,GAAG;AACrD,eAAS,QAAQ,IAAI,iBAAiB,UAAU;AAChD,aAAO;AAAA,IACT;AAEA,WAAO,oBAAoB,MAAM,KAAK,CAAC;AAAA,EACzC;AACF;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  export { AccessGuardOptions, AccessGuardResult, createAccessGuard } from './access.js';
2
- export { CheckOffer, CheckStock, CheckoutHandler, CheckoutHandlerOptions, CheckoutOffer, CheckoutOfferUnavailable, CheckoutProductsMap, createCheckoutHandler } from './checkout.js';
2
+ export { CheckOffer, CheckStock, CheckoutCustomField, CheckoutHandler, CheckoutHandlerOptions, CheckoutOffer, CheckoutOfferUnavailable, CheckoutProductsMap, createCheckoutHandler } from './checkout.js';
3
3
  export { UmecConfig, UmecConfigInput, UmecProduct, UmecShipping, defineUmecConfig, formatEmailFrom, umecConfigSchema } from './config.js';
4
4
  export { ServerEnv, getServerEnv } from './env.js';
5
- export { UmecMiddleware, UmecMiddlewareContext, UmecMiddlewareNext, UmecMiddlewareOptions, createUmecMiddleware } from './middleware.js';
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
8
  export { OrderEmailData, OrderEmailLineItem, RenderOrderEmail, SupportedWebhookEvent, WebhookHandler, WebhookHandlerOptions, WebhookItem, WebhookOrder, WebhookRecordedOrder, createWebhookHandler, GET as webhookGET } from './webhook.js';
9
9
  import 'jose';
10
- import 'zod';
11
10
  import 'stripe';
11
+ import 'zod';
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-2SNGXEIL.js";
8
8
  import {
9
9
  createCheckoutHandler
10
- } from "./chunk-2NYFCO5V.js";
10
+ } from "./chunk-23EI6CWP.js";
11
11
  import {
12
12
  defineUmecConfig,
13
13
  formatEmailFrom,
@@ -15,7 +15,7 @@ import {
15
15
  } from "./chunk-7ZJOZAEO.js";
16
16
  import {
17
17
  createUmecMiddleware
18
- } from "./chunk-WZFM6R5J.js";
18
+ } from "./chunk-4V4GYE7E.js";
19
19
  import {
20
20
  notifyUnshipped
21
21
  } from "./chunk-QRFFBMFN.js";
@@ -5,9 +5,14 @@ type UmecMiddlewareContext = {
5
5
  };
6
6
  type UmecMiddlewareNext = () => Response | Promise<Response>;
7
7
  type UmecMiddleware = (context: UmecMiddlewareContext, next: UmecMiddlewareNext) => Promise<Response>;
8
+ type UmecLimitedPage = {
9
+ pathPrefix: string;
10
+ expiresAt: number;
11
+ };
8
12
  type UmecMiddlewareOptions = {
9
13
  maintenanceMode: boolean;
14
+ limitedPages?: UmecLimitedPage[];
10
15
  };
11
- declare function createUmecMiddleware({ maintenanceMode }: UmecMiddlewareOptions): UmecMiddleware;
16
+ declare function createUmecMiddleware({ maintenanceMode, limitedPages }: UmecMiddlewareOptions): UmecMiddleware;
12
17
 
13
- export { type UmecMiddleware, type UmecMiddlewareContext, type UmecMiddlewareNext, type UmecMiddlewareOptions, type UmecRedirectStatus, createUmecMiddleware };
18
+ export { type UmecLimitedPage, type UmecMiddleware, type UmecMiddlewareContext, type UmecMiddlewareNext, type UmecMiddlewareOptions, type UmecRedirectStatus, createUmecMiddleware };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createUmecMiddleware
3
- } from "./chunk-WZFM6R5J.js";
3
+ } from "./chunk-4V4GYE7E.js";
4
4
  export {
5
5
  createUmecMiddleware
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umec/core",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "Headless EC logic for umec — config schema and shared handlers",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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 CheckoutOffer = {\n priceOverrides: Record<string, number>;\n freeShipping: boolean;\n recipientInfo: boolean;\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 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 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 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 recipientInfo = false;\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 recipientInfo = offer.recipientInfo;\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 あり — 低頻度の emu では許容\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 }\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 // TOCTOU race is acceptable for low-frequency emu sales. Move to Durable Objects if needed.\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 customFields: Stripe.Checkout.SessionCreateParams.CustomField[] = [];\n if (recipientInfo) {\n customFields.push(\n {\n key: \"class_name\",\n label: { type: \"custom\", custom: \"クラス名\" },\n type: \"text\",\n optional: false,\n text: { maximum_length: 50 },\n },\n {\n key: \"child_name\",\n label: { type: \"custom\", custom: \"お子さんのお名前\" },\n type: \"text\",\n optional: false,\n text: { maximum_length: 50 },\n },\n );\n }\n customFields.push({\n key: \"message\",\n label: { type: \"custom\", custom: \"メッセージ・ご要望(任意)\" },\n type: \"text\",\n optional: true,\n text: { maximum_length: 255 },\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 items: JSON.stringify(checkoutItems),\n ...(typeof offerSlug === \"string\" && offerSlug.trim() ? { offer_slug: offerSlug.trim() } : {}),\n },\n phone_number_collection: { enabled: true },\n custom_fields: customFields,\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":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["const SECURITY_HEADERS = {\n \"Strict-Transport-Security\": \"max-age=63072000; includeSubDomains\",\n \"X-Content-Type-Options\": \"nosniff\",\n \"X-Frame-Options\": \"SAMEORIGIN\",\n \"Referrer-Policy\": \"strict-origin-when-cross-origin\",\n};\n\nconst LIMITED_DOCS = [\n {\n pathPrefix: \"/docs/asahiko2026special\",\n expiresAt: Date.parse(\"2026-08-12T14:59:59.999Z\"),\n },\n] as const;\n\n// Astro の ValidRedirectStatus と同一のユニオンに揃える。\n// number に広げると Astro の APIContext が strict モードで代入不能になる(TS2345)\nexport type UmecRedirectStatus = 300 | 301 | 302 | 303 | 304 | 307 | 308;\n\nexport type UmecMiddlewareContext = {\n url: URL;\n redirect: (path: string, status?: UmecRedirectStatus) => Response;\n};\n\nexport type UmecMiddlewareNext = () => Response | Promise<Response>;\n\nexport type UmecMiddleware = (context: UmecMiddlewareContext, next: UmecMiddlewareNext) => Promise<Response>;\n\nexport type UmecMiddlewareOptions = {\n maintenanceMode: boolean;\n};\n\nfunction withSecurityHeaders(response: Response) {\n const securedResponse = new Response(response.body, response);\n\n for (const [name, value] of Object.entries(SECURITY_HEADERS)) {\n securedResponse.headers.set(name, value);\n }\n\n return securedResponse;\n}\n\nexport function createUmecMiddleware({ maintenanceMode }: UmecMiddlewareOptions): UmecMiddleware {\n return async (context, next) => {\n const path = context.url.pathname;\n\n const limitedDoc = LIMITED_DOCS.find(({ pathPrefix }) => path === pathPrefix || path.startsWith(`${pathPrefix}/`));\n if (limitedDoc && Date.now() > limitedDoc.expiresAt) {\n return withSecurityHeaders(\n new Response(\"Not found\", {\n status: 404,\n headers: {\n \"Cache-Control\": \"no-store\",\n },\n }),\n );\n }\n\n if (maintenanceMode && path !== \"/maintenance\" && !path.startsWith(\"/api/\")) {\n const response = context.redirect(\"/maintenance\", 302);\n response.headers.set(\"Cache-Control\", \"no-store\");\n return response;\n }\n\n return withSecurityHeaders(await next());\n };\n}\n"],"mappings":";AAAA,IAAM,mBAAmB;AAAA,EACvB,6BAA6B;AAAA,EAC7B,0BAA0B;AAAA,EAC1B,mBAAmB;AAAA,EACnB,mBAAmB;AACrB;AAEA,IAAM,eAAe;AAAA,EACnB;AAAA,IACE,YAAY;AAAA,IACZ,WAAW,KAAK,MAAM,0BAA0B;AAAA,EAClD;AACF;AAmBA,SAAS,oBAAoB,UAAoB;AAC/C,QAAM,kBAAkB,IAAI,SAAS,SAAS,MAAM,QAAQ;AAE5D,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AAC5D,oBAAgB,QAAQ,IAAI,MAAM,KAAK;AAAA,EACzC;AAEA,SAAO;AACT;AAEO,SAAS,qBAAqB,EAAE,gBAAgB,GAA0C;AAC/F,SAAO,OAAO,SAAS,SAAS;AAC9B,UAAM,OAAO,QAAQ,IAAI;AAEzB,UAAM,aAAa,aAAa,KAAK,CAAC,EAAE,WAAW,MAAM,SAAS,cAAc,KAAK,WAAW,GAAG,UAAU,GAAG,CAAC;AACjH,QAAI,cAAc,KAAK,IAAI,IAAI,WAAW,WAAW;AACnD,aAAO;AAAA,QACL,IAAI,SAAS,aAAa;AAAA,UACxB,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,iBAAiB;AAAA,UACnB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI,mBAAmB,SAAS,kBAAkB,CAAC,KAAK,WAAW,OAAO,GAAG;AAC3E,YAAM,WAAW,QAAQ,SAAS,gBAAgB,GAAG;AACrD,eAAS,QAAQ,IAAI,iBAAiB,UAAU;AAChD,aAAO;AAAA,IACT;AAEA,WAAO,oBAAoB,MAAM,KAAK,CAAC;AAAA,EACzC;AACF;","names":[]}