sanity-plugin-stripe 0.1.0 → 0.1.1
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/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/package.json +15 -34
package/dist/server.js
CHANGED
|
@@ -670,7 +670,7 @@ function createStripe(secretKey, config = {}) {
|
|
|
670
670
|
apiVersion: STRIPE_API_VERSION,
|
|
671
671
|
appInfo: {
|
|
672
672
|
name: "sanity-plugin-stripe",
|
|
673
|
-
version: "0.1.
|
|
673
|
+
version: "0.1.1",
|
|
674
674
|
url: "https://github.com/ConfederationStudio/sanity-plugin-stripe"
|
|
675
675
|
}
|
|
676
676
|
});
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":[],"sources":["../src/server/email.ts","../src/server/events.ts","../src/core/queries.ts","../src/core/regions.ts","../src/server/checkout.ts","../src/server/once.ts","../src/server/order.ts","../src/server/sanity.ts","../src/server/stock.ts","../src/server/routes.ts","../src/version.ts","../src/server/stripe.ts"],"sourcesContent":["import type Stripe from 'stripe'\n\nimport {formatMoney} from '../core/money'\nimport type {Order} from './order'\nimport type {StockChange} from './stock'\n\nconst escapeHtml = (value: string) =>\n value.replace(\n /[&<>\"']/g,\n (char) => ({'&': '&', '<': '<', '>': '>', '\"': '"', \"'\": '''})[char] ?? char,\n )\n\nfunction addressLines(name: string | null, address: Stripe.Address | null): string[] {\n if (!address) return name ? [name] : []\n const city = [address.postal_code, address.city].filter(Boolean).join(' ')\n return [name, address.line1, address.line2, city, address.state, address.country].filter(\n (line): line is string => Boolean(line),\n )\n}\n\n/** Where the payment is in the Stripe Dashboard. */\nexport function dashboardUrl(\n order: Pick<Order, 'livemode' | 'paymentIntentId' | 'sessionId'>,\n): string {\n const base = `https://dashboard.stripe.com/${order.livemode ? '' : 'test/'}`\n return order.paymentIntentId\n ? `${base}payments/${order.paymentIntentId}`\n : `${base}checkout/sessions/${order.sessionId}`\n}\n\nexport interface OrderEmailOptions {\n /** Starts the subject line. */\n shopName: string\n /** For amounts: a BCP 47 tag such as `en`, `nl-NL`. Default `en`. */\n locale?: string\n /** What the order did to stock counts. Anything sold below 0 is called out. */\n stock?: readonly StockChange[]\n /** Why stock could not be lowered, from the webhook's context. The email says to check it. */\n stockError?: string\n}\n\nexport interface OrderEmail {\n subject: string\n text: string\n html: string\n}\n\n/** A plain order email for the shop: what to pack, where to send it and who paid. */\nexport function orderEmail(order: Order, options: OrderEmailOptions): OrderEmail {\n const {shopName, locale = 'en', stock = [], stockError} = options\n const money = (minor: number) => formatMoney(minor, order.currency, locale)\n const count = order.items.reduce((sum, item) => sum + item.quantity, 0)\n const who = order.shipping.name ?? order.customer.name ?? order.customer.email ?? 'a customer'\n const subject = `${order.livemode ? '' : '[Test] '}${shopName} order from ${who}: ${count} ${count === 1 ? 'item' : 'items'}, ${money(order.amountTotal)}`\n const address = addressLines(order.shipping.name, order.shipping.address)\n const contact = [order.customer.email, order.customer.phone].filter((line): line is string =>\n Boolean(line),\n )\n const shippingLabel = order.regionTitle ? `Shipping (${order.regionTitle})` : 'Shipping'\n const link = dashboardUrl(order)\n const date = order.createdAt.toISOString().slice(0, 10)\n\n const oversold = stock\n .filter((change) => change.to < 0)\n .map((change) => {\n const item = order.items.find(\n (candidate) =>\n candidate.productId === change.productId &&\n (candidate.variantKey ?? '') === (change.variantKey ?? ''),\n )\n return item?.name ?? change.productId\n })\n const warnings = [\n ...(oversold.length\n ? [`Check your stock: more was sold than was counted in stock for ${oversold.join(', ')}.`]\n : []),\n ...(stockError\n ? [`Stock counts were not lowered for this order. Change them by hand. (${stockError})`]\n : []),\n ]\n\n const text = [\n `New order on ${date}${order.livemode ? '' : ' (test mode, no money moved)'}`,\n '',\n ...warnings.flatMap((warning) => [warning, '']),\n ...order.items.map((item) => `${item.quantity} × ${item.name} ${money(item.amountTotal)}`),\n `${shippingLabel} ${money(order.amountShipping)}`,\n `Total ${money(order.amountTotal)}`,\n '',\n 'Ship to:',\n ...address,\n '',\n 'Contact:',\n ...contact,\n '',\n `In Stripe: ${link}`,\n ].join('\\n')\n\n const rows = order.items\n .map(\n (item) =>\n `<tr><td style=\"padding:4px 12px 4px 0\">${item.quantity} ×</td><td style=\"padding:4px 12px 4px 0\">${escapeHtml(item.name)}</td><td style=\"padding:4px 0;text-align:right\">${money(item.amountTotal)}</td></tr>`,\n )\n .join('')\n const html = `<div style=\"font-family:Helvetica,Arial,sans-serif;font-size:15px;line-height:1.45;color:#191919\">\n<p>New order on ${date}${order.livemode ? '' : ' <strong>(test mode, no money moved)</strong>'}</p>\n${warnings.map((warning) => `<p><strong>${escapeHtml(warning)}</strong></p>\\n`).join('')}<table style=\"border-collapse:collapse\">${rows}\n<tr><td></td><td style=\"padding:4px 12px 4px 0\">${escapeHtml(shippingLabel)}</td><td style=\"padding:4px 0;text-align:right\">${money(order.amountShipping)}</td></tr>\n<tr><td></td><td style=\"padding:4px 12px 4px 0\"><strong>Total</strong></td><td style=\"padding:4px 0;text-align:right\"><strong>${money(order.amountTotal)}</strong></td></tr>\n</table>\n<p><strong>Ship to</strong><br>${address.map(escapeHtml).join('<br>')}</p>\n<p><strong>Contact</strong><br>${contact.map(escapeHtml).join('<br>')}</p>\n<p><a href=\"${link}\">Open the payment in Stripe</a></p>\n</div>`\n\n return {subject, text, html}\n}\n","import type Stripe from 'stripe'\n\n/** Checks the signature and parses the event. Throws when the signature doesn't match. */\nexport function verifyEvent(\n stripe: Stripe,\n payload: string,\n signature: string | null,\n secret: string,\n): Stripe.Event {\n if (!signature) throw new Error('Missing Stripe-Signature header')\n return stripe.webhooks.constructEvent(payload, signature, secret)\n}\n\nexport type CheckoutEvent =\n /** Paid: fulfil the order. */\n | {type: 'paid'; session: Stripe.Checkout.Session}\n /** Completed, but a delayed payment method hasn't settled yet. Wait for async_payment_succeeded. */\n | {type: 'pending'; session: Stripe.Checkout.Session}\n /** A delayed payment failed. Nothing to fulfil. */\n | {type: 'failed'; session: Stripe.Checkout.Session}\n | {type: 'ignored'}\n\n/** The events a webhook endpoint for the shop listens to. */\nexport const WEBHOOK_EVENTS = [\n 'checkout.session.completed',\n 'checkout.session.async_payment_succeeded',\n 'checkout.session.async_payment_failed',\n] as const\n\n/** What a webhook event means for the shop. Only `paid` is fulfilled. */\nexport function classifyEvent(event: Stripe.Event): CheckoutEvent {\n switch (event.type) {\n case 'checkout.session.completed':\n case 'checkout.session.async_payment_succeeded': {\n const session = event.data.object\n return session.payment_status === 'unpaid'\n ? {type: 'pending', session}\n : {type: 'paid', session}\n }\n case 'checkout.session.async_payment_failed':\n return {type: 'failed', session: event.data.object}\n default:\n return {type: 'ignored'}\n }\n}\n","import {SHOP_SETTINGS_ID, SHOP_SETTINGS_TYPE} from './ids'\n\n/** Where a site keeps its products, for the queries the plugin writes. */\nexport interface ProductQueryOptions {\n /** Document types that can be bought. Default `['product']`. */\n types?: readonly string[]\n /** The name of the `stripeCommerce` field on those types. Default `commerce`. */\n field?: string\n /** GROQ for the product's name. Default `title`. */\n title?: string\n /** GROQ for the product's slug. Default `slug.current`. */\n slug?: string\n /** GROQ for the URL of the image shown in the cart and on Stripe's page. */\n image?: string\n}\n\nconst NAME = /^[A-Za-z_]\\w*$/\nconst TYPE_NAME = /^[A-Za-z_][\\w.-]*$/\n\n/** The options with their defaults, checked: names end up inside GROQ. */\nexport function resolveProductQuery(options: ProductQueryOptions = {}) {\n const types = options.types?.length ? [...options.types] : ['product']\n const field = options.field ?? 'commerce'\n for (const type of types) {\n if (!TYPE_NAME.test(type))\n throw new TypeError(`sanity-plugin-stripe: \"${type}\" is not a document type name`)\n }\n if (!NAME.test(field)) throw new TypeError(`sanity-plugin-stripe: \"${field}\" is not a field name`)\n return {\n types,\n field,\n title: options.title ?? 'title',\n slug: options.slug ?? 'slug.current',\n image:\n options.image ?? 'coalesce(images[0].asset->url, image.asset->url, mainImage.asset->url)',\n }\n}\n\nconst typeFilter = (types: readonly string[]) =>\n `_type in [${types.map((type) => JSON.stringify(type)).join(', ')}]`\n\n/** The price-and-availability field as the cart and the checkout read it. No stock counts. */\nexport const COMMERCE_PROJECTION = `{availability, price, taxClass, variants[]{_key, label, availability}}`\n\n/**\n * What the cart and the checkout need of a product, for the site's own queries:\n *\n * ```ts\n * defineQuery(`*[_type == \"product\"]{${cartProductProjection()}}`)\n * ```\n */\nexport function cartProductProjection(options?: ProductQueryOptions): string {\n const {field, title, slug, image} = resolveProductQuery(options)\n return `_id, \"title\": ${title}, \"slug\": ${slug}, \"imageUrl\": ${image}, \"commerce\": ${field}${COMMERCE_PROJECTION}`\n}\n\n/** Every product that has the commerce field filled in, for the cart page. */\nexport function cartProductsQuery(options?: ProductQueryOptions): string {\n const {types, field} = resolveProductQuery(options)\n return `*[${typeFilter(types)} && defined(${field})]{${cartProductProjection(options)}}`\n}\n\n/** The products with the ids in `$ids`, for the checkout. */\nexport function checkoutProductsQuery(options?: ProductQueryOptions): string {\n const {types} = resolveProductQuery(options)\n return `*[${typeFilter(types)} && _id in $ids]{${cartProductProjection(options)}}`\n}\n\n/**\n * Every version of the products in `productIds` (published, draft, and copies inside a Content\n * Release), with the keys of its variants: what the webhook marks sold out. It needs the raw\n * perspective, a token, and API version 2025-02-19 or later.\n */\nexport function productVersionsQuery(\n productIds: readonly string[],\n options?: ProductQueryOptions,\n): {query: string; params: Record<string, string>} {\n const {types, field} = resolveProductQuery(options)\n const params: Record<string, string> = {}\n const anyVersion = productIds.map((id, index) => {\n params[`product${index}`] = id\n return `sanity::versionOf($product${index})`\n })\n return {\n query: `*[${typeFilter(types)} && defined(${field}) && (${anyVersion.join(' || ') || 'false'})]{_id, \"variants\": ${field}.variants[]._key}`,\n params,\n }\n}\n\n/** The shop settings: shipping regions and whether stock sells out by itself. */\nexport const SHOP_SETTINGS_QUERY = `*[_type == \"${SHOP_SETTINGS_TYPE}\" && _id == \"${SHOP_SETTINGS_ID}\"][0]{shippingRegions[]{_key, title, everywhereElse, countries, rate}, autoSoldOut}`\n","import {STRIPE_SHIPPING_COUNTRIES} from './countries'\nimport {asArray, asNumber, asString, isObject} from './guards'\n\n/** A shipping region with one flat rate per order. */\nexport interface ShippingRegion {\n id: string\n title: string\n /** ISO 3166-1 alpha-2 codes. Empty for the \"everywhere else\" region until resolved. */\n countries: string[]\n everywhereElse: boolean\n /** In currency units. */\n rate: number\n}\n\n/** The shop settings document, as `SHOP_SETTINGS_QUERY` returns it. */\nexport interface ShopSettings {\n shippingRegions?: Array<{\n _key: string\n title?: string | null\n everywhereElse?: boolean | null\n countries?: string[] | null\n rate?: number | null\n }> | null\n autoSoldOut?: boolean | null\n}\n\n/** The shop settings as Sanity returned them, or null when they aren't published yet. */\nexport function readShopSettings(value: unknown): ShopSettings | null {\n if (!isObject(value)) return null\n return {\n shippingRegions: asArray(value.shippingRegions).flatMap((region) =>\n isObject(region) && typeof region._key === 'string'\n ? [\n {\n _key: region._key,\n title: asString(region.title),\n everywhereElse: region.everywhereElse === true,\n countries: asArray(region.countries).filter(\n (country): country is string => typeof country === 'string',\n ),\n rate: asNumber(region.rate),\n },\n ]\n : [],\n ),\n autoSoldOut: typeof value.autoSoldOut === 'boolean' ? value.autoSoldOut : null,\n }\n}\n\n/** Shipping regions from the shop settings, leaving out any that aren't filled in. */\nexport function regionsFromSettings(settings: ShopSettings | null | undefined): ShippingRegion[] {\n return (settings?.shippingRegions ?? []).flatMap((region) =>\n region.title && typeof region.rate === 'number'\n ? [\n {\n id: region._key,\n title: region.title,\n countries: region.countries ?? [],\n everywhereElse: Boolean(region.everywhereElse),\n rate: region.rate,\n },\n ]\n : [],\n )\n}\n\n/**\n * Fills in the \"everywhere else\" region with every country Stripe ships to that no other region\n * lists, and drops country codes Stripe doesn't accept. Regions left without countries are removed.\n */\nexport function resolveRegions(regions: readonly ShippingRegion[]): ShippingRegion[] {\n const supported = new Set<string>(STRIPE_SHIPPING_COUNTRIES)\n const listed = new Set(\n regions.filter((region) => !region.everywhereElse).flatMap((region) => region.countries),\n )\n return regions\n .map((region) => ({\n ...region,\n countries: region.everywhereElse\n ? STRIPE_SHIPPING_COUNTRIES.filter((country) => !listed.has(country))\n : region.countries.filter((country) => supported.has(country)),\n }))\n .filter((region) => region.countries.length > 0)\n}\n\n/** What a cart page needs of each region: no country lists. */\nexport type RegionOption = Pick<ShippingRegion, 'id' | 'title' | 'rate'>\n\n/** The regions a buyer can choose from, the same ones the checkout accepts. */\nexport function regionOptions(settings: ShopSettings | null | undefined): RegionOption[] {\n return resolveRegions(regionsFromSettings(settings)).map(({id, title, rate}) => ({\n id,\n title,\n rate,\n }))\n}\n","import type Stripe from 'stripe'\n\nimport {\n lineName,\n lineStatus,\n MAX_QUANTITY,\n sanitizeLines,\n type CartLine,\n type CartProduct,\n type CheckoutProblem,\n} from '../core/cart'\nimport {toMinorUnits} from '../core/money'\nimport {resolveRegions, type ShippingRegion} from '../core/regions'\nimport {stockLeft, type ProductStock} from '../core/stock'\n\n/**\n * Builds a Checkout Session from the cart and fresh product data. Nothing here trusts a price\n * sent by the browser: the cart holds references, and prices come from `products`.\n */\n\ntype SessionParams = Stripe.Checkout.SessionCreateParams\ntype LineItemParams = Stripe.Checkout.SessionCreateParams.LineItem\n\nexport interface CheckoutInput {\n /** The cart as the browser sent it. Checked here, so pass the raw value. */\n lines: unknown\n /** The shipping region the buyer chose, as the browser sent it. */\n regionId: unknown\n /** Fresh product data (not from a CDN cache) for every product in the cart. */\n products: readonly CartProduct[]\n /** Stock by product id, from `readStock`. Without it nothing is counted. */\n stock?: ReadonlyMap<string, ProductStock>\n /** Shipping regions from the shop settings, before `resolveRegions`. */\n regions: readonly ShippingRegion[]\n /** ISO 4217 code, such as `eur`. */\n currency: string\n successUrl: string\n cancelUrl: string\n /** Marks the session as this shop's, for the webhook and the Stripe Dashboard. */\n integrationIdentifier: string\n /** Stripe tax rate ids (`txr_…`) by tax class, so receipts show the tax. */\n taxRates?: Partial<Record<string, string>>\n /** Most of one item a buyer can order at once. Default 10. */\n maxQuantity?: number\n /** Don't say how many are left when a cart asks for too many. */\n hideRemaining?: boolean\n /** When the session stops taking payment, as a Unix time. Stripe's default is 24 hours on. */\n expiresAt?: number\n}\n\nexport type CheckoutResult =\n | {ok: true; params: SessionParams; lines: CartLine[]; region: ShippingRegion}\n | {ok: false; error: 'empty' | 'region' | 'unavailable'; problems: CheckoutProblem[]}\n\nexport function buildCheckoutSession(input: CheckoutInput): CheckoutResult {\n const max = input.maxQuantity ?? MAX_QUANTITY\n const currency = input.currency.toLowerCase()\n const lines = sanitizeLines(input.lines, max)\n if (!lines.length) return {ok: false, error: 'empty', problems: []}\n\n const region = resolveRegions(input.regions).find((candidate) => candidate.id === input.regionId)\n if (!region) return {ok: false, error: 'region', problems: []}\n\n const products = new Map(input.products.map((product) => [product._id, product]))\n const problems: CheckoutProblem[] = []\n const lineItems: LineItemParams[] = []\n\n for (const line of lines) {\n const product = products.get(line.productId)\n const status = lineStatus(product, line.variantKey, max)\n const ref = {\n productId: line.productId,\n ...(line.variantKey ? {variantKey: line.variantKey} : {}),\n }\n if (!product || !status.available) {\n problems.push({\n ...ref,\n ...(product?.title ? {name: product.title} : {}),\n reason: status.available ? 'missing' : status.reason,\n })\n continue\n }\n const name = lineName(product, status.variant)\n\n // Counts are private, so this is the one place a buyer meets them.\n const left = stockLeft(input.stock?.get(line.productId), line.variantKey)\n if (left !== null && left <= 0) {\n problems.push({...ref, name, reason: 'soldOut'})\n continue\n }\n const most = left === null ? status.maxQuantity : Math.min(status.maxQuantity, left)\n if (line.quantity > most) {\n problems.push({\n ...ref,\n name,\n reason: 'tooMany',\n ...(input.hideRemaining ? {} : {maxQuantity: most}),\n })\n continue\n }\n\n const taxClass = product.commerce?.taxClass\n const taxRate = taxClass ? input.taxRates?.[taxClass] : undefined\n lineItems.push({\n quantity: line.quantity,\n price_data: {\n currency,\n unit_amount: toMinorUnits(status.price, currency),\n product_data: {\n name,\n ...(product.imageUrl ? {images: [product.imageUrl]} : {}),\n // Read back by the webhook, to know which product and variant was bought.\n metadata: ref,\n },\n },\n ...(taxRate ? {tax_rates: [taxRate]} : {}),\n })\n }\n\n if (problems.length) return {ok: false, error: 'unavailable', problems}\n\n return {\n ok: true,\n lines,\n region,\n params: {\n mode: 'payment',\n line_items: lineItems,\n // No payment method list: Stripe shows what the Dashboard has switched on.\n shipping_address_collection: {allowed_countries: region.countries},\n shipping_options: [\n {\n shipping_rate_data: {\n type: 'fixed_amount',\n display_name: region.title,\n fixed_amount: {amount: toMinorUnits(region.rate, currency), currency},\n },\n },\n ],\n success_url: input.successUrl,\n cancel_url: input.cancelUrl,\n metadata: {region: region.id, regionTitle: region.title},\n integration_identifier: input.integrationIdentifier,\n ...(input.expiresAt ? {expires_at: input.expiresAt} : {}),\n },\n }\n}\n","import type Stripe from 'stripe'\n\n/**\n * Runs a fulfilment step once per Checkout Session, even when Stripe delivers the event again.\n * The step's completion is recorded in the session's own metadata, so a failed step is retried on\n * the next delivery while finished steps are skipped. It works for free orders too, which have no\n * PaymentIntent.\n *\n * `session` must be read fresh from Stripe's API. Two deliveries arriving in the same moment can\n * both run the step, so it is for steps where a rare repeat is harmless, such as an email sent\n * with an idempotency key. Stock is guarded on its own, in `decrementStock`.\n */\nexport async function once(\n stripe: Stripe,\n session: Stripe.Checkout.Session,\n step: string,\n fn: () => Promise<void>,\n): Promise<'done' | 'skipped'> {\n const key = `fulfilled_${step}`\n if (session.metadata?.[key]) return 'skipped'\n await fn()\n await stripe.checkout.sessions.update(session.id, {\n metadata: {[key]: new Date().toISOString()},\n })\n return 'done'\n}\n","import type Stripe from 'stripe'\n\nexport interface OrderItem {\n /** Sanity document id of the product. */\n productId?: string\n variantKey?: string\n name: string\n quantity: number\n /** For the whole line, in the currency's minor unit (cents). */\n amountTotal: number\n}\n\n/** A paid Checkout Session, read into what a shop needs to send it. It is never saved in Sanity. */\nexport interface Order {\n sessionId: string\n paymentIntentId: string | null\n /** False for a payment made in a sandbox or in test mode. */\n livemode: boolean\n currency: string\n /** Including shipping, in the currency's minor unit. */\n amountTotal: number\n amountShipping: number\n regionId?: string\n regionTitle?: string\n customer: {name: string | null; email: string | null; phone: string | null}\n shipping: {name: string | null; address: Stripe.Address | null}\n items: OrderItem[]\n createdAt: Date\n}\n\n/** Reads a completed session and its line items into an order. */\nexport async function getOrder(stripe: Stripe, session: Stripe.Checkout.Session): Promise<Order> {\n const lineItems = await stripe.checkout.sessions.listLineItems(session.id, {\n limit: 100,\n expand: ['data.price.product'],\n })\n const items = lineItems.data.map((item): OrderItem => {\n const product = item.price?.product\n const metadata =\n product && typeof product === 'object' && !product.deleted ? product.metadata : {}\n const ordered: OrderItem = {\n name: item.description ?? 'Item',\n quantity: item.quantity ?? 1,\n amountTotal: item.amount_total,\n }\n if (metadata.productId) ordered.productId = metadata.productId\n if (metadata.variantKey) ordered.variantKey = metadata.variantKey\n return ordered\n })\n const shippingDetails = session.collected_information?.shipping_details\n const paymentIntent = session.payment_intent\n return {\n sessionId: session.id,\n paymentIntentId:\n typeof paymentIntent === 'string' ? paymentIntent : (paymentIntent?.id ?? null),\n livemode: session.livemode,\n currency: session.currency ?? '',\n amountTotal: session.amount_total ?? 0,\n amountShipping: session.shipping_cost?.amount_total ?? 0,\n ...(session.metadata?.region ? {regionId: session.metadata.region} : {}),\n ...(session.metadata?.regionTitle ? {regionTitle: session.metadata.regionTitle} : {}),\n customer: {\n name: session.customer_details?.name ?? null,\n email: session.customer_details?.email ?? null,\n phone: session.customer_details?.phone ?? null,\n },\n shipping: {name: shippingDetails?.name ?? null, address: shippingDetails?.address ?? null},\n items,\n createdAt: new Date(session.created * 1000),\n }\n}\n","/**\n * As much of a Sanity client as the plugin uses, so `/server` needs no Sanity package of its own.\n * A client from `@sanity/client` or `next-sanity` fits all three.\n */\n\n/** What the plugin asks of a client that can see private documents: everything, uncached. */\nexport interface RawConfig {\n perspective: 'raw'\n useCdn: false\n apiVersion: string\n}\n\n/** Reads from Sanity. For products and the shop settings, use one that skips the CDN. */\nexport interface SanityReader {\n fetch(query: string, params?: Record<string, unknown>): Promise<unknown>\n /** When the client has it, the plugin asks for the raw perspective to read stock. */\n withConfig?(config: RawConfig): SanityReader\n}\n\nexport interface SanityTransaction {\n create(document: {_id: string; _type: string; [field: string]: unknown}): SanityTransaction\n patch(\n documentId: string,\n operations: {dec?: Record<string, number>; set?: Record<string, unknown>},\n ): SanityTransaction\n delete(documentId: string): SanityTransaction\n commit(): Promise<unknown>\n}\n\n/** Reads and lowers stock counts. It needs a token that can write: Editor rights are enough. */\nexport interface SanityWriter extends SanityReader {\n transaction(): SanityTransaction\n withConfig?(config: RawConfig): SanityWriter\n}\n\n/**\n * Stock documents and every version of a product are only visible with a token, the raw\n * perspective and an API version that knows Content Releases.\n */\nconst RAW: RawConfig = {perspective: 'raw', useCdn: false, apiVersion: '2025-02-19'}\n\nexport function rawReader(client: SanityReader): SanityReader {\n return client.withConfig?.(RAW) ?? client\n}\n\nexport function rawWriter(client: SanityWriter): SanityWriter {\n return client.withConfig?.(RAW) ?? client\n}\n","import {asArray, isObject} from '../core/guards'\nimport {publishedId, STOCK_CHANGE_TYPE, stockChangeId, stockId} from '../core/ids'\nimport {productVersionsQuery, resolveProductQuery, type ProductQueryOptions} from '../core/queries'\nimport {readStockDocuments, STOCK_QUERY, stockLeft, type ProductStock} from '../core/stock'\nimport type {OrderItem} from './order'\nimport {rawReader, rawWriter, type SanityReader, type SanityWriter} from './sanity'\n\nconst SAFE_KEY = /^[\\w-]+$/\n\n/** Stripe stops redelivering an event after three days; a month leaves a wide margin. */\nconst KEEP_CHANGES_DAYS = 30\n\n/**\n * The stock of the given products, by product id, read from their private stock documents. The\n * client needs a token. A product without a stock document isn't counted and has no entry.\n *\n * A site can use it on the server to say \"only 2 left\"; it should never send the counts of\n * everything to the browser.\n */\nexport async function readStock(\n client: SanityReader,\n productIds: readonly string[],\n): Promise<Map<string, ProductStock>> {\n const ids = productIds.filter((id) => id && !id.includes('.')).map((id) => stockId(id))\n if (!ids.length) return new Map()\n return readStockDocuments(await rawReader(client).fetch(STOCK_QUERY, {ids}))\n}\n\n/** What an order did to one stock count. */\nexport interface StockChange {\n productId: string\n variantKey?: string\n from: number\n /** Below 0 when more was sold than was in stock. */\n to: number\n}\n\nexport interface StockResult {\n /**\n * `lowered`: counts were changed now. `already`: this session's stock was lowered before, and\n * `changes` is what happened then. `untracked`: nothing in the order has a stock count.\n */\n status: 'lowered' | 'already' | 'untracked'\n changes: StockChange[]\n}\n\nexport interface StockOptions {\n /** Mark a product or variant sold out when its count reaches 0. Default true. */\n autoSoldOut?: boolean\n /** Where the site keeps its products. */\n products?: ProductQueryOptions\n /** The current time, for tests. */\n now?: Date\n}\n\nfunction readChanges(value: unknown): StockChange[] {\n return asArray(value).flatMap((item) => {\n if (!isObject(item)) return []\n const {productId, variantKey, from, to} = item\n if (typeof productId !== 'string') return []\n if (typeof from !== 'number' || typeof to !== 'number') return []\n const change: StockChange = {productId, from, to}\n if (typeof variantKey === 'string') change.variantKey = variantKey\n return [change]\n })\n}\n\ninterface ProductVersion {\n _id: string\n variants: string[]\n}\n\nfunction readVersions(value: unknown): ProductVersion[] {\n return asArray(value).flatMap((item) =>\n isObject(item) && typeof item._id === 'string'\n ? [\n {\n _id: item._id,\n variants: asArray(item.variants).filter(\n (key): key is string => typeof key === 'string',\n ),\n },\n ]\n : [],\n )\n}\n\nconst isConflict = (error: unknown) => isObject(error) && error.statusCode === 409\n\n/**\n * Lowers the stock count of every ordered product or variant that has one. Products and variants\n * without a count are left alone. When a count reaches 0 and `autoSoldOut` is on, the product or\n * variant is marked sold out in every version of the product document, so publishing a draft or a\n * release can't put it back on sale.\n *\n * It happens once per Checkout Session, however often it is called: the counts change in one\n * transaction with a record of the session, and Sanity refuses a second record with the same id.\n * The record holds product ids and counts only.\n */\nexport async function decrementStock(\n client: SanityWriter,\n order: {sessionId: string; items: readonly OrderItem[]},\n options: StockOptions = {},\n): Promise<StockResult> {\n const {field} = resolveProductQuery(options.products)\n const autoSoldOut = options.autoSoldOut ?? true\n const now = options.now ?? new Date()\n\n const ordered: Array<{productId: string; variantKey?: string; quantity: number}> = []\n for (const item of order.items) {\n const {productId, variantKey, quantity} = item\n if (!productId || productId.includes('.')) continue\n if (variantKey && !SAFE_KEY.test(variantKey)) continue\n const same = ordered.find(\n (line) => line.productId === productId && line.variantKey === variantKey,\n )\n if (same) same.quantity += quantity\n else ordered.push(variantKey ? {productId, variantKey, quantity} : {productId, quantity})\n }\n if (!ordered.length) return {status: 'untracked', changes: []}\n\n const raw = rawWriter(client)\n const recordId = stockChangeId(order.sessionId)\n const readRecord = async (): Promise<StockChange[] | null> => {\n const record = await raw.fetch(`*[_id == $id][0]{changes}`, {id: recordId})\n return isObject(record) ? readChanges(record.changes) : null\n }\n\n // Three reads at once: Stripe waits for the webhook's answer before it sends the buyer on.\n const productIds = [...new Set(ordered.map((line) => line.productId))]\n const cutoff = new Date(now.getTime() - KEEP_CHANGES_DAYS * 24 * 60 * 60 * 1000)\n const [before, stock, oldRecords] = await Promise.all([\n readRecord(),\n readStock(raw, productIds),\n raw.fetch(`*[_type == $type && loweredAt < $before][0...25]._id`, {\n type: STOCK_CHANGE_TYPE,\n before: cutoff.toISOString(),\n }),\n ])\n if (before) return {status: 'already', changes: before}\n const old = asArray(oldRecords).filter((id): id is string => typeof id === 'string')\n\n const changes: StockChange[] = []\n const lower = new Map<string, Record<string, number>>()\n for (const {productId, variantKey, quantity} of ordered) {\n const from = stockLeft(stock.get(productId), variantKey)\n if (from === null) continue\n const dec = lower.get(productId) ?? {}\n dec[variantKey ? `variants[_key==\"${variantKey}\"].stockCount` : 'stockCount'] = quantity\n lower.set(productId, dec)\n const change: StockChange = {productId, from, to: from - quantity}\n if (variantKey) change.variantKey = variantKey\n changes.push(change)\n }\n if (!changes.length) return {status: 'untracked', changes: []}\n\n // The public sees \"sold out\", never the count: set it on every version of the product.\n const soldOut = autoSoldOut ? changes.filter((change) => change.to <= 0) : []\n const mark = new Map<string, Record<string, string>>()\n if (soldOut.length) {\n const {query, params} = productVersionsQuery(\n [...new Set(soldOut.map((change) => change.productId))],\n options.products,\n )\n for (const version of readVersions(await raw.fetch(query, params))) {\n const productId = publishedId(version._id)\n const set: Record<string, string> = {}\n for (const change of soldOut) {\n if (change.productId !== productId) continue\n if (!change.variantKey) set[`${field}.availability`] = 'soldOut'\n else if (version.variants.includes(change.variantKey))\n set[`${field}.variants[_key==\"${change.variantKey}\"].availability`] = 'soldOut'\n }\n if (Object.keys(set).length) mark.set(version._id, set)\n }\n }\n\n const transaction = client.transaction().create({\n _id: recordId,\n _type: STOCK_CHANGE_TYPE,\n sessionId: order.sessionId,\n loweredAt: now.toISOString(),\n changes: changes.map((change, index) => Object.assign({_key: `change-${index}`}, change)),\n })\n for (const [productId, dec] of lower) transaction.patch(stockId(productId), {dec})\n for (const [documentId, set] of mark) transaction.patch(documentId, {set})\n for (const documentId of old) transaction.delete(documentId)\n\n try {\n await transaction.commit()\n } catch (error) {\n // The record exists: another delivery of the same event got here first, and nothing changed now.\n if (isConflict(error)) {\n const winner = await readRecord()\n if (winner) return {status: 'already', changes: winner}\n }\n throw error\n }\n return {status: 'lowered', changes}\n}\n","import type Stripe from 'stripe'\n\nimport {readCartProducts, sanitizeLines, type CartLine, type CartProduct} from '../core/cart'\nimport {isObject} from '../core/guards'\nimport {\n checkoutProductsQuery,\n resolveProductQuery,\n SHOP_SETTINGS_QUERY,\n type ProductQueryOptions,\n} from '../core/queries'\nimport {\n readShopSettings,\n regionsFromSettings,\n type ShippingRegion,\n type ShopSettings,\n} from '../core/regions'\nimport type {ProductStock} from '../core/stock'\nimport {buildCheckoutSession} from './checkout'\nimport {classifyEvent, verifyEvent} from './events'\nimport {once} from './once'\nimport {getOrder, type Order} from './order'\nimport type {SanityReader, SanityWriter} from './sanity'\nimport {decrementStock, readStock, type StockChange} from './stock'\n\ntype Handler = (request: Request) => Promise<Response>\ntype Log = (level: 'info' | 'warn' | 'error', message: string) => void\n\n// Info lines go to the server's log too: they say what each payment did to stock and orders.\nconst consoleLog: Log = (level, message) => console[level](message)\n\nconst json = (body: unknown, status = 200) =>\n new Response(JSON.stringify(body), {\n status,\n headers: {'Content-Type': 'application/json', 'Cache-Control': 'no-store'},\n })\n\nconst reason = (error: unknown) => (error instanceof Error ? error.message : String(error))\n\n/** Stripe wants 30 minutes at least, counted on its own clock: leave a minute's margin. */\nconst SHORTEST_EXPIRY = 31 * 60\n\n// Checkout\n\nexport interface CheckoutMessages {\n /** The checkout can't be started: no key, no shipping regions, or Stripe refused. */\n unavailable: string\n /** The request couldn't be read. */\n invalid: string\n empty: string\n /** No shipping region, or one that no longer exists. */\n region: string\n /** A product in the cart sold out, changed or went away. */\n changed: string\n}\n\nconst CHECKOUT_MESSAGES: CheckoutMessages = {\n unavailable: \"Checkout isn't available right now. Please try again later.\",\n invalid: 'The cart could not be read.',\n empty: 'Your cart is empty.',\n region: 'Choose where to ship the order.',\n changed: 'Some things in your cart have changed. Check them and try again.',\n}\n\nexport interface CheckoutRouteOptions {\n /** From `createStripe`. Without one the route answers 503, so a site runs before it has keys. */\n stripe: Stripe | null | undefined\n /**\n * Reads products and the shop settings, without a token, so it sees published documents only.\n * It must skip the CDN, so prices are fresh.\n */\n client: SanityReader\n /**\n * A Sanity client with a token, to read stock counts, which the public can't. Without it nothing\n * is counted and the shop sells by the on-sale switch alone.\n */\n stockClient?: SanityReader | null\n /** ISO 4217 code, the same one the Studio plugin has. */\n currency: string\n /**\n * Where Stripe sends the buyer after paying: a path on the site or a full address. Include\n * `{CHECKOUT_SESSION_ID}`, such as `/shop/thanks?session_id={CHECKOUT_SESSION_ID}`.\n */\n successUrl: string\n /** Where Stripe's back link goes: the cart. A path or a full address. */\n cancelUrl: string\n /**\n * Marks this shop's sessions: the webhook fulfils only sessions that carry it, and the Stripe\n * Dashboard groups them by it. Give the webhook route the same value. End it with 8 random\n * letters, such as `myshop_qhzvmkpt`.\n */\n integrationIdentifier: string\n /** Where the site keeps its products. Default: `product` documents with a `commerce` field. */\n products?: ProductQueryOptions\n /** Stripe tax rate ids (`txr_…`) by tax class, so receipts show the tax. */\n taxRates?: Partial<Record<string, string>>\n /** Most of one item a buyer can order at once. Default 10. */\n maxQuantity?: number\n /** Answer \"not that many left\" without saying how many, when a cart asks for too many. */\n hideRemaining?: boolean\n /** Minutes until an unpaid session expires, about 30 at least. Stripe's default is 24 hours. */\n expiresInMinutes?: number\n /** Changes the session before it is created: promotion codes, phone numbers, consent. */\n extend?: (\n params: Stripe.Checkout.SessionCreateParams,\n context: {lines: CartLine[]; region: ShippingRegion; request: Request},\n ) => Stripe.Checkout.SessionCreateParams | Promise<Stripe.Checkout.SessionCreateParams>\n /** What buyers read when the checkout can't start. */\n messages?: Partial<CheckoutMessages>\n /** Adds the reason to error replies. For development only. */\n debug?: boolean\n log?: Log\n}\n\n/**\n * The site's checkout route. The browser posts the cart's product references and the chosen\n * shipping region; prices, stock and shipping are read fresh from Sanity here.\n *\n * ```ts\n * // app/api/checkout/route.ts\n * export const POST = checkoutRoute({stripe, client, stockClient, currency: 'eur', …})\n * ```\n *\n * It answers `{url}` to go to, or `{error, message, problems}`.\n */\nexport function checkoutRoute(options: CheckoutRouteOptions): Handler {\n const {client, currency, log = consoleLog} = options\n const messages = {...CHECKOUT_MESSAGES, ...options.messages}\n const productsQuery = checkoutProductsQuery(options.products)\n let saidUncounted = false\n\n const unavailable = (detail: string, status = 503) => {\n log('error', `[checkout] ${detail}`)\n return json(\n {error: 'unavailable', message: messages.unavailable, ...(options.debug ? {detail} : {})},\n status,\n )\n }\n\n return async (request) => {\n const {stripe, stockClient} = options\n if (!stripe) return unavailable('There is no Stripe key. Set one in the environment.')\n\n const body: unknown = await request.json().catch(() => null)\n if (!isObject(body)) return json({error: 'invalid', message: messages.invalid}, 400)\n\n const lines = sanitizeLines(body.lines, options.maxQuantity)\n const ids = [...new Set(lines.map((line) => line.productId))]\n let products: CartProduct[]\n let settings: ShopSettings | null\n let stock: Map<string, ProductStock> | undefined\n try {\n const [foundProducts, foundSettings, foundStock] = await Promise.all([\n ids.length ? client.fetch(productsQuery, {ids}) : [],\n client.fetch(SHOP_SETTINGS_QUERY),\n stockClient ? readStock(stockClient, ids) : undefined,\n ])\n products = readCartProducts(foundProducts)\n settings = readShopSettings(foundSettings)\n stock = foundStock\n } catch (error) {\n return unavailable(`Sanity could not be read: ${reason(error)}`, 502)\n }\n if (!stockClient && !saidUncounted) {\n saidUncounted = true\n log('info', '[checkout] No stockClient: stock counts are not checked at checkout.')\n }\n const regions = regionsFromSettings(settings)\n if (!regions.length)\n return unavailable('No shipping regions. Publish the shop settings in the Studio.')\n\n const origin = new URL(request.url).origin\n const result = buildCheckoutSession({\n lines,\n regionId: body.regionId,\n products,\n regions,\n currency,\n successUrl: absoluteUrl(options.successUrl, origin),\n cancelUrl: absoluteUrl(options.cancelUrl, origin),\n integrationIdentifier: options.integrationIdentifier,\n ...(stock ? {stock} : {}),\n ...(options.hideRemaining ? {hideRemaining: true} : {}),\n ...(options.taxRates ? {taxRates: options.taxRates} : {}),\n ...(options.maxQuantity ? {maxQuantity: options.maxQuantity} : {}),\n ...(options.expiresInMinutes\n ? {\n expiresAt:\n Math.floor(Date.now() / 1000) +\n Math.max(SHORTEST_EXPIRY, options.expiresInMinutes * 60),\n }\n : {}),\n })\n\n if (!result.ok) {\n const message = {\n empty: messages.empty,\n region: messages.region,\n unavailable: messages.changed,\n }[result.error]\n return json({error: result.error, message, problems: result.problems}, 409)\n }\n\n try {\n const params = options.extend\n ? await options.extend(result.params, {lines: result.lines, region: result.region, request})\n : result.params\n const session = await stripe.checkout.sessions.create(params)\n if (!session.url)\n return unavailable(`Checkout Session ${session.id} came back without a URL.`, 502)\n return json({url: session.url})\n } catch (error) {\n return unavailable(`Stripe refused the Checkout Session: ${reason(error)}`, 502)\n }\n }\n}\n\n/** Stripe needs full addresses. `{CHECKOUT_SESSION_ID}` must reach it as written, not encoded. */\nfunction absoluteUrl(url: string, origin: string): string {\n return /^https?:\\/\\//.test(url) ? url : `${origin}${url.startsWith('/') ? '' : '/'}${url}`\n}\n\n// Webhook\n\nexport interface OrderContext {\n /** The shop settings as published. Null before they are published. */\n settings: ShopSettings | null\n /** What the order did to stock counts. Empty when nothing ordered is counted. */\n stock: StockChange[]\n /**\n * Set when stock could not be lowered, with the reason: say so to whoever packs the order.\n * Stripe is asked to deliver the event again, and only the stock is tried again.\n */\n stockError?: string\n session: Stripe.Checkout.Session\n stripe: Stripe\n}\n\nexport interface WebhookRouteOptions {\n /** From `createStripe`. */\n stripe: Stripe | null | undefined\n /** The endpoint's signing secret (`whsec_…`), from the environment. */\n webhookSecret: string | undefined\n /** The same value the checkout route has. Sessions without it are not this shop's. */\n integrationIdentifier: string\n /** Reads the shop settings, without a token. It must skip the CDN. */\n client: SanityReader\n /**\n * A Sanity client with a token that can write, to lower stock counts when an order is paid.\n * Without one, stock is left as it is.\n */\n stockClient?: SanityWriter | null\n /** Where the site keeps its products. The same as the checkout route's. */\n products?: ProductQueryOptions\n /**\n * Called once for each paid order, after stock is lowered: send the order email here. Throw\n * and Stripe delivers the event again later; stock isn't lowered twice.\n */\n onOrder?: (order: Order, context: OrderContext) => void | Promise<void>\n /** Called when a delayed payment (a bank transfer, for one) fails. Nothing was fulfilled. */\n onPaymentFailed?: (session: Stripe.Checkout.Session) => void | Promise<void>\n log?: Log\n}\n\n/**\n * The site's Stripe webhook. A paid order of this shop lowers stock and calls `onOrder`, each\n * once per payment however often Stripe delivers the event. Payments made any other way on the\n * same Stripe account (a Payment Link, another site) are left alone. Nothing is fulfilled on the\n * thank-you page.\n *\n * ```ts\n * // app/api/stripe/webhook/route.ts\n * export const POST = webhookRoute({stripe, webhookSecret, integrationIdentifier, client, …})\n * ```\n *\n * Point a Stripe webhook endpoint at it with the events in `WEBHOOK_EVENTS`.\n */\nexport function webhookRoute(options: WebhookRouteOptions): Handler {\n const {client, log = consoleLog} = options\n // Fail when the site starts, not on the first order.\n resolveProductQuery(options.products)\n let saidUncounted = false\n\n /**\n * The session as this client's API version shapes it, or null when it isn't this shop's. The\n * event carries the session as the endpoint's version shapes it, so it is read again.\n */\n const ownSession = async (stripe: Stripe, sessionId: string) => {\n const session = await stripe.checkout.sessions.retrieve(sessionId)\n const own =\n session.mode === 'payment' && session.integration_identifier === options.integrationIdentifier\n if (!own) log('info', `[stripe webhook] ${sessionId}: not one of this shop's, left alone.`)\n return own ? session : null\n }\n\n const fulfil = async (stripe: Stripe, sessionId: string) => {\n const session = await ownSession(stripe, sessionId)\n if (!session) return\n // Trust the payment status Stripe's API gives now over the one in the event.\n if (session.payment_status === 'unpaid') {\n log('info', `[stripe webhook] ${sessionId}: not paid yet.`)\n return\n }\n const [order, foundSettings] = await Promise.all([\n getOrder(stripe, session),\n client.fetch(SHOP_SETTINGS_QUERY),\n ])\n const settings = readShopSettings(foundSettings)\n\n let stock: StockChange[] = []\n let stockError: string | undefined\n if (options.stockClient) {\n try {\n const result = await decrementStock(options.stockClient, order, {\n autoSoldOut: settings?.autoSoldOut ?? true,\n ...(options.products ? {products: options.products} : {}),\n })\n stock = result.changes\n if (result.status === 'lowered') {\n const summary = stock\n .map(\n ({productId, variantKey, from, to}) =>\n `${productId}${variantKey ? ` ${variantKey}` : ''} ${from} → ${to}`,\n )\n .join(', ')\n log('info', `[stripe webhook] ${sessionId}: stock ${summary}`)\n }\n } catch (error) {\n // The order still has to reach the shop. It is told, and Stripe is asked to come back.\n stockError = reason(error)\n log('error', `[stripe webhook] ${sessionId}: stock was not lowered: ${stockError}`)\n }\n } else if (!saidUncounted) {\n saidUncounted = true\n log('info', '[stripe webhook] No stockClient: stock counts are left as they are.')\n }\n\n const {onOrder} = options\n if (onOrder) {\n const outcome = await once(stripe, session, 'order', async () => {\n await onOrder(order, {\n settings,\n stock,\n ...(stockError ? {stockError} : {}),\n session,\n stripe,\n })\n })\n log('info', `[stripe webhook] ${sessionId}: order ${outcome}.`)\n }\n\n if (stockError) throw new Error(`Stock was not lowered: ${stockError}`)\n }\n\n return async (request) => {\n const {stripe, webhookSecret} = options\n if (!stripe || !webhookSecret) {\n log('error', '[stripe webhook] The Stripe key or the webhook secret is not set.')\n return json({error: 'Webhook not configured'}, 503)\n }\n\n // The signature covers the exact bytes Stripe sent, so read the body as text before parsing.\n const payload = await request.text()\n let event: Stripe.Event\n try {\n event = verifyEvent(stripe, payload, request.headers.get('stripe-signature'), webhookSecret)\n } catch (error) {\n log('warn', `[stripe webhook] Signature check failed: ${reason(error)}`)\n return json({error: 'Invalid signature'}, 400)\n }\n\n const result = classifyEvent(event)\n try {\n switch (result.type) {\n case 'paid':\n await fulfil(stripe, result.session.id)\n break\n case 'pending':\n log('info', `[stripe webhook] ${result.session.id}: waiting for a delayed payment.`)\n break\n case 'failed': {\n const session = await ownSession(stripe, result.session.id)\n if (session) {\n log('warn', `[stripe webhook] ${session.id}: the delayed payment failed.`)\n await options.onPaymentFailed?.(session)\n }\n break\n }\n case 'ignored':\n break\n }\n } catch (error) {\n // A 500 makes Stripe deliver the event again. Steps that already finished are skipped then.\n log('error', `[stripe webhook] ${event.type} ${event.id} failed: ${reason(error)}`)\n return json({error: 'Fulfilment failed'}, 500)\n }\n\n return json({received: true})\n }\n}\n","/** The package's version, sent to Stripe with each call. A test keeps it in step with package.json. */\nexport const VERSION = '0.1.0'\n","import {Stripe} from 'stripe'\n\nimport {VERSION} from '../version'\n\n/** The Stripe API version the plugin is written and tested against (stripe-node 23). */\nexport const STRIPE_API_VERSION = '2026-10-28.endive'\n\n/**\n * A Stripe client pinned to the API version the plugin is tested against, so a Stripe upgrade\n * never changes the API under the shop. Pass the key from the site's environment, never from code:\n *\n * ```ts\n * const stripe = process.env.STRIPE_SECRET_KEY ? createStripe(process.env.STRIPE_SECRET_KEY) : null\n * ```\n *\n * A restricted key (`rk_…`) is enough, and safer than a secret key. `config` takes the rest of\n * stripe-node's options, such as `maxNetworkRetries` or `httpClient`.\n */\nexport function createStripe(\n secretKey: string,\n config: Omit<Stripe.StripeConfig, 'apiVersion' | 'appInfo'> = {},\n): Stripe {\n return new Stripe(secretKey, {\n ...config,\n apiVersion: STRIPE_API_VERSION,\n appInfo: {\n name: 'sanity-plugin-stripe',\n version: VERSION,\n url: 'https://github.com/ConfederationStudio/sanity-plugin-stripe',\n },\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../src/server/email.ts","../src/server/events.ts","../src/core/queries.ts","../src/core/regions.ts","../src/server/checkout.ts","../src/server/once.ts","../src/server/order.ts","../src/server/sanity.ts","../src/server/stock.ts","../src/server/routes.ts","../src/version.ts","../src/server/stripe.ts"],"sourcesContent":["import type Stripe from 'stripe'\n\nimport {formatMoney} from '../core/money'\nimport type {Order} from './order'\nimport type {StockChange} from './stock'\n\nconst escapeHtml = (value: string) =>\n value.replace(\n /[&<>\"']/g,\n (char) => ({'&': '&', '<': '<', '>': '>', '\"': '"', \"'\": '''})[char] ?? char,\n )\n\nfunction addressLines(name: string | null, address: Stripe.Address | null): string[] {\n if (!address) return name ? [name] : []\n const city = [address.postal_code, address.city].filter(Boolean).join(' ')\n return [name, address.line1, address.line2, city, address.state, address.country].filter(\n (line): line is string => Boolean(line),\n )\n}\n\n/** Where the payment is in the Stripe Dashboard. */\nexport function dashboardUrl(\n order: Pick<Order, 'livemode' | 'paymentIntentId' | 'sessionId'>,\n): string {\n const base = `https://dashboard.stripe.com/${order.livemode ? '' : 'test/'}`\n return order.paymentIntentId\n ? `${base}payments/${order.paymentIntentId}`\n : `${base}checkout/sessions/${order.sessionId}`\n}\n\nexport interface OrderEmailOptions {\n /** Starts the subject line. */\n shopName: string\n /** For amounts: a BCP 47 tag such as `en`, `nl-NL`. Default `en`. */\n locale?: string\n /** What the order did to stock counts. Anything sold below 0 is called out. */\n stock?: readonly StockChange[]\n /** Why stock could not be lowered, from the webhook's context. The email says to check it. */\n stockError?: string\n}\n\nexport interface OrderEmail {\n subject: string\n text: string\n html: string\n}\n\n/** A plain order email for the shop: what to pack, where to send it and who paid. */\nexport function orderEmail(order: Order, options: OrderEmailOptions): OrderEmail {\n const {shopName, locale = 'en', stock = [], stockError} = options\n const money = (minor: number) => formatMoney(minor, order.currency, locale)\n const count = order.items.reduce((sum, item) => sum + item.quantity, 0)\n const who = order.shipping.name ?? order.customer.name ?? order.customer.email ?? 'a customer'\n const subject = `${order.livemode ? '' : '[Test] '}${shopName} order from ${who}: ${count} ${count === 1 ? 'item' : 'items'}, ${money(order.amountTotal)}`\n const address = addressLines(order.shipping.name, order.shipping.address)\n const contact = [order.customer.email, order.customer.phone].filter((line): line is string =>\n Boolean(line),\n )\n const shippingLabel = order.regionTitle ? `Shipping (${order.regionTitle})` : 'Shipping'\n const link = dashboardUrl(order)\n const date = order.createdAt.toISOString().slice(0, 10)\n\n const oversold = stock\n .filter((change) => change.to < 0)\n .map((change) => {\n const item = order.items.find(\n (candidate) =>\n candidate.productId === change.productId &&\n (candidate.variantKey ?? '') === (change.variantKey ?? ''),\n )\n return item?.name ?? change.productId\n })\n const warnings = [\n ...(oversold.length\n ? [`Check your stock: more was sold than was counted in stock for ${oversold.join(', ')}.`]\n : []),\n ...(stockError\n ? [`Stock counts were not lowered for this order. Change them by hand. (${stockError})`]\n : []),\n ]\n\n const text = [\n `New order on ${date}${order.livemode ? '' : ' (test mode, no money moved)'}`,\n '',\n ...warnings.flatMap((warning) => [warning, '']),\n ...order.items.map((item) => `${item.quantity} × ${item.name} ${money(item.amountTotal)}`),\n `${shippingLabel} ${money(order.amountShipping)}`,\n `Total ${money(order.amountTotal)}`,\n '',\n 'Ship to:',\n ...address,\n '',\n 'Contact:',\n ...contact,\n '',\n `In Stripe: ${link}`,\n ].join('\\n')\n\n const rows = order.items\n .map(\n (item) =>\n `<tr><td style=\"padding:4px 12px 4px 0\">${item.quantity} ×</td><td style=\"padding:4px 12px 4px 0\">${escapeHtml(item.name)}</td><td style=\"padding:4px 0;text-align:right\">${money(item.amountTotal)}</td></tr>`,\n )\n .join('')\n const html = `<div style=\"font-family:Helvetica,Arial,sans-serif;font-size:15px;line-height:1.45;color:#191919\">\n<p>New order on ${date}${order.livemode ? '' : ' <strong>(test mode, no money moved)</strong>'}</p>\n${warnings.map((warning) => `<p><strong>${escapeHtml(warning)}</strong></p>\\n`).join('')}<table style=\"border-collapse:collapse\">${rows}\n<tr><td></td><td style=\"padding:4px 12px 4px 0\">${escapeHtml(shippingLabel)}</td><td style=\"padding:4px 0;text-align:right\">${money(order.amountShipping)}</td></tr>\n<tr><td></td><td style=\"padding:4px 12px 4px 0\"><strong>Total</strong></td><td style=\"padding:4px 0;text-align:right\"><strong>${money(order.amountTotal)}</strong></td></tr>\n</table>\n<p><strong>Ship to</strong><br>${address.map(escapeHtml).join('<br>')}</p>\n<p><strong>Contact</strong><br>${contact.map(escapeHtml).join('<br>')}</p>\n<p><a href=\"${link}\">Open the payment in Stripe</a></p>\n</div>`\n\n return {subject, text, html}\n}\n","import type Stripe from 'stripe'\n\n/** Checks the signature and parses the event. Throws when the signature doesn't match. */\nexport function verifyEvent(\n stripe: Stripe,\n payload: string,\n signature: string | null,\n secret: string,\n): Stripe.Event {\n if (!signature) throw new Error('Missing Stripe-Signature header')\n return stripe.webhooks.constructEvent(payload, signature, secret)\n}\n\nexport type CheckoutEvent =\n /** Paid: fulfil the order. */\n | {type: 'paid'; session: Stripe.Checkout.Session}\n /** Completed, but a delayed payment method hasn't settled yet. Wait for async_payment_succeeded. */\n | {type: 'pending'; session: Stripe.Checkout.Session}\n /** A delayed payment failed. Nothing to fulfil. */\n | {type: 'failed'; session: Stripe.Checkout.Session}\n | {type: 'ignored'}\n\n/** The events a webhook endpoint for the shop listens to. */\nexport const WEBHOOK_EVENTS = [\n 'checkout.session.completed',\n 'checkout.session.async_payment_succeeded',\n 'checkout.session.async_payment_failed',\n] as const\n\n/** What a webhook event means for the shop. Only `paid` is fulfilled. */\nexport function classifyEvent(event: Stripe.Event): CheckoutEvent {\n switch (event.type) {\n case 'checkout.session.completed':\n case 'checkout.session.async_payment_succeeded': {\n const session = event.data.object\n return session.payment_status === 'unpaid'\n ? {type: 'pending', session}\n : {type: 'paid', session}\n }\n case 'checkout.session.async_payment_failed':\n return {type: 'failed', session: event.data.object}\n default:\n return {type: 'ignored'}\n }\n}\n","import {SHOP_SETTINGS_ID, SHOP_SETTINGS_TYPE} from './ids'\n\n/** Where a site keeps its products, for the queries the plugin writes. */\nexport interface ProductQueryOptions {\n /** Document types that can be bought. Default `['product']`. */\n types?: readonly string[]\n /** The name of the `stripeCommerce` field on those types. Default `commerce`. */\n field?: string\n /** GROQ for the product's name. Default `title`. */\n title?: string\n /** GROQ for the product's slug. Default `slug.current`. */\n slug?: string\n /** GROQ for the URL of the image shown in the cart and on Stripe's page. */\n image?: string\n}\n\nconst NAME = /^[A-Za-z_]\\w*$/\nconst TYPE_NAME = /^[A-Za-z_][\\w.-]*$/\n\n/** The options with their defaults, checked: names end up inside GROQ. */\nexport function resolveProductQuery(options: ProductQueryOptions = {}) {\n const types = options.types?.length ? [...options.types] : ['product']\n const field = options.field ?? 'commerce'\n for (const type of types) {\n if (!TYPE_NAME.test(type))\n throw new TypeError(`sanity-plugin-stripe: \"${type}\" is not a document type name`)\n }\n if (!NAME.test(field)) throw new TypeError(`sanity-plugin-stripe: \"${field}\" is not a field name`)\n return {\n types,\n field,\n title: options.title ?? 'title',\n slug: options.slug ?? 'slug.current',\n image:\n options.image ?? 'coalesce(images[0].asset->url, image.asset->url, mainImage.asset->url)',\n }\n}\n\nconst typeFilter = (types: readonly string[]) =>\n `_type in [${types.map((type) => JSON.stringify(type)).join(', ')}]`\n\n/** The price-and-availability field as the cart and the checkout read it. No stock counts. */\nexport const COMMERCE_PROJECTION = `{availability, price, taxClass, variants[]{_key, label, availability}}`\n\n/**\n * What the cart and the checkout need of a product, for the site's own queries:\n *\n * ```ts\n * defineQuery(`*[_type == \"product\"]{${cartProductProjection()}}`)\n * ```\n */\nexport function cartProductProjection(options?: ProductQueryOptions): string {\n const {field, title, slug, image} = resolveProductQuery(options)\n return `_id, \"title\": ${title}, \"slug\": ${slug}, \"imageUrl\": ${image}, \"commerce\": ${field}${COMMERCE_PROJECTION}`\n}\n\n/** Every product that has the commerce field filled in, for the cart page. */\nexport function cartProductsQuery(options?: ProductQueryOptions): string {\n const {types, field} = resolveProductQuery(options)\n return `*[${typeFilter(types)} && defined(${field})]{${cartProductProjection(options)}}`\n}\n\n/** The products with the ids in `$ids`, for the checkout. */\nexport function checkoutProductsQuery(options?: ProductQueryOptions): string {\n const {types} = resolveProductQuery(options)\n return `*[${typeFilter(types)} && _id in $ids]{${cartProductProjection(options)}}`\n}\n\n/**\n * Every version of the products in `productIds` (published, draft, and copies inside a Content\n * Release), with the keys of its variants: what the webhook marks sold out. It needs the raw\n * perspective, a token, and API version 2025-02-19 or later.\n */\nexport function productVersionsQuery(\n productIds: readonly string[],\n options?: ProductQueryOptions,\n): {query: string; params: Record<string, string>} {\n const {types, field} = resolveProductQuery(options)\n const params: Record<string, string> = {}\n const anyVersion = productIds.map((id, index) => {\n params[`product${index}`] = id\n return `sanity::versionOf($product${index})`\n })\n return {\n query: `*[${typeFilter(types)} && defined(${field}) && (${anyVersion.join(' || ') || 'false'})]{_id, \"variants\": ${field}.variants[]._key}`,\n params,\n }\n}\n\n/** The shop settings: shipping regions and whether stock sells out by itself. */\nexport const SHOP_SETTINGS_QUERY = `*[_type == \"${SHOP_SETTINGS_TYPE}\" && _id == \"${SHOP_SETTINGS_ID}\"][0]{shippingRegions[]{_key, title, everywhereElse, countries, rate}, autoSoldOut}`\n","import {STRIPE_SHIPPING_COUNTRIES} from './countries'\nimport {asArray, asNumber, asString, isObject} from './guards'\n\n/** A shipping region with one flat rate per order. */\nexport interface ShippingRegion {\n id: string\n title: string\n /** ISO 3166-1 alpha-2 codes. Empty for the \"everywhere else\" region until resolved. */\n countries: string[]\n everywhereElse: boolean\n /** In currency units. */\n rate: number\n}\n\n/** The shop settings document, as `SHOP_SETTINGS_QUERY` returns it. */\nexport interface ShopSettings {\n shippingRegions?: Array<{\n _key: string\n title?: string | null\n everywhereElse?: boolean | null\n countries?: string[] | null\n rate?: number | null\n }> | null\n autoSoldOut?: boolean | null\n}\n\n/** The shop settings as Sanity returned them, or null when they aren't published yet. */\nexport function readShopSettings(value: unknown): ShopSettings | null {\n if (!isObject(value)) return null\n return {\n shippingRegions: asArray(value.shippingRegions).flatMap((region) =>\n isObject(region) && typeof region._key === 'string'\n ? [\n {\n _key: region._key,\n title: asString(region.title),\n everywhereElse: region.everywhereElse === true,\n countries: asArray(region.countries).filter(\n (country): country is string => typeof country === 'string',\n ),\n rate: asNumber(region.rate),\n },\n ]\n : [],\n ),\n autoSoldOut: typeof value.autoSoldOut === 'boolean' ? value.autoSoldOut : null,\n }\n}\n\n/** Shipping regions from the shop settings, leaving out any that aren't filled in. */\nexport function regionsFromSettings(settings: ShopSettings | null | undefined): ShippingRegion[] {\n return (settings?.shippingRegions ?? []).flatMap((region) =>\n region.title && typeof region.rate === 'number'\n ? [\n {\n id: region._key,\n title: region.title,\n countries: region.countries ?? [],\n everywhereElse: Boolean(region.everywhereElse),\n rate: region.rate,\n },\n ]\n : [],\n )\n}\n\n/**\n * Fills in the \"everywhere else\" region with every country Stripe ships to that no other region\n * lists, and drops country codes Stripe doesn't accept. Regions left without countries are removed.\n */\nexport function resolveRegions(regions: readonly ShippingRegion[]): ShippingRegion[] {\n const supported = new Set<string>(STRIPE_SHIPPING_COUNTRIES)\n const listed = new Set(\n regions.filter((region) => !region.everywhereElse).flatMap((region) => region.countries),\n )\n return regions\n .map((region) => ({\n ...region,\n countries: region.everywhereElse\n ? STRIPE_SHIPPING_COUNTRIES.filter((country) => !listed.has(country))\n : region.countries.filter((country) => supported.has(country)),\n }))\n .filter((region) => region.countries.length > 0)\n}\n\n/** What a cart page needs of each region: no country lists. */\nexport type RegionOption = Pick<ShippingRegion, 'id' | 'title' | 'rate'>\n\n/** The regions a buyer can choose from, the same ones the checkout accepts. */\nexport function regionOptions(settings: ShopSettings | null | undefined): RegionOption[] {\n return resolveRegions(regionsFromSettings(settings)).map(({id, title, rate}) => ({\n id,\n title,\n rate,\n }))\n}\n","import type Stripe from 'stripe'\n\nimport {\n lineName,\n lineStatus,\n MAX_QUANTITY,\n sanitizeLines,\n type CartLine,\n type CartProduct,\n type CheckoutProblem,\n} from '../core/cart'\nimport {toMinorUnits} from '../core/money'\nimport {resolveRegions, type ShippingRegion} from '../core/regions'\nimport {stockLeft, type ProductStock} from '../core/stock'\n\n/**\n * Builds a Checkout Session from the cart and fresh product data. Nothing here trusts a price\n * sent by the browser: the cart holds references, and prices come from `products`.\n */\n\ntype SessionParams = Stripe.Checkout.SessionCreateParams\ntype LineItemParams = Stripe.Checkout.SessionCreateParams.LineItem\n\nexport interface CheckoutInput {\n /** The cart as the browser sent it. Checked here, so pass the raw value. */\n lines: unknown\n /** The shipping region the buyer chose, as the browser sent it. */\n regionId: unknown\n /** Fresh product data (not from a CDN cache) for every product in the cart. */\n products: readonly CartProduct[]\n /** Stock by product id, from `readStock`. Without it nothing is counted. */\n stock?: ReadonlyMap<string, ProductStock>\n /** Shipping regions from the shop settings, before `resolveRegions`. */\n regions: readonly ShippingRegion[]\n /** ISO 4217 code, such as `eur`. */\n currency: string\n successUrl: string\n cancelUrl: string\n /** Marks the session as this shop's, for the webhook and the Stripe Dashboard. */\n integrationIdentifier: string\n /** Stripe tax rate ids (`txr_…`) by tax class, so receipts show the tax. */\n taxRates?: Partial<Record<string, string>>\n /** Most of one item a buyer can order at once. Default 10. */\n maxQuantity?: number\n /** Don't say how many are left when a cart asks for too many. */\n hideRemaining?: boolean\n /** When the session stops taking payment, as a Unix time. Stripe's default is 24 hours on. */\n expiresAt?: number\n}\n\nexport type CheckoutResult =\n | {ok: true; params: SessionParams; lines: CartLine[]; region: ShippingRegion}\n | {ok: false; error: 'empty' | 'region' | 'unavailable'; problems: CheckoutProblem[]}\n\nexport function buildCheckoutSession(input: CheckoutInput): CheckoutResult {\n const max = input.maxQuantity ?? MAX_QUANTITY\n const currency = input.currency.toLowerCase()\n const lines = sanitizeLines(input.lines, max)\n if (!lines.length) return {ok: false, error: 'empty', problems: []}\n\n const region = resolveRegions(input.regions).find((candidate) => candidate.id === input.regionId)\n if (!region) return {ok: false, error: 'region', problems: []}\n\n const products = new Map(input.products.map((product) => [product._id, product]))\n const problems: CheckoutProblem[] = []\n const lineItems: LineItemParams[] = []\n\n for (const line of lines) {\n const product = products.get(line.productId)\n const status = lineStatus(product, line.variantKey, max)\n const ref = {\n productId: line.productId,\n ...(line.variantKey ? {variantKey: line.variantKey} : {}),\n }\n if (!product || !status.available) {\n problems.push({\n ...ref,\n ...(product?.title ? {name: product.title} : {}),\n reason: status.available ? 'missing' : status.reason,\n })\n continue\n }\n const name = lineName(product, status.variant)\n\n // Counts are private, so this is the one place a buyer meets them.\n const left = stockLeft(input.stock?.get(line.productId), line.variantKey)\n if (left !== null && left <= 0) {\n problems.push({...ref, name, reason: 'soldOut'})\n continue\n }\n const most = left === null ? status.maxQuantity : Math.min(status.maxQuantity, left)\n if (line.quantity > most) {\n problems.push({\n ...ref,\n name,\n reason: 'tooMany',\n ...(input.hideRemaining ? {} : {maxQuantity: most}),\n })\n continue\n }\n\n const taxClass = product.commerce?.taxClass\n const taxRate = taxClass ? input.taxRates?.[taxClass] : undefined\n lineItems.push({\n quantity: line.quantity,\n price_data: {\n currency,\n unit_amount: toMinorUnits(status.price, currency),\n product_data: {\n name,\n ...(product.imageUrl ? {images: [product.imageUrl]} : {}),\n // Read back by the webhook, to know which product and variant was bought.\n metadata: ref,\n },\n },\n ...(taxRate ? {tax_rates: [taxRate]} : {}),\n })\n }\n\n if (problems.length) return {ok: false, error: 'unavailable', problems}\n\n return {\n ok: true,\n lines,\n region,\n params: {\n mode: 'payment',\n line_items: lineItems,\n // No payment method list: Stripe shows what the Dashboard has switched on.\n shipping_address_collection: {allowed_countries: region.countries},\n shipping_options: [\n {\n shipping_rate_data: {\n type: 'fixed_amount',\n display_name: region.title,\n fixed_amount: {amount: toMinorUnits(region.rate, currency), currency},\n },\n },\n ],\n success_url: input.successUrl,\n cancel_url: input.cancelUrl,\n metadata: {region: region.id, regionTitle: region.title},\n integration_identifier: input.integrationIdentifier,\n ...(input.expiresAt ? {expires_at: input.expiresAt} : {}),\n },\n }\n}\n","import type Stripe from 'stripe'\n\n/**\n * Runs a fulfilment step once per Checkout Session, even when Stripe delivers the event again.\n * The step's completion is recorded in the session's own metadata, so a failed step is retried on\n * the next delivery while finished steps are skipped. It works for free orders too, which have no\n * PaymentIntent.\n *\n * `session` must be read fresh from Stripe's API. Two deliveries arriving in the same moment can\n * both run the step, so it is for steps where a rare repeat is harmless, such as an email sent\n * with an idempotency key. Stock is guarded on its own, in `decrementStock`.\n */\nexport async function once(\n stripe: Stripe,\n session: Stripe.Checkout.Session,\n step: string,\n fn: () => Promise<void>,\n): Promise<'done' | 'skipped'> {\n const key = `fulfilled_${step}`\n if (session.metadata?.[key]) return 'skipped'\n await fn()\n await stripe.checkout.sessions.update(session.id, {\n metadata: {[key]: new Date().toISOString()},\n })\n return 'done'\n}\n","import type Stripe from 'stripe'\n\nexport interface OrderItem {\n /** Sanity document id of the product. */\n productId?: string\n variantKey?: string\n name: string\n quantity: number\n /** For the whole line, in the currency's minor unit (cents). */\n amountTotal: number\n}\n\n/** A paid Checkout Session, read into what a shop needs to send it. It is never saved in Sanity. */\nexport interface Order {\n sessionId: string\n paymentIntentId: string | null\n /** False for a payment made in a sandbox or in test mode. */\n livemode: boolean\n currency: string\n /** Including shipping, in the currency's minor unit. */\n amountTotal: number\n amountShipping: number\n regionId?: string\n regionTitle?: string\n customer: {name: string | null; email: string | null; phone: string | null}\n shipping: {name: string | null; address: Stripe.Address | null}\n items: OrderItem[]\n createdAt: Date\n}\n\n/** Reads a completed session and its line items into an order. */\nexport async function getOrder(stripe: Stripe, session: Stripe.Checkout.Session): Promise<Order> {\n const lineItems = await stripe.checkout.sessions.listLineItems(session.id, {\n limit: 100,\n expand: ['data.price.product'],\n })\n const items = lineItems.data.map((item): OrderItem => {\n const product = item.price?.product\n const metadata =\n product && typeof product === 'object' && !product.deleted ? product.metadata : {}\n const ordered: OrderItem = {\n name: item.description ?? 'Item',\n quantity: item.quantity ?? 1,\n amountTotal: item.amount_total,\n }\n if (metadata.productId) ordered.productId = metadata.productId\n if (metadata.variantKey) ordered.variantKey = metadata.variantKey\n return ordered\n })\n const shippingDetails = session.collected_information?.shipping_details\n const paymentIntent = session.payment_intent\n return {\n sessionId: session.id,\n paymentIntentId:\n typeof paymentIntent === 'string' ? paymentIntent : (paymentIntent?.id ?? null),\n livemode: session.livemode,\n currency: session.currency ?? '',\n amountTotal: session.amount_total ?? 0,\n amountShipping: session.shipping_cost?.amount_total ?? 0,\n ...(session.metadata?.region ? {regionId: session.metadata.region} : {}),\n ...(session.metadata?.regionTitle ? {regionTitle: session.metadata.regionTitle} : {}),\n customer: {\n name: session.customer_details?.name ?? null,\n email: session.customer_details?.email ?? null,\n phone: session.customer_details?.phone ?? null,\n },\n shipping: {name: shippingDetails?.name ?? null, address: shippingDetails?.address ?? null},\n items,\n createdAt: new Date(session.created * 1000),\n }\n}\n","/**\n * As much of a Sanity client as the plugin uses, so `/server` needs no Sanity package of its own.\n * A client from `@sanity/client` or `next-sanity` fits all three.\n */\n\n/** What the plugin asks of a client that can see private documents: everything, uncached. */\nexport interface RawConfig {\n perspective: 'raw'\n useCdn: false\n apiVersion: string\n}\n\n/** Reads from Sanity. For products and the shop settings, use one that skips the CDN. */\nexport interface SanityReader {\n fetch(query: string, params?: Record<string, unknown>): Promise<unknown>\n /** When the client has it, the plugin asks for the raw perspective to read stock. */\n withConfig?(config: RawConfig): SanityReader\n}\n\nexport interface SanityTransaction {\n create(document: {_id: string; _type: string; [field: string]: unknown}): SanityTransaction\n patch(\n documentId: string,\n operations: {dec?: Record<string, number>; set?: Record<string, unknown>},\n ): SanityTransaction\n delete(documentId: string): SanityTransaction\n commit(): Promise<unknown>\n}\n\n/** Reads and lowers stock counts. It needs a token that can write: Editor rights are enough. */\nexport interface SanityWriter extends SanityReader {\n transaction(): SanityTransaction\n withConfig?(config: RawConfig): SanityWriter\n}\n\n/**\n * Stock documents and every version of a product are only visible with a token, the raw\n * perspective and an API version that knows Content Releases.\n */\nconst RAW: RawConfig = {perspective: 'raw', useCdn: false, apiVersion: '2025-02-19'}\n\nexport function rawReader(client: SanityReader): SanityReader {\n return client.withConfig?.(RAW) ?? client\n}\n\nexport function rawWriter(client: SanityWriter): SanityWriter {\n return client.withConfig?.(RAW) ?? client\n}\n","import {asArray, isObject} from '../core/guards'\nimport {publishedId, STOCK_CHANGE_TYPE, stockChangeId, stockId} from '../core/ids'\nimport {productVersionsQuery, resolveProductQuery, type ProductQueryOptions} from '../core/queries'\nimport {readStockDocuments, STOCK_QUERY, stockLeft, type ProductStock} from '../core/stock'\nimport type {OrderItem} from './order'\nimport {rawReader, rawWriter, type SanityReader, type SanityWriter} from './sanity'\n\nconst SAFE_KEY = /^[\\w-]+$/\n\n/** Stripe stops redelivering an event after three days; a month leaves a wide margin. */\nconst KEEP_CHANGES_DAYS = 30\n\n/**\n * The stock of the given products, by product id, read from their private stock documents. The\n * client needs a token. A product without a stock document isn't counted and has no entry.\n *\n * A site can use it on the server to say \"only 2 left\"; it should never send the counts of\n * everything to the browser.\n */\nexport async function readStock(\n client: SanityReader,\n productIds: readonly string[],\n): Promise<Map<string, ProductStock>> {\n const ids = productIds.filter((id) => id && !id.includes('.')).map((id) => stockId(id))\n if (!ids.length) return new Map()\n return readStockDocuments(await rawReader(client).fetch(STOCK_QUERY, {ids}))\n}\n\n/** What an order did to one stock count. */\nexport interface StockChange {\n productId: string\n variantKey?: string\n from: number\n /** Below 0 when more was sold than was in stock. */\n to: number\n}\n\nexport interface StockResult {\n /**\n * `lowered`: counts were changed now. `already`: this session's stock was lowered before, and\n * `changes` is what happened then. `untracked`: nothing in the order has a stock count.\n */\n status: 'lowered' | 'already' | 'untracked'\n changes: StockChange[]\n}\n\nexport interface StockOptions {\n /** Mark a product or variant sold out when its count reaches 0. Default true. */\n autoSoldOut?: boolean\n /** Where the site keeps its products. */\n products?: ProductQueryOptions\n /** The current time, for tests. */\n now?: Date\n}\n\nfunction readChanges(value: unknown): StockChange[] {\n return asArray(value).flatMap((item) => {\n if (!isObject(item)) return []\n const {productId, variantKey, from, to} = item\n if (typeof productId !== 'string') return []\n if (typeof from !== 'number' || typeof to !== 'number') return []\n const change: StockChange = {productId, from, to}\n if (typeof variantKey === 'string') change.variantKey = variantKey\n return [change]\n })\n}\n\ninterface ProductVersion {\n _id: string\n variants: string[]\n}\n\nfunction readVersions(value: unknown): ProductVersion[] {\n return asArray(value).flatMap((item) =>\n isObject(item) && typeof item._id === 'string'\n ? [\n {\n _id: item._id,\n variants: asArray(item.variants).filter(\n (key): key is string => typeof key === 'string',\n ),\n },\n ]\n : [],\n )\n}\n\nconst isConflict = (error: unknown) => isObject(error) && error.statusCode === 409\n\n/**\n * Lowers the stock count of every ordered product or variant that has one. Products and variants\n * without a count are left alone. When a count reaches 0 and `autoSoldOut` is on, the product or\n * variant is marked sold out in every version of the product document, so publishing a draft or a\n * release can't put it back on sale.\n *\n * It happens once per Checkout Session, however often it is called: the counts change in one\n * transaction with a record of the session, and Sanity refuses a second record with the same id.\n * The record holds product ids and counts only.\n */\nexport async function decrementStock(\n client: SanityWriter,\n order: {sessionId: string; items: readonly OrderItem[]},\n options: StockOptions = {},\n): Promise<StockResult> {\n const {field} = resolveProductQuery(options.products)\n const autoSoldOut = options.autoSoldOut ?? true\n const now = options.now ?? new Date()\n\n const ordered: Array<{productId: string; variantKey?: string; quantity: number}> = []\n for (const item of order.items) {\n const {productId, variantKey, quantity} = item\n if (!productId || productId.includes('.')) continue\n if (variantKey && !SAFE_KEY.test(variantKey)) continue\n const same = ordered.find(\n (line) => line.productId === productId && line.variantKey === variantKey,\n )\n if (same) same.quantity += quantity\n else ordered.push(variantKey ? {productId, variantKey, quantity} : {productId, quantity})\n }\n if (!ordered.length) return {status: 'untracked', changes: []}\n\n const raw = rawWriter(client)\n const recordId = stockChangeId(order.sessionId)\n const readRecord = async (): Promise<StockChange[] | null> => {\n const record = await raw.fetch(`*[_id == $id][0]{changes}`, {id: recordId})\n return isObject(record) ? readChanges(record.changes) : null\n }\n\n // Three reads at once: Stripe waits for the webhook's answer before it sends the buyer on.\n const productIds = [...new Set(ordered.map((line) => line.productId))]\n const cutoff = new Date(now.getTime() - KEEP_CHANGES_DAYS * 24 * 60 * 60 * 1000)\n const [before, stock, oldRecords] = await Promise.all([\n readRecord(),\n readStock(raw, productIds),\n raw.fetch(`*[_type == $type && loweredAt < $before][0...25]._id`, {\n type: STOCK_CHANGE_TYPE,\n before: cutoff.toISOString(),\n }),\n ])\n if (before) return {status: 'already', changes: before}\n const old = asArray(oldRecords).filter((id): id is string => typeof id === 'string')\n\n const changes: StockChange[] = []\n const lower = new Map<string, Record<string, number>>()\n for (const {productId, variantKey, quantity} of ordered) {\n const from = stockLeft(stock.get(productId), variantKey)\n if (from === null) continue\n const dec = lower.get(productId) ?? {}\n dec[variantKey ? `variants[_key==\"${variantKey}\"].stockCount` : 'stockCount'] = quantity\n lower.set(productId, dec)\n const change: StockChange = {productId, from, to: from - quantity}\n if (variantKey) change.variantKey = variantKey\n changes.push(change)\n }\n if (!changes.length) return {status: 'untracked', changes: []}\n\n // The public sees \"sold out\", never the count: set it on every version of the product.\n const soldOut = autoSoldOut ? changes.filter((change) => change.to <= 0) : []\n const mark = new Map<string, Record<string, string>>()\n if (soldOut.length) {\n const {query, params} = productVersionsQuery(\n [...new Set(soldOut.map((change) => change.productId))],\n options.products,\n )\n for (const version of readVersions(await raw.fetch(query, params))) {\n const productId = publishedId(version._id)\n const set: Record<string, string> = {}\n for (const change of soldOut) {\n if (change.productId !== productId) continue\n if (!change.variantKey) set[`${field}.availability`] = 'soldOut'\n else if (version.variants.includes(change.variantKey))\n set[`${field}.variants[_key==\"${change.variantKey}\"].availability`] = 'soldOut'\n }\n if (Object.keys(set).length) mark.set(version._id, set)\n }\n }\n\n const transaction = client.transaction().create({\n _id: recordId,\n _type: STOCK_CHANGE_TYPE,\n sessionId: order.sessionId,\n loweredAt: now.toISOString(),\n changes: changes.map((change, index) => Object.assign({_key: `change-${index}`}, change)),\n })\n for (const [productId, dec] of lower) transaction.patch(stockId(productId), {dec})\n for (const [documentId, set] of mark) transaction.patch(documentId, {set})\n for (const documentId of old) transaction.delete(documentId)\n\n try {\n await transaction.commit()\n } catch (error) {\n // The record exists: another delivery of the same event got here first, and nothing changed now.\n if (isConflict(error)) {\n const winner = await readRecord()\n if (winner) return {status: 'already', changes: winner}\n }\n throw error\n }\n return {status: 'lowered', changes}\n}\n","import type Stripe from 'stripe'\n\nimport {readCartProducts, sanitizeLines, type CartLine, type CartProduct} from '../core/cart'\nimport {isObject} from '../core/guards'\nimport {\n checkoutProductsQuery,\n resolveProductQuery,\n SHOP_SETTINGS_QUERY,\n type ProductQueryOptions,\n} from '../core/queries'\nimport {\n readShopSettings,\n regionsFromSettings,\n type ShippingRegion,\n type ShopSettings,\n} from '../core/regions'\nimport type {ProductStock} from '../core/stock'\nimport {buildCheckoutSession} from './checkout'\nimport {classifyEvent, verifyEvent} from './events'\nimport {once} from './once'\nimport {getOrder, type Order} from './order'\nimport type {SanityReader, SanityWriter} from './sanity'\nimport {decrementStock, readStock, type StockChange} from './stock'\n\ntype Handler = (request: Request) => Promise<Response>\ntype Log = (level: 'info' | 'warn' | 'error', message: string) => void\n\n// Info lines go to the server's log too: they say what each payment did to stock and orders.\nconst consoleLog: Log = (level, message) => console[level](message)\n\nconst json = (body: unknown, status = 200) =>\n new Response(JSON.stringify(body), {\n status,\n headers: {'Content-Type': 'application/json', 'Cache-Control': 'no-store'},\n })\n\nconst reason = (error: unknown) => (error instanceof Error ? error.message : String(error))\n\n/** Stripe wants 30 minutes at least, counted on its own clock: leave a minute's margin. */\nconst SHORTEST_EXPIRY = 31 * 60\n\n// Checkout\n\nexport interface CheckoutMessages {\n /** The checkout can't be started: no key, no shipping regions, or Stripe refused. */\n unavailable: string\n /** The request couldn't be read. */\n invalid: string\n empty: string\n /** No shipping region, or one that no longer exists. */\n region: string\n /** A product in the cart sold out, changed or went away. */\n changed: string\n}\n\nconst CHECKOUT_MESSAGES: CheckoutMessages = {\n unavailable: \"Checkout isn't available right now. Please try again later.\",\n invalid: 'The cart could not be read.',\n empty: 'Your cart is empty.',\n region: 'Choose where to ship the order.',\n changed: 'Some things in your cart have changed. Check them and try again.',\n}\n\nexport interface CheckoutRouteOptions {\n /** From `createStripe`. Without one the route answers 503, so a site runs before it has keys. */\n stripe: Stripe | null | undefined\n /**\n * Reads products and the shop settings, without a token, so it sees published documents only.\n * It must skip the CDN, so prices are fresh.\n */\n client: SanityReader\n /**\n * A Sanity client with a token, to read stock counts, which the public can't. Without it nothing\n * is counted and the shop sells by the on-sale switch alone.\n */\n stockClient?: SanityReader | null\n /** ISO 4217 code, the same one the Studio plugin has. */\n currency: string\n /**\n * Where Stripe sends the buyer after paying: a path on the site or a full address. Include\n * `{CHECKOUT_SESSION_ID}`, such as `/shop/thanks?session_id={CHECKOUT_SESSION_ID}`.\n */\n successUrl: string\n /** Where Stripe's back link goes: the cart. A path or a full address. */\n cancelUrl: string\n /**\n * Marks this shop's sessions: the webhook fulfils only sessions that carry it, and the Stripe\n * Dashboard groups them by it. Give the webhook route the same value. End it with 8 random\n * letters, such as `myshop_qhzvmkpt`.\n */\n integrationIdentifier: string\n /** Where the site keeps its products. Default: `product` documents with a `commerce` field. */\n products?: ProductQueryOptions\n /** Stripe tax rate ids (`txr_…`) by tax class, so receipts show the tax. */\n taxRates?: Partial<Record<string, string>>\n /** Most of one item a buyer can order at once. Default 10. */\n maxQuantity?: number\n /** Answer \"not that many left\" without saying how many, when a cart asks for too many. */\n hideRemaining?: boolean\n /** Minutes until an unpaid session expires, about 30 at least. Stripe's default is 24 hours. */\n expiresInMinutes?: number\n /** Changes the session before it is created: promotion codes, phone numbers, consent. */\n extend?: (\n params: Stripe.Checkout.SessionCreateParams,\n context: {lines: CartLine[]; region: ShippingRegion; request: Request},\n ) => Stripe.Checkout.SessionCreateParams | Promise<Stripe.Checkout.SessionCreateParams>\n /** What buyers read when the checkout can't start. */\n messages?: Partial<CheckoutMessages>\n /** Adds the reason to error replies. For development only. */\n debug?: boolean\n log?: Log\n}\n\n/**\n * The site's checkout route. The browser posts the cart's product references and the chosen\n * shipping region; prices, stock and shipping are read fresh from Sanity here.\n *\n * ```ts\n * // app/api/checkout/route.ts\n * export const POST = checkoutRoute({stripe, client, stockClient, currency: 'eur', …})\n * ```\n *\n * It answers `{url}` to go to, or `{error, message, problems}`.\n */\nexport function checkoutRoute(options: CheckoutRouteOptions): Handler {\n const {client, currency, log = consoleLog} = options\n const messages = {...CHECKOUT_MESSAGES, ...options.messages}\n const productsQuery = checkoutProductsQuery(options.products)\n let saidUncounted = false\n\n const unavailable = (detail: string, status = 503) => {\n log('error', `[checkout] ${detail}`)\n return json(\n {error: 'unavailable', message: messages.unavailable, ...(options.debug ? {detail} : {})},\n status,\n )\n }\n\n return async (request) => {\n const {stripe, stockClient} = options\n if (!stripe) return unavailable('There is no Stripe key. Set one in the environment.')\n\n const body: unknown = await request.json().catch(() => null)\n if (!isObject(body)) return json({error: 'invalid', message: messages.invalid}, 400)\n\n const lines = sanitizeLines(body.lines, options.maxQuantity)\n const ids = [...new Set(lines.map((line) => line.productId))]\n let products: CartProduct[]\n let settings: ShopSettings | null\n let stock: Map<string, ProductStock> | undefined\n try {\n const [foundProducts, foundSettings, foundStock] = await Promise.all([\n ids.length ? client.fetch(productsQuery, {ids}) : [],\n client.fetch(SHOP_SETTINGS_QUERY),\n stockClient ? readStock(stockClient, ids) : undefined,\n ])\n products = readCartProducts(foundProducts)\n settings = readShopSettings(foundSettings)\n stock = foundStock\n } catch (error) {\n return unavailable(`Sanity could not be read: ${reason(error)}`, 502)\n }\n if (!stockClient && !saidUncounted) {\n saidUncounted = true\n log('info', '[checkout] No stockClient: stock counts are not checked at checkout.')\n }\n const regions = regionsFromSettings(settings)\n if (!regions.length)\n return unavailable('No shipping regions. Publish the shop settings in the Studio.')\n\n const origin = new URL(request.url).origin\n const result = buildCheckoutSession({\n lines,\n regionId: body.regionId,\n products,\n regions,\n currency,\n successUrl: absoluteUrl(options.successUrl, origin),\n cancelUrl: absoluteUrl(options.cancelUrl, origin),\n integrationIdentifier: options.integrationIdentifier,\n ...(stock ? {stock} : {}),\n ...(options.hideRemaining ? {hideRemaining: true} : {}),\n ...(options.taxRates ? {taxRates: options.taxRates} : {}),\n ...(options.maxQuantity ? {maxQuantity: options.maxQuantity} : {}),\n ...(options.expiresInMinutes\n ? {\n expiresAt:\n Math.floor(Date.now() / 1000) +\n Math.max(SHORTEST_EXPIRY, options.expiresInMinutes * 60),\n }\n : {}),\n })\n\n if (!result.ok) {\n const message = {\n empty: messages.empty,\n region: messages.region,\n unavailable: messages.changed,\n }[result.error]\n return json({error: result.error, message, problems: result.problems}, 409)\n }\n\n try {\n const params = options.extend\n ? await options.extend(result.params, {lines: result.lines, region: result.region, request})\n : result.params\n const session = await stripe.checkout.sessions.create(params)\n if (!session.url)\n return unavailable(`Checkout Session ${session.id} came back without a URL.`, 502)\n return json({url: session.url})\n } catch (error) {\n return unavailable(`Stripe refused the Checkout Session: ${reason(error)}`, 502)\n }\n }\n}\n\n/** Stripe needs full addresses. `{CHECKOUT_SESSION_ID}` must reach it as written, not encoded. */\nfunction absoluteUrl(url: string, origin: string): string {\n return /^https?:\\/\\//.test(url) ? url : `${origin}${url.startsWith('/') ? '' : '/'}${url}`\n}\n\n// Webhook\n\nexport interface OrderContext {\n /** The shop settings as published. Null before they are published. */\n settings: ShopSettings | null\n /** What the order did to stock counts. Empty when nothing ordered is counted. */\n stock: StockChange[]\n /**\n * Set when stock could not be lowered, with the reason: say so to whoever packs the order.\n * Stripe is asked to deliver the event again, and only the stock is tried again.\n */\n stockError?: string\n session: Stripe.Checkout.Session\n stripe: Stripe\n}\n\nexport interface WebhookRouteOptions {\n /** From `createStripe`. */\n stripe: Stripe | null | undefined\n /** The endpoint's signing secret (`whsec_…`), from the environment. */\n webhookSecret: string | undefined\n /** The same value the checkout route has. Sessions without it are not this shop's. */\n integrationIdentifier: string\n /** Reads the shop settings, without a token. It must skip the CDN. */\n client: SanityReader\n /**\n * A Sanity client with a token that can write, to lower stock counts when an order is paid.\n * Without one, stock is left as it is.\n */\n stockClient?: SanityWriter | null\n /** Where the site keeps its products. The same as the checkout route's. */\n products?: ProductQueryOptions\n /**\n * Called once for each paid order, after stock is lowered: send the order email here. Throw\n * and Stripe delivers the event again later; stock isn't lowered twice.\n */\n onOrder?: (order: Order, context: OrderContext) => void | Promise<void>\n /** Called when a delayed payment (a bank transfer, for one) fails. Nothing was fulfilled. */\n onPaymentFailed?: (session: Stripe.Checkout.Session) => void | Promise<void>\n log?: Log\n}\n\n/**\n * The site's Stripe webhook. A paid order of this shop lowers stock and calls `onOrder`, each\n * once per payment however often Stripe delivers the event. Payments made any other way on the\n * same Stripe account (a Payment Link, another site) are left alone. Nothing is fulfilled on the\n * thank-you page.\n *\n * ```ts\n * // app/api/stripe/webhook/route.ts\n * export const POST = webhookRoute({stripe, webhookSecret, integrationIdentifier, client, …})\n * ```\n *\n * Point a Stripe webhook endpoint at it with the events in `WEBHOOK_EVENTS`.\n */\nexport function webhookRoute(options: WebhookRouteOptions): Handler {\n const {client, log = consoleLog} = options\n // Fail when the site starts, not on the first order.\n resolveProductQuery(options.products)\n let saidUncounted = false\n\n /**\n * The session as this client's API version shapes it, or null when it isn't this shop's. The\n * event carries the session as the endpoint's version shapes it, so it is read again.\n */\n const ownSession = async (stripe: Stripe, sessionId: string) => {\n const session = await stripe.checkout.sessions.retrieve(sessionId)\n const own =\n session.mode === 'payment' && session.integration_identifier === options.integrationIdentifier\n if (!own) log('info', `[stripe webhook] ${sessionId}: not one of this shop's, left alone.`)\n return own ? session : null\n }\n\n const fulfil = async (stripe: Stripe, sessionId: string) => {\n const session = await ownSession(stripe, sessionId)\n if (!session) return\n // Trust the payment status Stripe's API gives now over the one in the event.\n if (session.payment_status === 'unpaid') {\n log('info', `[stripe webhook] ${sessionId}: not paid yet.`)\n return\n }\n const [order, foundSettings] = await Promise.all([\n getOrder(stripe, session),\n client.fetch(SHOP_SETTINGS_QUERY),\n ])\n const settings = readShopSettings(foundSettings)\n\n let stock: StockChange[] = []\n let stockError: string | undefined\n if (options.stockClient) {\n try {\n const result = await decrementStock(options.stockClient, order, {\n autoSoldOut: settings?.autoSoldOut ?? true,\n ...(options.products ? {products: options.products} : {}),\n })\n stock = result.changes\n if (result.status === 'lowered') {\n const summary = stock\n .map(\n ({productId, variantKey, from, to}) =>\n `${productId}${variantKey ? ` ${variantKey}` : ''} ${from} → ${to}`,\n )\n .join(', ')\n log('info', `[stripe webhook] ${sessionId}: stock ${summary}`)\n }\n } catch (error) {\n // The order still has to reach the shop. It is told, and Stripe is asked to come back.\n stockError = reason(error)\n log('error', `[stripe webhook] ${sessionId}: stock was not lowered: ${stockError}`)\n }\n } else if (!saidUncounted) {\n saidUncounted = true\n log('info', '[stripe webhook] No stockClient: stock counts are left as they are.')\n }\n\n const {onOrder} = options\n if (onOrder) {\n const outcome = await once(stripe, session, 'order', async () => {\n await onOrder(order, {\n settings,\n stock,\n ...(stockError ? {stockError} : {}),\n session,\n stripe,\n })\n })\n log('info', `[stripe webhook] ${sessionId}: order ${outcome}.`)\n }\n\n if (stockError) throw new Error(`Stock was not lowered: ${stockError}`)\n }\n\n return async (request) => {\n const {stripe, webhookSecret} = options\n if (!stripe || !webhookSecret) {\n log('error', '[stripe webhook] The Stripe key or the webhook secret is not set.')\n return json({error: 'Webhook not configured'}, 503)\n }\n\n // The signature covers the exact bytes Stripe sent, so read the body as text before parsing.\n const payload = await request.text()\n let event: Stripe.Event\n try {\n event = verifyEvent(stripe, payload, request.headers.get('stripe-signature'), webhookSecret)\n } catch (error) {\n log('warn', `[stripe webhook] Signature check failed: ${reason(error)}`)\n return json({error: 'Invalid signature'}, 400)\n }\n\n const result = classifyEvent(event)\n try {\n switch (result.type) {\n case 'paid':\n await fulfil(stripe, result.session.id)\n break\n case 'pending':\n log('info', `[stripe webhook] ${result.session.id}: waiting for a delayed payment.`)\n break\n case 'failed': {\n const session = await ownSession(stripe, result.session.id)\n if (session) {\n log('warn', `[stripe webhook] ${session.id}: the delayed payment failed.`)\n await options.onPaymentFailed?.(session)\n }\n break\n }\n case 'ignored':\n break\n }\n } catch (error) {\n // A 500 makes Stripe deliver the event again. Steps that already finished are skipped then.\n log('error', `[stripe webhook] ${event.type} ${event.id} failed: ${reason(error)}`)\n return json({error: 'Fulfilment failed'}, 500)\n }\n\n return json({received: true})\n }\n}\n","/** The package's version, sent to Stripe with each call. A test keeps it in step with package.json. */\nexport const VERSION = '0.1.1'\n","import {Stripe} from 'stripe'\n\nimport {VERSION} from '../version'\n\n/** The Stripe API version the plugin is written and tested against (stripe-node 23). */\nexport const STRIPE_API_VERSION = '2026-10-28.endive'\n\n/**\n * A Stripe client pinned to the API version the plugin is tested against, so a Stripe upgrade\n * never changes the API under the shop. Pass the key from the site's environment, never from code:\n *\n * ```ts\n * const stripe = process.env.STRIPE_SECRET_KEY ? createStripe(process.env.STRIPE_SECRET_KEY) : null\n * ```\n *\n * A restricted key (`rk_…`) is enough, and safer than a secret key. `config` takes the rest of\n * stripe-node's options, such as `maxNetworkRetries` or `httpClient`.\n */\nexport function createStripe(\n secretKey: string,\n config: Omit<Stripe.StripeConfig, 'apiVersion' | 'appInfo'> = {},\n): Stripe {\n return new Stripe(secretKey, {\n ...config,\n apiVersion: STRIPE_API_VERSION,\n appInfo: {\n name: 'sanity-plugin-stripe',\n version: VERSION,\n url: 'https://github.com/ConfederationStudio/sanity-plugin-stripe',\n },\n })\n}\n"],"mappings":";;;;AAMA,MAAM,cAAc,UAClB,MAAM,QACJ,aACC,UAAU;CAAC,KAAK;CAAS,KAAK;CAAQ,KAAK;CAAQ,MAAK;CAAU,KAAK;AAAO,EAAA,CAAG,SAAS,IAC7F;AAEF,SAAS,aAAa,MAAqB,SAA0C;CACnF,IAAI,CAAC,SAAS,OAAO,OAAO,CAAC,IAAI,IAAI,CAAC;CACtC,IAAM,OAAO,CAAC,QAAQ,aAAa,QAAQ,IAAI,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;CACzE,OAAO;EAAC;EAAM,QAAQ;EAAO,QAAQ;EAAO;EAAM,QAAQ;EAAO,QAAQ;CAAO,CAAC,CAAC,QAC/E,SAAyB,EAAQ,IACpC;AACF;;AAGA,SAAgB,aACd,OACQ;CACR,IAAM,OAAO,gCAAgC,MAAM,WAAW,KAAK;CACnE,OAAO,MAAM,kBACT,GAAG,KAAK,WAAW,MAAM,oBACzB,GAAG,KAAK,oBAAoB,MAAM;AACxC;;AAoBA,SAAgB,WAAW,OAAc,SAAwC;CAC/E,IAAM,EAAC,UAAU,SAAS,MAAM,QAAQ,CAAC,GAAG,eAAc,SACpD,SAAS,UAAkB,YAAY,OAAO,MAAM,UAAU,MAAM,GACpE,QAAQ,MAAM,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,UAAU,CAAC,GAChE,MAAM,MAAM,SAAS,QAAQ,MAAM,SAAS,QAAQ,MAAM,SAAS,SAAS,cAC5E,UAAU,GAAG,MAAM,WAAW,KAAK,YAAY,SAAS,cAAc,IAAI,IAAI,MAAM,GAAG,UAAU,IAAI,SAAS,QAAQ,IAAI,MAAM,MAAM,WAAW,KACjJ,UAAU,aAAa,MAAM,SAAS,MAAM,MAAM,SAAS,OAAO,GAClE,UAAU,CAAC,MAAM,SAAS,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,QAAQ,SACnE,EAAQ,IACV,GACM,gBAAgB,MAAM,cAAc,aAAa,MAAM,YAAY,KAAK,YACxE,OAAO,aAAa,KAAK,GACzB,OAAO,MAAM,UAAU,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE,GAEhD,WAAW,MACd,QAAQ,WAAW,OAAO,KAAK,CAAC,CAAC,CACjC,KAAK,WACS,MAAM,MAAM,MACtB,cACC,UAAU,cAAc,OAAO,cAC9B,UAAU,cAAc,SAAS,OAAO,cAAc,GAEjD,CAAC,EAAE,QAAQ,OAAO,SAC7B,GACG,WAAW,CACf,GAAI,SAAS,SACT,CAAC,iEAAiE,SAAS,KAAK,IAAI,EAAE,EAAE,IACxF,CAAC,GACL,GAAI,aACA,CAAC,uEAAuE,WAAW,EAAE,IACrF,CAAC,CACP,GAEM,OAAO;EACX,gBAAgB,OAAO,MAAM,WAAW,KAAK;EAC7C;EACA,GAAG,SAAS,SAAS,YAAY,CAAC,SAAS,EAAE,CAAC;EAC9C,GAAG,MAAM,MAAM,KAAK,SAAS,GAAG,KAAK,SAAS,KAAK,KAAK,KAAK,IAAI,MAAM,KAAK,WAAW,GAAG;EAC1F,GAAG,cAAc,IAAI,MAAM,MAAM,cAAc;EAC/C,UAAU,MAAM,MAAM,WAAW;EACjC;EACA;EACA,GAAG;EACH;EACA;EACA,GAAG;EACH;EACA,cAAc;CAChB,CAAC,CAAC,KAAK,IAAI,GAEL,OAAO,MAAM,MAChB,KACE,SACC,0CAA0C,KAAK,SAAS,4CAA4C,WAAW,KAAK,IAAI,EAAE,kDAAkD,MAAM,KAAK,WAAW,EAAE,WACxM,CAAC,CACA,KAAK,EAAE;CAYV,OAAO;EAAC;EAAS;EAAM,MAAA;kBAVP,OAAO,MAAM,WAAW,KAAK,gDAAgD;EAC7F,SAAS,KAAK,YAAY,cAAc,WAAW,OAAO,EAAE,gBAAgB,CAAC,CAAC,KAAK,EAAE,EAAE,0CAA0C,KAAK;kDACtF,WAAW,aAAa,EAAE,kDAAkD,MAAM,MAAM,cAAc,EAAE;gIAC1B,MAAM,MAAM,WAAW,EAAE;;iCAExH,QAAQ,IAAI,UAAU,CAAC,CAAC,KAAK,MAAM,EAAE;iCACrC,QAAQ,IAAI,UAAU,CAAC,CAAC,KAAK,MAAM,EAAE;cACxD,KAAK;;CAGU;AAC7B;;ACjHA,SAAgB,YACd,QACA,SACA,WACA,QACc;CACd,IAAI,CAAC,WAAW,MAAU,MAAM,iCAAiC;CACjE,OAAO,OAAO,SAAS,eAAe,SAAS,WAAW,MAAM;AAClE;;AAYA,MAAa,iBAAiB;CAC5B;CACA;CACA;AACF;;AAGA,SAAgB,cAAc,OAAoC;CAChE,QAAQ,MAAM,MAAd;EACE,KAAK;EACL,KAAK,4CAA4C;GAC/C,IAAM,UAAU,MAAM,KAAK;GAC3B,OAAO,QAAQ,mBAAmB,WAC9B;IAAC,MAAM;IAAW;GAAO,IACzB;IAAC,MAAM;IAAQ;GAAO;EAC5B;EACA,KAAK,yCACH,OAAO;GAAC,MAAM;GAAU,SAAS,MAAM,KAAK;EAAM;EACpD,SACE,OAAO,EAAC,MAAM,UAAS;CAC3B;AACF;AC5BA,MAAM,OAAO,kBACP,YAAY;;AAGlB,SAAgB,oBAAoB,UAA+B,CAAC,GAAG;CACrE,IAAM,QAAQ,QAAQ,OAAO,SAAS,CAAC,GAAG,QAAQ,KAAK,IAAI,CAAC,SAAS,GAC/D,QAAQ,QAAQ,SAAS;CAC/B,KAAK,IAAM,QAAQ,OACjB,IAAI,CAAC,UAAU,KAAK,IAAI,GACtB,MAAU,UAAU,0BAA0B,KAAK,8BAA8B;CAErF,IAAI,CAAC,KAAK,KAAK,KAAK,GAAG,MAAU,UAAU,0BAA0B,MAAM,sBAAsB;CACjG,OAAO;EACL;EACA;EACA,OAAO,QAAQ,SAAS;EACxB,MAAM,QAAQ,QAAQ;EACtB,OACE,QAAQ,SAAS;CACrB;AACF;AAEA,MAAM,cAAc,UAClB,aAAa,MAAM,KAAK,SAAS,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE;;;;;;;;AAYpE,SAAgB,sBAAsB,SAAuC;CAC3E,IAAM,EAAC,OAAO,OAAO,MAAM,UAAS,oBAAoB,OAAO;CAC/D,OAAO,iBAAiB,MAAM,YAAY,KAAK,gBAAgB,MAAM,gBAAgB;AACvF;;AAGA,SAAgB,kBAAkB,SAAuC;CACvE,IAAM,EAAC,OAAO,UAAS,oBAAoB,OAAO;CAClD,OAAO,KAAK,WAAW,KAAK,EAAE,cAAc,MAAM,KAAK,sBAAsB,OAAO,EAAE;AACxF;;AAGA,SAAgB,sBAAsB,SAAuC;CAC3E,IAAM,EAAC,UAAS,oBAAoB,OAAO;CAC3C,OAAO,KAAK,WAAW,KAAK,EAAE,mBAAmB,sBAAsB,OAAO,EAAE;AAClF;;;;;;AAOA,SAAgB,qBACd,YACA,SACiD;CACjD,IAAM,EAAC,OAAO,UAAS,oBAAoB,OAAO,GAC5C,SAAiC,CAAC,GAClC,aAAa,WAAW,KAAK,IAAI,WACrC,OAAO,UAAU,WAAW,IACrB,6BAA6B,MAAM,GAC3C;CACD,OAAO;EACL,OAAO,KAAK,WAAW,KAAK,EAAE,cAAc,MAAM,QAAQ,WAAW,KAAK,MAAM,KAAK,QAAQ,sBAAsB,MAAM;EACzH;CACF;AACF;;AAGA,MAAa,sBAAsB,eAAe,mBAAmB,eAAe,iBAAiB;;AC/DrG,SAAgB,iBAAiB,OAAqC;CAEpE,OADK,SAAS,KAAK,IACZ;EACL,iBAAiB,QAAQ,MAAM,eAAe,CAAC,CAAC,SAAS,WACvD,SAAS,MAAM,KAAK,OAAO,OAAO,QAAS,WACvC,CACE;GACE,MAAM,OAAO;GACb,OAAO,SAAS,OAAO,KAAK;GAC5B,gBAAgB,OAAO,mBAAmB;GAC1C,WAAW,QAAQ,OAAO,SAAS,CAAC,CAAC,QAClC,YAA+B,OAAO,WAAY,QACrD;GACA,MAAM,SAAS,OAAO,IAAI;EAC5B,CACF,IACA,CAAC,CACP;EACA,aAAa,OAAO,MAAM,eAAgB,YAAY,MAAM,cAAc;CAC5E,IAlB6B;AAmB/B;;AAGA,SAAgB,oBAAoB,UAA6D;CAC/F,QAAQ,UAAU,mBAAmB,CAAC,EAAA,CAAG,SAAS,WAChD,OAAO,SAAS,OAAO,OAAO,QAAS,WACnC,CACE;EACE,IAAI,OAAO;EACX,OAAO,OAAO;EACd,WAAW,OAAO,aAAa,CAAC;EAChC,gBAAgB,EAAQ,OAAO;EAC/B,MAAM,OAAO;CACf,CACF,IACA,CAAC,CACP;AACF;;;;;AAMA,SAAgB,eAAe,SAAsD;CACnF,IAAM,YAAY,IAAI,IAAY,yBAAyB,GACrD,SAAS,IAAI,IACjB,QAAQ,QAAQ,WAAW,CAAC,OAAO,cAAc,CAAC,CAAC,SAAS,WAAW,OAAO,SAAS,CACzF;CACA,OAAO,QACJ,KAAK,YAAY;EAChB,GAAG;EACH,WAAW,OAAO,iBACd,0BAA0B,QAAQ,YAAY,CAAC,OAAO,IAAI,OAAO,CAAC,IAClE,OAAO,UAAU,QAAQ,YAAY,UAAU,IAAI,OAAO,CAAC;CACjE,EAAE,CAAC,CACF,QAAQ,WAAW,OAAO,UAAU,SAAS,CAAC;AACnD;;AAMA,SAAgB,cAAc,UAA2D;CACvF,OAAO,eAAe,oBAAoB,QAAQ,CAAC,CAAC,CAAC,KAAK,EAAC,IAAI,OAAO,YAAW;EAC/E;EACA;EACA;CACF,EAAE;AACJ;ACzCA,SAAgB,qBAAqB,OAAsC;CACzE,IAAM,MAAM,MAAM,eAAA,IACZ,WAAW,MAAM,SAAS,YAAY,GACtC,QAAQ,cAAc,MAAM,OAAO,GAAG;CAC5C,IAAI,CAAC,MAAM,QAAQ,OAAO;EAAC,IAAI;EAAO,OAAO;EAAS,UAAU,CAAC;CAAC;CAElE,IAAM,SAAS,eAAe,MAAM,OAAO,CAAC,CAAC,MAAM,cAAc,UAAU,OAAO,MAAM,QAAQ;CAChG,IAAI,CAAC,QAAQ,OAAO;EAAC,IAAI;EAAO,OAAO;EAAU,UAAU,CAAC;CAAC;CAE7D,IAAM,WAAW,IAAI,IAAI,MAAM,SAAS,KAAK,YAAY,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,GAC1E,WAA8B,CAAC,GAC/B,YAA8B,CAAC;CAErC,KAAK,IAAM,QAAQ,OAAO;EACxB,IAAM,UAAU,SAAS,IAAI,KAAK,SAAS,GACrC,SAAS,WAAW,SAAS,KAAK,YAAY,GAAG,GACjD,MAAM;GACV,WAAW,KAAK;GAChB,GAAI,KAAK,aAAa,EAAC,YAAY,KAAK,WAAU,IAAI,CAAC;EACzD;EACA,IAAI,CAAC,WAAW,CAAC,OAAO,WAAW;GACjC,SAAS,KAAK;IACZ,GAAG;IACH,GAAI,SAAS,QAAQ,EAAC,MAAM,QAAQ,MAAK,IAAI,CAAC;IAC9C,QAAQ,OAAO,YAAY,YAAY,OAAO;GAChD,CAAC;GACD;EACF;EACA,IAAM,OAAO,SAAS,SAAS,OAAO,OAAO,GAGvC,OAAO,UAAU,MAAM,OAAO,IAAI,KAAK,SAAS,GAAG,KAAK,UAAU;EACxE,IAAI,SAAS,QAAQ,QAAQ,GAAG;GAC9B,SAAS,KAAK;IAAC,GAAG;IAAK;IAAM,QAAQ;GAAS,CAAC;GAC/C;EACF;EACA,IAAM,OAAO,SAAS,OAAO,OAAO,cAAc,KAAK,IAAI,OAAO,aAAa,IAAI;EACnF,IAAI,KAAK,WAAW,MAAM;GACxB,SAAS,KAAK;IACZ,GAAG;IACH;IACA,QAAQ;IACR,GAAI,MAAM,gBAAgB,CAAC,IAAI,EAAC,aAAa,KAAI;GACnD,CAAC;GACD;EACF;EAEA,IAAM,WAAW,QAAQ,UAAU,UAC7B,UAAU,WAAW,MAAM,WAAW,YAAY,KAAA;EACxD,UAAU,KAAK;GACb,UAAU,KAAK;GACf,YAAY;IACV;IACA,aAAa,aAAa,OAAO,OAAO,QAAQ;IAChD,cAAc;KACZ;KACA,GAAI,QAAQ,WAAW,EAAC,QAAQ,CAAC,QAAQ,QAAQ,EAAC,IAAI,CAAC;KAEvD,UAAU;IACZ;GACF;GACA,GAAI,UAAU,EAAC,WAAW,CAAC,OAAO,EAAC,IAAI,CAAC;EAC1C,CAAC;CACH;CAIA,OAFI,SAAS,SAAe;EAAC,IAAI;EAAO,OAAO;EAAe;CAAQ,IAE/D;EACL,IAAI;EACJ;EACA;EACA,QAAQ;GACN,MAAM;GACN,YAAY;GAEZ,6BAA6B,EAAC,mBAAmB,OAAO,UAAS;GACjE,kBAAkB,CAChB,EACE,oBAAoB;IAClB,MAAM;IACN,cAAc,OAAO;IACrB,cAAc;KAAC,QAAQ,aAAa,OAAO,MAAM,QAAQ;KAAG;IAAQ;GACtE,EACF,CACF;GACA,aAAa,MAAM;GACnB,YAAY,MAAM;GAClB,UAAU;IAAC,QAAQ,OAAO;IAAI,aAAa,OAAO;GAAK;GACvD,wBAAwB,MAAM;GAC9B,GAAI,MAAM,YAAY,EAAC,YAAY,MAAM,UAAS,IAAI,CAAC;EACzD;CACF;AACF;;;;;;;;;;;ACtIA,eAAsB,KACpB,QACA,SACA,MACA,IAC6B;CAC7B,IAAM,MAAM,aAAa;CAMzB,OALI,QAAQ,WAAW,OAAa,aACpC,MAAM,GAAG,GACT,MAAM,OAAO,SAAS,SAAS,OAAO,QAAQ,IAAI,EAChD,UAAU,GAAE,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY,EAAC,EAC5C,CAAC,GACM;AACT;;ACMA,eAAsB,SAAS,QAAgB,SAAkD;CAK/F,IAAM,SAAQ,MAJU,OAAO,SAAS,SAAS,cAAc,QAAQ,IAAI;EACzE,OAAO;EACP,QAAQ,CAAC,oBAAoB;CAC/B,CAAC,EAAA,CACuB,KAAK,KAAK,SAAoB;EACpD,IAAM,UAAU,KAAK,OAAO,SACtB,WACJ,WAAW,OAAO,WAAY,YAAY,CAAC,QAAQ,UAAU,QAAQ,WAAW,CAAC,GAC7E,UAAqB;GACzB,MAAM,KAAK,eAAe;GAC1B,UAAU,KAAK,YAAY;GAC3B,aAAa,KAAK;EACpB;EAGA,OAFI,SAAS,cAAW,QAAQ,YAAY,SAAS,YACjD,SAAS,eAAY,QAAQ,aAAa,SAAS,aAChD;CACT,CAAC,GACK,kBAAkB,QAAQ,uBAAuB,kBACjD,gBAAgB,QAAQ;CAC9B,OAAO;EACL,WAAW,QAAQ;EACnB,iBACE,OAAO,iBAAkB,WAAW,gBAAiB,eAAe,MAAM;EAC5E,UAAU,QAAQ;EAClB,UAAU,QAAQ,YAAY;EAC9B,aAAa,QAAQ,gBAAgB;EACrC,gBAAgB,QAAQ,eAAe,gBAAgB;EACvD,GAAI,QAAQ,UAAU,SAAS,EAAC,UAAU,QAAQ,SAAS,OAAM,IAAI,CAAC;EACtE,GAAI,QAAQ,UAAU,cAAc,EAAC,aAAa,QAAQ,SAAS,YAAW,IAAI,CAAC;EACnF,UAAU;GACR,MAAM,QAAQ,kBAAkB,QAAQ;GACxC,OAAO,QAAQ,kBAAkB,SAAS;GAC1C,OAAO,QAAQ,kBAAkB,SAAS;EAC5C;EACA,UAAU;GAAC,MAAM,iBAAiB,QAAQ;GAAM,SAAS,iBAAiB,WAAW;EAAI;EACzF;EACA,2BAAW,IAAI,KAAK,QAAQ,UAAU,GAAI;CAC5C;AACF;;;;;AC/BA,MAAM,MAAiB;CAAC,aAAa;CAAO,QAAQ;CAAO,YAAY;AAAY;AAEnF,SAAgB,UAAU,QAAoC;CAC5D,OAAO,OAAO,aAAa,GAAG,KAAK;AACrC;AAEA,SAAgB,UAAU,QAAoC;CAC5D,OAAO,OAAO,aAAa,GAAG,KAAK;AACrC;ACxCA,MAAM,WAAW;;;;;;;;AAYjB,eAAsB,UACpB,QACA,YACoC;CACpC,IAAM,MAAM,WAAW,QAAQ,OAAO,MAAM,CAAC,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,QAAQ,EAAE,CAAC;CAEtF,OADK,IAAI,SACF,mBAAmB,MAAM,UAAU,MAAM,CAAC,CAAC,MAAM,aAAa,EAAC,IAAG,CAAC,CAAC,oBADnD,IAAI,IAAI;AAElC;AA6BA,SAAS,YAAY,OAA+B;CAClD,OAAO,QAAQ,KAAK,CAAC,CAAC,SAAS,SAAS;EACtC,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO,CAAC;EAC7B,IAAM,EAAC,WAAW,YAAY,MAAM,OAAM;EAE1C,IADI,OAAO,aAAc,YACrB,OAAO,QAAS,YAAY,OAAO,MAAO,UAAU,OAAO,CAAC;EAChE,IAAM,SAAsB;GAAC;GAAW;GAAM;EAAE;EAEhD,OADI,OAAO,cAAe,aAAU,OAAO,aAAa,aACjD,CAAC,MAAM;CAChB,CAAC;AACH;AAOA,SAAS,aAAa,OAAkC;CACtD,OAAO,QAAQ,KAAK,CAAC,CAAC,SAAS,SAC7B,SAAS,IAAI,KAAK,OAAO,KAAK,OAAQ,WAClC,CACE;EACE,KAAK,KAAK;EACV,UAAU,QAAQ,KAAK,QAAQ,CAAC,CAAC,QAC9B,QAAuB,OAAO,OAAQ,QACzC;CACF,CACF,IACA,CAAC,CACP;AACF;AAEA,MAAM,cAAc,UAAmB,SAAS,KAAK,KAAK,MAAM,eAAe;;;;;;;;;;;AAY/E,eAAsB,eACpB,QACA,OACA,UAAwB,CAAC,GACH;CACtB,IAAM,EAAC,UAAS,oBAAoB,QAAQ,QAAQ,GAC9C,cAAc,QAAQ,eAAe,IACrC,MAAM,QAAQ,uBAAO,IAAI,KAAK,GAE9B,UAA6E,CAAC;CACpF,KAAK,IAAM,QAAQ,MAAM,OAAO;EAC9B,IAAM,EAAC,WAAW,YAAY,aAAY;EAE1C,IADI,CAAC,aAAa,UAAU,SAAS,GAAG,KACpC,cAAc,CAAC,SAAS,KAAK,UAAU,GAAG;EAC9C,IAAM,OAAO,QAAQ,MAClB,SAAS,KAAK,cAAc,aAAa,KAAK,eAAe,UAChE;EACA,AAAI,OAAM,KAAK,YAAY,WACtB,QAAQ,KAAK,aAAa;GAAC;GAAW;GAAY;EAAQ,IAAI;GAAC;GAAW;EAAQ,CAAC;CAC1F;CACA,IAAI,CAAC,QAAQ,QAAQ,OAAO;EAAC,QAAQ;EAAa,SAAS,CAAC;CAAC;CAE7D,IAAM,MAAM,UAAU,MAAM,GACtB,WAAW,cAAc,MAAM,SAAS,GACxC,aAAa,YAA2C;EAC5D,IAAM,SAAS,MAAM,IAAI,MAAM,6BAA6B,EAAC,IAAI,SAAQ,CAAC;EAC1E,OAAO,SAAS,MAAM,IAAI,YAAY,OAAO,OAAO,IAAI;CAC1D,GAGM,aAAa,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAK,SAAS,KAAK,SAAS,CAAC,CAAC,GAC/D,yBAAS,IAAI,KAAK,IAAI,QAAQ,IAAI,MAAuC,GACzE,CAAC,QAAQ,OAAO,cAAc,MAAM,QAAQ,IAAI;EACpD,WAAW;EACX,UAAU,KAAK,UAAU;EACzB,IAAI,MAAM,wDAAwD;GAChE,MAAM;GACN,QAAQ,OAAO,YAAY;EAC7B,CAAC;CACH,CAAC;CACD,IAAI,QAAQ,OAAO;EAAC,QAAQ;EAAW,SAAS;CAAM;CACtD,IAAM,MAAM,QAAQ,UAAU,CAAC,CAAC,QAAQ,OAAqB,OAAO,MAAO,QAAQ,GAE7E,UAAyB,CAAC,GAC1B,wBAAQ,IAAI,IAAoC;CACtD,KAAK,IAAM,EAAC,WAAW,YAAY,cAAa,SAAS;EACvD,IAAM,OAAO,UAAU,MAAM,IAAI,SAAS,GAAG,UAAU;EACvD,IAAI,SAAS,MAAM;EACnB,IAAM,MAAM,MAAM,IAAI,SAAS,KAAK,CAAC;EAErC,AADA,IAAI,aAAa,mBAAmB,WAAW,iBAAiB,gBAAgB,UAChF,MAAM,IAAI,WAAW,GAAG;EACxB,IAAM,SAAsB;GAAC;GAAW;GAAM,IAAI,OAAO;EAAQ;EAEjE,AADI,eAAY,OAAO,aAAa,aACpC,QAAQ,KAAK,MAAM;CACrB;CACA,IAAI,CAAC,QAAQ,QAAQ,OAAO;EAAC,QAAQ;EAAa,SAAS,CAAC;CAAC;CAG7D,IAAM,UAAU,cAAc,QAAQ,QAAQ,WAAW,OAAO,MAAM,CAAC,IAAI,CAAC,GACtE,uBAAO,IAAI,IAAoC;CACrD,IAAI,QAAQ,QAAQ;EAClB,IAAM,EAAC,OAAO,WAAU,qBACtB,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAK,WAAW,OAAO,SAAS,CAAC,CAAC,GACtD,QAAQ,QACV;EACA,KAAK,IAAM,WAAW,aAAa,MAAM,IAAI,MAAM,OAAO,MAAM,CAAC,GAAG;GAClE,IAAM,YAAY,YAAY,QAAQ,GAAG,GACnC,MAA8B,CAAC;GACrC,KAAK,IAAM,UAAU,SACnB,AAAI,OAAO,cAAc,cACpB,OAAO,aACH,QAAQ,SAAS,SAAS,OAAO,UAAU,MAClD,IAAI,GAAG,MAAM,mBAAmB,OAAO,WAAW,oBAAoB,aAFhD,IAAI,GAAG,MAAM,kBAAkB;GAIzD,AAAI,OAAO,KAAK,GAAG,CAAC,CAAC,UAAQ,KAAK,IAAI,QAAQ,KAAK,GAAG;EACxD;CACF;CAEA,IAAM,cAAc,OAAO,YAAY,CAAC,CAAC,OAAO;EAC9C,KAAK;EACL,OAAO;EACP,WAAW,MAAM;EACjB,WAAW,IAAI,YAAY;EAC3B,SAAS,QAAQ,KAAK,QAAQ,UAAU,OAAO,OAAO,EAAC,MAAM,UAAU,QAAO,GAAG,MAAM,CAAC;CAC1F,CAAC;CACD,KAAK,IAAM,CAAC,WAAW,QAAQ,OAAO,YAAY,MAAM,QAAQ,SAAS,GAAG,EAAC,IAAG,CAAC;CACjF,KAAK,IAAM,CAAC,YAAY,QAAQ,MAAM,YAAY,MAAM,YAAY,EAAC,IAAG,CAAC;CACzE,KAAK,IAAM,cAAc,KAAK,YAAY,OAAO,UAAU;CAE3D,IAAI;EACF,MAAM,YAAY,OAAO;CAC3B,SAAS,OAAO;EAEd,IAAI,WAAW,KAAK,GAAG;GACrB,IAAM,SAAS,MAAM,WAAW;GAChC,IAAI,QAAQ,OAAO;IAAC,QAAQ;IAAW,SAAS;GAAM;EACxD;EACA,MAAM;CACR;CACA,OAAO;EAAC,QAAQ;EAAW;CAAO;AACpC;AC3KA,MAAM,cAAmB,OAAO,YAAY,QAAQ,MAAM,CAAC,OAAO,GAE5D,QAAQ,MAAe,SAAS,QACpC,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;CACjC;CACA,SAAS;EAAC,gBAAgB;EAAoB,iBAAiB;CAAU;AAC3E,CAAC,GAEG,UAAU,UAAoB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAmBnF,oBAAsC;CAC1C,aAAa;CACb,SAAS;CACT,OAAO;CACP,QAAQ;CACR,SAAS;AACX;;;;;;;;;;;;AA+DA,SAAgB,cAAc,SAAwC;CACpE,IAAM,EAAC,QAAQ,UAAU,MAAM,eAAc,SACvC,WAAW;EAAC,GAAG;EAAmB,GAAG,QAAQ;CAAQ,GACrD,gBAAgB,sBAAsB,QAAQ,QAAQ,GACxD,gBAAgB,IAEd,eAAe,QAAgB,SAAS,SAC5C,IAAI,SAAS,cAAc,QAAQ,GAC5B,KACL;EAAC,OAAO;EAAe,SAAS,SAAS;EAAa,GAAI,QAAQ,QAAQ,EAAC,OAAM,IAAI,CAAC;CAAE,GACxF,MACF;CAGF,OAAO,OAAO,YAAY;EACxB,IAAM,EAAC,QAAQ,gBAAe;EAC9B,IAAI,CAAC,QAAQ,OAAO,YAAY,qDAAqD;EAErF,IAAM,OAAgB,MAAM,QAAQ,KAAK,CAAC,CAAC,YAAY,IAAI;EAC3D,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO,KAAK;GAAC,OAAO;GAAW,SAAS,SAAS;EAAO,GAAG,GAAG;EAEnF,IAAM,QAAQ,cAAc,KAAK,OAAO,QAAQ,WAAW,GACrD,MAAM,CAAC,GAAG,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,SAAS,CAAC,CAAC,GACxD,UACA,UACA;EACJ,IAAI;GACF,IAAM,CAAC,eAAe,eAAe,cAAc,MAAM,QAAQ,IAAI;IACnE,IAAI,SAAS,OAAO,MAAM,eAAe,EAAC,IAAG,CAAC,IAAI,CAAC;IACnD,OAAO,MAAM,mBAAmB;IAChC,cAAc,UAAU,aAAa,GAAG,IAAI,KAAA;GAC9C,CAAC;GAGD,AAFA,WAAW,iBAAiB,aAAa,GACzC,WAAW,iBAAiB,aAAa,GACzC,QAAQ;EACV,SAAS,OAAO;GACd,OAAO,YAAY,6BAA6B,OAAO,KAAK,KAAK,GAAG;EACtE;EACA,AAAI,CAAC,eAAe,CAAC,kBACnB,gBAAgB,IAChB,IAAI,QAAQ,sEAAsE;EAEpF,IAAM,UAAU,oBAAoB,QAAQ;EAC5C,IAAI,CAAC,QAAQ,QACX,OAAO,YAAY,+DAA+D;EAEpF,IAAM,SAAS,IAAI,IAAI,QAAQ,GAAG,CAAC,CAAC,QAC9B,SAAS,qBAAqB;GAClC;GACA,UAAU,KAAK;GACf;GACA;GACA;GACA,YAAY,YAAY,QAAQ,YAAY,MAAM;GAClD,WAAW,YAAY,QAAQ,WAAW,MAAM;GAChD,uBAAuB,QAAQ;GAC/B,GAAI,QAAQ,EAAC,MAAK,IAAI,CAAC;GACvB,GAAI,QAAQ,gBAAgB,EAAC,eAAe,GAAI,IAAI,CAAC;GACrD,GAAI,QAAQ,WAAW,EAAC,UAAU,QAAQ,SAAQ,IAAI,CAAC;GACvD,GAAI,QAAQ,cAAc,EAAC,aAAa,QAAQ,YAAW,IAAI,CAAC;GAChE,GAAI,QAAQ,mBACR,EACE,WACE,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAC5B,KAAK,IAAI,MAAiB,QAAQ,mBAAmB,EAAE,EAC3D,IACA,CAAC;EACP,CAAC;EAED,IAAI,CAAC,OAAO,IAAI;GACd,IAAM,UAAU;IACd,OAAO,SAAS;IAChB,QAAQ,SAAS;IACjB,aAAa,SAAS;GACxB,EAAE,OAAO;GACT,OAAO,KAAK;IAAC,OAAO,OAAO;IAAO;IAAS,UAAU,OAAO;GAAQ,GAAG,GAAG;EAC5E;EAEA,IAAI;GACF,IAAM,SAAS,QAAQ,SACnB,MAAM,QAAQ,OAAO,OAAO,QAAQ;IAAC,OAAO,OAAO;IAAO,QAAQ,OAAO;IAAQ;GAAO,CAAC,IACzF,OAAO,QACL,UAAU,MAAM,OAAO,SAAS,SAAS,OAAO,MAAM;GAG5D,OAFK,QAAQ,MAEN,KAAK,EAAC,KAAK,QAAQ,IAAG,CAAC,IADrB,YAAY,oBAAoB,QAAQ,GAAG,4BAA4B,GAAG;EAErF,SAAS,OAAO;GACd,OAAO,YAAY,wCAAwC,OAAO,KAAK,KAAK,GAAG;EACjF;CACF;AACF;;AAGA,SAAS,YAAY,KAAa,QAAwB;CACxD,OAAO,eAAe,KAAK,GAAG,IAAI,MAAM,GAAG,SAAS,IAAI,WAAW,GAAG,IAAI,KAAK,MAAM;AACvF;;;;;;;;;;;;;;AAyDA,SAAgB,aAAa,SAAuC;CAClE,IAAM,EAAC,QAAQ,MAAM,eAAc;CAEnC,oBAAoB,QAAQ,QAAQ;CACpC,IAAI,gBAAgB,IAMd,aAAa,OAAO,QAAgB,cAAsB;EAC9D,IAAM,UAAU,MAAM,OAAO,SAAS,SAAS,SAAS,SAAS,GAC3D,MACJ,QAAQ,SAAS,aAAa,QAAQ,2BAA2B,QAAQ;EAE3E,OADK,OAAK,IAAI,QAAQ,oBAAoB,UAAU,sCAAsC,GACnF,MAAM,UAAU;CACzB,GAEM,SAAS,OAAO,QAAgB,cAAsB;EAC1D,IAAM,UAAU,MAAM,WAAW,QAAQ,SAAS;EAClD,IAAI,CAAC,SAAS;EAEd,IAAI,QAAQ,mBAAmB,UAAU;GACvC,IAAI,QAAQ,oBAAoB,UAAU,gBAAgB;GAC1D;EACF;EACA,IAAM,CAAC,OAAO,iBAAiB,MAAM,QAAQ,IAAI,CAC/C,SAAS,QAAQ,OAAO,GACxB,OAAO,MAAM,mBAAmB,CAClC,CAAC,GACK,WAAW,iBAAiB,aAAa,GAE3C,QAAuB,CAAC,GACxB;EACJ,IAAI,QAAQ,aACV,IAAI;GACF,IAAM,SAAS,MAAM,eAAe,QAAQ,aAAa,OAAO;IAC9D,aAAa,UAAU,eAAe;IACtC,GAAI,QAAQ,WAAW,EAAC,UAAU,QAAQ,SAAQ,IAAI,CAAC;GACzD,CAAC;GAED,IADA,QAAQ,OAAO,SACX,OAAO,WAAW,WAAW;IAC/B,IAAM,UAAU,MACb,KACE,EAAC,WAAW,YAAY,MAAM,SAC7B,GAAG,YAAY,aAAa,IAAI,eAAe,GAAG,GAAG,KAAK,KAAK,IACnE,CAAC,CACA,KAAK,IAAI;IACZ,IAAI,QAAQ,oBAAoB,UAAU,UAAU,SAAS;GAC/D;EACF,SAAS,OAAO;GAGd,AADA,aAAa,OAAO,KAAK,GACzB,IAAI,SAAS,oBAAoB,UAAU,2BAA2B,YAAY;EACpF;OACK,AAAK,kBACV,gBAAgB,IAChB,IAAI,QAAQ,qEAAqE;EAGnF,IAAM,EAAC,YAAW;EAClB,IAAI,SAAS;GACX,IAAM,UAAU,MAAM,KAAK,QAAQ,SAAS,SAAS,YAAY;IAC/D,MAAM,QAAQ,OAAO;KACnB;KACA;KACA,GAAI,aAAa,EAAC,WAAU,IAAI,CAAC;KACjC;KACA;IACF,CAAC;GACH,CAAC;GACD,IAAI,QAAQ,oBAAoB,UAAU,UAAU,QAAQ,EAAE;EAChE;EAEA,IAAI,YAAY,MAAU,MAAM,0BAA0B,YAAY;CACxE;CAEA,OAAO,OAAO,YAAY;EACxB,IAAM,EAAC,QAAQ,kBAAiB;EAChC,IAAI,CAAC,UAAU,CAAC,eAEd,OADA,IAAI,SAAS,mEAAmE,GACzE,KAAK,EAAC,OAAO,yBAAwB,GAAG,GAAG;EAIpD,IAAM,UAAU,MAAM,QAAQ,KAAK,GAC/B;EACJ,IAAI;GACF,QAAQ,YAAY,QAAQ,SAAS,QAAQ,QAAQ,IAAI,kBAAkB,GAAG,aAAa;EAC7F,SAAS,OAAO;GAEd,OADA,IAAI,QAAQ,4CAA4C,OAAO,KAAK,GAAG,GAChE,KAAK,EAAC,OAAO,oBAAmB,GAAG,GAAG;EAC/C;EAEA,IAAM,SAAS,cAAc,KAAK;EAClC,IAAI;GACF,QAAQ,OAAO,MAAf;IACE,KAAK;KACH,MAAM,OAAO,QAAQ,OAAO,QAAQ,EAAE;KACtC;IACF,KAAK;KACH,IAAI,QAAQ,oBAAoB,OAAO,QAAQ,GAAG,iCAAiC;KACnF;IACF,KAAK,UAAU;KACb,IAAM,UAAU,MAAM,WAAW,QAAQ,OAAO,QAAQ,EAAE;KAC1D,AAAI,YACF,IAAI,QAAQ,oBAAoB,QAAQ,GAAG,8BAA8B,GACzE,MAAM,QAAQ,kBAAkB,OAAO;KAEzC;IACF;GAGF;EACF,SAAS,OAAO;GAGd,OADA,IAAI,SAAS,oBAAoB,MAAM,KAAK,GAAG,MAAM,GAAG,WAAW,OAAO,KAAK,GAAG,GAC3E,KAAK,EAAC,OAAO,oBAAmB,GAAG,GAAG;EAC/C;EAEA,OAAO,KAAK,EAAC,UAAU,GAAI,CAAC;CAC9B;AACF;;AC7YA,MCIa,qBAAqB;;;;;;;;;;;;AAalC,SAAgB,aACd,WACA,SAA8D,CAAC,GACvD;CACR,OAAO,IAAI,OAAO,WAAW;EAC3B,GAAG;EACH,YAAY;EACZ,SAAS;GACP,MAAM;GACN,SAAS;GACT,KAAK;EACP;CACF,CAAC;AACH"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-stripe",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Sell from a Sanity site with Stripe Checkout: price, stock and variants in the Studio, a cart for React, and checkout and webhook routes for the site.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"checkout",
|
|
@@ -31,39 +31,11 @@
|
|
|
31
31
|
"sideEffects": false,
|
|
32
32
|
"types": "./dist/index.d.ts",
|
|
33
33
|
"exports": {
|
|
34
|
-
".":
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
"./react": {
|
|
39
|
-
"source": "./src/react/index.ts",
|
|
40
|
-
"default": "./dist/react.js"
|
|
41
|
-
},
|
|
42
|
-
"./server": {
|
|
43
|
-
"source": "./src/server/index.ts",
|
|
44
|
-
"default": "./dist/server.js"
|
|
45
|
-
},
|
|
34
|
+
".": "./dist/index.js",
|
|
35
|
+
"./react": "./dist/react.js",
|
|
36
|
+
"./server": "./dist/server.js",
|
|
46
37
|
"./package.json": "./package.json"
|
|
47
38
|
},
|
|
48
|
-
"publishConfig": {
|
|
49
|
-
"exports": {
|
|
50
|
-
".": "./dist/index.js",
|
|
51
|
-
"./react": "./dist/react.js",
|
|
52
|
-
"./server": "./dist/server.js",
|
|
53
|
-
"./package.json": "./package.json"
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
"scripts": {
|
|
57
|
-
"build": "plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
|
|
58
|
-
"check": "tsc --noEmit && oxlint && vitest run",
|
|
59
|
-
"format": "oxfmt",
|
|
60
|
-
"link-watch": "plugin-kit link-watch",
|
|
61
|
-
"lint": "oxlint",
|
|
62
|
-
"prepublishOnly": "npm run build",
|
|
63
|
-
"test": "vitest run",
|
|
64
|
-
"typecheck": "tsc --noEmit",
|
|
65
|
-
"watch": "pkg-utils watch --strict"
|
|
66
|
-
},
|
|
67
39
|
"dependencies": {
|
|
68
40
|
"@sanity/icons": "^5.2.3",
|
|
69
41
|
"@sanity/ui": "^4.4.10"
|
|
@@ -109,5 +81,14 @@
|
|
|
109
81
|
"engines": {
|
|
110
82
|
"node": ">=20.19 <22 || >=22.12"
|
|
111
83
|
},
|
|
112
|
-
"
|
|
113
|
-
|
|
84
|
+
"scripts": {
|
|
85
|
+
"build": "plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
|
|
86
|
+
"check": "tsc --noEmit && oxlint && vitest run",
|
|
87
|
+
"format": "oxfmt",
|
|
88
|
+
"link-watch": "plugin-kit link-watch",
|
|
89
|
+
"lint": "oxlint",
|
|
90
|
+
"test": "vitest run",
|
|
91
|
+
"typecheck": "tsc --noEmit",
|
|
92
|
+
"watch": "pkg-utils watch --strict"
|
|
93
|
+
}
|
|
94
|
+
}
|