@sunbay/checkout-sdk-protocol 0.0.2 → 0.0.3

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/envelope.ts","../src/errors.ts","../src/commands.ts","../src/events.ts","../src/iframeLayout.ts"],"sourcesContent":["/**\n * 信封与握手契约(spec 5.1,真源)。\n *\n * 协议名 `sunbay-checkout`,`major = 1`。与现有 Native Bridge `{v:1,type,...}`\n * 是不同协议,数字相同不代表兼容。\n *\n * `encodeEnvelope`/`decodeEnvelope`:JSON 序列化 + 最小运行时结构校验\n * (校验 protocol/major/kind/必填字段),非法抛错。解码器**不**校验 request.name\n * 是否为已知 CommandName —— 未知 name 仍解为 kind==='request',由调用方拒绝。\n */\n\nimport type { CheckoutError } from './types';\n\n/** 协议名常量。 */\nexport const PROTOCOL_NAME = 'sunbay-checkout' as const;\n/** 当前主版本。 */\nexport const PROTOCOL_MAJOR = 1 as const;\n\n/**\n * 握手消息(spec 5.1)。main iframe 加载后先广播,此时尚无 checkoutId。\n */\nexport type RuntimeHello = {\n protocol: 'sunbay-checkout';\n kind: 'hello';\n runtimeId: string;\n supportedMajorVersions: number[];\n /** 首期:['card','splitCard','address','express','paymentMethodFilter']。 */\n capabilities: string[];\n};\n\n/**\n * 初始化后的公共信封(spec 5.1)。\n */\nexport type Envelope = {\n protocol: 'sunbay-checkout';\n major: 1;\n runtimeId: string;\n checkoutId: string;\n elementId?: string;\n} & (\n | { kind: 'request'; requestId: string; name: string; payload: unknown }\n | { kind: 'response'; requestId: string; ok: true; data: unknown }\n | { kind: 'response'; requestId: string; ok: false; error: CheckoutError }\n | { kind: 'event'; eventId: string; name: string; operationId?: string; payload: unknown }\n);\n\n/** 合法 kind 集合。 */\nconst ENVELOPE_KINDS = ['request', 'response', 'event'] as const;\n\n/** 协议校验失败错误。 */\nexport class EnvelopeDecodeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'EnvelopeDecodeError';\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction assertString(obj: Record<string, unknown>, key: string): void {\n if (typeof obj[key] !== 'string') {\n throw new EnvelopeDecodeError(`Envelope field \"${key}\" is missing or not a string`);\n }\n}\n\n/**\n * 将信封序列化为线缆字符串(JSON)。\n *\n * @param envelope 待发送的信封\n * @returns JSON 字符串\n * @example\n * const wire = encodeEnvelope({ protocol: 'sunbay-checkout', major: 1, ... });\n */\nexport function encodeEnvelope(envelope: Envelope): string {\n return JSON.stringify(envelope);\n}\n\n/**\n * 解析并最小结构校验信封。非法(缺 protocol、错 major、非法 kind、缺必填字段、\n * 非 JSON)一律抛 {@link EnvelopeDecodeError}。\n *\n * 注意:解码器不校验 request.name 是否为已知命令 —— 未知 name 仍解为\n * kind==='request',交由调用方(如 Orchestrator)拒绝。\n *\n * @param wire 线缆字符串\n * @returns 已校验结构的信封\n * @example\n * const env = decodeEnvelope(wire);\n * if (env.kind === 'request') { ... }\n */\nexport function decodeEnvelope(wire: string): Envelope {\n let parsed: unknown;\n try {\n parsed = JSON.parse(wire);\n } catch {\n throw new EnvelopeDecodeError('Envelope is not valid JSON');\n }\n\n if (!isRecord(parsed)) {\n throw new EnvelopeDecodeError('Envelope must be an object');\n }\n\n if (parsed.protocol !== PROTOCOL_NAME) {\n throw new EnvelopeDecodeError(\n `protocol must be \"${PROTOCOL_NAME}\", received ${JSON.stringify(parsed.protocol)}`\n );\n }\n if (parsed.major !== PROTOCOL_MAJOR) {\n throw new EnvelopeDecodeError(\n `major must be ${PROTOCOL_MAJOR}, received ${JSON.stringify(parsed.major)}`\n );\n }\n\n assertString(parsed, 'runtimeId');\n assertString(parsed, 'checkoutId');\n if (parsed.elementId !== undefined && typeof parsed.elementId !== 'string') {\n throw new EnvelopeDecodeError('elementId must be a string when present');\n }\n\n const kind = parsed.kind;\n if (typeof kind !== 'string' || !(ENVELOPE_KINDS as readonly string[]).includes(kind)) {\n throw new EnvelopeDecodeError(`Invalid kind: ${JSON.stringify(kind)}`);\n }\n\n switch (kind) {\n case 'request': {\n assertString(parsed, 'requestId');\n assertString(parsed, 'name');\n if (!('payload' in parsed)) {\n throw new EnvelopeDecodeError('request envelope is missing payload');\n }\n break;\n }\n case 'response': {\n assertString(parsed, 'requestId');\n if (typeof parsed.ok !== 'boolean') {\n throw new EnvelopeDecodeError('response envelope is missing boolean ok');\n }\n if (parsed.ok === true) {\n if (!('data' in parsed)) {\n throw new EnvelopeDecodeError('successful response is missing data');\n }\n } else if (!isRecord(parsed.error)) {\n throw new EnvelopeDecodeError('failed response is missing error object');\n }\n break;\n }\n case 'event': {\n assertString(parsed, 'eventId');\n assertString(parsed, 'name');\n if (!('payload' in parsed)) {\n throw new EnvelopeDecodeError('event envelope is missing payload');\n }\n if (\n 'operationId' in parsed &&\n parsed.operationId !== undefined &&\n typeof parsed.operationId !== 'string'\n ) {\n throw new EnvelopeDecodeError('operationId must be a string when present');\n }\n break;\n }\n }\n\n // 结构已校验,确认为 Envelope 形状。\n return parsed as unknown as Envelope;\n}\n","/**\n * 协议错误码常量(单一真源,spec 5.4)。\n *\n * 不提供可能误导重复付款的通用 `retryable` 布尔。\n * 仅保留实际会被产出的错误码;新增码须同时有产出点。\n */\n\nexport const ErrorCode = {\n INVALID_OPTIONS: 'INVALID_OPTIONS',\n ELEMENT_CONFLICT: 'ELEMENT_CONFLICT',\n INSTANCE_DESTROYED: 'INSTANCE_DESTROYED',\n CHECKOUT_BUSY: 'CHECKOUT_BUSY',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n SESSION_LOAD_FAILED: 'SESSION_LOAD_FAILED',\n RUNTIME_LOAD_FAILED: 'RUNTIME_LOAD_FAILED',\n PROTOCOL_UNSUPPORTED: 'PROTOCOL_UNSUPPORTED',\n REQUEST_TIMEOUT: 'REQUEST_TIMEOUT',\n PAYMENT_FLOW_ERROR: 'PAYMENT_FLOW_ERROR',\n UNSUPPORTED_CAPABILITY: 'UNSUPPORTED_CAPABILITY',\n} as const;\n\n/** 错误码字面量联合类型。 */\nexport type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode];\n","/**\n * 命令契约(商户 SDK → main,spec 5.2 表,真源)。\n *\n * 每命令的请求/响应类型在此定义;跨通道只传 JSON 可序列化数据。\n */\n\nimport type {\n Appearance,\n AddressInfo,\n ElementOptions,\n ElementType,\n PaymentMethodFilter,\n PublicSessionView,\n TxnStatus,\n} from './types';\n\n/** 全部命令名(spec 5.2)。 */\nexport type CommandName =\n | 'initialize'\n | 'createElement'\n | 'updateElement'\n | 'destroyElement'\n | 'focus'\n | 'blur'\n | 'clear'\n | 'updateCheckout'\n | 'getSession'\n | 'refresh'\n | 'getPaymentStatus'\n | 'confirm'\n | 'destroy';\n\n/** 已知命令名集合(供调用方校验 request.name 是否受支持)。 */\nexport const COMMAND_NAMES: readonly CommandName[] = [\n 'initialize',\n 'createElement',\n 'updateElement',\n 'destroyElement',\n 'focus',\n 'blur',\n 'clear',\n 'updateCheckout',\n 'getSession',\n 'refresh',\n 'getPaymentStatus',\n 'confirm',\n 'destroy',\n];\n\n/** 判断字符串是否为已知命令名。 */\nexport function isCommandName(name: string): name is CommandName {\n return (COMMAND_NAMES as readonly string[]).includes(name);\n}\n\n// --- initialize ---\nexport interface InitializeReq {\n sessionId: string;\n locale?: string;\n appearance?: Appearance;\n paymentMethods?: PaymentMethodFilter;\n}\nexport interface InitializeResp {\n capabilities: string[];\n session: PublicSessionView;\n}\n\n// --- createElement ---\nexport interface CreateElementReq {\n elementId: string;\n type: ElementType;\n options?: ElementOptions;\n}\nexport interface CreateElementResp {\n elementId: string;\n registered: true;\n}\n\n// --- updateElement ---\nexport interface UpdateElementReq {\n elementId: string;\n patch: Partial<ElementOptions>;\n}\nexport interface UpdateElementResp {\n elementId: string;\n options: ElementOptions;\n}\n\n// --- destroyElement ---\nexport interface DestroyElementReq {\n elementId: string;\n}\nexport interface DestroyElementResp {\n elementId: string;\n released: true;\n}\n\n// --- focus / blur / clear(仅输入类 Element)---\nexport interface ElementActionReq {\n elementId: string;\n}\nexport interface ElementActionResp {\n elementId: string;\n done: true;\n}\n\n// --- updateCheckout(不改会话/金额)---\nexport interface UpdateCheckoutReq {\n locale?: string;\n appearance?: Appearance;\n}\nexport interface UpdateCheckoutResp {\n updated: true;\n}\n\n// --- getSession / refresh ---\nexport type GetSessionReq = Record<string, never>;\nexport type GetSessionResp = PublicSessionView;\n\n// --- getPaymentStatus(原样返回 TxnStatus,不转义)---\nexport type GetPaymentStatusReq = Record<string, never>;\nexport interface GetPaymentStatusResp {\n txnStatus: TxnStatus;\n txnId?: string;\n}\n\n// --- confirm(仅表示开始执行)---\nexport interface ConfirmReq {\n operationId: string;\n billingAddress?: AddressInfo;\n shippingAddress?: AddressInfo;\n customerName?: string;\n email?: string;\n returnUrl?: string;\n}\nexport interface ConfirmResp {\n accepted: true;\n}\n\n// --- destroy ---\nexport type DestroyReq = Record<string, never>;\nexport interface DestroyResp {\n released: true;\n}\n\n/** 命令名 → 请求载荷映射。 */\nexport interface CommandRequestMap {\n initialize: InitializeReq;\n createElement: CreateElementReq;\n updateElement: UpdateElementReq;\n destroyElement: DestroyElementReq;\n focus: ElementActionReq;\n blur: ElementActionReq;\n clear: ElementActionReq;\n updateCheckout: UpdateCheckoutReq;\n getSession: GetSessionReq;\n refresh: GetSessionReq;\n getPaymentStatus: GetPaymentStatusReq;\n confirm: ConfirmReq;\n destroy: DestroyReq;\n}\n\n/** 命令名 → 成功响应载荷映射。 */\nexport interface CommandResponseMap {\n initialize: InitializeResp;\n createElement: CreateElementResp;\n updateElement: UpdateElementResp;\n destroyElement: DestroyElementResp;\n focus: ElementActionResp;\n blur: ElementActionResp;\n clear: ElementActionResp;\n updateCheckout: UpdateCheckoutResp;\n getSession: GetSessionResp;\n refresh: GetSessionResp;\n getPaymentStatus: GetPaymentStatusResp;\n confirm: ConfirmResp;\n destroy: DestroyResp;\n}\n","/**\n * 事件契约(main → 商户 SDK,spec 5.3 表,真源)。\n *\n * 公开事件仅暴露 empty/complete/brand/error 等允许状态,**绝不含卡值**,\n * 不提供卡数据 getValue。`on` 注册后不重放历史事件。\n */\n\nimport type {\n AvailableWallets,\n CheckoutError,\n PaymentAction,\n PaymentMethod,\n PaymentOutcome,\n} from './types';\n\n/** 全部事件名(spec 5.3)。 */\nexport type EventName =\n | 'ready'\n | 'change'\n | 'focus'\n | 'blur'\n | 'loadError'\n | 'elementReady'\n | 'expressAvailabilityChange'\n | 'paymentStateChange'\n | 'paymentResult'\n | 'error';\n\n/** 已知事件名集合。 */\nexport const EVENT_NAMES: readonly EventName[] = [\n 'ready',\n 'change',\n 'focus',\n 'blur',\n 'loadError',\n 'elementReady',\n 'expressAvailabilityChange',\n 'paymentStateChange',\n 'paymentResult',\n 'error',\n];\n\n/** 判断字符串是否为已知事件名。 */\nexport function isEventName(name: string): name is EventName {\n return (EVENT_NAMES as readonly string[]).includes(name);\n}\n\n// --- 输入 Element 事件 ---\nexport interface ReadyEvent {\n elementId: string;\n}\nexport interface ElementReadyEvent {\n elementId: string;\n /** 仅 expressCheckout Element ready 时携带:各钱包可用性。 */\n availableWallets?: AvailableWallets;\n}\nexport interface ChangeEvent {\n elementId: string;\n empty: boolean;\n complete: boolean;\n error?: CheckoutError;\n /** 仅卡号字段允许携带 brand;绝不含卡值。 */\n brand?: string;\n}\nexport interface FocusEvent {\n elementId: string;\n}\nexport interface BlurEvent {\n elementId: string;\n}\nexport interface LoadErrorEvent {\n elementId: string;\n error: CheckoutError;\n}\n\n// --- Express 事件 ---\n/**\n * express 可用钱包变化事件(spec 6)。\n * 专用于 expressCheckout:以 AvailableWallets 表达各钱包(Apple/Google Pay)可用性。\n */\nexport interface ExpressAvailabilityChangeEvent {\n availableWallets: AvailableWallets;\n}\n\n// --- Checkout 级事件 ---\nexport interface PaymentStateChangeEvent {\n operationId: string;\n method: PaymentMethod;\n /** 过程状态;requiresAction 时携带 action 信息。 */\n state: string;\n action?: PaymentAction;\n}\nexport interface PaymentResultEvent {\n operationId: string;\n method: PaymentMethod;\n outcome: PaymentOutcome;\n}\nexport interface ErrorEvent {\n /** 异步主动流程错误;可含 operationId(如已创建)。 */\n operationId?: string;\n error: CheckoutError;\n}\n\n/** 事件名 → 载荷映射。 */\nexport interface EventPayloadMap {\n ready: ReadyEvent;\n change: ChangeEvent;\n focus: FocusEvent;\n blur: BlurEvent;\n loadError: LoadErrorEvent;\n elementReady: ElementReadyEvent;\n expressAvailabilityChange: ExpressAvailabilityChangeEvent;\n paymentStateChange: PaymentStateChangeEvent;\n paymentResult: PaymentResultEvent;\n error: ErrorEvent;\n}\n","/**\n * iframe 布局协议:托管 iframe(checkout,child)→ 父窗口(商户 SDK,parent)的\n * **直连 postMessage** 契约,用于 iframe 自适应高度。\n *\n * 与外层 Envelope(商户 SDK ↔ main iframe)及内层点对点 port(element ↔ main)均区分:\n * 本消息由每个可见 field / element iframe 直接 postMessage 到其 `window.parent`(商户页),\n * 商户 SDK 的 Element 句柄监听后把外层 iframe 高度调整为内容真实高度(宽度保持 100%)。\n *\n * 安全约束(AGENTS/spec):本消息**绝不含任何卡值**(明文或密文),仅携带 height 与\n * elementId。父窗口侧须校验来源(`event.source === iframe.contentWindow` 且\n * `event.origin === hostOrigin`)后方可采信,防止跨源伪造。\n */\n\n/** iframe 自适应高度消息的判别字面量(命名空间化,避免与其它 postMessage 碰撞)。 */\nexport const IFRAME_RESIZE_MESSAGE_TYPE = 'sunbay:checkout:iframe-resize' as const;\n\n/**\n * iframe(child)→ 父窗口(SDK)的内容高度变更通知。\n *\n * child 用 `ResizeObserver` 观测内容高度,变化时发送(去重);parent 据此设\n * `iframe.style.height`。绝不含卡值。\n */\nexport interface IframeResizeMessage {\n /** 判别字段,固定为 {@link IFRAME_RESIZE_MESSAGE_TYPE}。 */\n type: typeof IFRAME_RESIZE_MESSAGE_TYPE;\n /**\n * 目标 Element 的唯一 id。由商户 SDK 经 iframe src 的 `&elementId=` 下发,child 原样\n * 回传,供父窗口在同一页面多个 iframe 间按句柄分发。\n */\n elementId: string;\n /** 内容高度(CSS px,已 `Math.ceil`;有限且非负)。 */\n height: number;\n}\n\n/**\n * 是否为合法的 {@link IframeResizeMessage}。\n *\n * 校验:非空对象 + type 字面量匹配 + elementId 为非空字符串 + height 为有限非负数。\n *\n * @param data - 任意来源数据(通常是 `MessageEvent.data`)。\n * @returns data 是否为合法的 iframe-resize 消息。\n */\nexport function isIframeResizeMessage(data: unknown): data is IframeResizeMessage {\n if (typeof data !== 'object' || data === null) {\n return false;\n }\n const msg = data as Partial<IframeResizeMessage>;\n return (\n msg.type === IFRAME_RESIZE_MESSAGE_TYPE &&\n typeof msg.elementId === 'string' &&\n msg.elementId.length > 0 &&\n typeof msg.height === 'number' &&\n Number.isFinite(msg.height) &&\n msg.height >= 0\n );\n}\n"],"mappings":";AAcO,IAAM,gBAAgB;AAEtB,IAAM,iBAAiB;AA+B9B,IAAM,iBAAiB,CAAC,WAAW,YAAY,OAAO;AAG/C,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC3C,YAAY,SAAiB;AACzB,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EAChB;AACJ;AAEA,SAAS,SAAS,OAAkD;AAChE,SAAO,OAAO,UAAU,YAAY,UAAU;AAClD;AAEA,SAAS,aAAa,KAA8B,KAAmB;AACnE,MAAI,OAAO,IAAI,GAAG,MAAM,UAAU;AAC9B,UAAM,IAAI,oBAAoB,mBAAmB,GAAG,8BAA8B;AAAA,EACtF;AACJ;AAUO,SAAS,eAAe,UAA4B;AACvD,SAAO,KAAK,UAAU,QAAQ;AAClC;AAeO,SAAS,eAAe,MAAwB;AACnD,MAAI;AACJ,MAAI;AACA,aAAS,KAAK,MAAM,IAAI;AAAA,EAC5B,QAAQ;AACJ,UAAM,IAAI,oBAAoB,4BAA4B;AAAA,EAC9D;AAEA,MAAI,CAAC,SAAS,MAAM,GAAG;AACnB,UAAM,IAAI,oBAAoB,4BAA4B;AAAA,EAC9D;AAEA,MAAI,OAAO,aAAa,eAAe;AACnC,UAAM,IAAI;AAAA,MACN,qBAAqB,aAAa,eAAe,KAAK,UAAU,OAAO,QAAQ,CAAC;AAAA,IACpF;AAAA,EACJ;AACA,MAAI,OAAO,UAAU,gBAAgB;AACjC,UAAM,IAAI;AAAA,MACN,iBAAiB,cAAc,cAAc,KAAK,UAAU,OAAO,KAAK,CAAC;AAAA,IAC7E;AAAA,EACJ;AAEA,eAAa,QAAQ,WAAW;AAChC,eAAa,QAAQ,YAAY;AACjC,MAAI,OAAO,cAAc,UAAa,OAAO,OAAO,cAAc,UAAU;AACxE,UAAM,IAAI,oBAAoB,yCAAyC;AAAA,EAC3E;AAEA,QAAM,OAAO,OAAO;AACpB,MAAI,OAAO,SAAS,YAAY,CAAE,eAAqC,SAAS,IAAI,GAAG;AACnF,UAAM,IAAI,oBAAoB,iBAAiB,KAAK,UAAU,IAAI,CAAC,EAAE;AAAA,EACzE;AAEA,UAAQ,MAAM;AAAA,IACV,KAAK,WAAW;AACZ,mBAAa,QAAQ,WAAW;AAChC,mBAAa,QAAQ,MAAM;AAC3B,UAAI,EAAE,aAAa,SAAS;AACxB,cAAM,IAAI,oBAAoB,qCAAqC;AAAA,MACvE;AACA;AAAA,IACJ;AAAA,IACA,KAAK,YAAY;AACb,mBAAa,QAAQ,WAAW;AAChC,UAAI,OAAO,OAAO,OAAO,WAAW;AAChC,cAAM,IAAI,oBAAoB,yCAAyC;AAAA,MAC3E;AACA,UAAI,OAAO,OAAO,MAAM;AACpB,YAAI,EAAE,UAAU,SAAS;AACrB,gBAAM,IAAI,oBAAoB,qCAAqC;AAAA,QACvE;AAAA,MACJ,WAAW,CAAC,SAAS,OAAO,KAAK,GAAG;AAChC,cAAM,IAAI,oBAAoB,yCAAyC;AAAA,MAC3E;AACA;AAAA,IACJ;AAAA,IACA,KAAK,SAAS;AACV,mBAAa,QAAQ,SAAS;AAC9B,mBAAa,QAAQ,MAAM;AAC3B,UAAI,EAAE,aAAa,SAAS;AACxB,cAAM,IAAI,oBAAoB,mCAAmC;AAAA,MACrE;AACA,UACI,iBAAiB,UACjB,OAAO,gBAAgB,UACvB,OAAO,OAAO,gBAAgB,UAChC;AACE,cAAM,IAAI,oBAAoB,2CAA2C;AAAA,MAC7E;AACA;AAAA,IACJ;AAAA,EACJ;AAGA,SAAO;AACX;;;ACjKO,IAAM,YAAY;AAAA,EACrB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,wBAAwB;AAC5B;;;ACcO,IAAM,gBAAwC;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAGO,SAAS,cAAc,MAAmC;AAC7D,SAAQ,cAAoC,SAAS,IAAI;AAC7D;;;ACvBO,IAAM,cAAoC;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAGO,SAAS,YAAY,MAAiC;AACzD,SAAQ,YAAkC,SAAS,IAAI;AAC3D;;;AC/BO,IAAM,6BAA6B;AA4BnC,SAAS,sBAAsB,MAA4C;AAC9E,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC3C,WAAO;AAAA,EACX;AACA,QAAM,MAAM;AACZ,SACI,IAAI,SAAS,8BACb,OAAO,IAAI,cAAc,YACzB,IAAI,UAAU,SAAS,KACvB,OAAO,IAAI,WAAW,YACtB,OAAO,SAAS,IAAI,MAAM,KAC1B,IAAI,UAAU;AAEtB;","names":[]}
1
+ {"version":3,"sources":["../src/envelope.ts","../src/errors.ts","../src/commands.ts","../src/events.ts","../src/iframeLayout.ts"],"sourcesContent":["/**\n * Envelope and handshake contract (spec 5.1, source of truth).\n *\n * Protocol name `sunbay-checkout`, `major = 1`. This is a different protocol from the existing\n * Native Bridge `{v:1,type,...}`; identical numbers do not imply compatibility.\n *\n * `encodeEnvelope`/`decodeEnvelope`: JSON serialization + minimal runtime structural validation\n * (validates protocol/major/kind/required fields), throwing on invalid input. The decoder does\n * **not** validate whether request.name is a known CommandName — an unknown name is still decoded\n * as kind==='request' and rejected by the caller.\n */\n\nimport type { CheckoutError } from './types';\n\n/** Protocol name constant. */\nexport const PROTOCOL_NAME = 'sunbay-checkout' as const;\n/** Current major version. */\nexport const PROTOCOL_MAJOR = 1 as const;\n\n/**\n * Handshake message (spec 5.1). Broadcast first after the main iframe loads, when there is no\n * checkoutId yet.\n */\nexport type RuntimeHello = {\n protocol: 'sunbay-checkout';\n kind: 'hello';\n runtimeId: string;\n supportedMajorVersions: number[];\n /** First phase: ['card','splitCard','address','express','paymentMethodFilter']. */\n capabilities: string[];\n};\n\n/**\n * Public envelope after initialization (spec 5.1).\n */\nexport type Envelope = {\n protocol: 'sunbay-checkout';\n major: 1;\n runtimeId: string;\n checkoutId: string;\n elementId?: string;\n} & (\n | { kind: 'request'; requestId: string; name: string; payload: unknown }\n | { kind: 'response'; requestId: string; ok: true; data: unknown }\n | { kind: 'response'; requestId: string; ok: false; error: CheckoutError }\n | { kind: 'event'; eventId: string; name: string; operationId?: string; payload: unknown }\n);\n\n/** Set of valid kinds. */\nconst ENVELOPE_KINDS = ['request', 'response', 'event'] as const;\n\n/** Protocol validation failure error. */\nexport class EnvelopeDecodeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'EnvelopeDecodeError';\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction assertString(obj: Record<string, unknown>, key: string): void {\n if (typeof obj[key] !== 'string') {\n throw new EnvelopeDecodeError(`Envelope field \"${key}\" is missing or not a string`);\n }\n}\n\n/**\n * Serialize an envelope into a wire string (JSON).\n *\n * @param envelope The envelope to send\n * @returns JSON string\n * @example\n * const wire = encodeEnvelope({ protocol: 'sunbay-checkout', major: 1, ... });\n */\nexport function encodeEnvelope(envelope: Envelope): string {\n return JSON.stringify(envelope);\n}\n\n/**\n * Parse and minimally structurally validate an envelope. Invalid input (missing protocol, wrong\n * major, invalid kind, missing required fields, non-JSON) always throws {@link EnvelopeDecodeError}.\n *\n * Note: the decoder does not validate whether request.name is a known command — an unknown name is\n * still decoded as kind==='request' and left for the caller (e.g. the Orchestrator) to reject.\n *\n * @param wire The wire string\n * @returns The structurally validated envelope\n * @example\n * const env = decodeEnvelope(wire);\n * if (env.kind === 'request') { ... }\n */\nexport function decodeEnvelope(wire: string): Envelope {\n let parsed: unknown;\n try {\n parsed = JSON.parse(wire);\n } catch {\n throw new EnvelopeDecodeError('Envelope is not valid JSON');\n }\n\n if (!isRecord(parsed)) {\n throw new EnvelopeDecodeError('Envelope must be an object');\n }\n\n if (parsed.protocol !== PROTOCOL_NAME) {\n throw new EnvelopeDecodeError(\n `protocol must be \"${PROTOCOL_NAME}\", received ${JSON.stringify(parsed.protocol)}`\n );\n }\n if (parsed.major !== PROTOCOL_MAJOR) {\n throw new EnvelopeDecodeError(\n `major must be ${PROTOCOL_MAJOR}, received ${JSON.stringify(parsed.major)}`\n );\n }\n\n assertString(parsed, 'runtimeId');\n assertString(parsed, 'checkoutId');\n if (parsed.elementId !== undefined && typeof parsed.elementId !== 'string') {\n throw new EnvelopeDecodeError('elementId must be a string when present');\n }\n\n const kind = parsed.kind;\n if (typeof kind !== 'string' || !(ENVELOPE_KINDS as readonly string[]).includes(kind)) {\n throw new EnvelopeDecodeError(`Invalid kind: ${JSON.stringify(kind)}`);\n }\n\n switch (kind) {\n case 'request': {\n assertString(parsed, 'requestId');\n assertString(parsed, 'name');\n if (!('payload' in parsed)) {\n throw new EnvelopeDecodeError('request envelope is missing payload');\n }\n break;\n }\n case 'response': {\n assertString(parsed, 'requestId');\n if (typeof parsed.ok !== 'boolean') {\n throw new EnvelopeDecodeError('response envelope is missing boolean ok');\n }\n if (parsed.ok === true) {\n if (!('data' in parsed)) {\n throw new EnvelopeDecodeError('successful response is missing data');\n }\n } else if (!isRecord(parsed.error)) {\n throw new EnvelopeDecodeError('failed response is missing error object');\n }\n break;\n }\n case 'event': {\n assertString(parsed, 'eventId');\n assertString(parsed, 'name');\n if (!('payload' in parsed)) {\n throw new EnvelopeDecodeError('event envelope is missing payload');\n }\n if (\n 'operationId' in parsed &&\n parsed.operationId !== undefined &&\n typeof parsed.operationId !== 'string'\n ) {\n throw new EnvelopeDecodeError('operationId must be a string when present');\n }\n break;\n }\n }\n\n // Structure validated; confirmed to be an Envelope shape.\n return parsed as unknown as Envelope;\n}\n","/**\n * Protocol error code constants (single source of truth, spec 5.4).\n *\n * Does not provide a generic `retryable` boolean that could mislead into duplicate payments.\n * Only keeps error codes that are actually produced; new codes must have a production point.\n */\n\nexport const ErrorCode = {\n INVALID_OPTIONS: 'INVALID_OPTIONS',\n ELEMENT_CONFLICT: 'ELEMENT_CONFLICT',\n INSTANCE_DESTROYED: 'INSTANCE_DESTROYED',\n CHECKOUT_BUSY: 'CHECKOUT_BUSY',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n SESSION_LOAD_FAILED: 'SESSION_LOAD_FAILED',\n RUNTIME_LOAD_FAILED: 'RUNTIME_LOAD_FAILED',\n PROTOCOL_UNSUPPORTED: 'PROTOCOL_UNSUPPORTED',\n REQUEST_TIMEOUT: 'REQUEST_TIMEOUT',\n PAYMENT_FLOW_ERROR: 'PAYMENT_FLOW_ERROR',\n UNSUPPORTED_CAPABILITY: 'UNSUPPORTED_CAPABILITY',\n} as const;\n\n/** Error code literal union type. */\nexport type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode];\n","/**\n * Command contract (merchant SDK → main, spec 5.2 table, source of truth).\n *\n * The request/response types for each command are defined here; only JSON-serializable data is sent\n * across the channel.\n */\n\nimport type {\n Appearance,\n AddressInfo,\n ElementOptions,\n ElementType,\n PaymentMethodFilter,\n PublicSessionView,\n TxnStatus,\n} from './types';\n\n/** All command names (spec 5.2). */\nexport type CommandName =\n | 'initialize'\n | 'createElement'\n | 'updateElement'\n | 'destroyElement'\n | 'focus'\n | 'blur'\n | 'clear'\n | 'updateCheckout'\n | 'getSession'\n | 'refresh'\n | 'getPaymentStatus'\n | 'confirm'\n | 'destroy';\n\n/** Set of known command names (for callers to validate whether request.name is supported). */\nexport const COMMAND_NAMES: readonly CommandName[] = [\n 'initialize',\n 'createElement',\n 'updateElement',\n 'destroyElement',\n 'focus',\n 'blur',\n 'clear',\n 'updateCheckout',\n 'getSession',\n 'refresh',\n 'getPaymentStatus',\n 'confirm',\n 'destroy',\n];\n\n/** Determine whether a string is a known command name. */\nexport function isCommandName(name: string): name is CommandName {\n return (COMMAND_NAMES as readonly string[]).includes(name);\n}\n\n// --- initialize ---\nexport interface InitializeReq {\n sessionId: string;\n locale?: string;\n appearance?: Appearance;\n paymentMethods?: PaymentMethodFilter;\n}\nexport interface InitializeResp {\n capabilities: string[];\n session: PublicSessionView;\n}\n\n// --- createElement ---\nexport interface CreateElementReq {\n elementId: string;\n type: ElementType;\n options?: ElementOptions;\n}\nexport interface CreateElementResp {\n elementId: string;\n registered: true;\n}\n\n// --- updateElement ---\nexport interface UpdateElementReq {\n elementId: string;\n patch: Partial<ElementOptions>;\n}\nexport interface UpdateElementResp {\n elementId: string;\n options: ElementOptions;\n}\n\n// --- destroyElement ---\nexport interface DestroyElementReq {\n elementId: string;\n}\nexport interface DestroyElementResp {\n elementId: string;\n released: true;\n}\n\n// --- focus / blur / clear (input-type Elements only) ---\nexport interface ElementActionReq {\n elementId: string;\n}\nexport interface ElementActionResp {\n elementId: string;\n done: true;\n}\n\n// --- updateCheckout (does not change session/amount) ---\nexport interface UpdateCheckoutReq {\n locale?: string;\n appearance?: Appearance;\n}\nexport interface UpdateCheckoutResp {\n updated: true;\n}\n\n// --- getSession / refresh ---\nexport type GetSessionReq = Record<string, never>;\nexport type GetSessionResp = PublicSessionView;\n\n// --- getPaymentStatus (returns TxnStatus verbatim, no translation) ---\nexport type GetPaymentStatusReq = Record<string, never>;\nexport interface GetPaymentStatusResp {\n txnStatus: TxnStatus;\n txnId?: string;\n}\n\n// --- confirm (only indicates that execution has started) ---\nexport interface ConfirmReq {\n operationId: string;\n billingAddress?: AddressInfo;\n shippingAddress?: AddressInfo;\n customerName?: string;\n email?: string;\n returnUrl?: string;\n}\nexport interface ConfirmResp {\n accepted: true;\n}\n\n// --- destroy ---\nexport type DestroyReq = Record<string, never>;\nexport interface DestroyResp {\n released: true;\n}\n\n/** Command name → request payload mapping. */\nexport interface CommandRequestMap {\n initialize: InitializeReq;\n createElement: CreateElementReq;\n updateElement: UpdateElementReq;\n destroyElement: DestroyElementReq;\n focus: ElementActionReq;\n blur: ElementActionReq;\n clear: ElementActionReq;\n updateCheckout: UpdateCheckoutReq;\n getSession: GetSessionReq;\n refresh: GetSessionReq;\n getPaymentStatus: GetPaymentStatusReq;\n confirm: ConfirmReq;\n destroy: DestroyReq;\n}\n\n/** Command name → success response payload mapping. */\nexport interface CommandResponseMap {\n initialize: InitializeResp;\n createElement: CreateElementResp;\n updateElement: UpdateElementResp;\n destroyElement: DestroyElementResp;\n focus: ElementActionResp;\n blur: ElementActionResp;\n clear: ElementActionResp;\n updateCheckout: UpdateCheckoutResp;\n getSession: GetSessionResp;\n refresh: GetSessionResp;\n getPaymentStatus: GetPaymentStatusResp;\n confirm: ConfirmResp;\n destroy: DestroyResp;\n}\n","/**\n * Event contract (main → merchant SDK, spec 5.3 table, source of truth).\n *\n * Public events only expose allowed states such as empty/complete/brand/error, and **never contain\n * card values**; no card-data getValue is provided. After `on` registration, historical events are\n * not replayed.\n */\n\nimport type {\n AvailableWallets,\n CheckoutError,\n PaymentAction,\n PaymentMethod,\n PaymentOutcome,\n} from './types';\n\n/** All event names (spec 5.3). */\nexport type EventName =\n | 'ready'\n | 'change'\n | 'focus'\n | 'blur'\n | 'loadError'\n | 'elementReady'\n | 'expressAvailabilityChange'\n | 'paymentStateChange'\n | 'paymentResult'\n | 'error';\n\n/** Set of known event names. */\nexport const EVENT_NAMES: readonly EventName[] = [\n 'ready',\n 'change',\n 'focus',\n 'blur',\n 'loadError',\n 'elementReady',\n 'expressAvailabilityChange',\n 'paymentStateChange',\n 'paymentResult',\n 'error',\n];\n\n/** Determine whether a string is a known event name. */\nexport function isEventName(name: string): name is EventName {\n return (EVENT_NAMES as readonly string[]).includes(name);\n}\n\n// --- Input Element events ---\nexport interface ReadyEvent {\n elementId: string;\n}\nexport interface ElementReadyEvent {\n elementId: string;\n /** Only carried when the expressCheckout Element is ready: per-wallet availability. */\n availableWallets?: AvailableWallets;\n}\nexport interface ChangeEvent {\n elementId: string;\n empty: boolean;\n complete: boolean;\n error?: CheckoutError;\n /** Only the card number field may carry brand; never contains card values. */\n brand?: string;\n}\nexport interface FocusEvent {\n elementId: string;\n}\nexport interface BlurEvent {\n elementId: string;\n}\nexport interface LoadErrorEvent {\n elementId: string;\n error: CheckoutError;\n}\n\n// --- Express events ---\n/**\n * Express available-wallets change event (spec 6).\n * Dedicated to expressCheckout: uses AvailableWallets to express the availability of each wallet\n * (Apple/Google Pay).\n */\nexport interface ExpressAvailabilityChangeEvent {\n availableWallets: AvailableWallets;\n}\n\n// --- Checkout-level events ---\nexport interface PaymentStateChangeEvent {\n operationId: string;\n method: PaymentMethod;\n /** Intermediate state; carries action info when requiresAction. */\n state: string;\n action?: PaymentAction;\n}\nexport interface PaymentResultEvent {\n operationId: string;\n method: PaymentMethod;\n outcome: PaymentOutcome;\n}\nexport interface ErrorEvent {\n /** Error in an asynchronous proactive flow; may include operationId (if already created). */\n operationId?: string;\n error: CheckoutError;\n}\n\n/** Event name → payload mapping. */\nexport interface EventPayloadMap {\n ready: ReadyEvent;\n change: ChangeEvent;\n focus: FocusEvent;\n blur: BlurEvent;\n loadError: LoadErrorEvent;\n elementReady: ElementReadyEvent;\n expressAvailabilityChange: ExpressAvailabilityChangeEvent;\n paymentStateChange: PaymentStateChangeEvent;\n paymentResult: PaymentResultEvent;\n error: ErrorEvent;\n}\n","/**\n * iframe layout protocol: the **direct postMessage** contract from the hosted iframe (checkout,\n * child) → parent window (merchant SDK, parent), used for iframe auto-height.\n *\n * Distinct from both the outer Envelope (merchant SDK ↔ main iframe) and the inner point-to-point\n * port (element ↔ main): this message is posted directly by each visible field / element iframe to\n * its `window.parent` (the merchant page), and the merchant SDK's Element handle listens for it and\n * adjusts the outer iframe height to the real content height (width stays 100%).\n *\n * Security constraint (AGENTS/spec): this message **never contains any card value** (plaintext or\n * ciphertext); it only carries height and elementId. The parent-window side must validate the\n * source (`event.source === iframe.contentWindow` and `event.origin === hostOrigin`) before\n * trusting it, to prevent cross-origin forgery.\n */\n\n/** Discriminant literal for the iframe auto-height message (namespaced to avoid colliding with other postMessages). */\nexport const IFRAME_RESIZE_MESSAGE_TYPE = 'sunbay:checkout:iframe-resize' as const;\n\n/**\n * Content height change notification from the iframe (child) → parent window (SDK).\n *\n * The child observes content height with a `ResizeObserver` and sends on change (deduplicated); the\n * parent sets `iframe.style.height` accordingly. Never contains card values.\n */\nexport interface IframeResizeMessage {\n /** Discriminant field, fixed to {@link IFRAME_RESIZE_MESSAGE_TYPE}. */\n type: typeof IFRAME_RESIZE_MESSAGE_TYPE;\n /**\n * The unique id of the target Element. Passed down by the merchant SDK via the iframe src's\n * `&elementId=`, echoed back verbatim by the child, so the parent window can dispatch by handle\n * among multiple iframes on the same page.\n */\n elementId: string;\n /** Content height (CSS px, already `Math.ceil`-ed; finite and non-negative). */\n height: number;\n}\n\n/**\n * Whether the value is a valid {@link IframeResizeMessage}.\n *\n * Validates: non-null object + type literal match + elementId is a non-empty string + height is a\n * finite non-negative number.\n *\n * @param data - Data from any source (typically `MessageEvent.data`).\n * @returns Whether data is a valid iframe-resize message.\n */\nexport function isIframeResizeMessage(data: unknown): data is IframeResizeMessage {\n if (typeof data !== 'object' || data === null) {\n return false;\n }\n const msg = data as Partial<IframeResizeMessage>;\n return (\n msg.type === IFRAME_RESIZE_MESSAGE_TYPE &&\n typeof msg.elementId === 'string' &&\n msg.elementId.length > 0 &&\n typeof msg.height === 'number' &&\n Number.isFinite(msg.height) &&\n msg.height >= 0\n );\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
1
  {
2
- "name": "@sunbay/checkout-sdk-protocol",
3
- "version": "0.0.2",
4
- "description": "Sunbay Checkout SDK ↔ hosted runtime 通信协议契约(单一真源)。",
5
- "type": "module",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/index.mjs",
8
- "types": "./dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.mjs",
13
- "require": "./dist/index.cjs"
14
- }
15
- },
16
- "files": [
17
- "dist"
18
- ],
19
- "publishConfig": {
20
- "access": "public"
21
- },
22
- "scripts": {
23
- "build": "tsup",
24
- "type-check": "tsc --noEmit"
25
- },
26
- "devDependencies": {
27
- "tsup": "^8.3.5",
28
- "typescript": "^5.6.3"
2
+ "name": "@sunbay/checkout-sdk-protocol",
3
+ "version": "0.0.3",
4
+ "description": "Shared protocol contract between the Sunbay Checkout SDK and the Sunbay-hosted runtime (single source of truth).",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
29
14
  }
30
- }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "devDependencies": {
23
+ "tsup": "^8.3.5",
24
+ "typescript": "^5.6.3"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "type-check": "tsc --noEmit"
29
+ }
30
+ }