@amos.com/amos-js 0.9.18 → 0.10.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
@@ -281,6 +281,7 @@ Mount the secure credit-card payment method form into a container element (an `H
281
281
  - `billingAddressRequirement` (`"country" | "full"`, defaults to `"country"`) — how much billing address the iframe collects. `country` collects country / region and, for CA / PR / GB / US, a postal code (labeled ZIP for the United States). `full` shows a full street address form with Smarty autocomplete.
282
282
 
283
283
  - `onValidityChange` (`(event: { isValid: boolean }) => void`) — called when form validity changes. `isValid` is true when all required fields are present and valid. Does not include PCI data. Use this to enable or disable your checkout button.
284
+ - `onCardBrandChanged` (`(event: { brand: CardBrand | null }) => void`) — called when the detected card brand changes. `brand` is `"visa"`, `"mastercard"`, `"amex"`, `"discover"`, `"diners"`, or `"jcb"`, or `null` when the field is empty or the number does not match a known brand. Does not include PCI data. Credit-card form only.
284
285
  - `onHeightChange`, `onAppearanceReady` (advanced — override the default iframe styling logic). The skeleton is removed and the iframe faded in when `onAppearanceReady` fires.
285
286
 
286
287
  **Returns** `AmosPaymentMethodFormMountController`:
@@ -415,7 +416,7 @@ Advanced helpers exposed for integrators that need to construct or inspect the m
415
416
 
416
417
  ### Exported types
417
418
 
418
- `ConfirmationResult`, `ConfirmationIncompleteReason`, `Message`, `Appearance`, `ThemeVariable`, `FormattedGooglePayPaymentData`, `PaymentMethodFormValidityChangeEvent`, plus the per-form `*Options` and `*Controller` types. For OpenAPI schema types, import `components` from `@amos.com/node`.
419
+ `ConfirmationResult`, `ConfirmationIncompleteReason`, `Message`, `Appearance`, `ThemeVariable`, `FormattedGooglePayPaymentData`, `PaymentMethodFormValidityChangeEvent`, `CardBrand`, `PaymentMethodFormCardBrandChangeEvent`, plus the per-form `*Options` and `*Controller` types. For OpenAPI schema types, import `components` from `@amos.com/node`.
419
420
 
420
421
  ## Notes and potential gotchas
421
422
 
package/dist/index.d.ts CHANGED
@@ -13,5 +13,5 @@ export type { BillingAddressRequirement, CreditCardAdditionalFields, PaymentMeth
13
13
  export { attachPaymentMethodFormListeners, getBankAccountFormInitialHeight, getBankAccountFormSrc, getCreditCardFormInitialHeight, getCreditCardFormSrc, } from './payment-method-form';
14
14
  export type { OpenPlaidLinkInput, PlaidCredentials, PlaidLinkOnSuccessMetadata, } from './plaid';
15
15
  export { linkedBankLabelFromMetadata, loadPlaidScript, openPlaidLink, plaidAccountIdFromMetadata, requiresAchVerification, } from './plaid';
16
- export type { Appearance, AppearanceLabels, ApplePayButtonElementProps, ConfirmationIncompleteReason, ConfirmationResult, GooglePayButtonElementProps, Message, PaymentMethodFormValidityChangeEvent, ThemeVariable, } from './types';
16
+ export type { Appearance, AppearanceLabels, ApplePayButtonElementProps, CardBrand, ConfirmationIncompleteReason, ConfirmationResult, GooglePayButtonElementProps, Message, PaymentMethodFormCardBrandChangeEvent, PaymentMethodFormValidityChangeEvent, ThemeVariable, } from './types';
17
17
  export { createMessage } from './types';
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=`data-a
65
65
  animation: none;
66
66
  }
67
67
  }
