@sunbay/checkout-sdk-protocol 0.0.1 → 0.0.2
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.
- package/dist/index.cjs +16 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -21
- package/dist/index.d.ts +131 -21
- package/dist/index.mjs +13 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -4
package/dist/index.cjs
CHANGED
|
@@ -24,12 +24,14 @@ __export(index_exports, {
|
|
|
24
24
|
EVENT_NAMES: () => EVENT_NAMES,
|
|
25
25
|
EnvelopeDecodeError: () => EnvelopeDecodeError,
|
|
26
26
|
ErrorCode: () => ErrorCode,
|
|
27
|
+
IFRAME_RESIZE_MESSAGE_TYPE: () => IFRAME_RESIZE_MESSAGE_TYPE,
|
|
27
28
|
PROTOCOL_MAJOR: () => PROTOCOL_MAJOR,
|
|
28
29
|
PROTOCOL_NAME: () => PROTOCOL_NAME,
|
|
29
30
|
decodeEnvelope: () => decodeEnvelope,
|
|
30
31
|
encodeEnvelope: () => encodeEnvelope,
|
|
31
32
|
isCommandName: () => isCommandName,
|
|
32
|
-
isEventName: () => isEventName
|
|
33
|
+
isEventName: () => isEventName,
|
|
34
|
+
isIframeResizeMessage: () => isIframeResizeMessage
|
|
33
35
|
});
|
|
34
36
|
module.exports = __toCommonJS(index_exports);
|
|
35
37
|
|
|
@@ -164,7 +166,6 @@ var EVENT_NAMES = [
|
|
|
164
166
|
"blur",
|
|
165
167
|
"loadError",
|
|
166
168
|
"elementReady",
|
|
167
|
-
"availablePaymentMethodsChange",
|
|
168
169
|
"expressAvailabilityChange",
|
|
169
170
|
"paymentStateChange",
|
|
170
171
|
"paymentResult",
|
|
@@ -173,17 +174,29 @@ var EVENT_NAMES = [
|
|
|
173
174
|
function isEventName(name) {
|
|
174
175
|
return EVENT_NAMES.includes(name);
|
|
175
176
|
}
|
|
177
|
+
|
|
178
|
+
// src/iframeLayout.ts
|
|
179
|
+
var IFRAME_RESIZE_MESSAGE_TYPE = "sunbay:checkout:iframe-resize";
|
|
180
|
+
function isIframeResizeMessage(data) {
|
|
181
|
+
if (typeof data !== "object" || data === null) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const msg = data;
|
|
185
|
+
return msg.type === IFRAME_RESIZE_MESSAGE_TYPE && typeof msg.elementId === "string" && msg.elementId.length > 0 && typeof msg.height === "number" && Number.isFinite(msg.height) && msg.height >= 0;
|
|
186
|
+
}
|
|
176
187
|
// Annotate the CommonJS export names for ESM import in node:
|
|
177
188
|
0 && (module.exports = {
|
|
178
189
|
COMMAND_NAMES,
|
|
179
190
|
EVENT_NAMES,
|
|
180
191
|
EnvelopeDecodeError,
|
|
181
192
|
ErrorCode,
|
|
193
|
+
IFRAME_RESIZE_MESSAGE_TYPE,
|
|
182
194
|
PROTOCOL_MAJOR,
|
|
183
195
|
PROTOCOL_NAME,
|
|
184
196
|
decodeEnvelope,
|
|
185
197
|
encodeEnvelope,
|
|
186
198
|
isCommandName,
|
|
187
|
-
isEventName
|
|
199
|
+
isEventName,
|
|
200
|
+
isIframeResizeMessage
|
|
188
201
|
});
|
|
189
202
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/envelope.ts","../src/errors.ts","../src/commands.ts","../src/events.ts"],"sourcesContent":["/**\n * 协议契约统一出口 —— `@sunbay/checkout-sdk-protocol`(单一真源)。\n *\n * 商户 SDK(checkout-sdk/web/js)与 checkout 托管运行时(checkout/src/sdk-runtime)\n * 共用本契约。过渡期两仓以 `scripts/sync-protocol.mjs` 从本真源自动同步副本(副本请勿\n * 手改);待本包发布为 npm 真包后,副本删除、改为直接依赖包名。依赖方向单向:零反向依赖。\n */\n\nexport {\n PROTOCOL_NAME,\n PROTOCOL_MAJOR,\n EnvelopeDecodeError,\n encodeEnvelope,\n decodeEnvelope,\n type Envelope,\n type RuntimeHello,\n} from './envelope';\n\nexport { ErrorCode, type ErrorCodeValue } from './errors';\n\nexport {\n COMMAND_NAMES,\n isCommandName,\n type CommandName,\n type CommandRequestMap,\n type CommandResponseMap,\n type InitializeReq,\n type InitializeResp,\n type CreateElementReq,\n type CreateElementResp,\n type UpdateElementReq,\n type UpdateElementResp,\n type DestroyElementReq,\n type DestroyElementResp,\n type ElementActionReq,\n type ElementActionResp,\n type UpdateCheckoutReq,\n type UpdateCheckoutResp,\n type GetSessionReq,\n type GetSessionResp,\n type GetPaymentStatusReq,\n type GetPaymentStatusResp,\n type ConfirmReq,\n type ConfirmResp,\n type DestroyReq,\n type DestroyResp,\n} from './commands';\n\nexport {\n EVENT_NAMES,\n isEventName,\n type EventName,\n type EventPayloadMap,\n type ReadyEvent,\n type ElementReadyEvent,\n type ChangeEvent,\n type FocusEvent,\n type BlurEvent,\n type LoadErrorEvent,\n type AvailablePaymentMethodsChangeEvent,\n type ExpressAvailabilityChangeEvent,\n type PaymentStateChangeEvent,\n type PaymentResultEvent,\n type ErrorEvent,\n} from './events';\n\nexport {\n type TxnStatus,\n type AddressInfo,\n type PaymentMethod,\n type PublicPaymentMethod,\n type PublicProductLine,\n type PublicSessionView,\n type PaymentOutcome,\n type CheckoutError,\n type CheckoutErrorCategory,\n type Appearance,\n type PaymentMethodFilter,\n type ElementType,\n type CardElementOptions,\n type CardFieldElementOptions,\n type AddressElementOptions,\n type ExpressCheckoutElementOptions,\n type AvailableWallets,\n type ElementOptions,\n type ElementStatus,\n type PaymentAction,\n} from './types';\n","/**\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 PublicPaymentMethod,\n} from './types';\n\n/** 全部事件名(spec 5.3)。 */\nexport type EventName =\n | 'ready'\n | 'change'\n | 'focus'\n | 'blur'\n | 'loadError'\n | 'elementReady'\n | 'availablePaymentMethodsChange'\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 'availablePaymentMethodsChange',\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 事件 ---\nexport interface AvailablePaymentMethodsChangeEvent {\n availablePaymentMethods: PublicPaymentMethod[];\n}\n\n/**\n * express 可用钱包变化事件(spec 6)。\n * 专用于 expressCheckout:以 AvailableWallets 表达各钱包可用性,\n * 与 AvailablePaymentMethodsChangeEvent(PublicPaymentMethod[])并存,互不破坏。\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 availablePaymentMethodsChange: AvailablePaymentMethodsChangeEvent;\n expressAvailabilityChange: ExpressAvailabilityChangeEvent;\n paymentStateChange: PaymentStateChangeEvent;\n paymentResult: PaymentResultEvent;\n error: ErrorEvent;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/envelope.ts","../src/errors.ts","../src/commands.ts","../src/events.ts","../src/iframeLayout.ts"],"sourcesContent":["/**\n * 协议契约统一出口 —— `@sunbay/checkout-sdk-protocol`(单一真源)。\n *\n * 商户 SDK(checkout-sdk-web/js)与 checkout 托管运行时(checkout/src/sdk-runtime)\n * 共用本契约。同仓的 `js` 通过 pnpm workspace(`workspace:*`)本地链接消费本包;\n * 跨仓的 `checkout` 消费已发布的 npm 包。依赖方向单向:零反向依赖。\n */\n\nexport {\n PROTOCOL_NAME,\n PROTOCOL_MAJOR,\n EnvelopeDecodeError,\n encodeEnvelope,\n decodeEnvelope,\n type Envelope,\n type RuntimeHello,\n} from './envelope';\n\nexport { ErrorCode, type ErrorCodeValue } from './errors';\n\nexport {\n COMMAND_NAMES,\n isCommandName,\n type CommandName,\n type CommandRequestMap,\n type CommandResponseMap,\n type InitializeReq,\n type InitializeResp,\n type CreateElementReq,\n type CreateElementResp,\n type UpdateElementReq,\n type UpdateElementResp,\n type DestroyElementReq,\n type DestroyElementResp,\n type ElementActionReq,\n type ElementActionResp,\n type UpdateCheckoutReq,\n type UpdateCheckoutResp,\n type GetSessionReq,\n type GetSessionResp,\n type GetPaymentStatusReq,\n type GetPaymentStatusResp,\n type ConfirmReq,\n type ConfirmResp,\n type DestroyReq,\n type DestroyResp,\n} from './commands';\n\nexport {\n EVENT_NAMES,\n isEventName,\n type EventName,\n type EventPayloadMap,\n type ReadyEvent,\n type ElementReadyEvent,\n type ChangeEvent,\n type FocusEvent,\n type BlurEvent,\n type LoadErrorEvent,\n type ExpressAvailabilityChangeEvent,\n type PaymentStateChangeEvent,\n type PaymentResultEvent,\n type ErrorEvent,\n} from './events';\n\nexport {\n type TxnStatus,\n type AddressInfo,\n type PaymentMethod,\n type PublicPaymentMethod,\n type PublicProductLine,\n type PublicSessionView,\n type PaymentOutcome,\n type CheckoutError,\n type CheckoutErrorCategory,\n type Appearance,\n type AppearanceVariables,\n type AppearanceRuleTarget,\n type AppearanceRuleStyle,\n type PaymentMethodFilter,\n type ElementType,\n type CardElementOptions,\n type CardFieldElementOptions,\n type AddressElementOptions,\n type ApplePayButtonTheme,\n type GooglePayButtonTheme,\n type ApplePayButtonType,\n type GooglePayButtonType,\n type ApplePayOptions,\n type GooglePayOptions,\n type ExpressCheckoutElementOptions,\n type AvailableWallets,\n type ElementOptions,\n type ElementStatus,\n type PaymentAction,\n} from './types';\n\nexport {\n IFRAME_RESIZE_MESSAGE_TYPE,\n isIframeResizeMessage,\n type IframeResizeMessage,\n} from './iframeLayout';\n","/**\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":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -105,11 +105,67 @@ interface CheckoutError {
|
|
|
105
105
|
traceId?: string;
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
108
|
+
* 白名单设计变量(映射到 hosted 侧 `ThemeConfig`,复用现有 `generateThemeStyles` 与深色推导)。
|
|
109
|
+
*
|
|
110
|
+
* 全部可选;值为颜色或长度字符串,由 hosted 侧白名单校验器校验后方可进入 `<style>`。
|
|
111
|
+
* 属性名使用 camelCase(如 `borderRadius`),hosted 侧转 kebab。
|
|
112
|
+
*/
|
|
113
|
+
interface AppearanceVariables {
|
|
114
|
+
/** 主色 → `ThemeConfig.primary`(并联动 ring)。 */
|
|
115
|
+
colorPrimary?: string;
|
|
116
|
+
/** 背景色 → background / card / popover。 */
|
|
117
|
+
colorBackground?: string;
|
|
118
|
+
/** 文字色 → foreground。 */
|
|
119
|
+
colorText?: string;
|
|
120
|
+
/** 边框色 → border / input。 */
|
|
121
|
+
colorBorder?: string;
|
|
122
|
+
/** 危险色 → destructive。 */
|
|
123
|
+
colorDanger?: string;
|
|
124
|
+
/** 圆角 → radius(长度值)。 */
|
|
125
|
+
borderRadius?: string;
|
|
126
|
+
/** 字体族 → `:root` font-family(不进 `ThemeConfig`,单独注入)。 */
|
|
127
|
+
fontFamily?: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* rules 可定制的元素锚点(语义 key,对应 hosted 侧 `data-sunbay-el`)。
|
|
131
|
+
*
|
|
132
|
+
* 选择器由 hosted 生成,商户只提供语义 key,不接受选择器字符串。
|
|
133
|
+
* - `'input'`:卡号 / 有效期 / CVV 输入框;
|
|
134
|
+
* - `'input:focus'`:输入框聚焦态;
|
|
135
|
+
* - `'input:invalid'`:输入框错误态(`[data-sunbay-invalid]`);
|
|
136
|
+
* - `'label'`:iframe 内标签文字;
|
|
137
|
+
* - `'error'`:iframe 内错误文字;
|
|
138
|
+
* - `'cardBrand'`:卡品牌图标容器;
|
|
139
|
+
* - `'cvvIcon'`:CVV 图标。
|
|
140
|
+
*/
|
|
141
|
+
type AppearanceRuleTarget = 'input' | 'input:focus' | 'input:invalid' | 'label' | 'error' | 'cardBrand' | 'cvvIcon';
|
|
142
|
+
/**
|
|
143
|
+
* 单条 rule:白名单 CSS 属性(camelCase 或 kebab)到值的映射。
|
|
144
|
+
*
|
|
145
|
+
* 属性名与值均由 hosted 侧白名单校验后方可进入 `<style>`;非白名单属性 / 非法值被丢弃。
|
|
146
|
+
*/
|
|
147
|
+
type AppearanceRuleStyle = Record<string, string>;
|
|
148
|
+
/**
|
|
149
|
+
* 外观定制(appearance)。受控两层:`variables`(整体风格的白名单设计变量)+
|
|
150
|
+
* `rules`(关键元素精调的受控语义选择器)。因 iframe 跨域 + PCI 合规,**不开放任意 CSS**。
|
|
151
|
+
*
|
|
152
|
+
* 所有进入 `<style>` 的文本全程由 hosted 侧白名单校验;appearance 不含任何卡值 / 支付参数。
|
|
109
153
|
*/
|
|
110
154
|
interface Appearance {
|
|
111
|
-
|
|
112
|
-
variables?:
|
|
155
|
+
/** 白名单设计变量(整体风格)。 */
|
|
156
|
+
variables?: AppearanceVariables;
|
|
157
|
+
/** 受控语义规则(关键元素精调);key 限 `AppearanceRuleTarget`。 */
|
|
158
|
+
rules?: Partial<Record<AppearanceRuleTarget, AppearanceRuleStyle>>;
|
|
159
|
+
/**
|
|
160
|
+
* 配色方案偏好。
|
|
161
|
+
* - `'light'`:浅色;
|
|
162
|
+
* - `'dark'`:深色;
|
|
163
|
+
* - `'auto'`:跟随系统 / 宿主环境。
|
|
164
|
+
*
|
|
165
|
+
* 协议层只定义类型;缺省时的默认语义(SDK 默认 `'light'`)由消费方实现,
|
|
166
|
+
* 协议不强制默认值。向后兼容的可选新增字段。
|
|
167
|
+
*/
|
|
168
|
+
colorScheme?: 'light' | 'dark' | 'auto';
|
|
113
169
|
}
|
|
114
170
|
/**
|
|
115
171
|
* 支付方式过滤配置(spec 9:只做减法/排序,禁止前端增开)。
|
|
@@ -138,27 +194,45 @@ interface AddressElementOptions {
|
|
|
138
194
|
mode: 'billing' | 'shipping';
|
|
139
195
|
appearance?: Appearance;
|
|
140
196
|
}
|
|
197
|
+
/** Apple Pay 官方按钮主题。缺省跟随当前配色(浅 black / 深 white)。 */
|
|
198
|
+
type ApplePayButtonTheme = 'black' | 'white' | 'white-outline';
|
|
199
|
+
/** Google Pay 官方按钮主题。缺省跟随当前配色(浅 black / 深 white)。 */
|
|
200
|
+
type GooglePayButtonTheme = 'black' | 'white' | 'default';
|
|
201
|
+
/** Apple Pay 官方按钮文案类型(`apple-pay-button` 的 `type` 属性,官方全集)。 */
|
|
202
|
+
type ApplePayButtonType = 'plain' | 'buy' | 'donate' | 'check-out' | 'book' | 'subscribe' | 'reload' | 'add-money' | 'top-up' | 'order' | 'rent' | 'support' | 'contribute' | 'tip';
|
|
203
|
+
/** Google Pay 官方按钮文案类型(`buttonType`,官方全集)。 */
|
|
204
|
+
type GooglePayButtonType = 'buy' | 'plain' | 'checkout' | 'pay' | 'donate' | 'book' | 'order' | 'subscribe';
|
|
205
|
+
/** Apple Pay 单钱包配置(显示时的细粒度选项)。 */
|
|
206
|
+
interface ApplePayOptions {
|
|
207
|
+
/** 按钮主题;缺省跟随当前配色。 */
|
|
208
|
+
buttonTheme?: ApplePayButtonTheme;
|
|
209
|
+
/** 按钮文案类型;缺省 `'pay'`。 */
|
|
210
|
+
buttonType?: ApplePayButtonType;
|
|
211
|
+
}
|
|
212
|
+
/** Google Pay 单钱包配置(显示时的细粒度选项)。 */
|
|
213
|
+
interface GooglePayOptions {
|
|
214
|
+
/** 按钮主题;缺省跟随当前配色。 */
|
|
215
|
+
buttonTheme?: GooglePayButtonTheme;
|
|
216
|
+
/** 按钮文案类型;缺省 `'pay'`。 */
|
|
217
|
+
buttonType?: GooglePayButtonType;
|
|
218
|
+
}
|
|
141
219
|
/** expressCheckout Element 配置(spec 6)。 */
|
|
142
220
|
interface ExpressCheckoutElementOptions {
|
|
143
221
|
/**
|
|
144
|
-
*
|
|
222
|
+
* 各钱包开关 / 配置:
|
|
145
223
|
* - `false`:强制隐藏该钱包;
|
|
146
|
-
* - `true`
|
|
224
|
+
* - `true` 或缺省:自动显示(后端业务允许 ∩ 环境可用),主题跟随当前配色;
|
|
225
|
+
* - 配置对象:自动显示,并应用对象内的细粒度配置(如 buttonTheme)。
|
|
147
226
|
*/
|
|
148
227
|
paymentMethods?: {
|
|
149
|
-
applePay?: boolean;
|
|
150
|
-
googlePay?: boolean;
|
|
228
|
+
applePay?: boolean | ApplePayOptions;
|
|
229
|
+
googlePay?: boolean | GooglePayOptions;
|
|
151
230
|
};
|
|
152
231
|
/** 布局约束(首期仅最大列数)。 */
|
|
153
232
|
layout?: {
|
|
154
233
|
maxColumns?: number;
|
|
155
234
|
};
|
|
156
|
-
/**
|
|
157
|
-
buttonTheme?: {
|
|
158
|
-
applePay?: string;
|
|
159
|
-
googlePay?: string;
|
|
160
|
-
};
|
|
161
|
-
/** 按钮高度(像素)。 */
|
|
235
|
+
/** 钱包按钮高度(像素,全局应用到所有钱包按钮)。hosted 侧 clamp 到 [30, 64];缺省 40。 */
|
|
162
236
|
buttonHeight?: number;
|
|
163
237
|
}
|
|
164
238
|
/**
|
|
@@ -411,7 +485,7 @@ interface CommandResponseMap {
|
|
|
411
485
|
*/
|
|
412
486
|
|
|
413
487
|
/** 全部事件名(spec 5.3)。 */
|
|
414
|
-
type EventName = 'ready' | 'change' | 'focus' | 'blur' | 'loadError' | 'elementReady' | '
|
|
488
|
+
type EventName = 'ready' | 'change' | 'focus' | 'blur' | 'loadError' | 'elementReady' | 'expressAvailabilityChange' | 'paymentStateChange' | 'paymentResult' | 'error';
|
|
415
489
|
/** 已知事件名集合。 */
|
|
416
490
|
declare const EVENT_NAMES: readonly EventName[];
|
|
417
491
|
/** 判断字符串是否为已知事件名。 */
|
|
@@ -442,13 +516,9 @@ interface LoadErrorEvent {
|
|
|
442
516
|
elementId: string;
|
|
443
517
|
error: CheckoutError;
|
|
444
518
|
}
|
|
445
|
-
interface AvailablePaymentMethodsChangeEvent {
|
|
446
|
-
availablePaymentMethods: PublicPaymentMethod[];
|
|
447
|
-
}
|
|
448
519
|
/**
|
|
449
520
|
* express 可用钱包变化事件(spec 6)。
|
|
450
|
-
* 专用于 expressCheckout:以 AvailableWallets
|
|
451
|
-
* 与 AvailablePaymentMethodsChangeEvent(PublicPaymentMethod[])并存,互不破坏。
|
|
521
|
+
* 专用于 expressCheckout:以 AvailableWallets 表达各钱包(Apple/Google Pay)可用性。
|
|
452
522
|
*/
|
|
453
523
|
interface ExpressAvailabilityChangeEvent {
|
|
454
524
|
availableWallets: AvailableWallets;
|
|
@@ -478,11 +548,51 @@ interface EventPayloadMap {
|
|
|
478
548
|
blur: BlurEvent;
|
|
479
549
|
loadError: LoadErrorEvent;
|
|
480
550
|
elementReady: ElementReadyEvent;
|
|
481
|
-
availablePaymentMethodsChange: AvailablePaymentMethodsChangeEvent;
|
|
482
551
|
expressAvailabilityChange: ExpressAvailabilityChangeEvent;
|
|
483
552
|
paymentStateChange: PaymentStateChangeEvent;
|
|
484
553
|
paymentResult: PaymentResultEvent;
|
|
485
554
|
error: ErrorEvent;
|
|
486
555
|
}
|
|
487
556
|
|
|
488
|
-
|
|
557
|
+
/**
|
|
558
|
+
* iframe 布局协议:托管 iframe(checkout,child)→ 父窗口(商户 SDK,parent)的
|
|
559
|
+
* **直连 postMessage** 契约,用于 iframe 自适应高度。
|
|
560
|
+
*
|
|
561
|
+
* 与外层 Envelope(商户 SDK ↔ main iframe)及内层点对点 port(element ↔ main)均区分:
|
|
562
|
+
* 本消息由每个可见 field / element iframe 直接 postMessage 到其 `window.parent`(商户页),
|
|
563
|
+
* 商户 SDK 的 Element 句柄监听后把外层 iframe 高度调整为内容真实高度(宽度保持 100%)。
|
|
564
|
+
*
|
|
565
|
+
* 安全约束(AGENTS/spec):本消息**绝不含任何卡值**(明文或密文),仅携带 height 与
|
|
566
|
+
* elementId。父窗口侧须校验来源(`event.source === iframe.contentWindow` 且
|
|
567
|
+
* `event.origin === hostOrigin`)后方可采信,防止跨源伪造。
|
|
568
|
+
*/
|
|
569
|
+
/** iframe 自适应高度消息的判别字面量(命名空间化,避免与其它 postMessage 碰撞)。 */
|
|
570
|
+
declare const IFRAME_RESIZE_MESSAGE_TYPE: "sunbay:checkout:iframe-resize";
|
|
571
|
+
/**
|
|
572
|
+
* iframe(child)→ 父窗口(SDK)的内容高度变更通知。
|
|
573
|
+
*
|
|
574
|
+
* child 用 `ResizeObserver` 观测内容高度,变化时发送(去重);parent 据此设
|
|
575
|
+
* `iframe.style.height`。绝不含卡值。
|
|
576
|
+
*/
|
|
577
|
+
interface IframeResizeMessage {
|
|
578
|
+
/** 判别字段,固定为 {@link IFRAME_RESIZE_MESSAGE_TYPE}。 */
|
|
579
|
+
type: typeof IFRAME_RESIZE_MESSAGE_TYPE;
|
|
580
|
+
/**
|
|
581
|
+
* 目标 Element 的唯一 id。由商户 SDK 经 iframe src 的 `&elementId=` 下发,child 原样
|
|
582
|
+
* 回传,供父窗口在同一页面多个 iframe 间按句柄分发。
|
|
583
|
+
*/
|
|
584
|
+
elementId: string;
|
|
585
|
+
/** 内容高度(CSS px,已 `Math.ceil`;有限且非负)。 */
|
|
586
|
+
height: number;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* 是否为合法的 {@link IframeResizeMessage}。
|
|
590
|
+
*
|
|
591
|
+
* 校验:非空对象 + type 字面量匹配 + elementId 为非空字符串 + height 为有限非负数。
|
|
592
|
+
*
|
|
593
|
+
* @param data - 任意来源数据(通常是 `MessageEvent.data`)。
|
|
594
|
+
* @returns data 是否为合法的 iframe-resize 消息。
|
|
595
|
+
*/
|
|
596
|
+
declare function isIframeResizeMessage(data: unknown): data is IframeResizeMessage;
|
|
597
|
+
|
|
598
|
+
export { type AddressElementOptions, type AddressInfo, type Appearance, type AppearanceRuleStyle, type AppearanceRuleTarget, type AppearanceVariables, type ApplePayButtonTheme, type ApplePayButtonType, type ApplePayOptions, type AvailableWallets, type BlurEvent, COMMAND_NAMES, type CardElementOptions, type CardFieldElementOptions, type ChangeEvent, type CheckoutError, type CheckoutErrorCategory, type CommandName, type CommandRequestMap, type CommandResponseMap, type ConfirmReq, type ConfirmResp, type CreateElementReq, type CreateElementResp, type DestroyElementReq, type DestroyElementResp, type DestroyReq, type DestroyResp, EVENT_NAMES, type ElementActionReq, type ElementActionResp, type ElementOptions, type ElementReadyEvent, type ElementStatus, type ElementType, type Envelope, EnvelopeDecodeError, ErrorCode, type ErrorCodeValue, type ErrorEvent, type EventName, type EventPayloadMap, type ExpressAvailabilityChangeEvent, type ExpressCheckoutElementOptions, type FocusEvent, type GetPaymentStatusReq, type GetPaymentStatusResp, type GetSessionReq, type GetSessionResp, type GooglePayButtonTheme, type GooglePayButtonType, type GooglePayOptions, IFRAME_RESIZE_MESSAGE_TYPE, type IframeResizeMessage, type InitializeReq, type InitializeResp, type LoadErrorEvent, PROTOCOL_MAJOR, PROTOCOL_NAME, type PaymentAction, type PaymentMethod, type PaymentMethodFilter, type PaymentOutcome, type PaymentResultEvent, type PaymentStateChangeEvent, type PublicPaymentMethod, type PublicProductLine, type PublicSessionView, type ReadyEvent, type RuntimeHello, type TxnStatus, type UpdateCheckoutReq, type UpdateCheckoutResp, type UpdateElementReq, type UpdateElementResp, decodeEnvelope, encodeEnvelope, isCommandName, isEventName, isIframeResizeMessage };
|
package/dist/index.d.ts
CHANGED
|
@@ -105,11 +105,67 @@ interface CheckoutError {
|
|
|
105
105
|
traceId?: string;
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
108
|
+
* 白名单设计变量(映射到 hosted 侧 `ThemeConfig`,复用现有 `generateThemeStyles` 与深色推导)。
|
|
109
|
+
*
|
|
110
|
+
* 全部可选;值为颜色或长度字符串,由 hosted 侧白名单校验器校验后方可进入 `<style>`。
|
|
111
|
+
* 属性名使用 camelCase(如 `borderRadius`),hosted 侧转 kebab。
|
|
112
|
+
*/
|
|
113
|
+
interface AppearanceVariables {
|
|
114
|
+
/** 主色 → `ThemeConfig.primary`(并联动 ring)。 */
|
|
115
|
+
colorPrimary?: string;
|
|
116
|
+
/** 背景色 → background / card / popover。 */
|
|
117
|
+
colorBackground?: string;
|
|
118
|
+
/** 文字色 → foreground。 */
|
|
119
|
+
colorText?: string;
|
|
120
|
+
/** 边框色 → border / input。 */
|
|
121
|
+
colorBorder?: string;
|
|
122
|
+
/** 危险色 → destructive。 */
|
|
123
|
+
colorDanger?: string;
|
|
124
|
+
/** 圆角 → radius(长度值)。 */
|
|
125
|
+
borderRadius?: string;
|
|
126
|
+
/** 字体族 → `:root` font-family(不进 `ThemeConfig`,单独注入)。 */
|
|
127
|
+
fontFamily?: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* rules 可定制的元素锚点(语义 key,对应 hosted 侧 `data-sunbay-el`)。
|
|
131
|
+
*
|
|
132
|
+
* 选择器由 hosted 生成,商户只提供语义 key,不接受选择器字符串。
|
|
133
|
+
* - `'input'`:卡号 / 有效期 / CVV 输入框;
|
|
134
|
+
* - `'input:focus'`:输入框聚焦态;
|
|
135
|
+
* - `'input:invalid'`:输入框错误态(`[data-sunbay-invalid]`);
|
|
136
|
+
* - `'label'`:iframe 内标签文字;
|
|
137
|
+
* - `'error'`:iframe 内错误文字;
|
|
138
|
+
* - `'cardBrand'`:卡品牌图标容器;
|
|
139
|
+
* - `'cvvIcon'`:CVV 图标。
|
|
140
|
+
*/
|
|
141
|
+
type AppearanceRuleTarget = 'input' | 'input:focus' | 'input:invalid' | 'label' | 'error' | 'cardBrand' | 'cvvIcon';
|
|
142
|
+
/**
|
|
143
|
+
* 单条 rule:白名单 CSS 属性(camelCase 或 kebab)到值的映射。
|
|
144
|
+
*
|
|
145
|
+
* 属性名与值均由 hosted 侧白名单校验后方可进入 `<style>`;非白名单属性 / 非法值被丢弃。
|
|
146
|
+
*/
|
|
147
|
+
type AppearanceRuleStyle = Record<string, string>;
|
|
148
|
+
/**
|
|
149
|
+
* 外观定制(appearance)。受控两层:`variables`(整体风格的白名单设计变量)+
|
|
150
|
+
* `rules`(关键元素精调的受控语义选择器)。因 iframe 跨域 + PCI 合规,**不开放任意 CSS**。
|
|
151
|
+
*
|
|
152
|
+
* 所有进入 `<style>` 的文本全程由 hosted 侧白名单校验;appearance 不含任何卡值 / 支付参数。
|
|
109
153
|
*/
|
|
110
154
|
interface Appearance {
|
|
111
|
-
|
|
112
|
-
variables?:
|
|
155
|
+
/** 白名单设计变量(整体风格)。 */
|
|
156
|
+
variables?: AppearanceVariables;
|
|
157
|
+
/** 受控语义规则(关键元素精调);key 限 `AppearanceRuleTarget`。 */
|
|
158
|
+
rules?: Partial<Record<AppearanceRuleTarget, AppearanceRuleStyle>>;
|
|
159
|
+
/**
|
|
160
|
+
* 配色方案偏好。
|
|
161
|
+
* - `'light'`:浅色;
|
|
162
|
+
* - `'dark'`:深色;
|
|
163
|
+
* - `'auto'`:跟随系统 / 宿主环境。
|
|
164
|
+
*
|
|
165
|
+
* 协议层只定义类型;缺省时的默认语义(SDK 默认 `'light'`)由消费方实现,
|
|
166
|
+
* 协议不强制默认值。向后兼容的可选新增字段。
|
|
167
|
+
*/
|
|
168
|
+
colorScheme?: 'light' | 'dark' | 'auto';
|
|
113
169
|
}
|
|
114
170
|
/**
|
|
115
171
|
* 支付方式过滤配置(spec 9:只做减法/排序,禁止前端增开)。
|
|
@@ -138,27 +194,45 @@ interface AddressElementOptions {
|
|
|
138
194
|
mode: 'billing' | 'shipping';
|
|
139
195
|
appearance?: Appearance;
|
|
140
196
|
}
|
|
197
|
+
/** Apple Pay 官方按钮主题。缺省跟随当前配色(浅 black / 深 white)。 */
|
|
198
|
+
type ApplePayButtonTheme = 'black' | 'white' | 'white-outline';
|
|
199
|
+
/** Google Pay 官方按钮主题。缺省跟随当前配色(浅 black / 深 white)。 */
|
|
200
|
+
type GooglePayButtonTheme = 'black' | 'white' | 'default';
|
|
201
|
+
/** Apple Pay 官方按钮文案类型(`apple-pay-button` 的 `type` 属性,官方全集)。 */
|
|
202
|
+
type ApplePayButtonType = 'plain' | 'buy' | 'donate' | 'check-out' | 'book' | 'subscribe' | 'reload' | 'add-money' | 'top-up' | 'order' | 'rent' | 'support' | 'contribute' | 'tip';
|
|
203
|
+
/** Google Pay 官方按钮文案类型(`buttonType`,官方全集)。 */
|
|
204
|
+
type GooglePayButtonType = 'buy' | 'plain' | 'checkout' | 'pay' | 'donate' | 'book' | 'order' | 'subscribe';
|
|
205
|
+
/** Apple Pay 单钱包配置(显示时的细粒度选项)。 */
|
|
206
|
+
interface ApplePayOptions {
|
|
207
|
+
/** 按钮主题;缺省跟随当前配色。 */
|
|
208
|
+
buttonTheme?: ApplePayButtonTheme;
|
|
209
|
+
/** 按钮文案类型;缺省 `'pay'`。 */
|
|
210
|
+
buttonType?: ApplePayButtonType;
|
|
211
|
+
}
|
|
212
|
+
/** Google Pay 单钱包配置(显示时的细粒度选项)。 */
|
|
213
|
+
interface GooglePayOptions {
|
|
214
|
+
/** 按钮主题;缺省跟随当前配色。 */
|
|
215
|
+
buttonTheme?: GooglePayButtonTheme;
|
|
216
|
+
/** 按钮文案类型;缺省 `'pay'`。 */
|
|
217
|
+
buttonType?: GooglePayButtonType;
|
|
218
|
+
}
|
|
141
219
|
/** expressCheckout Element 配置(spec 6)。 */
|
|
142
220
|
interface ExpressCheckoutElementOptions {
|
|
143
221
|
/**
|
|
144
|
-
*
|
|
222
|
+
* 各钱包开关 / 配置:
|
|
145
223
|
* - `false`:强制隐藏该钱包;
|
|
146
|
-
* - `true`
|
|
224
|
+
* - `true` 或缺省:自动显示(后端业务允许 ∩ 环境可用),主题跟随当前配色;
|
|
225
|
+
* - 配置对象:自动显示,并应用对象内的细粒度配置(如 buttonTheme)。
|
|
147
226
|
*/
|
|
148
227
|
paymentMethods?: {
|
|
149
|
-
applePay?: boolean;
|
|
150
|
-
googlePay?: boolean;
|
|
228
|
+
applePay?: boolean | ApplePayOptions;
|
|
229
|
+
googlePay?: boolean | GooglePayOptions;
|
|
151
230
|
};
|
|
152
231
|
/** 布局约束(首期仅最大列数)。 */
|
|
153
232
|
layout?: {
|
|
154
233
|
maxColumns?: number;
|
|
155
234
|
};
|
|
156
|
-
/**
|
|
157
|
-
buttonTheme?: {
|
|
158
|
-
applePay?: string;
|
|
159
|
-
googlePay?: string;
|
|
160
|
-
};
|
|
161
|
-
/** 按钮高度(像素)。 */
|
|
235
|
+
/** 钱包按钮高度(像素,全局应用到所有钱包按钮)。hosted 侧 clamp 到 [30, 64];缺省 40。 */
|
|
162
236
|
buttonHeight?: number;
|
|
163
237
|
}
|
|
164
238
|
/**
|
|
@@ -411,7 +485,7 @@ interface CommandResponseMap {
|
|
|
411
485
|
*/
|
|
412
486
|
|
|
413
487
|
/** 全部事件名(spec 5.3)。 */
|
|
414
|
-
type EventName = 'ready' | 'change' | 'focus' | 'blur' | 'loadError' | 'elementReady' | '
|
|
488
|
+
type EventName = 'ready' | 'change' | 'focus' | 'blur' | 'loadError' | 'elementReady' | 'expressAvailabilityChange' | 'paymentStateChange' | 'paymentResult' | 'error';
|
|
415
489
|
/** 已知事件名集合。 */
|
|
416
490
|
declare const EVENT_NAMES: readonly EventName[];
|
|
417
491
|
/** 判断字符串是否为已知事件名。 */
|
|
@@ -442,13 +516,9 @@ interface LoadErrorEvent {
|
|
|
442
516
|
elementId: string;
|
|
443
517
|
error: CheckoutError;
|
|
444
518
|
}
|
|
445
|
-
interface AvailablePaymentMethodsChangeEvent {
|
|
446
|
-
availablePaymentMethods: PublicPaymentMethod[];
|
|
447
|
-
}
|
|
448
519
|
/**
|
|
449
520
|
* express 可用钱包变化事件(spec 6)。
|
|
450
|
-
* 专用于 expressCheckout:以 AvailableWallets
|
|
451
|
-
* 与 AvailablePaymentMethodsChangeEvent(PublicPaymentMethod[])并存,互不破坏。
|
|
521
|
+
* 专用于 expressCheckout:以 AvailableWallets 表达各钱包(Apple/Google Pay)可用性。
|
|
452
522
|
*/
|
|
453
523
|
interface ExpressAvailabilityChangeEvent {
|
|
454
524
|
availableWallets: AvailableWallets;
|
|
@@ -478,11 +548,51 @@ interface EventPayloadMap {
|
|
|
478
548
|
blur: BlurEvent;
|
|
479
549
|
loadError: LoadErrorEvent;
|
|
480
550
|
elementReady: ElementReadyEvent;
|
|
481
|
-
availablePaymentMethodsChange: AvailablePaymentMethodsChangeEvent;
|
|
482
551
|
expressAvailabilityChange: ExpressAvailabilityChangeEvent;
|
|
483
552
|
paymentStateChange: PaymentStateChangeEvent;
|
|
484
553
|
paymentResult: PaymentResultEvent;
|
|
485
554
|
error: ErrorEvent;
|
|
486
555
|
}
|
|
487
556
|
|
|
488
|
-
|
|
557
|
+
/**
|
|
558
|
+
* iframe 布局协议:托管 iframe(checkout,child)→ 父窗口(商户 SDK,parent)的
|
|
559
|
+
* **直连 postMessage** 契约,用于 iframe 自适应高度。
|
|
560
|
+
*
|
|
561
|
+
* 与外层 Envelope(商户 SDK ↔ main iframe)及内层点对点 port(element ↔ main)均区分:
|
|
562
|
+
* 本消息由每个可见 field / element iframe 直接 postMessage 到其 `window.parent`(商户页),
|
|
563
|
+
* 商户 SDK 的 Element 句柄监听后把外层 iframe 高度调整为内容真实高度(宽度保持 100%)。
|
|
564
|
+
*
|
|
565
|
+
* 安全约束(AGENTS/spec):本消息**绝不含任何卡值**(明文或密文),仅携带 height 与
|
|
566
|
+
* elementId。父窗口侧须校验来源(`event.source === iframe.contentWindow` 且
|
|
567
|
+
* `event.origin === hostOrigin`)后方可采信,防止跨源伪造。
|
|
568
|
+
*/
|
|
569
|
+
/** iframe 自适应高度消息的判别字面量(命名空间化,避免与其它 postMessage 碰撞)。 */
|
|
570
|
+
declare const IFRAME_RESIZE_MESSAGE_TYPE: "sunbay:checkout:iframe-resize";
|
|
571
|
+
/**
|
|
572
|
+
* iframe(child)→ 父窗口(SDK)的内容高度变更通知。
|
|
573
|
+
*
|
|
574
|
+
* child 用 `ResizeObserver` 观测内容高度,变化时发送(去重);parent 据此设
|
|
575
|
+
* `iframe.style.height`。绝不含卡值。
|
|
576
|
+
*/
|
|
577
|
+
interface IframeResizeMessage {
|
|
578
|
+
/** 判别字段,固定为 {@link IFRAME_RESIZE_MESSAGE_TYPE}。 */
|
|
579
|
+
type: typeof IFRAME_RESIZE_MESSAGE_TYPE;
|
|
580
|
+
/**
|
|
581
|
+
* 目标 Element 的唯一 id。由商户 SDK 经 iframe src 的 `&elementId=` 下发,child 原样
|
|
582
|
+
* 回传,供父窗口在同一页面多个 iframe 间按句柄分发。
|
|
583
|
+
*/
|
|
584
|
+
elementId: string;
|
|
585
|
+
/** 内容高度(CSS px,已 `Math.ceil`;有限且非负)。 */
|
|
586
|
+
height: number;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* 是否为合法的 {@link IframeResizeMessage}。
|
|
590
|
+
*
|
|
591
|
+
* 校验:非空对象 + type 字面量匹配 + elementId 为非空字符串 + height 为有限非负数。
|
|
592
|
+
*
|
|
593
|
+
* @param data - 任意来源数据(通常是 `MessageEvent.data`)。
|
|
594
|
+
* @returns data 是否为合法的 iframe-resize 消息。
|
|
595
|
+
*/
|
|
596
|
+
declare function isIframeResizeMessage(data: unknown): data is IframeResizeMessage;
|
|
597
|
+
|
|
598
|
+
export { type AddressElementOptions, type AddressInfo, type Appearance, type AppearanceRuleStyle, type AppearanceRuleTarget, type AppearanceVariables, type ApplePayButtonTheme, type ApplePayButtonType, type ApplePayOptions, type AvailableWallets, type BlurEvent, COMMAND_NAMES, type CardElementOptions, type CardFieldElementOptions, type ChangeEvent, type CheckoutError, type CheckoutErrorCategory, type CommandName, type CommandRequestMap, type CommandResponseMap, type ConfirmReq, type ConfirmResp, type CreateElementReq, type CreateElementResp, type DestroyElementReq, type DestroyElementResp, type DestroyReq, type DestroyResp, EVENT_NAMES, type ElementActionReq, type ElementActionResp, type ElementOptions, type ElementReadyEvent, type ElementStatus, type ElementType, type Envelope, EnvelopeDecodeError, ErrorCode, type ErrorCodeValue, type ErrorEvent, type EventName, type EventPayloadMap, type ExpressAvailabilityChangeEvent, type ExpressCheckoutElementOptions, type FocusEvent, type GetPaymentStatusReq, type GetPaymentStatusResp, type GetSessionReq, type GetSessionResp, type GooglePayButtonTheme, type GooglePayButtonType, type GooglePayOptions, IFRAME_RESIZE_MESSAGE_TYPE, type IframeResizeMessage, type InitializeReq, type InitializeResp, type LoadErrorEvent, PROTOCOL_MAJOR, PROTOCOL_NAME, type PaymentAction, type PaymentMethod, type PaymentMethodFilter, type PaymentOutcome, type PaymentResultEvent, type PaymentStateChangeEvent, type PublicPaymentMethod, type PublicProductLine, type PublicSessionView, type ReadyEvent, type RuntimeHello, type TxnStatus, type UpdateCheckoutReq, type UpdateCheckoutResp, type UpdateElementReq, type UpdateElementResp, decodeEnvelope, encodeEnvelope, isCommandName, isEventName, isIframeResizeMessage };
|
package/dist/index.mjs
CHANGED
|
@@ -129,7 +129,6 @@ var EVENT_NAMES = [
|
|
|
129
129
|
"blur",
|
|
130
130
|
"loadError",
|
|
131
131
|
"elementReady",
|
|
132
|
-
"availablePaymentMethodsChange",
|
|
133
132
|
"expressAvailabilityChange",
|
|
134
133
|
"paymentStateChange",
|
|
135
134
|
"paymentResult",
|
|
@@ -138,16 +137,28 @@ var EVENT_NAMES = [
|
|
|
138
137
|
function isEventName(name) {
|
|
139
138
|
return EVENT_NAMES.includes(name);
|
|
140
139
|
}
|
|
140
|
+
|
|
141
|
+
// src/iframeLayout.ts
|
|
142
|
+
var IFRAME_RESIZE_MESSAGE_TYPE = "sunbay:checkout:iframe-resize";
|
|
143
|
+
function isIframeResizeMessage(data) {
|
|
144
|
+
if (typeof data !== "object" || data === null) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const msg = data;
|
|
148
|
+
return msg.type === IFRAME_RESIZE_MESSAGE_TYPE && typeof msg.elementId === "string" && msg.elementId.length > 0 && typeof msg.height === "number" && Number.isFinite(msg.height) && msg.height >= 0;
|
|
149
|
+
}
|
|
141
150
|
export {
|
|
142
151
|
COMMAND_NAMES,
|
|
143
152
|
EVENT_NAMES,
|
|
144
153
|
EnvelopeDecodeError,
|
|
145
154
|
ErrorCode,
|
|
155
|
+
IFRAME_RESIZE_MESSAGE_TYPE,
|
|
146
156
|
PROTOCOL_MAJOR,
|
|
147
157
|
PROTOCOL_NAME,
|
|
148
158
|
decodeEnvelope,
|
|
149
159
|
encodeEnvelope,
|
|
150
160
|
isCommandName,
|
|
151
|
-
isEventName
|
|
161
|
+
isEventName,
|
|
162
|
+
isIframeResizeMessage
|
|
152
163
|
};
|
|
153
164
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/envelope.ts","../src/errors.ts","../src/commands.ts","../src/events.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 PublicPaymentMethod,\n} from './types';\n\n/** 全部事件名(spec 5.3)。 */\nexport type EventName =\n | 'ready'\n | 'change'\n | 'focus'\n | 'blur'\n | 'loadError'\n | 'elementReady'\n | 'availablePaymentMethodsChange'\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 'availablePaymentMethodsChange',\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 事件 ---\nexport interface AvailablePaymentMethodsChangeEvent {\n availablePaymentMethods: PublicPaymentMethod[];\n}\n\n/**\n * express 可用钱包变化事件(spec 6)。\n * 专用于 expressCheckout:以 AvailableWallets 表达各钱包可用性,\n * 与 AvailablePaymentMethodsChangeEvent(PublicPaymentMethod[])并存,互不破坏。\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 availablePaymentMethodsChange: AvailablePaymentMethodsChangeEvent;\n expressAvailabilityChange: ExpressAvailabilityChangeEvent;\n paymentStateChange: PaymentStateChangeEvent;\n paymentResult: PaymentResultEvent;\n error: ErrorEvent;\n}\n"],"mappings":"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","names":[]}
|
|
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":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sunbay/checkout-sdk-protocol",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Sunbay Checkout SDK ↔ hosted runtime 通信协议契约(单一真源)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -21,9 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsup",
|
|
24
|
-
"type-check": "tsc --noEmit"
|
|
25
|
-
"sync": "node scripts/sync-protocol.mjs",
|
|
26
|
-
"sync:check": "node scripts/sync-protocol.mjs --check"
|
|
24
|
+
"type-check": "tsc --noEmit"
|
|
27
25
|
},
|
|
28
26
|
"devDependencies": {
|
|
29
27
|
"tsup": "^8.3.5",
|