@vexpay/js 0.1.0 → 0.2.0

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/README.md CHANGED
@@ -88,6 +88,10 @@ redirectToCheckout({ clientSecret });
88
88
 
89
89
  Sends the buyer to the hosted checkout page. No `allowedOrigins` needed.
90
90
 
91
+ ## USDT
92
+
93
+ When USDT is enabled on your VEXPay account, sessions can offer it (`methods: ['usdt']`, or the default of every method you accept). The buyer picks a network and sends USDT to the address shown; the embedded checkout keeps polling and emits `completed` once the transfer is confirmed, which can take a few minutes. Confirm on your server before fulfilling, and don't fulfil when the `payment.completed` webhook has `underpaid: true`.
94
+
91
95
  ## Options
92
96
 
93
97
  | Option | Default | |
package/dist/index.cjs CHANGED
@@ -80,7 +80,7 @@ var Checkout = class {
80
80
  iframe.allow = "payment";
81
81
  iframe.setAttribute(
82
82
  "sandbox",
83
- "allow-scripts allow-forms allow-same-origin allow-popups allow-top-navigation-by-user-activation"
83
+ "allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
84
84
  );
85
85
  iframe.style.cssText = "width:100%;min-height:480px;border:0;display:block;background:transparent;";
86
86
  this.iframe = iframe;
@@ -166,5 +166,5 @@ function redirectToCheckout(options) {
166
166
  }
167
167
 
168
168
  // src/index.ts
169
- var VERSION = "0.1.0";
169
+ var VERSION = "0.2.0";
170
170
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/checkout.ts"],"sourcesContent":["export {\n Checkout,\n checkout,\n redirectToCheckout,\n DEFAULT_CHECKOUT_ORIGIN,\n type CheckoutEventName,\n type CheckoutEvents,\n type CheckoutOptions,\n} from './checkout';\n\nexport const VERSION = '0.1.0';\n","export const DEFAULT_CHECKOUT_ORIGIN = 'https://pay.vexwallet.co';\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst PREFIX = 'vexpay:';\nconst SECRET_PATTERN = /^([a-z0-9-]+)_secret_[A-Za-z0-9_-]{20,}$/;\n\nexport interface CheckoutOptions {\n /** `clientSecret` from `POST /v1/checkout/sessions` (created on your server). */\n clientSecret: string;\n /** Where the hosted checkout lives. Default `https://pay.vexwallet.co`. */\n checkoutOrigin?: string;\n /** How long to wait for the checkout to load before emitting `origin_not_allowed`. Default 15 s. */\n readyTimeoutMs?: number;\n}\n\nexport interface CheckoutEvents {\n /** The checkout loaded and accepted this page. */\n ready: { sessionId: string };\n /** Payment succeeded. Advisory — confirm on your server before fulfilling. */\n completed: { sessionId: string; paymentId: string | null };\n /** A payment attempt failed; the buyer can retry until the session expires. */\n failed: { sessionId: string; code: string };\n /** The session expired. Create a new one. */\n expired: { sessionId: string };\n /** The checkout cannot be shown or used here. */\n error: { code: 'origin_not_allowed' | 'invalid_client_secret' | 'mount_target_not_found' | string; message: string };\n}\n\nexport type CheckoutEventName = keyof CheckoutEvents;\ntype Handler<E extends CheckoutEventName> = (payload: CheckoutEvents[E]) => void;\n\nfunction parseSecret(clientSecret: unknown): string {\n const match = typeof clientSecret === 'string' ? SECRET_PATTERN.exec(clientSecret) : null;\n if (!match) {\n throw new Error('VexPay: clientSecret is missing or malformed. Pass the value returned by POST /v1/checkout/sessions.');\n }\n return match[1]!;\n}\n\nfunction normalizeOrigin(origin: string): string {\n return new URL(origin).origin;\n}\n\n/**\n * An embedded checkout. Card data and bank codes are entered inside VEXPay's\n * iframe and never reach your page — you only receive ids and status codes.\n */\nexport class Checkout {\n private readonly slug: string;\n private readonly origin: string;\n private readonly readyTimeoutMs: number;\n private readonly handlers = new Map<CheckoutEventName, Set<(payload: never) => void>>();\n private iframe: HTMLIFrameElement | null = null;\n private sessionId = '';\n private readyTimer: ReturnType<typeof setTimeout> | undefined;\n private readonly onMessage = (event: MessageEvent) => this.handleMessage(event);\n\n constructor(private readonly options: CheckoutOptions) {\n this.slug = parseSecret(options.clientSecret);\n this.origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n this.readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;\n }\n\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<E extends CheckoutEventName>(event: E, handler: Handler<E>): () => void {\n let set = this.handlers.get(event);\n if (!set) this.handlers.set(event, (set = new Set()));\n set.add(handler as (payload: never) => void);\n return () => this.off(event, handler);\n }\n\n off<E extends CheckoutEventName>(event: E, handler: Handler<E>): void {\n this.handlers.get(event)?.delete(handler as (payload: never) => void);\n }\n\n /** Render the checkout inside `target` (an element or a CSS selector). */\n mount(target: string | HTMLElement): this {\n if (this.iframe) this.unmount();\n const container = typeof target === 'string' ? document.querySelector<HTMLElement>(target) : target;\n if (!container) {\n this.emit('error', { code: 'mount_target_not_found', message: `VexPay: no element matches ${String(target)}.` });\n return this;\n }\n\n const iframe = document.createElement('iframe');\n iframe.src = `${this.origin}/pay/${encodeURIComponent(this.slug)}?embed=1`;\n iframe.title = 'VEXPay checkout';\n iframe.allow = 'payment';\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-forms allow-same-origin allow-popups allow-top-navigation-by-user-activation',\n );\n iframe.style.cssText = 'width:100%;min-height:480px;border:0;display:block;background:transparent;';\n this.iframe = iframe;\n\n window.addEventListener('message', this.onMessage);\n container.appendChild(iframe);\n\n // A frame blocked by CSP frame-ancestors never announces itself.\n this.readyTimer = setTimeout(() => {\n this.emit('error', {\n code: 'origin_not_allowed',\n message: `VexPay: the checkout did not load. Add ${window.location.origin} to the session's allowedOrigins.`,\n });\n }, this.readyTimeoutMs);\n return this;\n }\n\n /** Remove the checkout from the page and stop listening. */\n unmount(): void {\n clearTimeout(this.readyTimer);\n window.removeEventListener('message', this.onMessage);\n this.iframe?.remove();\n this.iframe = null;\n }\n\n private emit<E extends CheckoutEventName>(event: E, payload: CheckoutEvents[E]): void {\n for (const handler of this.handlers.get(event) ?? []) {\n try {\n (handler as Handler<E>)(payload);\n } catch (error) {\n setTimeout(() => {\n throw error;\n });\n }\n }\n }\n\n private handleMessage(event: MessageEvent): void {\n // Only our own iframe, on the checkout origin, is trusted.\n if (!this.iframe || event.source !== this.iframe.contentWindow || event.origin !== this.origin) return;\n const data = event.data as { type?: unknown } & Record<string, unknown>;\n if (!data || typeof data.type !== 'string' || !data.type.startsWith(PREFIX)) return;\n\n switch (data.type.slice(PREFIX.length)) {\n case 'ready':\n // The secret goes only to the checkout origin, never '*'.\n this.iframe.contentWindow?.postMessage(\n { type: `${PREFIX}init`, clientSecret: this.options.clientSecret },\n this.origin,\n );\n return;\n case 'verified':\n clearTimeout(this.readyTimer);\n this.sessionId = String(data.sessionId ?? '');\n this.emit('ready', { sessionId: this.sessionId });\n return;\n case 'resize':\n if (typeof data.height === 'number' && data.height > 0) {\n this.iframe.style.height = `${Math.ceil(data.height)}px`;\n }\n return;\n case 'completed':\n this.emit('completed', {\n sessionId: String(data.sessionId ?? this.sessionId),\n paymentId: typeof data.paymentId === 'string' ? data.paymentId : null,\n });\n return;\n case 'failed':\n this.emit('failed', { sessionId: String(data.sessionId ?? this.sessionId), code: String(data.code ?? 'payment_failed') });\n return;\n case 'expired':\n this.emit('expired', { sessionId: String(data.sessionId ?? this.sessionId) });\n return;\n case 'error':\n clearTimeout(this.readyTimer);\n this.emit('error', { code: String(data.code ?? 'unknown'), message: String(data.message ?? 'Checkout error.') });\n return;\n }\n }\n}\n\n/** Create an embedded checkout for a session. Call `.mount(el)` to show it. */\nexport function checkout(options: CheckoutOptions): Checkout {\n return new Checkout(options);\n}\n\n/** Send the buyer to the hosted checkout page instead of embedding it. */\nexport function redirectToCheckout(options: Pick<CheckoutOptions, 'clientSecret' | 'checkoutOrigin'>): void {\n const slug = parseSecret(options.clientSecret);\n const origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n window.location.assign(`${origin}/pay/${encodeURIComponent(slug)}`);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/checkout.ts"],"sourcesContent":["export {\n Checkout,\n checkout,\n redirectToCheckout,\n DEFAULT_CHECKOUT_ORIGIN,\n type CheckoutEventName,\n type CheckoutEvents,\n type CheckoutOptions,\n} from './checkout';\n\nexport const VERSION = '0.2.0';\n","export const DEFAULT_CHECKOUT_ORIGIN = 'https://pay.vexwallet.co';\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst PREFIX = 'vexpay:';\nconst SECRET_PATTERN = /^([a-z0-9-]+)_secret_[A-Za-z0-9_-]{20,}$/;\n\nexport interface CheckoutOptions {\n /** `clientSecret` from `POST /v1/checkout/sessions` (created on your server). */\n clientSecret: string;\n /** Where the hosted checkout lives. Default `https://pay.vexwallet.co`. */\n checkoutOrigin?: string;\n /** How long to wait for the checkout to load before emitting `origin_not_allowed`. Default 15 s. */\n readyTimeoutMs?: number;\n}\n\nexport interface CheckoutEvents {\n /** The checkout loaded and accepted this page. */\n ready: { sessionId: string };\n /** Payment succeeded. Advisory — confirm on your server before fulfilling. */\n completed: { sessionId: string; paymentId: string | null };\n /** A payment attempt failed; the buyer can retry until the session expires. */\n failed: { sessionId: string; code: string };\n /** The session expired. Create a new one. */\n expired: { sessionId: string };\n /** The checkout cannot be shown or used here. */\n error: { code: 'origin_not_allowed' | 'invalid_client_secret' | 'mount_target_not_found' | string; message: string };\n}\n\nexport type CheckoutEventName = keyof CheckoutEvents;\ntype Handler<E extends CheckoutEventName> = (payload: CheckoutEvents[E]) => void;\n\nfunction parseSecret(clientSecret: unknown): string {\n const match = typeof clientSecret === 'string' ? SECRET_PATTERN.exec(clientSecret) : null;\n if (!match) {\n throw new Error('VexPay: clientSecret is missing or malformed. Pass the value returned by POST /v1/checkout/sessions.');\n }\n return match[1]!;\n}\n\nfunction normalizeOrigin(origin: string): string {\n return new URL(origin).origin;\n}\n\n/**\n * An embedded checkout. Card data and bank codes are entered inside VEXPay's\n * iframe and never reach your page — you only receive ids and status codes.\n */\nexport class Checkout {\n private readonly slug: string;\n private readonly origin: string;\n private readonly readyTimeoutMs: number;\n private readonly handlers = new Map<CheckoutEventName, Set<(payload: never) => void>>();\n private iframe: HTMLIFrameElement | null = null;\n private sessionId = '';\n private readyTimer: ReturnType<typeof setTimeout> | undefined;\n private readonly onMessage = (event: MessageEvent) => this.handleMessage(event);\n\n constructor(private readonly options: CheckoutOptions) {\n this.slug = parseSecret(options.clientSecret);\n this.origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n this.readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;\n }\n\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<E extends CheckoutEventName>(event: E, handler: Handler<E>): () => void {\n let set = this.handlers.get(event);\n if (!set) this.handlers.set(event, (set = new Set()));\n set.add(handler as (payload: never) => void);\n return () => this.off(event, handler);\n }\n\n off<E extends CheckoutEventName>(event: E, handler: Handler<E>): void {\n this.handlers.get(event)?.delete(handler as (payload: never) => void);\n }\n\n /** Render the checkout inside `target` (an element or a CSS selector). */\n mount(target: string | HTMLElement): this {\n if (this.iframe) this.unmount();\n const container = typeof target === 'string' ? document.querySelector<HTMLElement>(target) : target;\n if (!container) {\n this.emit('error', { code: 'mount_target_not_found', message: `VexPay: no element matches ${String(target)}.` });\n return this;\n }\n\n const iframe = document.createElement('iframe');\n iframe.src = `${this.origin}/pay/${encodeURIComponent(this.slug)}?embed=1`;\n iframe.title = 'VEXPay checkout';\n iframe.allow = 'payment';\n // allow-popups-to-escape-sandbox: USDT opens the processor's payment page in a new\n // window, which must run as a normal page (not inherit this iframe's sandbox).\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation',\n );\n iframe.style.cssText = 'width:100%;min-height:480px;border:0;display:block;background:transparent;';\n this.iframe = iframe;\n\n window.addEventListener('message', this.onMessage);\n container.appendChild(iframe);\n\n // A frame blocked by CSP frame-ancestors never announces itself.\n this.readyTimer = setTimeout(() => {\n this.emit('error', {\n code: 'origin_not_allowed',\n message: `VexPay: the checkout did not load. Add ${window.location.origin} to the session's allowedOrigins.`,\n });\n }, this.readyTimeoutMs);\n return this;\n }\n\n /** Remove the checkout from the page and stop listening. */\n unmount(): void {\n clearTimeout(this.readyTimer);\n window.removeEventListener('message', this.onMessage);\n this.iframe?.remove();\n this.iframe = null;\n }\n\n private emit<E extends CheckoutEventName>(event: E, payload: CheckoutEvents[E]): void {\n for (const handler of this.handlers.get(event) ?? []) {\n try {\n (handler as Handler<E>)(payload);\n } catch (error) {\n setTimeout(() => {\n throw error;\n });\n }\n }\n }\n\n private handleMessage(event: MessageEvent): void {\n // Only our own iframe, on the checkout origin, is trusted.\n if (!this.iframe || event.source !== this.iframe.contentWindow || event.origin !== this.origin) return;\n const data = event.data as { type?: unknown } & Record<string, unknown>;\n if (!data || typeof data.type !== 'string' || !data.type.startsWith(PREFIX)) return;\n\n switch (data.type.slice(PREFIX.length)) {\n case 'ready':\n // The secret goes only to the checkout origin, never '*'.\n this.iframe.contentWindow?.postMessage(\n { type: `${PREFIX}init`, clientSecret: this.options.clientSecret },\n this.origin,\n );\n return;\n case 'verified':\n clearTimeout(this.readyTimer);\n this.sessionId = String(data.sessionId ?? '');\n this.emit('ready', { sessionId: this.sessionId });\n return;\n case 'resize':\n if (typeof data.height === 'number' && data.height > 0) {\n this.iframe.style.height = `${Math.ceil(data.height)}px`;\n }\n return;\n case 'completed':\n this.emit('completed', {\n sessionId: String(data.sessionId ?? this.sessionId),\n paymentId: typeof data.paymentId === 'string' ? data.paymentId : null,\n });\n return;\n case 'failed':\n this.emit('failed', { sessionId: String(data.sessionId ?? this.sessionId), code: String(data.code ?? 'payment_failed') });\n return;\n case 'expired':\n this.emit('expired', { sessionId: String(data.sessionId ?? this.sessionId) });\n return;\n case 'error':\n clearTimeout(this.readyTimer);\n this.emit('error', { code: String(data.code ?? 'unknown'), message: String(data.message ?? 'Checkout error.') });\n return;\n }\n }\n}\n\n/** Create an embedded checkout for a session. Call `.mount(el)` to show it. */\nexport function checkout(options: CheckoutOptions): Checkout {\n return new Checkout(options);\n}\n\n/** Send the buyer to the hosted checkout page instead of embedding it. */\nexport function redirectToCheckout(options: Pick<CheckoutOptions, 'clientSecret' | 'checkoutOrigin'>): void {\n const slug = parseSecret(options.clientSecret);\n const origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n window.location.assign(`${origin}/pay/${encodeURIComponent(slug)}`);\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -64,6 +64,6 @@ declare function checkout(options: CheckoutOptions): Checkout;
64
64
  /** Send the buyer to the hosted checkout page instead of embedding it. */
65
65
  declare function redirectToCheckout(options: Pick<CheckoutOptions, 'clientSecret' | 'checkoutOrigin'>): void;
66
66
 
67
- declare const VERSION = "0.1.0";
67
+ declare const VERSION = "0.2.0";
68
68
 
69
69
  export { Checkout, type CheckoutEventName, type CheckoutEvents, type CheckoutOptions, DEFAULT_CHECKOUT_ORIGIN, VERSION, checkout, redirectToCheckout };
package/dist/index.d.ts CHANGED
@@ -64,6 +64,6 @@ declare function checkout(options: CheckoutOptions): Checkout;
64
64
  /** Send the buyer to the hosted checkout page instead of embedding it. */
65
65
  declare function redirectToCheckout(options: Pick<CheckoutOptions, 'clientSecret' | 'checkoutOrigin'>): void;
66
66
 
67
- declare const VERSION = "0.1.0";
67
+ declare const VERSION = "0.2.0";
68
68
 
69
69
  export { Checkout, type CheckoutEventName, type CheckoutEvents, type CheckoutOptions, DEFAULT_CHECKOUT_ORIGIN, VERSION, checkout, redirectToCheckout };
package/dist/index.js CHANGED
@@ -50,7 +50,7 @@ var Checkout = class {
50
50
  iframe.allow = "payment";
51
51
  iframe.setAttribute(
52
52
  "sandbox",
53
- "allow-scripts allow-forms allow-same-origin allow-popups allow-top-navigation-by-user-activation"
53
+ "allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
54
54
  );
55
55
  iframe.style.cssText = "width:100%;min-height:480px;border:0;display:block;background:transparent;";
56
56
  this.iframe = iframe;
@@ -136,7 +136,7 @@ function redirectToCheckout(options) {
136
136
  }
137
137
 
138
138
  // src/index.ts
139
- var VERSION = "0.1.0";
139
+ var VERSION = "0.2.0";
140
140
  export {
141
141
  Checkout,
142
142
  DEFAULT_CHECKOUT_ORIGIN,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/checkout.ts","../src/index.ts"],"sourcesContent":["export const DEFAULT_CHECKOUT_ORIGIN = 'https://pay.vexwallet.co';\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst PREFIX = 'vexpay:';\nconst SECRET_PATTERN = /^([a-z0-9-]+)_secret_[A-Za-z0-9_-]{20,}$/;\n\nexport interface CheckoutOptions {\n /** `clientSecret` from `POST /v1/checkout/sessions` (created on your server). */\n clientSecret: string;\n /** Where the hosted checkout lives. Default `https://pay.vexwallet.co`. */\n checkoutOrigin?: string;\n /** How long to wait for the checkout to load before emitting `origin_not_allowed`. Default 15 s. */\n readyTimeoutMs?: number;\n}\n\nexport interface CheckoutEvents {\n /** The checkout loaded and accepted this page. */\n ready: { sessionId: string };\n /** Payment succeeded. Advisory — confirm on your server before fulfilling. */\n completed: { sessionId: string; paymentId: string | null };\n /** A payment attempt failed; the buyer can retry until the session expires. */\n failed: { sessionId: string; code: string };\n /** The session expired. Create a new one. */\n expired: { sessionId: string };\n /** The checkout cannot be shown or used here. */\n error: { code: 'origin_not_allowed' | 'invalid_client_secret' | 'mount_target_not_found' | string; message: string };\n}\n\nexport type CheckoutEventName = keyof CheckoutEvents;\ntype Handler<E extends CheckoutEventName> = (payload: CheckoutEvents[E]) => void;\n\nfunction parseSecret(clientSecret: unknown): string {\n const match = typeof clientSecret === 'string' ? SECRET_PATTERN.exec(clientSecret) : null;\n if (!match) {\n throw new Error('VexPay: clientSecret is missing or malformed. Pass the value returned by POST /v1/checkout/sessions.');\n }\n return match[1]!;\n}\n\nfunction normalizeOrigin(origin: string): string {\n return new URL(origin).origin;\n}\n\n/**\n * An embedded checkout. Card data and bank codes are entered inside VEXPay's\n * iframe and never reach your page — you only receive ids and status codes.\n */\nexport class Checkout {\n private readonly slug: string;\n private readonly origin: string;\n private readonly readyTimeoutMs: number;\n private readonly handlers = new Map<CheckoutEventName, Set<(payload: never) => void>>();\n private iframe: HTMLIFrameElement | null = null;\n private sessionId = '';\n private readyTimer: ReturnType<typeof setTimeout> | undefined;\n private readonly onMessage = (event: MessageEvent) => this.handleMessage(event);\n\n constructor(private readonly options: CheckoutOptions) {\n this.slug = parseSecret(options.clientSecret);\n this.origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n this.readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;\n }\n\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<E extends CheckoutEventName>(event: E, handler: Handler<E>): () => void {\n let set = this.handlers.get(event);\n if (!set) this.handlers.set(event, (set = new Set()));\n set.add(handler as (payload: never) => void);\n return () => this.off(event, handler);\n }\n\n off<E extends CheckoutEventName>(event: E, handler: Handler<E>): void {\n this.handlers.get(event)?.delete(handler as (payload: never) => void);\n }\n\n /** Render the checkout inside `target` (an element or a CSS selector). */\n mount(target: string | HTMLElement): this {\n if (this.iframe) this.unmount();\n const container = typeof target === 'string' ? document.querySelector<HTMLElement>(target) : target;\n if (!container) {\n this.emit('error', { code: 'mount_target_not_found', message: `VexPay: no element matches ${String(target)}.` });\n return this;\n }\n\n const iframe = document.createElement('iframe');\n iframe.src = `${this.origin}/pay/${encodeURIComponent(this.slug)}?embed=1`;\n iframe.title = 'VEXPay checkout';\n iframe.allow = 'payment';\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-forms allow-same-origin allow-popups allow-top-navigation-by-user-activation',\n );\n iframe.style.cssText = 'width:100%;min-height:480px;border:0;display:block;background:transparent;';\n this.iframe = iframe;\n\n window.addEventListener('message', this.onMessage);\n container.appendChild(iframe);\n\n // A frame blocked by CSP frame-ancestors never announces itself.\n this.readyTimer = setTimeout(() => {\n this.emit('error', {\n code: 'origin_not_allowed',\n message: `VexPay: the checkout did not load. Add ${window.location.origin} to the session's allowedOrigins.`,\n });\n }, this.readyTimeoutMs);\n return this;\n }\n\n /** Remove the checkout from the page and stop listening. */\n unmount(): void {\n clearTimeout(this.readyTimer);\n window.removeEventListener('message', this.onMessage);\n this.iframe?.remove();\n this.iframe = null;\n }\n\n private emit<E extends CheckoutEventName>(event: E, payload: CheckoutEvents[E]): void {\n for (const handler of this.handlers.get(event) ?? []) {\n try {\n (handler as Handler<E>)(payload);\n } catch (error) {\n setTimeout(() => {\n throw error;\n });\n }\n }\n }\n\n private handleMessage(event: MessageEvent): void {\n // Only our own iframe, on the checkout origin, is trusted.\n if (!this.iframe || event.source !== this.iframe.contentWindow || event.origin !== this.origin) return;\n const data = event.data as { type?: unknown } & Record<string, unknown>;\n if (!data || typeof data.type !== 'string' || !data.type.startsWith(PREFIX)) return;\n\n switch (data.type.slice(PREFIX.length)) {\n case 'ready':\n // The secret goes only to the checkout origin, never '*'.\n this.iframe.contentWindow?.postMessage(\n { type: `${PREFIX}init`, clientSecret: this.options.clientSecret },\n this.origin,\n );\n return;\n case 'verified':\n clearTimeout(this.readyTimer);\n this.sessionId = String(data.sessionId ?? '');\n this.emit('ready', { sessionId: this.sessionId });\n return;\n case 'resize':\n if (typeof data.height === 'number' && data.height > 0) {\n this.iframe.style.height = `${Math.ceil(data.height)}px`;\n }\n return;\n case 'completed':\n this.emit('completed', {\n sessionId: String(data.sessionId ?? this.sessionId),\n paymentId: typeof data.paymentId === 'string' ? data.paymentId : null,\n });\n return;\n case 'failed':\n this.emit('failed', { sessionId: String(data.sessionId ?? this.sessionId), code: String(data.code ?? 'payment_failed') });\n return;\n case 'expired':\n this.emit('expired', { sessionId: String(data.sessionId ?? this.sessionId) });\n return;\n case 'error':\n clearTimeout(this.readyTimer);\n this.emit('error', { code: String(data.code ?? 'unknown'), message: String(data.message ?? 'Checkout error.') });\n return;\n }\n }\n}\n\n/** Create an embedded checkout for a session. Call `.mount(el)` to show it. */\nexport function checkout(options: CheckoutOptions): Checkout {\n return new Checkout(options);\n}\n\n/** Send the buyer to the hosted checkout page instead of embedding it. */\nexport function redirectToCheckout(options: Pick<CheckoutOptions, 'clientSecret' | 'checkoutOrigin'>): void {\n const slug = parseSecret(options.clientSecret);\n const origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n window.location.assign(`${origin}/pay/${encodeURIComponent(slug)}`);\n}\n","export {\n Checkout,\n checkout,\n redirectToCheckout,\n DEFAULT_CHECKOUT_ORIGIN,\n type CheckoutEventName,\n type CheckoutEvents,\n type CheckoutOptions,\n} from './checkout';\n\nexport const VERSION = '0.1.0';\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/checkout.ts","../src/index.ts"],"sourcesContent":["export const DEFAULT_CHECKOUT_ORIGIN = 'https://pay.vexwallet.co';\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst PREFIX = 'vexpay:';\nconst SECRET_PATTERN = /^([a-z0-9-]+)_secret_[A-Za-z0-9_-]{20,}$/;\n\nexport interface CheckoutOptions {\n /** `clientSecret` from `POST /v1/checkout/sessions` (created on your server). */\n clientSecret: string;\n /** Where the hosted checkout lives. Default `https://pay.vexwallet.co`. */\n checkoutOrigin?: string;\n /** How long to wait for the checkout to load before emitting `origin_not_allowed`. Default 15 s. */\n readyTimeoutMs?: number;\n}\n\nexport interface CheckoutEvents {\n /** The checkout loaded and accepted this page. */\n ready: { sessionId: string };\n /** Payment succeeded. Advisory — confirm on your server before fulfilling. */\n completed: { sessionId: string; paymentId: string | null };\n /** A payment attempt failed; the buyer can retry until the session expires. */\n failed: { sessionId: string; code: string };\n /** The session expired. Create a new one. */\n expired: { sessionId: string };\n /** The checkout cannot be shown or used here. */\n error: { code: 'origin_not_allowed' | 'invalid_client_secret' | 'mount_target_not_found' | string; message: string };\n}\n\nexport type CheckoutEventName = keyof CheckoutEvents;\ntype Handler<E extends CheckoutEventName> = (payload: CheckoutEvents[E]) => void;\n\nfunction parseSecret(clientSecret: unknown): string {\n const match = typeof clientSecret === 'string' ? SECRET_PATTERN.exec(clientSecret) : null;\n if (!match) {\n throw new Error('VexPay: clientSecret is missing or malformed. Pass the value returned by POST /v1/checkout/sessions.');\n }\n return match[1]!;\n}\n\nfunction normalizeOrigin(origin: string): string {\n return new URL(origin).origin;\n}\n\n/**\n * An embedded checkout. Card data and bank codes are entered inside VEXPay's\n * iframe and never reach your page — you only receive ids and status codes.\n */\nexport class Checkout {\n private readonly slug: string;\n private readonly origin: string;\n private readonly readyTimeoutMs: number;\n private readonly handlers = new Map<CheckoutEventName, Set<(payload: never) => void>>();\n private iframe: HTMLIFrameElement | null = null;\n private sessionId = '';\n private readyTimer: ReturnType<typeof setTimeout> | undefined;\n private readonly onMessage = (event: MessageEvent) => this.handleMessage(event);\n\n constructor(private readonly options: CheckoutOptions) {\n this.slug = parseSecret(options.clientSecret);\n this.origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n this.readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;\n }\n\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<E extends CheckoutEventName>(event: E, handler: Handler<E>): () => void {\n let set = this.handlers.get(event);\n if (!set) this.handlers.set(event, (set = new Set()));\n set.add(handler as (payload: never) => void);\n return () => this.off(event, handler);\n }\n\n off<E extends CheckoutEventName>(event: E, handler: Handler<E>): void {\n this.handlers.get(event)?.delete(handler as (payload: never) => void);\n }\n\n /** Render the checkout inside `target` (an element or a CSS selector). */\n mount(target: string | HTMLElement): this {\n if (this.iframe) this.unmount();\n const container = typeof target === 'string' ? document.querySelector<HTMLElement>(target) : target;\n if (!container) {\n this.emit('error', { code: 'mount_target_not_found', message: `VexPay: no element matches ${String(target)}.` });\n return this;\n }\n\n const iframe = document.createElement('iframe');\n iframe.src = `${this.origin}/pay/${encodeURIComponent(this.slug)}?embed=1`;\n iframe.title = 'VEXPay checkout';\n iframe.allow = 'payment';\n // allow-popups-to-escape-sandbox: USDT opens the processor's payment page in a new\n // window, which must run as a normal page (not inherit this iframe's sandbox).\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation',\n );\n iframe.style.cssText = 'width:100%;min-height:480px;border:0;display:block;background:transparent;';\n this.iframe = iframe;\n\n window.addEventListener('message', this.onMessage);\n container.appendChild(iframe);\n\n // A frame blocked by CSP frame-ancestors never announces itself.\n this.readyTimer = setTimeout(() => {\n this.emit('error', {\n code: 'origin_not_allowed',\n message: `VexPay: the checkout did not load. Add ${window.location.origin} to the session's allowedOrigins.`,\n });\n }, this.readyTimeoutMs);\n return this;\n }\n\n /** Remove the checkout from the page and stop listening. */\n unmount(): void {\n clearTimeout(this.readyTimer);\n window.removeEventListener('message', this.onMessage);\n this.iframe?.remove();\n this.iframe = null;\n }\n\n private emit<E extends CheckoutEventName>(event: E, payload: CheckoutEvents[E]): void {\n for (const handler of this.handlers.get(event) ?? []) {\n try {\n (handler as Handler<E>)(payload);\n } catch (error) {\n setTimeout(() => {\n throw error;\n });\n }\n }\n }\n\n private handleMessage(event: MessageEvent): void {\n // Only our own iframe, on the checkout origin, is trusted.\n if (!this.iframe || event.source !== this.iframe.contentWindow || event.origin !== this.origin) return;\n const data = event.data as { type?: unknown } & Record<string, unknown>;\n if (!data || typeof data.type !== 'string' || !data.type.startsWith(PREFIX)) return;\n\n switch (data.type.slice(PREFIX.length)) {\n case 'ready':\n // The secret goes only to the checkout origin, never '*'.\n this.iframe.contentWindow?.postMessage(\n { type: `${PREFIX}init`, clientSecret: this.options.clientSecret },\n this.origin,\n );\n return;\n case 'verified':\n clearTimeout(this.readyTimer);\n this.sessionId = String(data.sessionId ?? '');\n this.emit('ready', { sessionId: this.sessionId });\n return;\n case 'resize':\n if (typeof data.height === 'number' && data.height > 0) {\n this.iframe.style.height = `${Math.ceil(data.height)}px`;\n }\n return;\n case 'completed':\n this.emit('completed', {\n sessionId: String(data.sessionId ?? this.sessionId),\n paymentId: typeof data.paymentId === 'string' ? data.paymentId : null,\n });\n return;\n case 'failed':\n this.emit('failed', { sessionId: String(data.sessionId ?? this.sessionId), code: String(data.code ?? 'payment_failed') });\n return;\n case 'expired':\n this.emit('expired', { sessionId: String(data.sessionId ?? this.sessionId) });\n return;\n case 'error':\n clearTimeout(this.readyTimer);\n this.emit('error', { code: String(data.code ?? 'unknown'), message: String(data.message ?? 'Checkout error.') });\n return;\n }\n }\n}\n\n/** Create an embedded checkout for a session. Call `.mount(el)` to show it. */\nexport function checkout(options: CheckoutOptions): Checkout {\n return new Checkout(options);\n}\n\n/** Send the buyer to the hosted checkout page instead of embedding it. */\nexport function redirectToCheckout(options: Pick<CheckoutOptions, 'clientSecret' | 'checkoutOrigin'>): void {\n const slug = parseSecret(options.clientSecret);\n const origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n window.location.assign(`${origin}/pay/${encodeURIComponent(slug)}`);\n}\n","export {\n Checkout,\n checkout,\n redirectToCheckout,\n DEFAULT_CHECKOUT_ORIGIN,\n type CheckoutEventName,\n type CheckoutEvents,\n type CheckoutOptions,\n} from './checkout';\n\nexport const VERSION = '0.2.0';\n"],"mappings":"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","names":[]}
@@ -1,2 +1,2 @@
1
- "use strict";var VexPay=(()=>{var d=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var w=(s,e)=>{for(var t in e)d(s,t,{get:e[t],enumerable:!0})},C=(s,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of T(e))!I.call(s,n)&&n!==t&&d(s,n,{get:()=>e[n],enumerable:!(i=k(e,n))||i.enumerable});return s};var _=s=>C(d({},"__esModule",{value:!0}),s);var S={};w(S,{DEFAULT_CHECKOUT_ORIGIN:()=>o,VERSION:()=>v,checkout:()=>h,redirectToCheckout:()=>u});var o="https://pay.vexwallet.co";var c="vexpay:",x=/^([a-z0-9-]+)_secret_[A-Za-z0-9_-]{20,}$/;function E(s){let e=typeof s=="string"?x.exec(s):null;if(!e)throw new Error("VexPay: clientSecret is missing or malformed. Pass the value returned by POST /v1/checkout/sessions.");return e[1]}function f(s){return new URL(s).origin}var a=class{constructor(e){this.options=e;this.handlers=new Map;this.iframe=null;this.sessionId="";this.onMessage=e=>this.handleMessage(e);var t,i;this.slug=E(e.clientSecret),this.origin=f((t=e.checkoutOrigin)!=null?t:o),this.readyTimeoutMs=(i=e.readyTimeoutMs)!=null?i:15e3}on(e,t){let i=this.handlers.get(e);return i||this.handlers.set(e,i=new Set),i.add(t),()=>this.off(e,t)}off(e,t){var i;(i=this.handlers.get(e))==null||i.delete(t)}mount(e){this.iframe&&this.unmount();let t=typeof e=="string"?document.querySelector(e):e;if(!t)return this.emit("error",{code:"mount_target_not_found",message:`VexPay: no element matches ${String(e)}.`}),this;let i=document.createElement("iframe");return i.src=`${this.origin}/pay/${encodeURIComponent(this.slug)}?embed=1`,i.title="VEXPay checkout",i.allow="payment",i.setAttribute("sandbox","allow-scripts allow-forms allow-same-origin allow-popups allow-top-navigation-by-user-activation"),i.style.cssText="width:100%;min-height:480px;border:0;display:block;background:transparent;",this.iframe=i,window.addEventListener("message",this.onMessage),t.appendChild(i),this.readyTimer=setTimeout(()=>{this.emit("error",{code:"origin_not_allowed",message:`VexPay: the checkout did not load. Add ${window.location.origin} to the session's allowedOrigins.`})},this.readyTimeoutMs),this}unmount(){var e;clearTimeout(this.readyTimer),window.removeEventListener("message",this.onMessage),(e=this.iframe)==null||e.remove(),this.iframe=null}emit(e,t){var i;for(let n of(i=this.handlers.get(e))!=null?i:[])try{n(t)}catch(r){setTimeout(()=>{throw r})}}handleMessage(e){var i,n,r,l,m,g,p,y;if(!this.iframe||e.source!==this.iframe.contentWindow||e.origin!==this.origin)return;let t=e.data;if(!(!t||typeof t.type!="string"||!t.type.startsWith(c)))switch(t.type.slice(c.length)){case"ready":(i=this.iframe.contentWindow)==null||i.postMessage({type:`${c}init`,clientSecret:this.options.clientSecret},this.origin);return;case"verified":clearTimeout(this.readyTimer),this.sessionId=String((n=t.sessionId)!=null?n:""),this.emit("ready",{sessionId:this.sessionId});return;case"resize":typeof t.height=="number"&&t.height>0&&(this.iframe.style.height=`${Math.ceil(t.height)}px`);return;case"completed":this.emit("completed",{sessionId:String((r=t.sessionId)!=null?r:this.sessionId),paymentId:typeof t.paymentId=="string"?t.paymentId:null});return;case"failed":this.emit("failed",{sessionId:String((l=t.sessionId)!=null?l:this.sessionId),code:String((m=t.code)!=null?m:"payment_failed")});return;case"expired":this.emit("expired",{sessionId:String((g=t.sessionId)!=null?g:this.sessionId)});return;case"error":clearTimeout(this.readyTimer),this.emit("error",{code:String((p=t.code)!=null?p:"unknown"),message:String((y=t.message)!=null?y:"Checkout error.")});return}}};function h(s){return new a(s)}function u(s){var i;let e=E(s.clientSecret),t=f((i=s.checkoutOrigin)!=null?i:o);window.location.assign(`${t}/pay/${encodeURIComponent(e)}`)}var v="0.1.0";return _(S);})();
1
+ "use strict";var VexPay=(()=>{var d=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var w=(s,e)=>{for(var t in e)d(s,t,{get:e[t],enumerable:!0})},C=(s,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of T(e))!I.call(s,n)&&n!==t&&d(s,n,{get:()=>e[n],enumerable:!(i=k(e,n))||i.enumerable});return s};var _=s=>C(d({},"__esModule",{value:!0}),s);var S={};w(S,{DEFAULT_CHECKOUT_ORIGIN:()=>o,VERSION:()=>v,checkout:()=>h,redirectToCheckout:()=>u});var o="https://pay.vexwallet.co";var c="vexpay:",x=/^([a-z0-9-]+)_secret_[A-Za-z0-9_-]{20,}$/;function E(s){let e=typeof s=="string"?x.exec(s):null;if(!e)throw new Error("VexPay: clientSecret is missing or malformed. Pass the value returned by POST /v1/checkout/sessions.");return e[1]}function f(s){return new URL(s).origin}var a=class{constructor(e){this.options=e;this.handlers=new Map;this.iframe=null;this.sessionId="";this.onMessage=e=>this.handleMessage(e);var t,i;this.slug=E(e.clientSecret),this.origin=f((t=e.checkoutOrigin)!=null?t:o),this.readyTimeoutMs=(i=e.readyTimeoutMs)!=null?i:15e3}on(e,t){let i=this.handlers.get(e);return i||this.handlers.set(e,i=new Set),i.add(t),()=>this.off(e,t)}off(e,t){var i;(i=this.handlers.get(e))==null||i.delete(t)}mount(e){this.iframe&&this.unmount();let t=typeof e=="string"?document.querySelector(e):e;if(!t)return this.emit("error",{code:"mount_target_not_found",message:`VexPay: no element matches ${String(e)}.`}),this;let i=document.createElement("iframe");return i.src=`${this.origin}/pay/${encodeURIComponent(this.slug)}?embed=1`,i.title="VEXPay checkout",i.allow="payment",i.setAttribute("sandbox","allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"),i.style.cssText="width:100%;min-height:480px;border:0;display:block;background:transparent;",this.iframe=i,window.addEventListener("message",this.onMessage),t.appendChild(i),this.readyTimer=setTimeout(()=>{this.emit("error",{code:"origin_not_allowed",message:`VexPay: the checkout did not load. Add ${window.location.origin} to the session's allowedOrigins.`})},this.readyTimeoutMs),this}unmount(){var e;clearTimeout(this.readyTimer),window.removeEventListener("message",this.onMessage),(e=this.iframe)==null||e.remove(),this.iframe=null}emit(e,t){var i;for(let n of(i=this.handlers.get(e))!=null?i:[])try{n(t)}catch(r){setTimeout(()=>{throw r})}}handleMessage(e){var i,n,r,l,m,p,g,y;if(!this.iframe||e.source!==this.iframe.contentWindow||e.origin!==this.origin)return;let t=e.data;if(!(!t||typeof t.type!="string"||!t.type.startsWith(c)))switch(t.type.slice(c.length)){case"ready":(i=this.iframe.contentWindow)==null||i.postMessage({type:`${c}init`,clientSecret:this.options.clientSecret},this.origin);return;case"verified":clearTimeout(this.readyTimer),this.sessionId=String((n=t.sessionId)!=null?n:""),this.emit("ready",{sessionId:this.sessionId});return;case"resize":typeof t.height=="number"&&t.height>0&&(this.iframe.style.height=`${Math.ceil(t.height)}px`);return;case"completed":this.emit("completed",{sessionId:String((r=t.sessionId)!=null?r:this.sessionId),paymentId:typeof t.paymentId=="string"?t.paymentId:null});return;case"failed":this.emit("failed",{sessionId:String((l=t.sessionId)!=null?l:this.sessionId),code:String((m=t.code)!=null?m:"payment_failed")});return;case"expired":this.emit("expired",{sessionId:String((p=t.sessionId)!=null?p:this.sessionId)});return;case"error":clearTimeout(this.readyTimer),this.emit("error",{code:String((g=t.code)!=null?g:"unknown"),message:String((y=t.message)!=null?y:"Checkout error.")});return}}};function h(s){return new a(s)}function u(s){var i;let e=E(s.clientSecret),t=f((i=s.checkoutOrigin)!=null?i:o);window.location.assign(`${t}/pay/${encodeURIComponent(e)}`)}var v="0.2.0";return _(S);})();
2
2
  //# sourceMappingURL=vexpay.global.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/global.ts","../src/checkout.ts","../src/index.ts"],"sourcesContent":["// CDN entry: the IIFE build assigns these exports to window.VexPay.\nexport { checkout, redirectToCheckout, DEFAULT_CHECKOUT_ORIGIN, VERSION } from './index';\n","export const DEFAULT_CHECKOUT_ORIGIN = 'https://pay.vexwallet.co';\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst PREFIX = 'vexpay:';\nconst SECRET_PATTERN = /^([a-z0-9-]+)_secret_[A-Za-z0-9_-]{20,}$/;\n\nexport interface CheckoutOptions {\n /** `clientSecret` from `POST /v1/checkout/sessions` (created on your server). */\n clientSecret: string;\n /** Where the hosted checkout lives. Default `https://pay.vexwallet.co`. */\n checkoutOrigin?: string;\n /** How long to wait for the checkout to load before emitting `origin_not_allowed`. Default 15 s. */\n readyTimeoutMs?: number;\n}\n\nexport interface CheckoutEvents {\n /** The checkout loaded and accepted this page. */\n ready: { sessionId: string };\n /** Payment succeeded. Advisory — confirm on your server before fulfilling. */\n completed: { sessionId: string; paymentId: string | null };\n /** A payment attempt failed; the buyer can retry until the session expires. */\n failed: { sessionId: string; code: string };\n /** The session expired. Create a new one. */\n expired: { sessionId: string };\n /** The checkout cannot be shown or used here. */\n error: { code: 'origin_not_allowed' | 'invalid_client_secret' | 'mount_target_not_found' | string; message: string };\n}\n\nexport type CheckoutEventName = keyof CheckoutEvents;\ntype Handler<E extends CheckoutEventName> = (payload: CheckoutEvents[E]) => void;\n\nfunction parseSecret(clientSecret: unknown): string {\n const match = typeof clientSecret === 'string' ? SECRET_PATTERN.exec(clientSecret) : null;\n if (!match) {\n throw new Error('VexPay: clientSecret is missing or malformed. Pass the value returned by POST /v1/checkout/sessions.');\n }\n return match[1]!;\n}\n\nfunction normalizeOrigin(origin: string): string {\n return new URL(origin).origin;\n}\n\n/**\n * An embedded checkout. Card data and bank codes are entered inside VEXPay's\n * iframe and never reach your page — you only receive ids and status codes.\n */\nexport class Checkout {\n private readonly slug: string;\n private readonly origin: string;\n private readonly readyTimeoutMs: number;\n private readonly handlers = new Map<CheckoutEventName, Set<(payload: never) => void>>();\n private iframe: HTMLIFrameElement | null = null;\n private sessionId = '';\n private readyTimer: ReturnType<typeof setTimeout> | undefined;\n private readonly onMessage = (event: MessageEvent) => this.handleMessage(event);\n\n constructor(private readonly options: CheckoutOptions) {\n this.slug = parseSecret(options.clientSecret);\n this.origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n this.readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;\n }\n\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<E extends CheckoutEventName>(event: E, handler: Handler<E>): () => void {\n let set = this.handlers.get(event);\n if (!set) this.handlers.set(event, (set = new Set()));\n set.add(handler as (payload: never) => void);\n return () => this.off(event, handler);\n }\n\n off<E extends CheckoutEventName>(event: E, handler: Handler<E>): void {\n this.handlers.get(event)?.delete(handler as (payload: never) => void);\n }\n\n /** Render the checkout inside `target` (an element or a CSS selector). */\n mount(target: string | HTMLElement): this {\n if (this.iframe) this.unmount();\n const container = typeof target === 'string' ? document.querySelector<HTMLElement>(target) : target;\n if (!container) {\n this.emit('error', { code: 'mount_target_not_found', message: `VexPay: no element matches ${String(target)}.` });\n return this;\n }\n\n const iframe = document.createElement('iframe');\n iframe.src = `${this.origin}/pay/${encodeURIComponent(this.slug)}?embed=1`;\n iframe.title = 'VEXPay checkout';\n iframe.allow = 'payment';\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-forms allow-same-origin allow-popups allow-top-navigation-by-user-activation',\n );\n iframe.style.cssText = 'width:100%;min-height:480px;border:0;display:block;background:transparent;';\n this.iframe = iframe;\n\n window.addEventListener('message', this.onMessage);\n container.appendChild(iframe);\n\n // A frame blocked by CSP frame-ancestors never announces itself.\n this.readyTimer = setTimeout(() => {\n this.emit('error', {\n code: 'origin_not_allowed',\n message: `VexPay: the checkout did not load. Add ${window.location.origin} to the session's allowedOrigins.`,\n });\n }, this.readyTimeoutMs);\n return this;\n }\n\n /** Remove the checkout from the page and stop listening. */\n unmount(): void {\n clearTimeout(this.readyTimer);\n window.removeEventListener('message', this.onMessage);\n this.iframe?.remove();\n this.iframe = null;\n }\n\n private emit<E extends CheckoutEventName>(event: E, payload: CheckoutEvents[E]): void {\n for (const handler of this.handlers.get(event) ?? []) {\n try {\n (handler as Handler<E>)(payload);\n } catch (error) {\n setTimeout(() => {\n throw error;\n });\n }\n }\n }\n\n private handleMessage(event: MessageEvent): void {\n // Only our own iframe, on the checkout origin, is trusted.\n if (!this.iframe || event.source !== this.iframe.contentWindow || event.origin !== this.origin) return;\n const data = event.data as { type?: unknown } & Record<string, unknown>;\n if (!data || typeof data.type !== 'string' || !data.type.startsWith(PREFIX)) return;\n\n switch (data.type.slice(PREFIX.length)) {\n case 'ready':\n // The secret goes only to the checkout origin, never '*'.\n this.iframe.contentWindow?.postMessage(\n { type: `${PREFIX}init`, clientSecret: this.options.clientSecret },\n this.origin,\n );\n return;\n case 'verified':\n clearTimeout(this.readyTimer);\n this.sessionId = String(data.sessionId ?? '');\n this.emit('ready', { sessionId: this.sessionId });\n return;\n case 'resize':\n if (typeof data.height === 'number' && data.height > 0) {\n this.iframe.style.height = `${Math.ceil(data.height)}px`;\n }\n return;\n case 'completed':\n this.emit('completed', {\n sessionId: String(data.sessionId ?? this.sessionId),\n paymentId: typeof data.paymentId === 'string' ? data.paymentId : null,\n });\n return;\n case 'failed':\n this.emit('failed', { sessionId: String(data.sessionId ?? this.sessionId), code: String(data.code ?? 'payment_failed') });\n return;\n case 'expired':\n this.emit('expired', { sessionId: String(data.sessionId ?? this.sessionId) });\n return;\n case 'error':\n clearTimeout(this.readyTimer);\n this.emit('error', { code: String(data.code ?? 'unknown'), message: String(data.message ?? 'Checkout error.') });\n return;\n }\n }\n}\n\n/** Create an embedded checkout for a session. Call `.mount(el)` to show it. */\nexport function checkout(options: CheckoutOptions): Checkout {\n return new Checkout(options);\n}\n\n/** Send the buyer to the hosted checkout page instead of embedding it. */\nexport function redirectToCheckout(options: Pick<CheckoutOptions, 'clientSecret' | 'checkoutOrigin'>): void {\n const slug = parseSecret(options.clientSecret);\n const origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n window.location.assign(`${origin}/pay/${encodeURIComponent(slug)}`);\n}\n","export {\n Checkout,\n checkout,\n redirectToCheckout,\n DEFAULT_CHECKOUT_ORIGIN,\n type CheckoutEventName,\n type CheckoutEvents,\n type CheckoutOptions,\n} from './checkout';\n\nexport const VERSION = '0.1.0';\n"],"mappings":"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","names":["global_exports","__export","DEFAULT_CHECKOUT_ORIGIN","VERSION","checkout","redirectToCheckout","DEFAULT_CHECKOUT_ORIGIN","PREFIX","SECRET_PATTERN","parseSecret","clientSecret","match","normalizeOrigin","origin","Checkout","options","event","_a","_b","DEFAULT_CHECKOUT_ORIGIN","handler","set","target","container","iframe","payload","error","_c","_d","_e","_f","_g","_h","data","checkout","redirectToCheckout","slug","VERSION"]}
1
+ {"version":3,"sources":["../src/global.ts","../src/checkout.ts","../src/index.ts"],"sourcesContent":["// CDN entry: the IIFE build assigns these exports to window.VexPay.\nexport { checkout, redirectToCheckout, DEFAULT_CHECKOUT_ORIGIN, VERSION } from './index';\n","export const DEFAULT_CHECKOUT_ORIGIN = 'https://pay.vexwallet.co';\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst PREFIX = 'vexpay:';\nconst SECRET_PATTERN = /^([a-z0-9-]+)_secret_[A-Za-z0-9_-]{20,}$/;\n\nexport interface CheckoutOptions {\n /** `clientSecret` from `POST /v1/checkout/sessions` (created on your server). */\n clientSecret: string;\n /** Where the hosted checkout lives. Default `https://pay.vexwallet.co`. */\n checkoutOrigin?: string;\n /** How long to wait for the checkout to load before emitting `origin_not_allowed`. Default 15 s. */\n readyTimeoutMs?: number;\n}\n\nexport interface CheckoutEvents {\n /** The checkout loaded and accepted this page. */\n ready: { sessionId: string };\n /** Payment succeeded. Advisory — confirm on your server before fulfilling. */\n completed: { sessionId: string; paymentId: string | null };\n /** A payment attempt failed; the buyer can retry until the session expires. */\n failed: { sessionId: string; code: string };\n /** The session expired. Create a new one. */\n expired: { sessionId: string };\n /** The checkout cannot be shown or used here. */\n error: { code: 'origin_not_allowed' | 'invalid_client_secret' | 'mount_target_not_found' | string; message: string };\n}\n\nexport type CheckoutEventName = keyof CheckoutEvents;\ntype Handler<E extends CheckoutEventName> = (payload: CheckoutEvents[E]) => void;\n\nfunction parseSecret(clientSecret: unknown): string {\n const match = typeof clientSecret === 'string' ? SECRET_PATTERN.exec(clientSecret) : null;\n if (!match) {\n throw new Error('VexPay: clientSecret is missing or malformed. Pass the value returned by POST /v1/checkout/sessions.');\n }\n return match[1]!;\n}\n\nfunction normalizeOrigin(origin: string): string {\n return new URL(origin).origin;\n}\n\n/**\n * An embedded checkout. Card data and bank codes are entered inside VEXPay's\n * iframe and never reach your page — you only receive ids and status codes.\n */\nexport class Checkout {\n private readonly slug: string;\n private readonly origin: string;\n private readonly readyTimeoutMs: number;\n private readonly handlers = new Map<CheckoutEventName, Set<(payload: never) => void>>();\n private iframe: HTMLIFrameElement | null = null;\n private sessionId = '';\n private readyTimer: ReturnType<typeof setTimeout> | undefined;\n private readonly onMessage = (event: MessageEvent) => this.handleMessage(event);\n\n constructor(private readonly options: CheckoutOptions) {\n this.slug = parseSecret(options.clientSecret);\n this.origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n this.readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;\n }\n\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<E extends CheckoutEventName>(event: E, handler: Handler<E>): () => void {\n let set = this.handlers.get(event);\n if (!set) this.handlers.set(event, (set = new Set()));\n set.add(handler as (payload: never) => void);\n return () => this.off(event, handler);\n }\n\n off<E extends CheckoutEventName>(event: E, handler: Handler<E>): void {\n this.handlers.get(event)?.delete(handler as (payload: never) => void);\n }\n\n /** Render the checkout inside `target` (an element or a CSS selector). */\n mount(target: string | HTMLElement): this {\n if (this.iframe) this.unmount();\n const container = typeof target === 'string' ? document.querySelector<HTMLElement>(target) : target;\n if (!container) {\n this.emit('error', { code: 'mount_target_not_found', message: `VexPay: no element matches ${String(target)}.` });\n return this;\n }\n\n const iframe = document.createElement('iframe');\n iframe.src = `${this.origin}/pay/${encodeURIComponent(this.slug)}?embed=1`;\n iframe.title = 'VEXPay checkout';\n iframe.allow = 'payment';\n // allow-popups-to-escape-sandbox: USDT opens the processor's payment page in a new\n // window, which must run as a normal page (not inherit this iframe's sandbox).\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation',\n );\n iframe.style.cssText = 'width:100%;min-height:480px;border:0;display:block;background:transparent;';\n this.iframe = iframe;\n\n window.addEventListener('message', this.onMessage);\n container.appendChild(iframe);\n\n // A frame blocked by CSP frame-ancestors never announces itself.\n this.readyTimer = setTimeout(() => {\n this.emit('error', {\n code: 'origin_not_allowed',\n message: `VexPay: the checkout did not load. Add ${window.location.origin} to the session's allowedOrigins.`,\n });\n }, this.readyTimeoutMs);\n return this;\n }\n\n /** Remove the checkout from the page and stop listening. */\n unmount(): void {\n clearTimeout(this.readyTimer);\n window.removeEventListener('message', this.onMessage);\n this.iframe?.remove();\n this.iframe = null;\n }\n\n private emit<E extends CheckoutEventName>(event: E, payload: CheckoutEvents[E]): void {\n for (const handler of this.handlers.get(event) ?? []) {\n try {\n (handler as Handler<E>)(payload);\n } catch (error) {\n setTimeout(() => {\n throw error;\n });\n }\n }\n }\n\n private handleMessage(event: MessageEvent): void {\n // Only our own iframe, on the checkout origin, is trusted.\n if (!this.iframe || event.source !== this.iframe.contentWindow || event.origin !== this.origin) return;\n const data = event.data as { type?: unknown } & Record<string, unknown>;\n if (!data || typeof data.type !== 'string' || !data.type.startsWith(PREFIX)) return;\n\n switch (data.type.slice(PREFIX.length)) {\n case 'ready':\n // The secret goes only to the checkout origin, never '*'.\n this.iframe.contentWindow?.postMessage(\n { type: `${PREFIX}init`, clientSecret: this.options.clientSecret },\n this.origin,\n );\n return;\n case 'verified':\n clearTimeout(this.readyTimer);\n this.sessionId = String(data.sessionId ?? '');\n this.emit('ready', { sessionId: this.sessionId });\n return;\n case 'resize':\n if (typeof data.height === 'number' && data.height > 0) {\n this.iframe.style.height = `${Math.ceil(data.height)}px`;\n }\n return;\n case 'completed':\n this.emit('completed', {\n sessionId: String(data.sessionId ?? this.sessionId),\n paymentId: typeof data.paymentId === 'string' ? data.paymentId : null,\n });\n return;\n case 'failed':\n this.emit('failed', { sessionId: String(data.sessionId ?? this.sessionId), code: String(data.code ?? 'payment_failed') });\n return;\n case 'expired':\n this.emit('expired', { sessionId: String(data.sessionId ?? this.sessionId) });\n return;\n case 'error':\n clearTimeout(this.readyTimer);\n this.emit('error', { code: String(data.code ?? 'unknown'), message: String(data.message ?? 'Checkout error.') });\n return;\n }\n }\n}\n\n/** Create an embedded checkout for a session. Call `.mount(el)` to show it. */\nexport function checkout(options: CheckoutOptions): Checkout {\n return new Checkout(options);\n}\n\n/** Send the buyer to the hosted checkout page instead of embedding it. */\nexport function redirectToCheckout(options: Pick<CheckoutOptions, 'clientSecret' | 'checkoutOrigin'>): void {\n const slug = parseSecret(options.clientSecret);\n const origin = normalizeOrigin(options.checkoutOrigin ?? DEFAULT_CHECKOUT_ORIGIN);\n window.location.assign(`${origin}/pay/${encodeURIComponent(slug)}`);\n}\n","export {\n Checkout,\n checkout,\n redirectToCheckout,\n DEFAULT_CHECKOUT_ORIGIN,\n type CheckoutEventName,\n type CheckoutEvents,\n type CheckoutOptions,\n} from './checkout';\n\nexport const VERSION = '0.2.0';\n"],"mappings":"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","names":["global_exports","__export","DEFAULT_CHECKOUT_ORIGIN","VERSION","checkout","redirectToCheckout","DEFAULT_CHECKOUT_ORIGIN","PREFIX","SECRET_PATTERN","parseSecret","clientSecret","match","normalizeOrigin","origin","Checkout","options","event","_a","_b","DEFAULT_CHECKOUT_ORIGIN","handler","set","target","container","iframe","payload","error","_c","_d","_e","_f","_g","_h","data","checkout","redirectToCheckout","slug","VERSION"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vexpay/js",
3
- "version": "0.1.0",
4
- "description": "Embed the VEXPay checkout on your website — Pago Móvil C2P and cards, with payment details kept on VEXPay's origin.",
3
+ "version": "0.2.0",
4
+ "description": "Embed the VEXPay checkout on your website \u2014 Pago M\u00f3vil C2P and cards, with payment details kept on VEXPay's origin.",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "vexpay",
@@ -45,17 +45,17 @@
45
45
  "access": "public",
46
46
  "provenance": false
47
47
  },
48
+ "scripts": {
49
+ "build": "tsup && node scripts/check-size.mjs",
50
+ "typecheck": "tsc --noEmit",
51
+ "test": "vitest run",
52
+ "test:e2e": "node e2e/embed.e2e.mjs"
53
+ },
48
54
  "devDependencies": {
49
55
  "happy-dom": "^18",
50
56
  "playwright-core": "^1",
51
57
  "tsup": "^8",
52
58
  "typescript": "^5",
53
59
  "vitest": "^3"
54
- },
55
- "scripts": {
56
- "build": "tsup && node scripts/check-size.mjs",
57
- "typecheck": "tsc --noEmit",
58
- "test": "vitest run",
59
- "test:e2e": "node e2e/embed.e2e.mjs"
60
60
  }
61
- }
61
+ }