68
- `;function A(){if(document.getElementById(k))return;let e=document.createElement(`style`);e.id=k,e.textContent=ne,document.head.appendChild(e)}function j(e,t){for(let[t,n]of Object.entries(te))e.style.setProperty(t,n);let n=t?.themeVariables;if(n)for(let[t,r]of Object.entries(n))typeof r==`string`&&r.trim()!==``&&e.style.setProperty(t,r.trim())}function M(e,t){let n=document.createElement(`div`);if(n.className=e,t)for(let e of t)n.appendChild(e);return n}function N(e,t){let n=M(`amos-js-form-skeleton-field`);t!==void 0&&(n.style.flexGrow=String(t)),e===`above`&&n.appendChild(M(`amos-js-form-skeleton-label`));let r=M(`amos-js-form-skeleton-input`);return e===`floating`&&r.classList.add(`amos-js-form-skeleton-input-floating`),n.appendChild(r),n}function P(e,t){return M(t?`amos-js-form-skeleton-row-stack`:`amos-js-form-skeleton-row`,e)}function re({labels:e,requirement:t,wrapCountryZip:n}){return t===`full`?[N(e),N(e),P([N(e,1.4),N(e,.7),N(e,.8)],!1),N(e)]:[P([N(e),N(e)],n)]}function ie(e){let t=e.appearance?.labels??`above`,n=e.billingAddressRequirement??`country`;if(e.kind===`card`){let r=[N(t),P([N(t),N(t)],!1)];return e.additionalFields?.cardholderName&&r.push(N(t)),r.push(...re({labels:t,requirement:n,wrapCountryZip:!1})),r}return[N(t),P([N(t),N(t)],!0),N(t),P([N(`above`),N(`above`)],!0),...re({labels:t,requirement:n,wrapCountryZip:!0})]}function ae(e){A();let t=M(`amos-js-form-skeleton`);t.setAttribute(`aria-hidden`,`true`);function n(e){j(t,e.appearance),t.replaceChildren(...ie(e))}return n(e),{element:t,update:n}}function F(e){A();let t=M(`amos-js-form-skeleton-input amos-js-wallet-skeleton`);t.setAttribute(`aria-hidden`,`true`);function n(e){j(t,void 0),t.style.height=e.height,t.style.borderRadius=e.borderRadius??`4px`}return n(e),{element:t,update:n}}function I(e){if(typeof e==`number`&&Number.isFinite(e))return`${e}px`;if(typeof e==`string`&&e.trim()!==``)return e.trim()}function L({iframeStyle:e,buttonProps:t}){return I(e?.borderRadius)??I(t?.buttonRadius)??I(t?.style?.borderRadius)??I(t?.style?.[`--apple-pay-button-border-radius`])??`4px`}function R(e){return`${i(e)}/iframe/google-pay?token=${e}`}function z(){return`48px`}function B(e,{height:t=`48px`,...n}){let r={...n,height:t};function i(){h({iframe:e,amount:r.amount})}function a(){g({iframe:e,merchantName:r.merchantName})}function o(){m({iframe:e,height:r.height??`48px`,props:r.buttonProps??{}})}function s(t){if(t.source===e.contentWindow)switch(t.data.type){case`IFRAME_READY`:d(e),f({iframe:e,appearance:{}}),i(),a(),o();break;case`UPDATE_HEIGHT`:r.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:f({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:r.onAppearanceReady?.();break;case`CREATE_PAYMENT_INTENT`:r.onInitiatePaymentIntentRequest({paymentIntentCreateAttributes:t.data.paymentIntentCreateAttributes,customerCreateAttributes:t.data.customerCreateAttributes}).then(t=>{S({iframe:e,token:t})}).catch(e=>{r.onResult({status:`failed`,errorMessage:e instanceof Error?e.message:`Unknown error`})});break;case`CONFIRMATION_RESULT`:r.onResult(t.data.result)}}return window.addEventListener(`message`,s),{update(e){let t=`amount`in e,n=`merchantName`in e,s=`height`in e||`buttonProps`in e;r={...r,...e},t&&i(),n&&a(),s&&o()},destroy(){window.removeEventListener(`message`,s)}}}function oe({paymentData:e}){return{paymentMethod:{type:`googlepay`,billing_address_attributes:{name:e.shippingAddress?.name,address_line1:e.shippingAddress?.address1,address_line2:e.shippingAddress?.address2,city:e.shippingAddress?.locality,state:e.shippingAddress?.administrativeArea,postal_code:e.shippingAddress?.postalCode,country:e.shippingAddress?.countryCode,email:e.email,phone:e.shippingAddress?.phoneNumber},card_profile_attributes:{wallet_payload:e.paymentMethodData.tokenizationData.token}}}}var V={country:212,full:452},se=80,H={country:400,full:640};function U(e,t={cardholderName:!1},n=`country`){let r=Object.entries(t).filter(([,e])=>e).map(([e])=>e).join(`,`),a=new URLSearchParams({token:e,additionalFields:r,billingAddressRequirement:n});return`${i(e)}/iframe/card?${a}`}function W(e,t=`country`){let n=new URLSearchParams({token:e,billingAddressRequirement:t});return`${i(e)}/iframe/bank?${n}`}function G(e={cardholderName:!1},t=`country`){return`${(V[t]??V.country)+(e.cardholderName?se:0)}px`}function K(e=`country`){return`${H[e]??H.country}px`}function q(e,t){let n={...t};function r(t){if(t.source===e.contentWindow)switch(t.data.type){case`IFRAME_READY`:d(e),f({iframe:e,appearance:n.appearance});break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:f({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`FORM_VALIDITY_CHANGE`:if(s(e)?.requiresVerification)break;n.onValidityChange?.({isValid:t.data.isValid});break;case`CONFIRMATION_RESULT`:n.onResult(t.data.result)}}return window.addEventListener(`message`,r),{update(t){let r=`appearance`in t;n={...n,...t},r&&f({iframe:e,appearance:n.appearance})},destroy(){window.removeEventListener(`message`,r)}}}var J=`https://cdn.plaid.com/link/v2/stable/link-initialize.js`;function Y({amount:e,achThreshold:t}){return t==null?!1:e==null||e>=t}function ce(e){if(e==null)return;let t=e.trim();if(t===``)return;let n=Number(t.replace(/[^\d.]/g,``));if(Number.isFinite(n))return Math.round(n*100)}function le(e){return e.account_id??e.account?.id??e.accounts?.[0]?.id}function ue(e){let t=e.account??e.accounts?.[0];return{bankName:e.institution?.name??`Bank account`,last4:t?.mask??``}}var X;function de(){for(let e of document.querySelectorAll(`script[src="${J}"]`))e.remove()}function fe(){return typeof window>`u`?Promise.reject(Error(`Plaid Link requires a browser`)):window.Plaid?Promise.resolve():X||(de(),X=new Promise((e,t)=>{let n=document.createElement(`script`);n.src=J,n.async=!0;let r=e=>{n.remove(),X=void 0,t(Error(e))};n.addEventListener(`load`,()=>{if(window.Plaid){e();return}r(`Plaid Link failed to initialize`)},{once:!0}),n.addEventListener(`error`,()=>r(`Failed to load Plaid Link`),{once:!0}),document.head.append(n)}),X)}async function pe({token:e,onSuccess:t,onExit:n,signal:r}){if(await fe(),r?.aborted)return()=>{};if(!window.Plaid)throw Error(`Plaid Link failed to initialize`);let i=window.Plaid.create({token:e,onSuccess:t,onExit:e=>{n?.(e)}});return r?.aborted?(i.destroy(),()=>{}):(i.open(),()=>{i.destroy()})}var me=`amos-js-plaid-bank-ui-styles`,he=`
68
+ `;function A(){if(document.getElementById(k))return;let e=document.createElement(`style`);e.id=k,e.textContent=ne,document.head.appendChild(e)}function j(e,t){for(let[t,n]of Object.entries(te))e.style.setProperty(t,n);let n=t?.themeVariables;if(n)for(let[t,r]of Object.entries(n))typeof r==`string`&&r.trim()!==``&&e.style.setProperty(t,r.trim())}function M(e,t){let n=document.createElement(`div`);if(n.className=e,t)for(let e of t)n.appendChild(e);return n}function N(e,t){let n=M(`amos-js-form-skeleton-field`);t!==void 0&&(n.style.flexGrow=String(t)),e===`above`&&n.appendChild(M(`amos-js-form-skeleton-label`));let r=M(`amos-js-form-skeleton-input`);return e===`floating`&&r.classList.add(`amos-js-form-skeleton-input-floating`),n.appendChild(r),n}function P(e,t){return M(t?`amos-js-form-skeleton-row-stack`:`amos-js-form-skeleton-row`,e)}function re({labels:e,requirement:t,wrapCountryZip:n}){return t===`full`?[N(e),N(e),P([N(e,1.4),N(e,.7),N(e,.8)],!1),N(e)]:[P([N(e),N(e)],n)]}function ie(e){let t=e.appearance?.labels??`above`,n=e.billingAddressRequirement??`country`;if(e.kind===`card`){let r=[N(t),P([N(t),N(t)],!1)];return e.additionalFields?.cardholderName&&r.push(N(t)),r.push(...re({labels:t,requirement:n,wrapCountryZip:!1})),r}return[N(t),P([N(t),N(t)],!0),N(t),P([N(`above`),N(`above`)],!0),...re({labels:t,requirement:n,wrapCountryZip:!0})]}function ae(e){A();let t=M(`amos-js-form-skeleton`);t.setAttribute(`aria-hidden`,`true`);function n(e){j(t,e.appearance),t.replaceChildren(...ie(e))}return n(e),{element:t,update:n}}function F(e){A();let t=M(`amos-js-form-skeleton-input amos-js-wallet-skeleton`);t.setAttribute(`aria-hidden`,`true`);function n(e){j(t,void 0),t.style.height=e.height,t.style.borderRadius=e.borderRadius??`4px`}return n(e),{element:t,update:n}}function I(e){if(typeof e==`number`&&Number.isFinite(e))return`${e}px`;if(typeof e==`string`&&e.trim()!==``)return e.trim()}function L({iframeStyle:e,buttonProps:t}){return I(e?.borderRadius)??I(t?.buttonRadius)??I(t?.style?.borderRadius)??I(t?.style?.[`--apple-pay-button-border-radius`])??`4px`}function R(e){return`${i(e)}/iframe/google-pay?token=${e}`}function z(){return`48px`}function B(e,{height:t=`48px`,...n}){let r={...n,height:t};function i(){h({iframe:e,amount:r.amount})}function a(){g({iframe:e,merchantName:r.merchantName})}function o(){m({iframe:e,height:r.height??`48px`,props:r.buttonProps??{}})}function s(t){if(t.source===e.contentWindow)switch(t.data.type){case`IFRAME_READY`:d(e),f({iframe:e,appearance:{}}),i(),a(),o();break;case`UPDATE_HEIGHT`:r.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:f({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:r.onAppearanceReady?.();break;case`CREATE_PAYMENT_INTENT`:r.onInitiatePaymentIntentRequest({paymentIntentCreateAttributes:t.data.paymentIntentCreateAttributes,customerCreateAttributes:t.data.customerCreateAttributes}).then(t=>{S({iframe:e,token:t})}).catch(e=>{r.onResult({status:`failed`,errorMessage:e instanceof Error?e.message:`Unknown error`})});break;case`CONFIRMATION_RESULT`:r.onResult(t.data.result)}}return window.addEventListener(`message`,s),{update(e){let t=`amount`in e,n=`merchantName`in e,s=`height`in e||`buttonProps`in e;r={...r,...e},t&&i(),n&&a(),s&&o()},destroy(){window.removeEventListener(`message`,s)}}}function oe({paymentData:e}){return{paymentMethod:{type:`googlepay`,billing_address_attributes:{name:e.shippingAddress?.name,address_line1:e.shippingAddress?.address1,address_line2:e.shippingAddress?.address2,city:e.shippingAddress?.locality,state:e.shippingAddress?.administrativeArea,postal_code:e.shippingAddress?.postalCode,country:e.shippingAddress?.countryCode,email:e.email,phone:e.shippingAddress?.phoneNumber},card_profile_attributes:{wallet_payload:e.paymentMethodData.tokenizationData.token}}}}var V={country:212,full:452},se=80,H={country:400,full:640};function U(e,t={cardholderName:!1},n=`country`){let r=Object.entries(t).filter(([,e])=>e).map(([e])=>e).join(`,`),a=new URLSearchParams({token:e,additionalFields:r,billingAddressRequirement:n});return`${i(e)}/iframe/card?${a}`}function W(e,t=`country`){let n=new URLSearchParams({token:e,billingAddressRequirement:t});return`${i(e)}/iframe/bank?${n}`}function G(e={cardholderName:!1},t=`country`){return`${(V[t]??V.country)+(e.cardholderName?se:0)}px`}function K(e=`country`){return`${H[e]??H.country}px`}function q(e,t){let n={...t};function r(t){if(t.source===e.contentWindow)switch(t.data.type){case`IFRAME_READY`:d(e),f({iframe:e,appearance:n.appearance});break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:f({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`FORM_VALIDITY_CHANGE`:if(s(e)?.requiresVerification)break;n.onValidityChange?.({isValid:t.data.isValid});break;case`CARD_BRAND_CHANGE`:n.onCardBrandChanged?.({brand:t.data.brand});break;case`CONFIRMATION_RESULT`:n.onResult(t.data.result)}}return window.addEventListener(`message`,r),{update(t){let r=`appearance`in t;n={...n,...t},r&&f({iframe:e,appearance:n.appearance})},destroy(){window.removeEventListener(`message`,r)}}}var J=`https://cdn.plaid.com/link/v2/stable/link-initialize.js`;function Y({amount:e,achThreshold:t}){return t==null?!1:e==null||e>=t}function ce(e){if(e==null)return;let t=e.trim();if(t===``)return;let n=Number(t.replace(/[^\d.]/g,``));if(Number.isFinite(n))return Math.round(n*100)}function le(e){return e.account_id??e.account?.id??e.accounts?.[0]?.id}function ue(e){let t=e.account??e.accounts?.[0];return{bankName:e.institution?.name??`Bank account`,last4:t?.mask??``}}var X;function de(){for(let e of document.querySelectorAll(`script[src="${J}"]`))e.remove()}function fe(){return typeof window>`u`?Promise.reject(Error(`Plaid Link requires a browser`)):window.Plaid?Promise.resolve():X||(de(),X=new Promise((e,t)=>{let n=document.createElement(`script`);n.src=J,n.async=!0;let r=e=>{n.remove(),X=void 0,t(Error(e))};n.addEventListener(`load`,()=>{if(window.Plaid){e();return}r(`Plaid Link failed to initialize`)},{once:!0}),n.addEventListener(`error`,()=>r(`Failed to load Plaid Link`),{once:!0}),document.head.append(n)}),X)}async function pe({token:e,onSuccess:t,onExit:n,signal:r}){if(await fe(),r?.aborted)return()=>{};if(!window.Plaid)throw Error(`Plaid Link failed to initialize`);let i=window.Plaid.create({token:e,onSuccess:t,onExit:e=>{n?.(e)}});return r?.aborted?(i.destroy(),()=>{}):(i.open(),()=>{i.destroy()})}var me=`amos-js-plaid-bank-ui-styles`,he=`
69
69
  .amos-js-plaid-panel {
70
70
  box-sizing: border-box;
71
71
  color: var(--foreground, oklch(0.145 0 0));
package/dist/index.mjs CHANGED
@@ -578,6 +578,9 @@ function q(e, t) {
578
578
  if (s(e)?.requiresVerification) break;
579
579
  n.onValidityChange?.({ isValid: t.data.isValid });
580
580
  break;
581
+ case "CARD_BRAND_CHANGE":
582
+ n.onCardBrandChanged?.({ brand: t.data.brand });
583
+ break;
581
584
  case "CONFIRMATION_RESULT": n.onResult(t.data.result);
582
585
  }
583
586
  }
