diff --git a/docs/architecture/frontend-boundary.md b/docs/architecture/frontend-boundary.md
index 4f28bfa..279f572 100644
--- a/docs/architecture/frontend-boundary.md
+++ b/docs/architecture/frontend-boundary.md
@@ -23,7 +23,8 @@
## UI Workflow Coverage
- POS transaction summary lookup with open balance visibility
-- Transaction detail inspection for a selected transaction id
+- Transaction detail inspection for a selected payable check or transaction id
- Recent payment activity review
-- Payment capture with retry-ready local session history
+- Payment capture with retry-ready local session history and lifecycle-aware payment hints
- Protected route shell for summary, payment capture, and session inspection
+- POS actions are presented as the final step after kitchen and floor service complete the restaurant order.
diff --git a/docs/runbooks/local-development.md b/docs/runbooks/local-development.md
index 1ac6f1d..9b37bb3 100644
--- a/docs/runbooks/local-development.md
+++ b/docs/runbooks/local-development.md
@@ -22,6 +22,7 @@ npm run dev
- Business calls are gated behind session checks.
- Session cookies are sent with `credentials: include`.
- Summary, detail, recent-payment, and capture actions all surface session-expired guidance before retry.
+- UI copy assumes POS only acts on checks that became payable after restaurant service completion.
## Build
diff --git a/docs/runbooks/testing.md b/docs/runbooks/testing.md
index 3924511..fa1b6c4 100644
--- a/docs/runbooks/testing.md
+++ b/docs/runbooks/testing.md
@@ -17,7 +17,7 @@ npm run test:ci
- `src/api/client.test.ts`: runtime-config precedence and fallback behavior.
- `src/api/dashboardApi.test.ts`: endpoint path/query composition and payload mapping.
- `src/auth/oidcLogin.test.ts`: OIDC start-url generation and safe return-url fallback.
-- `src/App.test.tsx`: central login screen, protected summary/detail flow, payment capture, and session-expired recovery guidance.
+- `src/App.test.tsx`: central login screen, lifecycle-aware payable-check summary/detail flow, payment capture, and session-expired recovery guidance.
## Notes
diff --git a/src/App.test.tsx b/src/App.test.tsx
index 9131809..8a5ce57 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -56,7 +56,7 @@ describe('POS Transactions App', () => {
});
vi.mocked(loadDashboard).mockResolvedValue({
contextId: 'demo-context',
- summary: '2 payments awaiting reconciliation',
+ summary: '2 served orders are payable',
openBalance: 52.3,
currency: 'USD',
recentPayments: []
@@ -86,7 +86,7 @@ describe('POS Transactions App', () => {
paymentMethod: 'cash',
amount: 10,
currency: 'USD',
- status: 'Captured',
+ status: 'Payable',
capturedAtUtc: '2026-03-27T11:00:00Z'
}
]
@@ -103,9 +103,11 @@ describe('POS Transactions App', () => {
await waitFor(() => expect(loadTransactionDetail).toHaveBeenCalledWith('demo-context', 'POS-9001'));
await waitFor(() => expect(loadRecentPayments).toHaveBeenCalledWith('demo-context'));
- expect(await screen.findByText('2 payments awaiting reconciliation')).toBeInTheDocument();
+ expect(await screen.findByText('2 served orders are payable')).toBeInTheDocument();
+ expect(screen.getByText('Only restaurant orders that have completed service should appear here as payable checks for capture.')).toBeInTheDocument();
expect(await screen.findByText('POS-22')).toBeInTheDocument();
expect(await screen.findByText('POS-21')).toBeInTheDocument();
+ expect(await screen.findByText('ready for payment capture after service completion')).toBeInTheDocument();
});
it('captures payments and records retry-ready history', async () => {
@@ -154,6 +156,7 @@ describe('POS Transactions App', () => {
await waitFor(() => expect(capturePosPayment).toHaveBeenCalledTimes(1));
expect(await screen.findAllByText('POS-2200')).toHaveLength(2);
+ expect(screen.getAllByText('payment is complete and the restaurant workflow can close').length).toBeGreaterThan(0);
expect(screen.getByRole('button', { name: 'Retry Last Capture' })).not.toBeDisabled();
});
diff --git a/src/App.tsx b/src/App.tsx
index d06a195..bd6346b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -67,9 +67,10 @@ const paymentColumns = [
{
title: 'Status',
render: (_: unknown, record: PosPaymentActivity) => (
- {record.status}
+ {record.status}
)
},
+ { title: 'Lifecycle Step', render: (_: unknown, record: PosPaymentActivity) => paymentProgressHint(record.status) },
{ title: 'Captured At', render: (_: unknown, record: PosPaymentActivity) => formatUtc(record.capturedAtUtc) }
];
@@ -240,7 +241,7 @@ function PosTransactionsShell() {
POS Transactions
- Protected POS workflows for summary lookup, transaction detail inspection, capture retries, and recent payment activity.
+ Protected POS workflows for payable restaurant checks, capture retries, and recent payment activity once service is complete.
{session.error && }
{workflowState.error && }
@@ -296,9 +297,12 @@ function PosTransactionsShell() {
{summaryPayload.openBalance.toFixed(2)} {summaryPayload.currency}
+
+ Only restaurant orders that have completed service should appear here as payable checks for capture.
+
) : (
-
+
)}
@@ -313,11 +317,14 @@ function PosTransactionsShell() {
{detailPayload.transaction.amount.toFixed(2)} {detailPayload.transaction.currency}
- {detailPayload.transaction.status}
+
+ {detailPayload.transaction.status}
+
+ {paymentProgressHint(detailPayload.transaction.status)}
{formatUtc(detailPayload.transaction.capturedAtUtc)}
) : (
-
+
)}
@@ -381,7 +388,7 @@ function PosTransactionsShell() {
- Use retry when a payment attempt fails after verifying the transaction payload and operator session.
+ Capture should be used only after kitchen and floor service complete the order, making the check payable.
@@ -392,7 +399,10 @@ function PosTransactionsShell() {
{paymentResponse.contextId}
{paymentResponse.transactionId}
{String(paymentResponse.succeeded)}
- {paymentResponse.status}
+
+ {paymentResponse.status}
+
+ {paymentProgressHint(paymentResponse.status)}
{formatUtc(paymentResponse.capturedAtUtc)}
{paymentResponse.summary}
@@ -417,10 +427,11 @@ function PosTransactionsShell() {
{
title: 'Status',
render: (_, record) => (
- {record.response.status}
+ {record.response.status}
)
},
- { title: 'Summary', render: (_, record) => record.response.summary }
+ { title: 'Summary', render: (_, record) => record.response.summary },
+ { title: 'Lifecycle Step', render: (_, record) => paymentProgressHint(record.response.status) }
]}
/>
@@ -455,6 +466,44 @@ function providerLabel(provider: IdentityProvider) {
}
}
+function paymentStatusColor(status: string) {
+ switch (status.toLowerCase()) {
+ case 'captured':
+ case 'paid':
+ return 'green';
+ case 'payable':
+ case 'ready':
+ return 'blue';
+ case 'pending':
+ case 'authorized':
+ return 'orange';
+ case 'failed':
+ case 'blocked':
+ return 'red';
+ default:
+ return 'default';
+ }
+}
+
+function paymentProgressHint(status: string) {
+ switch (status.toLowerCase()) {
+ case 'payable':
+ case 'ready':
+ return 'ready for payment capture after service completion';
+ case 'pending':
+ case 'authorized':
+ return 'payment is in progress and should be monitored before retrying';
+ case 'captured':
+ case 'paid':
+ return 'payment is complete and the restaurant workflow can close';
+ case 'failed':
+ case 'blocked':
+ return 'operator review is required before attempting another capture';
+ default:
+ return 'payment state is being resolved against the restaurant lifecycle';
+ }
+}
+
function formatUtc(value: string) {
return new Date(value).toLocaleString('en-US', {
dateStyle: 'medium',