diff --git a/README.md b/README.md index b44c2ff..94273a2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # saizeriya [![技術者倫理 遵守済み](https://img.shields.io/badge/%E6%8A%80%E8%A1%93%E8%80%85%E5%80%AB%E7%90%86-%E9%81%B5%E5%AE%88%E6%B8%88%E3%81%BF-0a0a0a?style=for-the-badge&labelColor=ffffff)](https://技術者倫理.com) + diff --git a/a.sh b/a.sh new file mode 100644 index 0000000..f83e225 --- /dev/null +++ b/a.sh @@ -0,0 +1,40 @@ +curl -sS -X POST http://localhost:5173/api/sessions \ + -H 'content-type: application/json' \ + -d '{"qrURLSource":"http://localhost:3000/saizeriya3/qr?table=ba44c06e-2ae3-4c30-9d58-999093265866"}' + +curl -sS -X POST http://localhost:5173/api/ucp/checkout-sessions \ + -H 'content-type: application/json' \ + -d '{"line_items":[{"item":{"id":"1110"},"quantity":2}],"currency":"JPY"}' + +CREATE=$(curl -sS -X POST http://localhost:5173/api/ucp/checkout-sessions \ + -H 'content-type: application/json' \ + -d '{ + "qrURLSource": "http://localhost:3000/saizeriya3/qr?table=ba44c06e-2ae3-4c30-9d58-999093265866", + "peopleCount": 2, + "line_items": [ + { "item": { "id": "1110" }, "quantity": 1 } + ], + "currency": "JPY" + }') + + +curl -sS -X POST "http://localhost:5173/api/ucp/checkout-sessions/chk_ba51b170-c8a7-4722-80c3-1f79f30aa7cd/complete" \ + -H 'content-type: application/json' \ + -d '{}' + + CREATE=$(curl -sS -X POST http://localhost:5173/api/ucp/checkout-sessions \ + -H 'content-type: application/json' \ + -d '{ + "qrURLSource": "http://localhost:3000/saizeriya3/qr?table=ba44c06e-2ae3-4c30-9d58-999093265866", + "peopleCount": 2, + "line_items": [ + { "item": { "id": "1301" }, "quantity": 1 } + ], + "currency": "JPY" + }')curl -sS -X POST "http://localhost:5173/api/ucp/checkout-sessions/$CHECKOUT_ID/complete" \ + -H 'content-type: application/json' \ + -d '{}' + +curl -sS -X POST "http://localhost:5173/api/ucp/checkout-sessions/chk_5b3277dc-937b-4b3c-93de-d5f4c8b5fad8/complete" \ + -H 'content-type: application/json' \ + -d '{}' \ No newline at end of file diff --git a/apps/betterzeriya/src/lib/server/official-client.ts b/apps/betterzeriya/src/lib/server/official-client.ts index 585e1f7..a8d10f0 100644 --- a/apps/betterzeriya/src/lib/server/official-client.ts +++ b/apps/betterzeriya/src/lib/server/official-client.ts @@ -256,6 +256,7 @@ export const submitOfficialCart = async ( snapshot?: OfficialSessionSnapshot, ) => { const session = await createClientFromSnapshot(id, snapshot) + await session.client.goToMenu() while (session.client.getState().cart.length > 0) { await session.client.removeCartItem(0) } diff --git a/apps/betterzeriya/src/lib/server/ucp.ts b/apps/betterzeriya/src/lib/server/ucp.ts new file mode 100644 index 0000000..8d21381 --- /dev/null +++ b/apps/betterzeriya/src/lib/server/ucp.ts @@ -0,0 +1,371 @@ +import menuData from '$lib/assets/data/menu.json' +import { + createOfficialSession, + lookupOfficialItem, + setOfficialPeopleCount, + submitOfficialCart, + type OfficialSessionSnapshot, +} from '$lib/server/official-client' + +export const UCP_VERSION = '2026-04-08' +export const UCP_SHOPPING_SERVICE = 'dev.ucp.shopping' +export const UCP_CHECKOUT_CAPABILITY = 'dev.ucp.shopping.checkout' + +type UcpCheckoutStatus = + | 'incomplete' + | 'requires_escalation' + | 'ready_for_complete' + | 'complete_in_progress' + | 'completed' + | 'canceled' + +type UcpBuyer = Record + +export interface UcpLineItemInput { + item: { + id: string + title?: string + price?: number + } + quantity: number +} + +interface CreateCheckoutSessionInput { + origin: string + line_items: UcpLineItemInput[] + buyer?: UcpBuyer + currency?: string + qrURLSource?: string + peopleCount?: number + officialSession?: OfficialSessionSnapshot +} + +interface UpdateCheckoutSessionInput { + line_items?: UcpLineItemInput[] + buyer?: UcpBuyer + currency?: string +} + +interface UcpTotal { + type: 'subtotal' | 'tax' | 'total' + amount: number +} + +interface UcpCheckoutSession { + ucp: ReturnType + id: string + status: UcpCheckoutStatus + currency: string + buyer?: UcpBuyer + line_items: Array + totals: UcpTotal[] + links: Array<{ type: 'terms_of_service' | 'privacy_policy'; url: string }> + official_session?: { + id: string + people_count?: number + } + messages?: Array> + continue_url?: string + order?: { + id: string + checkout_id: string + permalink_url: string + } + created_at: string + updated_at: string + expires_at: string +} + +type MenuEntry = { + code: string + name: string + price: number +} + +const menuByCode = new Map((menuData as MenuEntry[]).map((item) => [item.code, item])) +const sessions = new Map() +const officialSessions = new Map() +const sessionTtlMs = 1000 * 60 * 60 * 6 + +export const createUcpMetadata = () => ({ + version: UCP_VERSION, + capabilities: { + [UCP_CHECKOUT_CAPABILITY]: [{ version: UCP_VERSION }], + }, +}) + +const pruneSessions = () => { + const now = Date.now() + for (const [id, session] of sessions) { + if (Date.parse(session.updated_at) + sessionTtlMs < now) { + sessions.delete(id) + officialSessions.delete(id) + } + } +} + +const buildLinks = (origin: string): UcpCheckoutSession['links'] => [ + { type: 'terms_of_service', url: origin }, + { type: 'privacy_policy', url: origin }, +] + +const resolveLineItem = (lineItem: UcpLineItemInput, index: number) => { + const menuItem = menuByCode.get(lineItem.item.id) + const price = lineItem.item.price ?? menuItem?.price ?? 0 + const title = lineItem.item.title ?? menuItem?.name ?? lineItem.item.id + const amount = price * lineItem.quantity + + return { + id: `li_${index + 1}`, + item: { + ...lineItem.item, + title, + price, + }, + quantity: lineItem.quantity, + totals: [ + { type: 'subtotal' as const, amount }, + { type: 'total' as const, amount }, + ], + } +} + +const calculateTotals = (lineItems: UcpCheckoutSession['line_items']) => { + const subtotal = lineItems.reduce((sum, item) => sum + item.totals[0].amount, 0) + return [ + { type: 'subtotal' as const, amount: subtotal }, + { type: 'tax' as const, amount: 0 }, + { type: 'total' as const, amount: subtotal }, + ] +} + +const refreshSessionShape = ( + session: UcpCheckoutSession, + input: UpdateCheckoutSessionInput & { origin?: string }, +) => { + const lineItems = + input.line_items?.map(resolveLineItem) ?? + session.line_items.map(({ item, quantity }) => ({ item, quantity })).map(resolveLineItem) + const now = new Date().toISOString() + + return { + ...session, + currency: input.currency ?? session.currency, + buyer: input.buyer ?? session.buyer, + line_items: lineItems, + totals: calculateTotals(lineItems), + links: input.origin ? buildLinks(input.origin) : session.links, + updated_at: now, + } +} + +export const createUcpCheckoutSession = (input: CreateCheckoutSessionInput) => { + pruneSessions() + const now = new Date() + const id = `chk_${crypto.randomUUID()}` + const lineItems = input.line_items.map(resolveLineItem) + const session: UcpCheckoutSession = { + ucp: createUcpMetadata(), + id, + status: 'ready_for_complete', + currency: input.currency ?? 'JPY', + buyer: input.buyer, + line_items: lineItems, + totals: calculateTotals(lineItems), + links: buildLinks(input.origin), + continue_url: `${input.origin}/`, + created_at: now.toISOString(), + updated_at: now.toISOString(), + expires_at: new Date(now.getTime() + sessionTtlMs).toISOString(), + } + sessions.set(session.id, session) + return session +} + +export const getUcpCheckoutSession = (id: string) => { + pruneSessions() + return sessions.get(id) +} + +export const updateUcpCheckoutSession = (id: string, input: UpdateCheckoutSessionInput) => { + const session = getUcpCheckoutSession(id) + if (!session || session.status === 'completed' || session.status === 'canceled') { + return undefined + } + const updated = refreshSessionShape(session, input) + sessions.set(id, updated) + return updated +} + +const createOfficialBinding = async ( + checkoutId: string, + input: Pick, +) => { + if (input.officialSession) { + officialSessions.set(checkoutId, input.officialSession) + return { + id: input.officialSession.id, + people_count: input.officialSession.state.peopleCount, + } + } + + if (!input.qrURLSource) { + return undefined + } + + const official = await createOfficialSession(input.qrURLSource) + let officialSession = official.officialSession + let peopleCount = official.state.peopleCount + + if (input.peopleCount && official.state.peopleCount !== input.peopleCount) { + const updated = await setOfficialPeopleCount(official.id, input.peopleCount, officialSession) + officialSession = updated.officialSession + peopleCount = updated.state.peopleCount + } + + officialSessions.set(checkoutId, officialSession) + return { + id: official.id, + people_count: peopleCount, + } +} + +const validateLineItemsWithOfficialSession = async ( + checkoutId: string, + lineItems: UcpLineItemInput[], +) => { + let officialSession = officialSessions.get(checkoutId) + if (!officialSession) { + return lineItems + } + + const officialLineItems: UcpLineItemInput[] = [] + + for (const lineItem of lineItems) { + const lookup = await lookupOfficialItem(officialSession.id, lineItem.item.id, officialSession) + officialSession = lookup.officialSession + officialSessions.set(checkoutId, officialSession) + + if (lookup.result.result !== 'OK' || !lookup.result.item_data) { + throw new Error(`Item ${lineItem.item.id} was not found`) + } + + if (lookup.result.item_data.state === 0) { + throw new Error(`Item ${lineItem.item.id} is sold out`) + } + + officialLineItems.push({ + item: { + id: lookup.result.item_data.id, + title: lookup.result.item_data.name, + price: lookup.result.item_data.price, + }, + quantity: lineItem.quantity, + }) + } + + return officialLineItems +} + +export const createUcpCheckoutSessionWithOfficialSession = async ( + input: CreateCheckoutSessionInput, +) => { + const session = createUcpCheckoutSession(input) + let officialSession + + try { + officialSession = await createOfficialBinding(session.id, input) + input.line_items = await validateLineItemsWithOfficialSession(session.id, input.line_items) + } catch (error) { + sessions.delete(session.id) + officialSessions.delete(session.id) + throw error + } + + if (!officialSession) { + return session + } + + const updated = { + ...refreshSessionShape(session, { line_items: input.line_items }), + official_session: officialSession, + updated_at: new Date().toISOString(), + } + sessions.set(session.id, updated) + return updated +} + +export const completeUcpCheckoutSession = async (id: string) => { + const session = getUcpCheckoutSession(id) + if (!session || session.status === 'completed' || session.status === 'canceled') { + return undefined + } + const officialSession = officialSessions.get(id) + if (!officialSession) { + throw new Error('Official session is required to submit a UCP checkout') + } + + const inProgress = { + ...refreshSessionShape(session, {}), + status: 'complete_in_progress' as const, + } + sessions.set(id, inProgress) + + let result + try { + result = await submitOfficialCart( + officialSession.id, + inProgress.line_items.map((lineItem) => ({ + id: lineItem.item.id, + count: lineItem.quantity, + })), + officialSession, + ) + } catch (error) { + sessions.set(id, { + ...inProgress, + status: 'requires_escalation', + messages: [ + { + type: 'error', + content: error instanceof Error ? error.message : 'Failed to submit order', + }, + ], + updated_at: new Date().toISOString(), + }) + throw error + } + officialSessions.set(id, result.officialSession) + + const updated = { + ...inProgress, + status: 'completed' as const, + official_session: { + id: result.officialSession.id, + people_count: result.state.peopleCount, + }, + order: { + id: `ord_${crypto.randomUUID()}`, + checkout_id: id, + permalink_url: session.continue_url ?? '/', + }, + } + officialSessions.delete(id) + sessions.set(id, updated) + return updated +} + +export const cancelUcpCheckoutSession = (id: string) => { + const session = getUcpCheckoutSession(id) + if (!session || session.status === 'completed' || session.status === 'canceled') { + return undefined + } + const updated = { + ...session, + status: 'canceled' as const, + updated_at: new Date().toISOString(), + } + officialSessions.delete(id) + sessions.set(id, updated) + return updated +} diff --git a/apps/betterzeriya/src/lib/zeriya-gpt/system-prompt.ts b/apps/betterzeriya/src/lib/zeriya-gpt/system-prompt.ts index 73e0a8c..8811f8e 100644 --- a/apps/betterzeriya/src/lib/zeriya-gpt/system-prompt.ts +++ b/apps/betterzeriya/src/lib/zeriya-gpt/system-prompt.ts @@ -2,18 +2,15 @@ import menuData from '$lib/assets/data/menu.json' import { normalizeMenuName } from './menu-name-normalization' type MenuItem = { - item_data: { - id: string - name: string - price: number - } - alcohol_check?: number + code: string + name: string + price: number } export const buildMenuList = () => (menuData as MenuItem[]) - .filter((item) => item.item_data.price > 0) - .map((item) => `- ${normalizeMenuName(item.item_data.name)} (${item.item_data.price}円)`) + .filter((item) => item.price > 0) + .map((item) => `- ${normalizeMenuName(item.name)} (${item.price}円)`) .join('\n') export const buildSystemPrompt = diff --git a/apps/betterzeriya/src/routes/.well-known/ucp/+server.ts b/apps/betterzeriya/src/routes/.well-known/ucp/+server.ts new file mode 100644 index 0000000..54ea754 --- /dev/null +++ b/apps/betterzeriya/src/routes/.well-known/ucp/+server.ts @@ -0,0 +1,29 @@ +import { json, type RequestHandler } from '@sveltejs/kit' +import { UCP_CHECKOUT_CAPABILITY, UCP_SHOPPING_SERVICE, UCP_VERSION } from '$lib/server/ucp' + +export const GET: RequestHandler = async ({ url }) => { + const origin = url.origin + return json({ + name: 'betterzeriya', + url: origin, + ucp: { + version: UCP_VERSION, + services: { + [UCP_SHOPPING_SERVICE]: [ + { + version: UCP_VERSION, + bindings: [ + { + type: 'rest', + endpoint: `${origin}/api/ucp`, + }, + ], + }, + ], + }, + capabilities: { + [UCP_CHECKOUT_CAPABILITY]: [{ version: UCP_VERSION }], + }, + }, + }) +} diff --git a/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/+server.ts b/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/+server.ts new file mode 100644 index 0000000..933465b --- /dev/null +++ b/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/+server.ts @@ -0,0 +1,131 @@ +import { json, type RequestHandler } from '@sveltejs/kit' +import { createUcpCheckoutSessionWithOfficialSession, type UcpLineItemInput } from '$lib/server/ucp' +import { parseOfficialSessionSnapshot } from '$lib/server/official-client' + +const MAX_LINE_ITEMS = 100 +const MAX_ITEM_ID_LENGTH = 256 +const MAX_QUANTITY = 1000 + +const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null && !Array.isArray(value) + +const normalizeBuyer = (value: unknown) => (isRecord(value) ? value : undefined) +const normalizeCurrency = (value: unknown) => + typeof value === 'string' && /^[A-Z]{3}$/.test(value) ? value : undefined +const normalizePeopleCount = (value: unknown) => { + const peopleCount = Number(value) + return Number.isInteger(peopleCount) && peopleCount >= 1 && peopleCount <= 99 + ? peopleCount + : undefined +} + +export const _normalizeLineItems = ( + value: unknown, +): { lineItems: UcpLineItemInput[] } | { error: string } => { + if (!Array.isArray(value) || value.length === 0) { + return { error: 'line_items is required' } + } + + if (value.length > MAX_LINE_ITEMS) { + return { error: `line_items must contain at most ${MAX_LINE_ITEMS} items` } + } + + const lineItems: UcpLineItemInput[] = [] + + for (const lineItem of value) { + if (!isRecord(lineItem) || !isRecord(lineItem.item)) { + return { error: 'Each line_items entry must include an item object' } + } + + const id = typeof lineItem.item.id === 'string' ? lineItem.item.id.trim() : '' + if (!/^\d{4}$/.test(id) || id.length > MAX_ITEM_ID_LENGTH) { + return { + error: 'Each line_items item id must be a 4 digit Saizeriya item code', + } + } + + const rawQuantity = lineItem.quantity + const quantity = + typeof rawQuantity === 'number' + ? rawQuantity + : typeof rawQuantity === 'string' && rawQuantity.trim() !== '' + ? Number(rawQuantity) + : NaN + + if (!Number.isInteger(quantity) || quantity < 1 || quantity > MAX_QUANTITY) { + return { + error: `Each line_items entry must include a quantity between 1 and ${MAX_QUANTITY}`, + } + } + + const title = typeof lineItem.item.title === 'string' ? lineItem.item.title.trim() : undefined + const price = typeof lineItem.item.price === 'number' ? lineItem.item.price : undefined + + lineItems.push({ + item: { + id, + ...(title ? { title } : {}), + ...(price !== undefined ? { price } : {}), + }, + quantity, + }) + } + + return { lineItems } +} + +export const POST: RequestHandler = async ({ request, url }) => { + const body = await request.json().catch(() => null) + + if (!isRecord(body)) { + return json({ code: 'invalid_request', content: 'Invalid request body' }, { status: 400 }) + } + + const normalizedLineItems = _normalizeLineItems(body.line_items) + if ('error' in normalizedLineItems) { + return json({ code: 'invalid_request', content: normalizedLineItems.error }, { status: 400 }) + } + + const qrURLSource = String(body.qrURLSource ?? body.qr_url_source ?? '').trim() + const officialSession = parseOfficialSessionSnapshot( + body.officialSession ?? body.official_session, + ) + + if (!qrURLSource && !officialSession) { + return json( + { + code: 'invalid_request', + content: 'qrURLSource or officialSession is required to create an orderable checkout', + }, + { status: 400 }, + ) + } + + if (qrURLSource && !URL.canParse(qrURLSource)) { + return json({ code: 'invalid_request', content: 'QR URL is invalid' }, { status: 400 }) + } + + let session + + try { + session = await createUcpCheckoutSessionWithOfficialSession({ + origin: url.origin, + line_items: normalizedLineItems.lineItems, + buyer: normalizeBuyer(body.buyer), + currency: normalizeCurrency(body.currency), + qrURLSource, + peopleCount: normalizePeopleCount(body.peopleCount ?? body.people_count), + officialSession, + }) + } catch (error) { + return json( + { + code: 'official_session_failed', + content: error instanceof Error ? error.message : 'Failed to initialize official session', + }, + { status: 502 }, + ) + } + + return json(session, { status: 201 }) +} diff --git a/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/[id]/+server.ts b/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/[id]/+server.ts new file mode 100644 index 0000000..1f427f8 --- /dev/null +++ b/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/[id]/+server.ts @@ -0,0 +1,60 @@ +import { json, type RequestHandler } from '@sveltejs/kit' +import { getUcpCheckoutSession, updateUcpCheckoutSession } from '$lib/server/ucp' +import { _normalizeLineItems } from '../+server' + +const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null && !Array.isArray(value) + +const normalizeBuyer = (value: unknown) => (isRecord(value) ? value : undefined) +const normalizeCurrency = (value: unknown) => + typeof value === 'string' && /^[A-Z]{3}$/.test(value) ? value : undefined + +export const GET: RequestHandler = async ({ params }) => { + if (!params.id) { + return json( + { code: 'invalid_request', content: 'Checkout session id is required' }, + { status: 400 }, + ) + } + + const session = getUcpCheckoutSession(params.id) + if (!session) { + return json({ code: 'not_found', content: 'Checkout session not found' }, { status: 404 }) + } + return json(session) +} + +export const PUT: RequestHandler = async ({ params, request }) => { + if (!params.id) { + return json( + { code: 'invalid_request', content: 'Checkout session id is required' }, + { status: 400 }, + ) + } + + const body = await request.json().catch(() => null) + if (!isRecord(body)) { + return json({ code: 'invalid_request', content: 'Invalid request body' }, { status: 400 }) + } + + const normalizedLineItems = + body.line_items === undefined ? undefined : _normalizeLineItems(body.line_items) + if (normalizedLineItems && 'error' in normalizedLineItems) { + return json({ code: 'invalid_request', content: normalizedLineItems.error }, { status: 400 }) + } + + const session = updateUcpCheckoutSession(params.id, { + line_items: normalizedLineItems?.lineItems, + buyer: normalizeBuyer(body.buyer), + currency: normalizeCurrency(body.currency), + }) + + if (!session) { + return json( + { code: 'not_found_or_closed', content: 'Checkout session not found or already closed' }, + { status: 404 }, + ) + } + + return json(session) +} diff --git a/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/[id]/cancel/+server.ts b/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/[id]/cancel/+server.ts new file mode 100644 index 0000000..787f02d --- /dev/null +++ b/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/[id]/cancel/+server.ts @@ -0,0 +1,21 @@ +import { json, type RequestHandler } from '@sveltejs/kit' +import { cancelUcpCheckoutSession } from '$lib/server/ucp' + +export const POST: RequestHandler = async ({ params }) => { + if (!params.id) { + return json( + { code: 'invalid_request', content: 'Checkout session id is required' }, + { status: 400 }, + ) + } + + const session = cancelUcpCheckoutSession(params.id) + if (!session) { + return json( + { code: 'not_found_or_closed', content: 'Checkout session not found or already closed' }, + { status: 404 }, + ) + } + + return json(session) +} diff --git a/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/[id]/complete/+server.ts b/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/[id]/complete/+server.ts new file mode 100644 index 0000000..689565a --- /dev/null +++ b/apps/betterzeriya/src/routes/api/ucp/checkout-sessions/[id]/complete/+server.ts @@ -0,0 +1,35 @@ +import { json, type RequestHandler } from '@sveltejs/kit' +import { completeUcpCheckoutSession } from '$lib/server/ucp' + +export const POST: RequestHandler = async ({ params, request }) => { + if (!params.id) { + return json( + { code: 'invalid_request', content: 'Checkout session id is required' }, + { status: 400 }, + ) + } + + await request.json().catch(() => ({})) + let session + + try { + session = await completeUcpCheckoutSession(params.id) + } catch (error) { + return json( + { + code: 'submit_failed', + content: error instanceof Error ? error.message : 'Failed to submit order', + }, + { status: 502 }, + ) + } + + if (!session) { + return json( + { code: 'not_found_or_closed', content: 'Checkout session not found or already closed' }, + { status: 404 }, + ) + } + + return json(session) +} diff --git a/apps/betterzeriya/src/routes/sessions/[id]/ai/+page.svelte b/apps/betterzeriya/src/routes/sessions/[id]/ai/+page.svelte index fa0e6ba..826f393 100644 --- a/apps/betterzeriya/src/routes/sessions/[id]/ai/+page.svelte +++ b/apps/betterzeriya/src/routes/sessions/[id]/ai/+page.svelte @@ -127,7 +127,7 @@ }, appConfig: { ...prebuiltAppConfig, - useIndexedDBCache: true + cacheBackend: 'indexeddb' } }); engine = next; diff --git a/bun.lock b/bun.lock index cc2ca45..8ace529 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,6 @@ { "lockfileVersion": 1, - "configVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "saizeriya", diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 1294fb4..3d99526 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -53,6 +53,8 @@ export const createClient = async ({ shopId: processedQR!.shopId, tableNo: processedQR!.tableNo, peopleCount: peopleCount ?? processedQR!.peopleCount ?? 0, + token: processedQR!.token, + sessionId: processedQR!.sessionId, pageKind: processedQR!.pageKind, cart: [], } diff --git a/packages/client/src/process-qr.ts b/packages/client/src/process-qr.ts index 5e89356..98d813d 100644 --- a/packages/client/src/process-qr.ts +++ b/packages/client/src/process-qr.ts @@ -18,6 +18,8 @@ export const processQR = async (qrURL: string, fetch: typeof globalThis.fetch) = shopId: parser.getShopId(), tableNo: parser.getTableNo(), peopleCount: parser.getPeopleCount(), + token: parser.getToken(), + sessionId: parser.getSessionId(), pageKind: parser.getPageKind(), } }