@@ -1,4 +1,4 @@
1
- import { Appearance, ConfirmationResult, PaymentMethodFormValidityChangeEvent } from './types';
1
+ import { Appearance, ConfirmationResult, PaymentMethodFormCardBrandChangeEvent, PaymentMethodFormValidityChangeEvent } from './types';
2
2
  /**
3
3
  * The additional fields beyond the standard card number, expiration
4
4
  * date, CVV, and billing address fields that are required to be filled
@@ -65,6 +65,13 @@ export type PaymentMethodFormListenerOptions = {
65
65
  * PCI data. Use this to enable or disable a host checkout button.
66
66
  */
67
67
  onValidityChange?: (event: PaymentMethodFormValidityChangeEvent) => void;
68
+ /**
69
+ * Called when the detected card brand changes. `brand` is the matched
70
+ * network, or `null` when the field is empty or the digits do not
71
+ * match a known brand. Does not include PCI data. Credit-card form
72
+ * only — never fired for bank account.
73
+ */
74
+ onCardBrandChanged?: (event: PaymentMethodFormCardBrandChangeEvent) => void;
68
75
  /**
69
76
  * Called when the interactive confirmation flow finishes (success or
70
77
  * terminal failure). Not settlement proof — verify via webhooks.
package/dist/types.d.ts CHANGED
@@ -7,6 +7,18 @@ import { components } from '@amos.com/node';
7
7
  export type PaymentMethodFormValidityChangeEvent = {
8
8
  isValid: boolean;
9
9
  };
10
+ /**
11
+ * Detected card network from the PAN prefix inside the iframe.
12
+ * `null` when the field is empty or the digits do not match a known brand.
13
+ */
14
+ export type CardBrand = "visa" | "mastercard" | "amex" | "discover" | "diners" | "jcb";
15
+ /**
16
+ * PCI-safe card brand update from the credit-card iframe. Does not
17
+ * include the PAN, last4, or BIN.
18
+ */
19
+ export type PaymentMethodFormCardBrandChangeEvent = {
20
+ brand: CardBrand | null;
21
+ };
10
22
  /**
11
23
  * Why an interactive confirmation attempt ended with `status: "incomplete"`.
12
24
  *
@@ -226,6 +238,14 @@ export type Message = {
226
238
  */
