diff --git a/.changeset/pos-intercept-api.md b/.changeset/pos-intercept-api.md new file mode 100644 index 0000000000..4987223fc7 --- /dev/null +++ b/.changeset/pos-intercept-api.md @@ -0,0 +1,5 @@ +--- +'@shopify/ui-extensions': minor +--- + +Add `shopify.intercept()` types for POS blocking workflows. diff --git a/packages/ui-extensions/src/surfaces/point-of-sale/events.ts b/packages/ui-extensions/src/surfaces/point-of-sale/events.ts index f8100cb31a..325d4b14ec 100644 --- a/packages/ui-extensions/src/surfaces/point-of-sale/events.ts +++ b/packages/ui-extensions/src/surfaces/point-of-sale/events.ts @@ -3,6 +3,7 @@ import type { CashTrackingSessionStartEvent, CashTrackingSessionCompleteEvent, } from './events/cash-tracking-session-events'; +import type {Cart} from './types/cart'; /** * Canonical event-name constants for POS host events. Prefer these over string @@ -16,6 +17,16 @@ export const POS_EVENT_NAMES = { CASH_TRACKING_SESSION_COMPLETE: 'cashtrackingsessioncomplete', } as const; +/** + * Canonical workflow-name constants for POS host interceptions. Prefer these + * over string literals when calling `shopify.intercept`. + * + * @publicDocs + */ +export const POS_INTERCEPT_NAMES = { + BEFORE_CHECKOUT: 'beforecheckout', +} as const; + /** * Maps Shopify POS event names to their corresponding `Event` subclass types. * @@ -30,6 +41,89 @@ export interface ShopifyEventMap { [POS_EVENT_NAMES.CASH_TRACKING_SESSION_COMPLETE]: CashTrackingSessionCompleteEvent; } +/** + * Maps POS interceptable workflow names to their corresponding `Event` types. + * + * Used as the generic type parameter for `shopify.intercept`. + * + * @publicDocs + */ +export interface ShopifyInterceptMap { + [POS_INTERCEPT_NAMES.BEFORE_CHECKOUT]: BeforeCheckoutEvent; +} + +/** + * Dispatched when staff attempts to leave the active cart for checkout. + * + * @publicDocs + */ +export interface BeforeCheckoutEvent extends Event { + readonly type: typeof POS_INTERCEPT_NAMES.BEFORE_CHECKOUT; + /** The POS cart at the point checkout was requested. */ + readonly cart: Cart; +} + +/** @publicDocs */ +export type ShopifyInterceptor = ( + event: TEvent, +) => InterceptResult; + +/** + * The result an interceptor returns. An empty `operations` list allows the + * workflow; an `ERROR` validation blocks it. + * + * @publicDocs + */ +export interface InterceptResult { + operations: Operation[]; +} + +/** + * A single host operation produced by an interceptor. + * + * @publicDocs + */ +export interface Operation { + validationAdd?: ValidationAdd; +} + +/** @publicDocs */ +export type ValidationLevel = 'INFO' | 'WARNING' | 'ERROR'; + +/** + * Adds a validation to the workflow being intercepted. + * + * @publicDocs + */ +export interface ValidationAdd { + /** `ERROR` blocks the workflow. `WARNING` and `INFO` do not. */ + level: ValidationLevel; + + /** Stable identifier for this validation. */ + handle: string; + + /** Host-facing message for support, observability, or staff UX. */ + message: string; + + /** JSON-path locator for where the validation applies. Defaults to `$.cart`. */ + target?: string; + + /** Optional structured data for custom UX or order metadata. */ + metafields?: Metafield[]; +} + +/** + * Metafield input attached to a validation. + * + * @publicDocs + */ +export interface Metafield { + namespace: string; + key: string; + value: string; + type: string; +} + export type { TransactionCompleteEvent, CashTrackingSessionStartEvent, diff --git a/packages/ui-extensions/src/surfaces/point-of-sale/globals.ts b/packages/ui-extensions/src/surfaces/point-of-sale/globals.ts index 67533a92d7..bae46a34dc 100644 --- a/packages/ui-extensions/src/surfaces/point-of-sale/globals.ts +++ b/packages/ui-extensions/src/surfaces/point-of-sale/globals.ts @@ -1,5 +1,9 @@ import type {Navigation} from './api/navigation-api/navigation-api'; -import type {ShopifyEventMap} from './events'; +import type { + ShopifyEventMap, + ShopifyInterceptMap, + ShopifyInterceptor, +} from './events'; /** * The `shopify` global provides APIs that are available to all POS extensions @@ -36,6 +40,15 @@ export interface BackgroundShopifyGlobal extends ShopifyGlobal { type: K, listener: (event: ShopifyEventMap[K]) => void, ): void; + + /** + * Register an interceptor for a POS host workflow that can be blocked. + * Returns a function that unregisters the interceptor. + */ + intercept( + type: K, + interceptor: ShopifyInterceptor, + ): () => void; } declare global {