Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -863,3 +863,7 @@ PIMLICO_PAYMASTER_KAIA_V08=
ALERT_THRESHOLD_POL_V08=
ALERT_THRESHOLD_ETH_V08=
ALERT_THRESHOLD_KAIA_V08=

# Server-only order binding rollout: OFF validates supplied bindings and warns on absent relay bindings.
# ON requires relay/recover binding. Standard ERC-20 and non-relay ERC-4337/7702 gasless payments remain unbound. Operator approval required.
ENABLE_ORDER_BIND_ENFORCE=false
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ The table below is a **curated subset** (core setup + production feature flags).
| `NEXT_PUBLIC_ENABLE_MOBILE_ORDER` | Mobile-order storefront feature (`@handle` shop page / `/order`) — **live on mainnet** (production sets `1`); code default **off**. | optional |
| `NEXT_PUBLIC_ENABLE_SHOPS_API` | JPYC Shops API and the explicit shop-owner opt-in that maintains its search index/summary. Default **off**; API routes additionally require the x402 facilitator, order relay, and server-only `ENABLE_AGENT_ORDER` flags so every returned `menuUrl` remains reachable. | optional |
| `NEXT_PUBLIC_ENABLE_ORDER_RELAY` | Order relay — delivers the paid order (items + table + settled amount) to the merchant via KV + on-chain verification. Needed for mobile ordering to be usable; requires KV configured. **Live on mainnet** (production sets `1`); code default **off**. | optional |
| `ENABLE_ORDER_BIND_ENFORCE` | Server-only, default OFF. OFF verifies any supplied order binding and rejects malformed bindings; relay orders with no binding carry a manual-verification warning. ON requires relay/recover binding. Standard ERC-20 transfers and non-relay ERC-4337/7702 gasless payments remain unbound (accepted residual). Enable only after explicit operator approval. `bindingConflict` detection covers only orders still in the capped order list; if the unbound winning order has been trimmed, a later verified notification can receive a plain duplicate acknowledgement. | optional |
| `NEXT_PUBLIC_ENABLE_ORDER_MEMO` | Optional customer order memo (allergies / requests), passed through same-tab `sessionStorage` to the same-origin order notify only; it is never added to the checkout URL or payment history. Structurally gated by `…ENABLE_ORDER_RELAY`. **Live on mainnet** (production sets `1`); code default **off**. | optional |
| `NEXT_PUBLIC_ENABLE_ORDER_CALL` | Dine-in staff call: shown only on an `@handle` page with a table number and this device's matching paid receipt from the last 2 hours. The server binds `orderId` + `txHash` + table to the merchant order list and atomically enforces per-order, per-handle, and table cooldown limits. Merchant boards poll it separately; it is deliberately excluded from Web Push. Structurally gated by `…ENABLE_ORDER_RELAY`; production light-up should also set `IP_HASH_SECRET`. **Live on mainnet** (production sets `1`); code default **off**. | optional |
| `NEXT_PUBLIC_ENABLE_SHOP_LIVE` | Live shop status for `@handle` storefronts — per-item sold-out / accepting-orders toggles, recommended items, and a register category filter & image toggle. High-frequency `shop:live:<handle>` KV record (advisory; on-chain stays authoritative). Requires KV + `…ENABLE_HANDLES` + `…ENABLE_MOBILE_ORDER`. Default **off**. | optional |
Expand Down
162 changes: 103 additions & 59 deletions app/api/order/notify/route.ts

Large diffs are not rendered by default.

250 changes: 234 additions & 16 deletions components/CheckoutForm.tsx

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions components/OrderBindingNotice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use client';

import { useTranslations } from 'next-intl';
import type { StoredOrder } from '@/lib/orderRelay';

export function OrderBindingNotice({ order }: { order: StoredOrder }) {
const t = useTranslations('OrderBinding');
if (order.bindingMissing) {
return <p role="alert" className="my-2 rounded-lg border-2 border-red-600 bg-red-50 p-3 font-sans text-sm font-bold tracking-normal text-red-900">{t('unverified')}</p>;
}
return null;
}

export function OrderBindingScope() {
const t = useTranslations('OrderBinding');
return <p className="rounded-lg bg-slate-50 p-3 text-xs text-slate-800">{t('scope')}</p>;
}
2 changes: 2 additions & 0 deletions components/OrderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// ageMin (表示分) を渡す = tick で表示分が変わらない限り再レンダーしない。テーブル訂正の入力ドラフトは
// 編集中の当該カードだけ描画に影響する (他カードは draft 変化で再レンダーしない)。

import { OrderBindingNotice } from '@/components/OrderBindingNotice';
import { memo } from 'react';
import { useTranslations } from 'next-intl';
import { formatUnits } from 'viem';
Expand Down Expand Up @@ -100,6 +101,7 @@ function OrderCardInner({
: 'border-slate-200 bg-white'
}`}
>
<OrderBindingNotice order={o} />
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<p className="text-xs font-medium text-slate-500">
Expand Down
3 changes: 3 additions & 0 deletions components/OrderFeedPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// react-query を使うため、親 (create ページ) は env.enableOrderRelay でこのパネルの**マウント自体**を
// ゲートする (OFF の単体テストで QueryClient を要求しない)。設計: plans/swift-puzzling-sky.md。

import { OrderBindingNotice, OrderBindingScope } from '@/components/OrderBindingNotice';
import { useMemo, useState } from 'react';
import { useTranslations, useLocale } from 'next-intl';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
Expand Down Expand Up @@ -137,6 +138,7 @@ export function OrderFeedPanel() {
: 'border-slate-200 bg-white'
}`}
>
<OrderBindingNotice order={o} />
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
{/* 受付番号 (受け渡し照合用・客の完了画面と同じコード)。 */}
Expand Down Expand Up @@ -260,6 +262,7 @@ export function OrderFeedPanel() {
<div>
<h2 className="text-lg font-semibold text-slate-800">{t('heading')}</h2>
<p className="mt-1 text-sm text-slate-500">{t('subheading')}</p>
<OrderBindingScope />
{/* 完了フローのヒント。飲食 (厨房/ホール) を使う場合は配膳済み=対応済みを案内。 */}
<p className="mt-2 rounded-lg bg-slate-50 px-3 py-2 text-xs text-slate-500">
{env.enableOrderFulfillment ? t('completionHintRestaurant') : t('completionHintRetail')}
Expand Down
3 changes: 3 additions & 0 deletions components/OrderFulfillmentBoard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client';

import { OrderBindingScope } from '@/components/OrderBindingNotice';

// 受注フルフィルメントの全画面ボード (Phase 3)。mode で 厨房 (調理) / ホール (配膳) を切替。
// - 厨房: 商品別「調理済み」トグル + 注文「調理済み」(中間 kitchenDone・厨房側だけ折りたたむ)。
// - ホール: 調理済み品は **青** (配膳待ちが一目) + 商品別「配膳済み」トグル + 注文「配膳済み」
Expand Down Expand Up @@ -318,6 +320,7 @@ export function OrderFulfillmentBoard({
</div>
</div>
<p className="text-xs text-slate-500">{t('autoRefresh')}</p>
<OrderBindingScope />
{env.enableOrderCall && mode === 'hall' ? (
<OrderCallSection
calls={callFeed.calls.data ?? []}
Expand Down
Loading
Loading