227
239
  type: "FORM_VALIDITY_CHANGE";
228
240
  isValid: boolean;
241
+ } | {
242
+ /**
243
+ * Embed → parent: detected card brand changed. `brand` is the
244
+ * matched network, or `null` when the field is empty or the
245
+ * digits do not match a known brand. Does not include PCI data.
246
+ */
247
+ type: "CARD_BRAND_CHANGE";
248
+ brand: CardBrand | null;
229
249
  } | {
230
250
  /**
231
251
  * Embed → parent: merchant ACH verification threshold for this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amos.com/amos-js",
3
- "version": "0.9.18",
3
+ "version": "0.10.0",
4
4
  "main": "dist/index.js",
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.ts CHANGED
@@ -94,10 +94,12 @@ export type {
94
94
  Appearance,
95
95
  AppearanceLabels,
96
96
  ApplePayButtonElementProps,
97
+ CardBrand,
97
98
  ConfirmationIncompleteReason,
98
99
  ConfirmationResult,
99
100
  GooglePayButtonElementProps,
100
101
  Message,
102
+ PaymentMethodFormCardBrandChangeEvent,
101
103
  PaymentMethodFormValidityChangeEvent,
102
104
  ThemeVariable,
103
105
  } from "./types";
@@ -8,6 +8,7 @@ import type {
8
8
  Appearance,
9
9
  ConfirmationResult,
10
10
  Message,
11
+ PaymentMethodFormCardBrandChangeEvent,
11
12
  PaymentMethodFormValidityChangeEvent,
12
13
  } from "./types";
13
14
 
@@ -138,6 +139,13 @@ export type PaymentMethodFormListenerOptions = {
138
139
  * PCI data. Use this to enable or disable a host checkout button.
139
140
  */
140
141
  onValidityChange?: (event: PaymentMethodFormValidityChangeEvent) => void;
142
+ /**
143
+ * Called when the detected card brand changes. `brand` is the matched
144
+ * network, or `null` when the field is empty or the digits do not
145
+ * match a known brand. Does not include PCI data. Credit-card form
146
+ * only — never fired for bank account.
147
+ */
148
+ onCardBrandChanged?: (event: PaymentMethodFormCardBrandChangeEvent) => void;
141
149
  /**
142
150
  * Called when the interactive confirmation flow finishes (success or
143
151
  * terminal failure). Not settlement proof — verify via webhooks.
@@ -210,6 +218,10 @@ export function attachPaymentMethodFormListeners(
210
218
  current.onValidityChange?.({ isValid: event.data.isValid });
211
219
  break;
212
220
 
221
+ case "CARD_BRAND_CHANGE":
222
+ current.onCardBrandChanged?.({ brand: event.data.brand });
223
+ break;
224
+
213
225
  case "CONFIRMATION_RESULT":
214
226
  current.onResult(event.data.result);
215
227
  break;
package/src/types.ts CHANGED
@@ -11,6 +11,26 @@ export type PaymentMethodFormValidityChangeEvent = {
11
11
  isValid: boolean;
12
12
  };
13
13
 
14
+ /**
15
+ * Detected card network from the PAN prefix inside the iframe.
16
+ * `null` when the field is empty or the digits do not match a known brand.
17
+ */
18
+ export type CardBrand =
19
+ | "visa"
20
+ | "mastercard"
21
+ | "amex"
22
+ | "discover"
23
+ | "diners"
24
+ | "jcb";
25
+
26
+ /**
27
+ * PCI-safe card brand update from the credit-card iframe. Does not
28
+ * include the PAN, last4, or BIN.
29
+ */
30
+ export type PaymentMethodFormCardBrandChangeEvent = {
31
+ brand: CardBrand | null;
32
+ };
33
+
14
34
  /**
15
35
  * Why an interactive confirmation attempt ended with `status: "incomplete"`.
16
36
  *
@@ -505,6 +525,15 @@ export type Message =
505
525
  type: "FORM_VALIDITY_CHANGE";
506
526
  isValid: boolean;
507
527
  }
528
+ | {
529
+ /**
530
+ * Embed → parent: detected card brand changed. `brand` is the
531
+ * matched network, or `null` when the field is empty or the
532
+ * digits do not match a known brand. Does not include PCI data.
533
+ */
534
+ type: "CARD_BRAND_CHANGE";
535
+ brand: CardBrand | null;
536
+ }
508
537
  | {
509
538
  /**
510
539
  * Embed → parent: merchant ACH verification threshold for this