diff --git a/src/generated/types/dropin.d.ts b/src/generated/types/dropin.d.ts index 90e0e78..72ab1bd 100644 --- a/src/generated/types/dropin.d.ts +++ b/src/generated/types/dropin.d.ts @@ -1,6 +1,8 @@ // Generated by dts-bundle-generator v9.5.1 import { AnyObject } from 'final-form'; +import { CSSResult, LitElement, PropertyValues, TemplateResult as TemplateResult_2, nothing } from 'lit'; +import { TemplateResult } from 'lit-html'; import { ZodIssue, z } from 'zod'; declare const Environments: readonly [ @@ -35,6 +37,7 @@ declare const Partners: { readonly illicado: "illicado"; readonly ingenico: "ingenico"; readonly klarna: "klarna"; + readonly lyra: "lyra"; readonly mercanet: "mercanet"; readonly oney: "oney"; readonly oneycard: "oneycard"; @@ -47,6 +50,7 @@ declare const Partners: { readonly riverty: "riverty"; readonly scalapay: "scalapay"; readonly scellius: "scellius"; + readonly sequra: "sequra"; readonly sipsv2: "sipsv2"; readonly slimpay: "slimpay"; readonly sogecommerce: "sogecommerce"; @@ -151,6 +155,14 @@ export type IntegratedToken = { source?: "MERCHANT" | "SHARED_WALLET"; savable_to_shared_wallet?: boolean; created_at?: string; + /** + * Network-token counterpart of this card token. When `available` is true and + * the experience enables network tokens (`settings.network_token`), the + * payment goes through the network token and no CVV is collected. + */ + network_token?: { + available?: boolean; + }; }; export interface MethodConfiguration { stable: boolean; @@ -166,12 +178,25 @@ export interface MethodConfiguration { settings: { registration_mode?: string; save_token?: boolean; + save_token_mode?: "ALWAYS" | "PREFER" | "PREFER_NO" | "NEVER"; direct_debit?: boolean; + /** + * Front-settings gate for network tokens. Tokens carrying an available + * `network_token` only pay through it (and hide their CVV) when this is + * true. + */ + network_token?: boolean; payment_min_amount?: number; payment_max_amount?: number; usage_per_session_limit?: number; merchant_specifics?: { integration_mode?: "redirection" | "popup"; + /** + * When truthy, the CVV field is hidden and not required when paying with + * a saved token. Configured per experience in the merchant back-office, + * so the value can reach the front as a string. + */ + pay_token_no_cvv?: boolean | "true" | "false"; }; }; } @@ -350,6 +375,11 @@ declare const PurseErrors: { message: string; documentationLink: string; }; + readonly API_UNKNOWN_ERROR: { + code: string; + message: string; + documentationLink: string; + }; }; export type PurseErrorCodes = keyof typeof PurseErrors; export type JsonSerializable = string | number | boolean | null | { @@ -449,7 +479,10 @@ export type ItemValidationData> = { partner: string; session_id: string; transaction_id: string; - plugin_result: Result; + plugin_result: Result & { + status?: string; + amount?: number; + }; status: { action: TransactionAction; state: StatusState; @@ -514,19 +547,33 @@ export interface LoanSimulation { export type WalletItem = { id: string; name?: string; + favorite?: boolean; }; declare class ApiError extends PurseError { readonly path: string; readonly error: string; responseHeaders?: Record; + /** HTTP status of the response that produced this error, when available (e.g. 400, 429, 500). */ + status?: number; constructor(error: (typeof PurseErrors)[PurseErrorCodes], path?: string, errorCode?: string, message?: string); } +/** + * Which product an SDK is embedded in, when it can be embedded by another. + * + * `direct` means the merchant integrated it themselves. Always sent explicitly so a + * missing value means "SDK build predates wrapper reporting" rather than "direct". + * + * @internal Not part of the public integration surface — set by the wrapping product. + */ +export type MonitoringWrapper = "direct" | "dropin"; export interface MonitoringApiEventMessage { instanceId?: string; entityId?: string; paymentSessionId?: string; walletSessionId?: string; version?: string; + /** @internal See {@link MonitoringWrapper}. */ + wrapper?: MonitoringWrapper; env?: EnvironmentTarget; event: Event; } @@ -556,11 +603,12 @@ export interface WalletAPI { deleteAllTokens(): Promise; setAsFavorite(data: { id: string; + favorite: boolean; }): Promise; } export interface MonitoringAPI { getApiKey(): string; - patchExtrasData(E: Pick, "instanceId" | "entityId" | "paymentSessionId" | "walletSessionId" | "version">): void; + patchExtrasData(E: Pick, "instanceId" | "entityId" | "paymentSessionId" | "walletSessionId" | "version" | "wrapper">): void; sendLogs(E: E[]): void; } export type APIPaths = { @@ -576,7 +624,7 @@ declare class FetchMonitoringAPI implements MonitoringAPI { private extras; constructor(env: EnvironmentTarget, root: string, apiKey: string); getApiKey(): string; - patchExtrasData: (payload: Partial, "instanceId" | "entityId" | "paymentSessionId" | "walletSessionId" | "version">>) => void; + patchExtrasData: (payload: Partial, "instanceId" | "entityId" | "paymentSessionId" | "walletSessionId" | "version" | "wrapper">>) => void; sendLogs: (events: Event[]) => void; } export type Subscriber = (value: T) => void; @@ -693,6 +741,42 @@ export declare interface CreditCardValidateUiResult { export declare type DeepPartial = T extends any[] ? T : { [P in keyof T]?: DeepPartial; }; +export declare type EnvSource = { + id: string; + singleton?: boolean; + production: Source; +} & { + [key in ExternalSourceEnv]?: Source; +}; +export declare type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes]; +declare const ErrorCodes: { + readonly MethodIsNotImplementedByPlugin: "MethodIsNotImplementedByPlugin"; + readonly PluginEncounteredALethalException: "PluginEncounteredALethalException"; + readonly PluginIsIncompatibleWithClient: "PluginIsIncompatibleWithClient"; + readonly PreValidationIsntAvailableForPayButton: "PreValidationIsntAvailableForPayButton"; + readonly PostValidationDataIsMissingInformations: "PostValidationDataIsMissingInformations"; + readonly CannotInitWithoutSession: "CannotInitWithoutSession"; + readonly CannotSubmitWithoutClient: "CannotSubmitWithoutClient"; + readonly CannotPreValidateWithoutPlugin: "CannotPreValidateWithoutPlugin"; + readonly FormValidationFailed: "FormValidationFailed"; + readonly ClientNotFoundWhenSettingSession: "ClientNotFoundWhenSettingSession"; + readonly ClientNotFoundAfterSourceLoad: "ClientNotFoundAfterSourceLoad"; + readonly PreValidationFailed: "PreValidationFailed"; + readonly PostValidationFailed: "PostValidationFailed"; + readonly DynamicAmountNotInSessionRange: "DynamicAmountNotInSessionRange"; + readonly CantSetBrandThatIsNotAmongMatches: "CantSetBrandThatIsNotAmongMatches"; + readonly CannotPostValidateWithoutPlugin: "CannotPostValidateWithoutPlugin"; + readonly MissingDataFromContext: "MissingDataFromContext"; + readonly "3DSCantProceedWithoutRedirectionURL": "3DSCantProceedWithoutRedirectionURL"; + readonly "3DSFingerprintingFailed": "3DSFingerprintingFailed"; + readonly APIResultUnsatisfying: "APIResultUnsatisfying"; + readonly PluginInitFailed: "PluginInitFailed"; + readonly TargetNotInDocument: "TargetNotInDocument"; + readonly MissingTargetFromConfig: "MissingTargetFromConfig"; + readonly MultipleMatchesForTargetInDocument: "MultipleMatchesForTargetInDocument"; + readonly PartnerDoesNotSupportUI: "PartnerDoesNotSupportUI"; + readonly HolderNameFieldRequired: "HolderNameFieldRequired"; +}; export declare type ErrorPayload = { reason?: string; } & Record; @@ -888,6 +972,25 @@ declare const Events: { "partnerError" ]; }; + /** + * The plugin's own integration broke for this attempt — a dead popup, a + * severed window transport — and the failure will not clear by retrying, but + * it is not permanent either: a fresh amount or session can make the method + * work again. + * + * Distinct from `partnerError`, which also carries ordinary payment outcomes + * (Lyra reports declined cards through it). Sharing both topics means every + * existing `error`/`partnerError` subscriber keeps receiving it; what the host + * does about it — deactivating the method, and when to lift that — is the + * host's policy, not the plugin's. + */ + readonly partnerRecoverableError: { + readonly code: "partnerRecoverableError"; + readonly topics: readonly [ + "error", + "partnerError" + ]; + }; readonly change: { readonly code: "change"; readonly topics: readonly [ @@ -955,9 +1058,10 @@ declare const Events: { ]; }; }; +export declare type ExternalSourceEnv = "production" | "sandbox" | "test" | "development"; export declare type FieldValidationError = (typeof FieldValidationErrors)[keyof typeof FieldValidationErrors]; declare const FieldValidationErrors: { - readonly cannotBeEmpty: "cannotBeEmpty"; + readonly required: "required"; readonly dateOutOfRange: "dateOutOfRange"; readonly format: "format"; readonly lengthOutOfRange: "lengthOutOfRange"; @@ -1063,19 +1167,24 @@ export declare interface HostedFieldsUIOptions { * Some partners displays a complete form and let you configure the text through a JS configuration. We've mapped those * texts and let you override all off them form this single object. During a session, while displaying a form some of * the variables might not be used, it depends on the context. + * + * Every field is plain text and is rendered as such. The only exceptions are `consentText` and + * `loanSimulation`, which accept limited HTML — see their own docs. */ export declare interface HostedFormUIOptions { panPlaceholder?: string; panInputLabel?: string; panRequiredError?: string; - panFormatError?: string; + /** @deprecated use panRequiredError */ panCannotBeEmptyError?: string; + panFormatError?: string; panTooltipText?: string; birthdatePlaceholder?: string; birthdateInputLabel?: string; birthdateRequiredError?: string; - birthdateFormatError?: string; + /** @deprecated use birthdateRequiredError */ birthdateCannotBeEmptyError?: string; + birthdateFormatError?: string; birthdateOutOfRangeError?: string; birthdateTooltipText?: string; cvvPlaceholder?: string; @@ -1086,6 +1195,9 @@ export declare interface HostedFormUIOptions { * This value is interpolated with {url:string} * use {{variable}} to use interpolation * @example 'Your will agree to the tos ' + * + * Accepts HTML, sanitised by allowlist: a, b, strong, em, i, u, span, br, p, ul, ol, li, + * with href/target/rel/style only. `href` must be an absolute https URL. */ consentText?: string; /** @@ -1097,8 +1209,9 @@ export declare interface HostedFormUIOptions { */ cvv4InputLabel?: string; cvvRequiredError?: string; - cvvFormatError?: string; + /** @deprecated use cvvRequiredError */ cvvCannotBeEmptyError?: string; + cvvFormatError?: string; cvvTooltipText?: string; /** * When true, the CVV field is hidden and not required for payment. @@ -1113,30 +1226,35 @@ export declare interface HostedFormUIOptions { expirationPlaceholder?: string; expirationInputLabel?: string; expirationRequiredError?: string; - expirationFormatError?: string; + /** @deprecated use expirationRequiredError */ expirationCannotBeEmptyError?: string; + expirationFormatError?: string; expirationOutOfRangeError?: string; expirationTooltipText?: string; holderPlaceholder?: string; holderInputLabel?: string; holderRequiredError?: string; - holderFormatError?: string; + /** @deprecated use holderRequiredError */ holderCannotBeEmptyError?: string; + holderFormatError?: string; ibanPlaceholder?: string; ibanInputLabel?: string; ibanRequiredError?: string; - ibanFormatError?: string; + /** @deprecated use ibanRequiredError */ ibanCannotBeEmptyError?: string; + ibanFormatError?: string; emailPlaceholder?: string; emailInputLabel?: string; emailRequiredError?: string; - emailFormatError?: string; + /** @deprecated use emailRequiredError */ emailCannotBeEmptyError?: string; + emailFormatError?: string; phonePlaceholder?: string; phoneInputLabel?: string; phoneRequiredError?: string; - phoneFormatError?: string; + /** @deprecated use phoneRequiredError */ phoneCannotBeEmptyError?: string; + phoneFormatError?: string; /** * This value is interpolated with: * - first @@ -1146,6 +1264,8 @@ export declare interface HostedFormUIOptions { * - cost * - taeg * use {{variable}} to use interpolation + * + * Accepts the same HTML allowlist as `consentText`. */ loanSimulation?: string; /** @@ -1165,16 +1285,13 @@ declare const IntegrationTypes: { readonly xPayButton: "xPayButton"; readonly redirection: "redirection"; }; -export declare interface KRError_2 { - errorCode: string; - errorMessage: string; - detailedErrorCode?: string; - detailedErrorMessage?: string; - field?: string; - children?: Array; - metadata?: Record; -} -export declare type ManagedHostedFieldValue = "valid" | Extract | null; +export declare type Interface = (typeof Interfaces)[keyof typeof Interfaces]; +declare const Interfaces: { + readonly form: "form"; + readonly hostedfields: "hostedfields"; + readonly payButton: "payButton"; +}; +export declare type ManagedHostedFieldValue = "valid" | Extract | null; /** * Options let you override theme configuration and translations on our UIs. */ @@ -1210,6 +1327,173 @@ export declare type Options = { */ locale?: string; }; +/** + * SDK-agnostic partner error contract. `events.ts` must not know any specific + * partner SDK's internals — the normalized `message` is the stable contract + * consumers read; the raw partner error is carried opaquely in `originalError`. + */ +export declare interface PartnerErrorPayload { + /** Normalized, contextual message. The stable contract consumers read. */ + message: string; + /** Raw partner SDK error. Opaque — debug only, never introspect its shape or cast it. */ + originalError?: unknown; +} +declare class PartnerUI> extends LitElement { + #private; + private sourceDownloaded; + private plugin; + private extraSources; + private data; + private readonly context; + session: LegacyPaymentSession | undefined; + env: APIEnv | null; + options: Options; + amount?: number; + register: boolean | undefined; + partner: Partner; + method: Method; + interface: Interface; + token?: Token; + /** + * A payzen xpay specific function to trigger onBeforeValidate hook outside the payment process + * This function should throw if the onBeforeValidate fails, allowing us to `return false` in the `onClick` of the smartForm + * Returning false in the onClick prevents to submit + */ + __xpay_payzen_specific_triggerOnBeforeValidate__: (method: Extract) => Promise; + constructor(); + private get envName(); + dispatchUnifiedEvent: (code: K, payload?: UnifiedEventPayload[K] | null) => void; + preValidation(): Promise; + postValidation(data: ValidationResultData, redirectionHandler?: RedirectionHandler): Promise; + /** + * Use this method to run the validation of the UI. It can be useful to trigger a validation of the UI, in a "onsubmit" validation workflow for instance. + * This should not be confused with the "validate" concept which is the API call to validate the payment. + * */ + validateUi(): Promise; + /** + * When detected brands are multiple, use this method to set the selected brand. + * default is detectedBrands[0] + * @param brand the brand to select + * @returns void + **/ + setSelectedBrand(brand: CardScheme): void; + connectedCallback(): void; + /** + * The element has no shadow root and is mounted into merchant-controlled DOM, so anything that + * serialises markup — session replay, error reporters, extension content scripts — captures + * attribute values. `session` alone carries every saved card in the session. These three stay + * supported as attribute *inputs* (`attribute: false` would silently starve callers that still + * pass them, breaking saved-card payments with no error), but nothing is left behind in the DOM. + * SDK-12163. + * + * Called from `connectedCallback`, which is where it is safe: custom-element upgrade replays + * `attributeChangedCallback` for existing attributes *before* `connectedCallback`, so Lit has + * already parsed them, and `connectedCallback` runs synchronously inside `appendChild`, so no + * script can read them in between. + * + * The value has to be re-assigned after removal: `removeAttribute` re-enters Lit's + * `attributeChangedCallback` with `null`, and the default `type: Object` converter maps that + * back to `null` — removing the attribute would otherwise wipe the property it just parsed. + * + * Only ever scrub inputs that callers pass by *property*. Svelte caches the attribute values it + * wrote on the node and skips a same-value rewrite, so an attribute removed behind its back + * never comes back — `amount`, `partner`, `method` and `interface` must stay out of here. + */ + private scrubSecretAttributes; + disconnectedCallback(): void; + performUpdate(): void; + private updateData; + private loadExtraSources; + private getSourceListener; + updated(changedProperties: PropertyValues): void; + willUpdate(changedProperties: PropertyValues): void; + createRenderRoot(): this; + render(): typeof nothing | TemplateResult<1>; +} +/** + * The partner ui context is the piece of data & abilities that we give to the plugins to operate with. + */ +export declare interface PartnerUIContext> { + /** + * The context provides a way to dispatch UnifiedEvent. This is required because our plugins is responsible for + * dispatching key events like fulfillment. + */ + dispatchEvent: (code: K, payload?: UnifiedEventPayload[K] | null) => void; + /** + * This is a reference to the partner ui root node, it is useful for plugins that need to append elements to the DOM + */ + shadowRoot: HTMLElement; + /** + * Data in the context is the "reactive" part, so you can read it through an accessor. + */ + data: () => Data; + /** + * This is the setter for the data. It takes a partial object of Data and patch the current state with it. + * If the references you passed changed the original ones, it will rerender the UI and also call the plugin's onDataChanged + */ + updateData: (data: Partial) => void; + /** + * The current payment session, can be undefined if no session is set. + */ + session?: LegacyPaymentSession; + /** + * This amount could be less than the session total, and plugins might want to perform action when it changes. + * This amount is less than the total when a secondary is involved for instance. + */ + amount?: number; + /** + * The redirection interface allows the plugin to gracefully redirect to another location. + */ + redirection: RedirectionHandler; + /** + * A helper to make calls to the API. We give it the API Root. + */ + api: PaymentAPI; + /** + * The env we're running the plugin in. This is useful for plugins that need to download extra sources. + */ + envName?: EnvironmentTarget; + /** + * Gives the ability to download extra sources at runtime + */ + loadExtraSources: (sources: EnvSource[]) => Promise; + /** + * If the partner UI renders a token, this will be set with the token extracted from session + */ + token?: Token; + /** + * The current partner ui requested interface + */ + ui: Interface; + /** + * The current partner ui's options + */ + options: Options; + /** + * Partner ui theme + */ + theme: Theme; + /** + * If the partners create some sort of client (Securefields, paypal...), its reference can be stored here + */ + client?: Client; + /** + * Current partner ui register attribute. + */ + register?: boolean; + /** + * This is a wild card that can be updated by the plugin to keep references without re-rendering the view + */ + dmz: { + initialStepId: number | null; + } & Record; + /** + * A payzen xpay specific function to trigger onBeforeValidate hook outside the payment process + * This function should throw if the onBeforeValidate fails, allowing us to `return false` in the `onClick` of the smartForm + * Returning false in the onClick prevents to submit + */ + __xpay_payzen_specific_triggerOnBeforeValidate__: (method: Extract) => Promise; +} /** * A theme is carrying all the style variables use across our UIs. * global can be seen as a default values store on which the other scopes will rely on to fall back a value. @@ -1324,6 +1608,13 @@ export declare type PartnerUITheme = { helperText: ThemeScope<"helperText">; tooltip: ThemeScope<"tooltip">; }; +declare class PursePartnerUI> extends PartnerUI { +} +export declare interface RedirectionHandler { + submitRedirectionForm(action: string, serializedFormData: string): Promise | void; + submitStringRedirectionForm(id: string, responseFormAsString: string): Promise | void; + redirectToUrl(url: string): Promise | void; +} export declare interface RedirectionUIOptions { title?: string; } @@ -1331,6 +1622,37 @@ export declare type Source = { type: "script" | "link"; attributes: Record; }; +declare class Theme { + static readonly UNEXPOSED: { + "background-color": string; + "brand-select-width": string; + "gap-small": string; + "gap-tiny": string; + "input-height": string; + "divider-margin": string; + "divider-bg": string; + }; + static readonly defaultTheme: PartnerUIOptionsTheme; + private _currentTheme; + private readonly _setProperty; + constructor(_setProperty: Theme["_setProperty"], theme?: PartnerUIOptionsTheme); + setTheme(theme?: PartnerUIOptionsTheme): void; + getValue(scope: Scope, key: keyof PartnerUITheme[Scope], variant?: ThemeScopesVariants): string | undefined; + getGlobalOverrides(): CSSResult; + applyCurrentTheme(): void; + private get currentTheme(); + private set currentTheme(value); + /** + * This method is in charge of creating a complete theme (not partial) using the default theme. + * if the `input['::placeholder']['color']` is not defined + * It will fall back to `input['color']` + * then to `global['::placeholder']['color']` + * and then to `global['color']` + * @param partialThemeFromOptions + * @private + */ + private scaffoldThemeValues; +} declare const THEME_SCOPES: readonly [ "global", "input", @@ -1432,6 +1754,16 @@ export declare type ThemeScopesVariants = ThemeSco export declare type ThemeScopesVariantsMap = { [K in ThemeScopeName]: (typeof THEME_SCOPES_VARIANTS)[K][number]; }; +export declare type Token = { + id: string; + expiration_date: string; + value: string; + selected_network: string; + holder_name: string; + description: { + display_token: string; + }; +}; export declare interface UnifiedEventPayload { clientError: ErrorPayload | null; clientNotFound: ErrorPayload | null; @@ -1484,9 +1816,13 @@ export declare interface UnifiedEventPayload { interface: string; fallBackInterface: string; }; + /** `code`/`reason` carry the rejecting `UnifiedError`, so the log says which check refused. */ pluginIsIncompatibleWithClient: { partner: Partner; method: Method; + code?: ErrorCode; + reason?: string; + stack?: string; }; pluginPostValidation: Record | null; pluginPreValidation: Record | null; @@ -1506,8 +1842,14 @@ export declare interface UnifiedEventPayload { } | null; other: Record | null; partnerError: { - error: Omit; + error: PartnerErrorPayload; + method: string; + partner: string; + }; + partnerRecoverableError: { + error: PartnerErrorPayload; method: string; + partner: string; }; change: HostedFieldsEventPayload | null; focus: HostedFieldsEventPayload | null; @@ -1542,16 +1884,45 @@ export declare interface XPayButtonUIOptions { * @example 'fr-FR' */ locale?: string; + /** + * Text preceding the Apple Pay logo, on partners rendering their own button label (e.g. Lyra smart form). + * Native Apple Pay buttons ignore it, they carry their own localized wording. + * Defaults to a locale-based translation. + * @example 'Buy with' + */ + label?: string; }; google?: GooglePayButtonOptions & { height?: number; + /** + * Text preceding the Google Pay logo, on partners rendering their own button label (e.g. Lyra smart form). + * Native Google Pay buttons ignore it, they carry their own localized wording. + * Defaults to a locale-based translation. + * @example 'Buy with' + */ + label?: string; }; paypal?: { + /** + * @deprecated Ignored since the migration to PayPal JS SDK v6: layout was a + * property of the v5 multi-button `paypal.Buttons()` container, and v6 renders + * exactly one button web component per `` element. Kept for + * source compatibility — remove on the next breaking release. + */ layout?: "vertical" | "horizontal"; color?: "silver" | "white" | "black" | "gold" | "blue"; - shape?: "rect" | "pill"; + shape?: "rect" | "pill" | "sharp"; height?: number; - label?: "paypal" | "checkout" | "buynow" | "pay" | "installment"; + /** + * Button wording. `paypal` renders the wordmark alone and is the default; + * `installment` was dropped by the PayPal JS SDK v6 and falls back to it. + */ + label?: "paypal" | "checkout" | "buynow" | "pay" | "subscribe" | "donate" | "installment"; + /** + * Corner radius of the button, in pixels. Takes precedence over `shape`. + * See https://developer.paypal.com/sdk/js/reference/#borderRadius + */ + borderRadius?: number; /** * When true, removes the max-width constraint applied by PayPal on the button container. * See https://developer.paypal.com/sdk/js/reference/#width @@ -1566,6 +1937,11 @@ export interface CardSchemeAsset { declare class ApiLogger extends FetchMonitoringAPI> { constructor(env: EnvironmentTarget, apiKey: string, apiRoot?: string); } +declare function buildPaymentElementAccessors(create: (inter: I) => I extends "form" ? PurseHeadlessCheckoutPaymentElement_2 : PurseHeadlessCheckoutHostedFields_2): { + getPaymentElement: (options?: Omit) => PurseHeadlessCheckoutPaymentElement_2; + getHostedFields: (options?: HostedFieldsOptions) => PurseHeadlessCheckoutHostedFields_2; + getElementInstance: () => PurseHeadlessCheckoutPaymentElement_2 | PurseHeadlessCheckoutHostedFields_2 | undefined; +}; /** * Represents the disabled state of a payment method. * @property code - {@link DisabledStateCode} The code indicating the reason for the disabled state. @@ -1590,6 +1966,7 @@ export declare type DisabledStatePayload = { PAYMENT_ELEMENT_RENDER_FAILED: never; PLUGIN_INCOMPATIBLE: never; PARTNER_UI_FATAL_ERROR: never; + PARTNER_UI_RECOVERABLE_ERROR: never; FATAL_ERROR_BEFORE_RENDER: never; MIN_AMOUNT_NOT_MET: { minAmount: number | null; @@ -1645,6 +2022,10 @@ export declare interface Payment { methods: Readable; tokens: Readable; walletTokens: Readable; + primaryMethodElementRegistry: Map; + registration: Writable; + }>; paymentSession: Readable; session: PaymentSession; walletSession: WalletSession | null; @@ -1865,9 +2246,11 @@ declare class PurseHeadlessCheckout { * - 'idle': The session is idle and not yet initialized. * - 'ready': The session is initialized and ready for payment method selection. * - 'submitting': The payment is being submitted. - * - 'submitted': The payment has been successfully submitted. + * - 'submitted': The session was validated successfully and is now spent. Further submissions + * are refused, since the API accepts one validation per session. * - 'expired': The session has expired. - * - 'error': An error occurred with the session. + * - 'error': An error occurred with the session, including a validation the API refused or a + * payment it declined. The session was not consumed, so submitting again is allowed. * @example * ```ts * headlessCheckout.sessionState.subscribe((state: PurseHeadlessCheckoutSessionState) => { @@ -1958,11 +2341,28 @@ declare class PurseHeadlessCheckout { */ setSession(widgetData: PurseHeadlessCheckoutV2Params): Promise; setSession(widgetData: PurseHeadlessCheckoutV1Params["paymentSession"]): Promise; + /** + * Shared submission path. Both entry points funnel through here — the public + * `submitPayment()` and express/wallet plugins requesting validation themselves. + * + * The `HEADLESS_CHECKOUT_SUBMIT_PAYMENT` event is emitted here rather than on the + * public method because `withMonitoringProxy` only sees calls made *through* the + * returned proxy. Internal callers hold a reference bound in the constructor, + * before the proxy exists, so monitoring attached to the public method never fires + * for them — which silently lost every wallet submission. + * + * It is emitted before the session and fulfilment guards on purpose: the event + * means "a submission was attempted", so guard failures stay visible. Pair it with + * `HEADLESS_CHECKOUT_PAYMENT_ERROR` to see which attempts were rejected. + */ private _submitPayment; /** * Submits the current payment configuration for processing. * This will validate and process all payment methods in the current split configuration. * + * Resolves without doing anything when a submission is already in flight, or when the session + * has already been validated successfully. Watch `sessionState` to tell the two apart. + * * @throws * - {@link PurseHeadlessCheckoutError} `MISSING_SESSION` \ * If the session is missing @@ -2071,6 +2471,67 @@ export declare interface PurseHeadlessCheckoutEditable { name: string; }): Promise; } +declare class PurseHeadlessCheckoutElement { + private readonly manuallyTriggerHook; + private readonly onSessionTerminated?; + protected readonly item: PurseHeadlessCheckoutPaymentItemBase; + protected readonly payment: Payment; + private fulfilled; + private _wasEverReady; + private readonly registration?; + private readonly amount?; + private readonly interface?; + private readonly env; + protected readonly method: Derived; + private storeListeners; + protected readonly options: Writable; + protected UIListeners: (() => void)[]; + private readonly eventsMap; + ui: PursePartnerUI | null; + constructor(item: PurseHeadlessCheckoutPaymentItemBase, data: { + payment: Payment; + env: APIEnv; + registration?: Readable; + amount?: Readable; + interface?: Interface; + }, manuallyTriggerHook: (hookName: keyof PurseHeadlessCheckoutHooks, context?: { + actualMethod?: Method; + sourceId?: string; + }) => Promise, onSessionTerminated?: (() => void) | undefined); + getInterface(): Interface | undefined; + wasEverReady(): boolean; + remove: () => void; + setOptions(options?: Options): void; + hasUI(): boolean; + /** + * This getter is in sync with the "formValid" property of the partner-ui + */ + isFulfilled(): boolean; + /** + * Registers a callback and returns the function that unregisters it. Hosts that + * mount the same element again (dropin remounts its whole list on `setSession`) + * must call it on teardown, otherwise every mount leaves another live callback + * behind and a single `ready` fans out to all of them (SDK-12109). + * + * `remove()` deliberately keeps the map intact: `createPaymentElement` wires the + * splitter and submit plumbing through it once per instance, and the instance is + * cached and reused across remounts, so clearing it here would silently break + * split computation and payment submission. + */ + on(eventName: K, callback: PaymentElementEventsCallback[K]): () => void; + private attachUIListeners; + protected buildUI(): PursePartnerUI; + /** + * Validates the UI of the payment element. + * + * This method triggers the validation process of the UI component associated with the payment element. + * This is purely a form validation function. It can be useful if you want to trigger validation before submitting payment. + * + * @returns {Promise} A promise that resolves with true or the result of the validation + */ + validateUi(): Promise; + private clearListeners; +} declare class PurseHeadlessCheckoutError extends Error { readonly code: PurseHeadlessCheckoutErrorCodes; readonly documentationLink: string; @@ -2274,6 +2735,11 @@ declare const PurseHeadlessCheckoutErrors: { message: string; documentationLink: string; }; + readonly LOYALTY_ACCOUNT_NOT_FOUND: { + code: string; + message: string; + documentationLink: string; + }; readonly SECONDARY_TOKEN_EMPTY_BALANCE: { code: string; message: string; @@ -2342,6 +2808,7 @@ export declare interface PurseHeadlessCheckoutErrorsPayload { method: Method; uniqueness_token: string; }; + LOYALTY_ACCOUNT_NOT_FOUND?: never; SECONDARY_TOKEN_EMPTY_BALANCE?: never; USER_ACTION_REQUEST_HAS_EXPIRED: never; USER_ACTION_REQUEST_ABORTED_BY_CUSTOMER: never; @@ -2434,7 +2901,10 @@ export declare interface PurseHeadlessCheckoutEventPayload { partner: string; method: string; }; - HEADLESS_CHECKOUT_CLIENT_HOOK_ERROR: Record; + HEADLESS_CHECKOUT_CLIENT_HOOK_ERROR: { + error: unknown; + message?: string; + }; HEADLESS_CHECKOUT_CREATE: { sessionType: "v1" | "v2"; screen: { @@ -2528,32 +2998,22 @@ export declare interface PurseHeadlessCheckoutEventPayload { }; HEADLESS_CHECKOUT_VALIDATION_SUCCESS: { global_status: string; - results: { - method: string; - partner: string; - status: { - state: string; - code: string; - description?: string; - }; - }[]; + results: PurseHeadlessCheckoutValidationResultSummary[]; }; HEADLESS_CHECKOUT_VALIDATION_FAILED: { global_status: string; - results: { - method: string; - partner: string; - status: { - state: string; - code: string; - description?: string; - }; - }[]; + results: PurseHeadlessCheckoutValidationResultSummary[]; }; HEADLESS_CHECKOUT_PAYMENT_ERROR: { message?: string; api_status?: string; stack?: string; + /** Why the payment failed, attempt by attempt. Empty array when the API returned no result. */ + results?: PurseHeadlessCheckoutValidationResultSummary[]; + /** Whether the `/validate` response carried a redirection URL of its own. */ + has_redirection?: boolean; + /** Set when the failure came from a thrown error rather than from an API status. */ + error_name?: string; }; HEADLESS_CHECKOUT_PAYMENT_FULFILLED: { isFulfilled: boolean; @@ -2596,8 +3056,18 @@ export declare interface PurseHeadlessCheckoutEventPayload { message?: string; stack?: string; }; + /** + * A submission was refused by the re-entrancy guard: one was already in flight, or the session + * had already been validated. Counterpart of widget V3's + * `CHECKOUT_PRE_VALIDATION_ALREADY_ROLLING` / `CHECKOUT_CURRENT_SESSION_ALREADY_VALIDATED`. + */ + HEADLESS_CHECKOUT_SUBMIT_BLOCKED: { + state: PurseHeadlessCheckoutSessionState; + trigger: PurseHeadlessCheckoutSubmitTrigger; + }; HEADLESS_CHECKOUT_SUBMIT_PAYMENT: { splits: PaymentSplit[]; + trigger: PurseHeadlessCheckoutSubmitTrigger; }; HEADLESS_CHECKOUT_TOKEN_DELETE: { partner: string; @@ -2609,7 +3079,16 @@ export declare interface PurseHeadlessCheckoutEventPayload { method: string; tokenId?: string; }; - HEADLESS_CHECKOUT_UNCAUGHT_ERROR: Record; + HEADLESS_CHECKOUT_TOKEN_SET_FAVORITE: { + partner: string; + method: string; + tokenId?: string; + favorite: boolean; + }; + HEADLESS_CHECKOUT_UNCAUGHT_ERROR: { + stack: string; + message?: string; + }; HEADLESS_CHECKOUT_ON_BEFORE_VALIDATE_HOOK_FAILED: { message: string; error: { @@ -2690,6 +3169,25 @@ export declare interface PurseHeadlessCheckoutHostedFields extends Omit; + detectedBrands: Readable; + selectedBrand: Readable; + constructor(item: PurseHeadlessCheckoutPaymentItemBase, data: { + payment: Payment; + env: APIEnv; + registration?: Readable; + amount?: Readable; + interface?: Interface; + }, manuallyTriggerHook: (hookName: keyof PurseHeadlessCheckoutHooks, context?: { + actualMethod?: Method; + sourceId?: string; + }) => Promise, onSessionTerminated?: () => void); + protected buildUI(): PursePartnerUI; + setSelectedBrand(brand: CardScheme): void; + setOptions(options?: Options): void; + render(): void; +} export declare interface PurseHeadlessCheckoutLoyaltyToken extends PurseHeadlessCheckoutTemporarySecondaryToken { method: "loyaltycard"; /** @@ -2844,15 +3342,25 @@ export declare interface PurseHeadlessCheckoutPaymentElement { * * @param {PaymentElementEventName} eventName - Name of the event to listen for * @param callback - Function to be called when the event occurs + * @returns A function that unregisters this callback. Call it when the host + * component unmounts: registering again on the same element without it leaves the + * previous callback live, and one event then fans out to every past registration. + * `remove()` does not unregister for you — the element instance is reused across + * remounts, so the callbacks it holds outlive it. * @example * ```typescript * // Listen for the UI ready event - * paymentElement.on('formValid', () => { + * const off = paymentElement.on('formValid', () => { * console.log('Payment UI form is valid'); * }); + * // …later, on teardown + * off(); * ``` */ - on(eventName: K, callback: PaymentElementEventsCallback[K]): void; + on(eventName: K, callback: PaymentElementEventsCallback[K]): () => void; +} +declare class PurseHeadlessCheckoutPaymentElement_2 extends PurseHeadlessCheckoutElement implements PurseHeadlessCheckoutPaymentElement { + appendTo(container: string | Element): void; } export declare type PurseHeadlessCheckoutPaymentItem = PurseHeadlessCheckoutPaymentMethod | PurseHeadlessCheckoutPaymentToken; /** @@ -3143,16 +3651,16 @@ export declare interface PurseHeadlessCheckoutPrimaryToken extends PurseHeadless /** Whether this token is the user's favorite payment method. Reflects `IntegratedToken.favorite`. */ isFavorite: Readable; /** - * Mark this token as the user's favorite. Persists the change via the wallet API. + * Toggle this token's favorite status. If it is already a favorite, it will be removed; + * otherwise it will be marked as the user's favorite. Persists the change via the wallet API. * * @throws - {@link PurseHeadlessCheckoutError} `MISSING_WALLET_SESSION` \ * If no wallet session is loaded - * @throws - {@link PurseHeadlessCheckoutError} `MISSING_TOKEN` \ - * If the token is not found in the wallet * * @example * ```typescript - * await token.setAsFavorite(); + * await token.setAsFavorite(); // marks as favorite + * await token.setAsFavorite(); // removes favorite status * ``` */ setAsFavorite(): Promise; @@ -3176,10 +3684,20 @@ export declare interface PurseHeadlessCheckoutRegisterable { name?: string; }): Promise; /** - * This property exposes the saveToken configuration of the method. - * A mean of payment cannot be saved if the method is not registerable. + * Whether the user can choose to register this method. + * True only for `save_token_mode` values `PREFER` and `PREFER_NO`. + * When false, the save-token checkbox must not be shown; the backend + * enforces the registration policy (ALWAYS/NEVER) without a frontend signal. */ canBeRegistered: boolean; + /** + * The effective `save_token_mode` resolved for this mean of payment + * (`ALWAYS` / `PREFER` / `PREFER_NO` / `NEVER`), including the `save_token` + * boolean fallback. Exposed for consumers that need to distinguish *why* + * registration is or isn't user-controllable — e.g. `ALWAYS` forces + * registration silently, with `canBeRegistered` false like `NEVER`. + */ + saveTokenMode: SaveTokenMode; /** * This property exposes if the mean of payment is bound for registration upon validate. */ @@ -3195,6 +3713,15 @@ export declare type PurseHeadlessCheckoutSecondaryMethod = PurseHeadlessCheckout limit?: number; /** Always true for secondary payment methods */ isSecondary: true; + /** + * Secondary methods (gift cards, vouchers, loyalty) have no payment element — they are + * entered via `getSecondaryToken()` / `take()`, not a rendered form. Calling this warns + * (with a documentation link) and returns `undefined` so the integration degrades + * gracefully instead of throwing a `TypeError`. + * + * @see https://docs.purse.tech/docs/integrate/purse-checkout/headless-checkout/advanced-guides/render-payment-methods#secondary-methods + */ + getPaymentElement(): undefined; } & SecondarySpecificConfig; /** * Represents a secondary token in the Purse checkout system. @@ -3212,6 +3739,15 @@ export declare type PurseHeadlessCheckoutSecondaryMethod = PurseHeadlessCheckout export declare interface PurseHeadlessCheckoutSecondaryToken extends PurseHeadlessCheckoutToken { type: "token" | "temporary_token"; isSecondary: true; + /** + * Secondary tokens (gift cards, vouchers, loyalty) have no payment element — they are + * applied via `take()`, not a rendered form. Calling this warns (with a documentation + * link) and returns `undefined` so the integration degrades gracefully instead of + * throwing a `TypeError`. + * + * @see https://docs.purse.tech/docs/integrate/purse-checkout/headless-checkout/advanced-guides/render-payment-methods#secondary-methods + */ + getPaymentElement(): undefined; limit: number; expiration_date: string | Date; /** @@ -3301,6 +3837,8 @@ export declare interface PurseHeadlessCheckoutSecondaryTokenProvider { * If secondary token creation fails * - {@link PurseHeadlessCheckoutError} `FAILED_TO_INIT_SECONDARY_TOKEN` \ * If secondary token initialization fails + * - {@link PurseHeadlessCheckoutError} `LOYALTY_ACCOUNT_NOT_FOUND` \ + * If the customer has no loyalty account on the partner side * - {@link PurseHeadlessCheckoutError} `MISSING_SESSION` \ * If the session is missing * - {@link PurseHeadlessCheckoutError} `PAYMENT_METHOD_NOT_FOUND` \ @@ -3315,6 +3853,14 @@ export declare interface PurseHeadlessCheckoutSecondaryTokenProvider { getSecondaryToken(pan: string, cvv?: string, signal?: AbortSignal): Promise; } export declare type PurseHeadlessCheckoutSessionState = "idle" | "ready" | "submitting" | "submitted" | "error" | "expired" | "terminated"; +/** + * How a payment submission was triggered. + * + * - `api`: the merchant called `submitPayment()`. + * - `xpay`: an express/wallet plugin (Apple Pay, Google Pay…) requested validation + * itself, without the merchant calling `submitPayment()`. + */ +export declare type PurseHeadlessCheckoutSubmitTrigger = "api" | "xpay"; export declare interface PurseHeadlessCheckoutTemporarySecondaryToken extends PurseHeadlessCheckoutSecondaryToken, PurseHeadlessCheckoutRegisterable { type: "temporary_token"; } @@ -3372,6 +3918,22 @@ export declare type PurseHeadlessCheckoutV1SessionData = LegacyPaymentSession; * @group Interfaces */ export declare type PurseHeadlessCheckoutV2Params = string; +/** + * Per-attempt detail extracted from a `/validate` response. Kept flat and small: it travels with + * every validation event, including the error ones, so that a failed payment can be diagnosed from + * the log alone without correlating several events. + */ +export declare interface PurseHeadlessCheckoutValidationResultSummary { + method?: string; + partner?: string; + status: { + state?: string; + code?: string; + description?: string; + }; + /** `plugin_result.status`, the partner-side outcome behind the Purse status code. */ + plugin_status?: string; +} declare class PursePayment implements Payment { readonly factoriesStaticData: { manuallyTriggerHook: (hookName: keyof PurseHeadlessCheckoutHooks, context?: { @@ -3392,6 +3954,18 @@ declare class PursePayment implements Payment { methods: Readable; tokens: Readable; walletTokens: Readable; + /** + * Primary-method element cache, keyed by the couple's stable `partner-method` + * id. `setSession` rebuilds `mops` with brand-new item objects on every + * refresh; this registry lets `buildPrimaryPaymentMethod` hand back the same + * `` element (and its `registration` store) for a couple + * whose `configurations` are unchanged, instead of tearing it down — see + * `hasSameMethodConfiguration` usage in `setSession` below. + */ + readonly primaryMethodElementRegistry: Map; + registration: Writable; + }>; constructor(env: APIEnv, factoriesStaticData: { manuallyTriggerHook: (hookName: keyof PurseHeadlessCheckoutHooks, context?: { actualMethod?: Method; @@ -3431,6 +4005,7 @@ export declare type RegistrationState = { registered: boolean; name: string | null; }; +export declare type SaveTokenMode = "ALWAYS" | "PREFER" | "PREFER_NO" | "NEVER"; /** * Secondaries are true Headless payment methods and are handled directly by the SDK for now. * They are not handled by the partner UI. @@ -3650,6 +4225,11 @@ declare const DropinThemeSchema: z.ZodObject<{ } | undefined; }>; export type DropinTheme = z.infer; +export type SaveTokenEventPayload = { + partner: string; + method: string; + save_token_mode: SaveTokenMode; +}; export interface DropinPrivateEventPayload { DROPIN_CHECKOUT_INIT_OPTIONS: { hidePayButton: boolean; @@ -3714,18 +4294,9 @@ export interface DropinPrivateEventPayload { redirect_domain?: string; redirect_method?: string; }; - DROPIN_CHECKOUT_SAVE_TOKEN_ENABLED: { - partner: string; - method: string; - }; - DROPIN_CHECKOUT_SAVE_TOKEN_OPT_IN: { - partner: string; - method: string; - }; - DROPIN_CHECKOUT_SAVE_TOKEN_OPT_OUT: { - partner: string; - method: string; - }; + DROPIN_CHECKOUT_SAVE_TOKEN_ENABLED: SaveTokenEventPayload; + DROPIN_CHECKOUT_SAVE_TOKEN_OPT_IN: SaveTokenEventPayload; + DROPIN_CHECKOUT_SAVE_TOKEN_OPT_OUT: SaveTokenEventPayload; DROPIN_CHECKOUT_SET_SESSION_CALLED: { session_id: string; }; @@ -3875,6 +4446,22 @@ export interface DropinPrivateEventPayload { sessionId: string; errors: ZodIssue[]; }; + DROPIN_CHECKOUT_CONFIG_UPDATED: { + hasTheme: boolean; + locale: string | undefined; + hidePayButton: boolean; + }; + DROPIN_CHECKOUT_REMOTE_CONFIG_LOADED: { + entityId: string; + }; + DROPIN_CHECKOUT_REMOTE_CONFIG_NOT_FOUND: { + entityId: string; + status: number; + }; + DROPIN_CHECKOUT_REMOTE_CONFIG_FAILED: { + entityId: string; + reason: string; + }; DROPIN_CHECKOUT_LOYALTY_DISPLAYED: { totalEuros: number; eligibleEuros: number; @@ -3905,6 +4492,22 @@ export interface DropinEventBusEvent[0]; +/** + * Per-method highlight configuration, exposed through the dropin `highlightMethods` + * init/config option. A merchant can flag one or more payment methods with a custom + * tag (label + optional background color). + * + * - `partner` is an optional filter: when omitted, the highlight applies to every + * partner exposing that `method`. + * - `color` is any valid CSS color. When omitted (or invalid), the tag falls back + * to the default theme styling. + */ +export type DropinHighlightMethod = { + method: string; + partner?: string; + label: string; + color?: string; +}; declare const translationFiles: { readonly "de-AT": { translation: { @@ -3978,7 +4581,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -3986,24 +4588,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -4189,7 +4787,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -4197,24 +4794,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -4400,7 +4993,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -4408,24 +5000,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -4632,7 +5220,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -4640,24 +5227,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -4864,7 +5447,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -4872,24 +5454,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -5096,7 +5674,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -5104,24 +5681,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -5328,7 +5901,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -5336,24 +5908,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -5560,7 +6128,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -5568,24 +6135,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -5792,7 +6355,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -5800,24 +6362,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -6024,7 +6582,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -6032,24 +6589,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -6256,7 +6809,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -6264,24 +6816,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -6488,7 +7036,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -6496,24 +7043,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -6720,7 +7263,6 @@ declare const translationFiles: { panInputLabel: string; panRequiredError: string; panFormatError: string; - panCannotBeEmptyError: string; panTooltipText: string; cvvPlaceholder: string; cvv4Placeholder: string; @@ -6728,24 +7270,20 @@ declare const translationFiles: { cvv4InputLabel: string; cvvRequiredError: string; cvvFormatError: string; - cvvCannotBeEmptyError: string; cvvTooltipText: string; expirationPlaceholder: string; expirationInputLabel: string; expirationRequiredError: string; expirationFormatError: string; - expirationCannotBeEmptyError: string; expirationOutOfRangeError: string; expirationTooltipText: string; holderPlaceholder: string; holderInputLabel: string; holderRequiredError: string; holderFormatError: string; - holderCannotBeEmptyError: string; birthdateInputLabel: string; birthdateRequiredError: string; birthdateFormatError: string; - birthdateCannotBeEmptyError: string; saveTokenLabel: string; saveAccountLabel: string; }; @@ -6887,15 +7425,21 @@ export type InitOptions = { theme?: DropinTheme; locale?: keyof typeof translationFiles; hidePayButton?: boolean; + highlightMethods?: DropinHighlightMethod[]; hooks?: { onRefreshSession?: () => PurseHeadlessCheckoutV1Params["paymentSession"] | string | void | Promise; }; }; +export type DropinConfig = { + theme: DropinTheme; + locale?: keyof typeof translationFiles; + hidePayButton: boolean; + highlightMethods?: DropinHighlightMethod[]; +}; declare class PurseDropin { private readonly _headlessCheckoutInstance; - private readonly _theme; - private readonly _locale?; - private readonly _hidePayButton; + private _config; + private readonly _remoteTheme; private readonly _onRefreshSession?; private _app?; private readonly _eventBus; @@ -6909,11 +7453,12 @@ declare class PurseDropin { }[]>; sessionState: Readable; private constructor(); - static init({ session, eventListener, theme, locale, hidePayButton, hooks }: InitOptions): Promise; + static init({ session, eventListener, theme, locale, hidePayButton, highlightMethods, hooks, }: InitOptions): Promise; setSession(session: PurseHeadlessCheckoutV1Params["paymentSession"] | string): Promise; setWalletSession(walletSession: OnePayWalletSession$2): Promise; submitPayment(): Promise; mount(target: HTMLElement): Promise; + setConfig(config: Partial): void; unmount(): Promise; } export declare function createDropinCheckout(options: InitOptions): Promise; diff --git a/src/generated/types/headless-checkout.d.ts b/src/generated/types/headless-checkout.d.ts index 643efb5..0fd4af2 100644 --- a/src/generated/types/headless-checkout.d.ts +++ b/src/generated/types/headless-checkout.d.ts @@ -3,6 +3,8 @@ /// import { AnyObject } from 'final-form'; +import { CSSResult, LitElement, PropertyValues, TemplateResult as TemplateResult_2, nothing } from 'lit'; +import { TemplateResult } from 'lit-html'; export type Subscriber = (value: T) => void; export type Unsubscriber = () => void; @@ -140,6 +142,7 @@ export declare const Partners: { readonly illicado: "illicado"; readonly ingenico: "ingenico"; readonly klarna: "klarna"; + readonly lyra: "lyra"; readonly mercanet: "mercanet"; readonly oney: "oney"; readonly oneycard: "oneycard"; @@ -152,6 +155,7 @@ export declare const Partners: { readonly riverty: "riverty"; readonly scalapay: "scalapay"; readonly scellius: "scellius"; + readonly sequra: "sequra"; readonly sipsv2: "sipsv2"; readonly slimpay: "slimpay"; readonly sogecommerce: "sogecommerce"; @@ -256,6 +260,14 @@ export type IntegratedToken = { source?: "MERCHANT" | "SHARED_WALLET"; savable_to_shared_wallet?: boolean; created_at?: string; + /** + * Network-token counterpart of this card token. When `available` is true and + * the experience enables network tokens (`settings.network_token`), the + * payment goes through the network token and no CVV is collected. + */ + network_token?: { + available?: boolean; + }; }; export interface MethodConfiguration { stable: boolean; @@ -271,12 +283,25 @@ export interface MethodConfiguration { settings: { registration_mode?: string; save_token?: boolean; + save_token_mode?: "ALWAYS" | "PREFER" | "PREFER_NO" | "NEVER"; direct_debit?: boolean; + /** + * Front-settings gate for network tokens. Tokens carrying an available + * `network_token` only pay through it (and hide their CVV) when this is + * true. + */ + network_token?: boolean; payment_min_amount?: number; payment_max_amount?: number; usage_per_session_limit?: number; merchant_specifics?: { integration_mode?: "redirection" | "popup"; + /** + * When truthy, the CVV field is hidden and not required when paying with + * a saved token. Configured per experience in the merchant back-office, + * so the value can reach the front as a string. + */ + pay_token_no_cvv?: boolean | "true" | "false"; }; }; } @@ -455,6 +480,11 @@ declare const PurseErrors: { message: string; documentationLink: string; }; + readonly API_UNKNOWN_ERROR: { + code: string; + message: string; + documentationLink: string; + }; }; export type PurseErrorCodes = keyof typeof PurseErrors; export type JsonSerializable = string | number | boolean | null | { @@ -554,7 +584,10 @@ export type ItemValidationData> = { partner: string; session_id: string; transaction_id: string; - plugin_result: Result; + plugin_result: Result & { + status?: string; + amount?: number; + }; status: { action: TransactionAction; state: StatusState; @@ -619,19 +652,33 @@ export interface LoanSimulation { export type WalletItem = { id: string; name?: string; + favorite?: boolean; }; declare class ApiError extends PurseError { readonly path: string; readonly error: string; responseHeaders?: Record; + /** HTTP status of the response that produced this error, when available (e.g. 400, 429, 500). */ + status?: number; constructor(error: (typeof PurseErrors)[PurseErrorCodes], path?: string, errorCode?: string, message?: string); } +/** + * Which product an SDK is embedded in, when it can be embedded by another. + * + * `direct` means the merchant integrated it themselves. Always sent explicitly so a + * missing value means "SDK build predates wrapper reporting" rather than "direct". + * + * @internal Not part of the public integration surface — set by the wrapping product. + */ +export type MonitoringWrapper = "direct" | "dropin"; export interface MonitoringApiEventMessage { instanceId?: string; entityId?: string; paymentSessionId?: string; walletSessionId?: string; version?: string; + /** @internal See {@link MonitoringWrapper}. */ + wrapper?: MonitoringWrapper; env?: EnvironmentTarget; event: Event; } @@ -661,11 +708,12 @@ export interface WalletAPI { deleteAllTokens(): Promise; setAsFavorite(data: { id: string; + favorite: boolean; }): Promise; } export interface MonitoringAPI { getApiKey(): string; - patchExtrasData(E: Pick, "instanceId" | "entityId" | "paymentSessionId" | "walletSessionId" | "version">): void; + patchExtrasData(E: Pick, "instanceId" | "entityId" | "paymentSessionId" | "walletSessionId" | "version" | "wrapper">): void; sendLogs(E: E[]): void; } export type APIPaths = { @@ -681,7 +729,7 @@ declare class FetchMonitoringAPI implements MonitoringAPI { private extras; constructor(env: EnvironmentTarget, root: string, apiKey: string); getApiKey(): string; - patchExtrasData: (payload: Partial, "instanceId" | "entityId" | "paymentSessionId" | "walletSessionId" | "version">>) => void; + patchExtrasData: (payload: Partial, "instanceId" | "entityId" | "paymentSessionId" | "walletSessionId" | "version" | "wrapper">>) => void; sendLogs: (events: Event[]) => void; } export declare type ButtonOptions = google.payments.api.ButtonOptions; @@ -694,6 +742,42 @@ export declare interface CreditCardValidateUiResult { export declare type DeepPartial = T extends any[] ? T : { [P in keyof T]?: DeepPartial; }; +export declare type EnvSource = { + id: string; + singleton?: boolean; + production: Source; +} & { + [key in ExternalSourceEnv]?: Source; +}; +export declare type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes]; +declare const ErrorCodes: { + readonly MethodIsNotImplementedByPlugin: "MethodIsNotImplementedByPlugin"; + readonly PluginEncounteredALethalException: "PluginEncounteredALethalException"; + readonly PluginIsIncompatibleWithClient: "PluginIsIncompatibleWithClient"; + readonly PreValidationIsntAvailableForPayButton: "PreValidationIsntAvailableForPayButton"; + readonly PostValidationDataIsMissingInformations: "PostValidationDataIsMissingInformations"; + readonly CannotInitWithoutSession: "CannotInitWithoutSession"; + readonly CannotSubmitWithoutClient: "CannotSubmitWithoutClient"; + readonly CannotPreValidateWithoutPlugin: "CannotPreValidateWithoutPlugin"; + readonly FormValidationFailed: "FormValidationFailed"; + readonly ClientNotFoundWhenSettingSession: "ClientNotFoundWhenSettingSession"; + readonly ClientNotFoundAfterSourceLoad: "ClientNotFoundAfterSourceLoad"; + readonly PreValidationFailed: "PreValidationFailed"; + readonly PostValidationFailed: "PostValidationFailed"; + readonly DynamicAmountNotInSessionRange: "DynamicAmountNotInSessionRange"; + readonly CantSetBrandThatIsNotAmongMatches: "CantSetBrandThatIsNotAmongMatches"; + readonly CannotPostValidateWithoutPlugin: "CannotPostValidateWithoutPlugin"; + readonly MissingDataFromContext: "MissingDataFromContext"; + readonly "3DSCantProceedWithoutRedirectionURL": "3DSCantProceedWithoutRedirectionURL"; + readonly "3DSFingerprintingFailed": "3DSFingerprintingFailed"; + readonly APIResultUnsatisfying: "APIResultUnsatisfying"; + readonly PluginInitFailed: "PluginInitFailed"; + readonly TargetNotInDocument: "TargetNotInDocument"; + readonly MissingTargetFromConfig: "MissingTargetFromConfig"; + readonly MultipleMatchesForTargetInDocument: "MultipleMatchesForTargetInDocument"; + readonly PartnerDoesNotSupportUI: "PartnerDoesNotSupportUI"; + readonly HolderNameFieldRequired: "HolderNameFieldRequired"; +}; export declare type ErrorPayload = { reason?: string; } & Record; @@ -889,6 +973,25 @@ declare const Events: { "partnerError" ]; }; + /** + * The plugin's own integration broke for this attempt — a dead popup, a + * severed window transport — and the failure will not clear by retrying, but + * it is not permanent either: a fresh amount or session can make the method + * work again. + * + * Distinct from `partnerError`, which also carries ordinary payment outcomes + * (Lyra reports declined cards through it). Sharing both topics means every + * existing `error`/`partnerError` subscriber keeps receiving it; what the host + * does about it — deactivating the method, and when to lift that — is the + * host's policy, not the plugin's. + */ + readonly partnerRecoverableError: { + readonly code: "partnerRecoverableError"; + readonly topics: readonly [ + "error", + "partnerError" + ]; + }; readonly change: { readonly code: "change"; readonly topics: readonly [ @@ -956,9 +1059,10 @@ declare const Events: { ]; }; }; +export declare type ExternalSourceEnv = "production" | "sandbox" | "test" | "development"; export declare type FieldValidationError = (typeof FieldValidationErrors)[keyof typeof FieldValidationErrors]; declare const FieldValidationErrors: { - readonly cannotBeEmpty: "cannotBeEmpty"; + readonly required: "required"; readonly dateOutOfRange: "dateOutOfRange"; readonly format: "format"; readonly lengthOutOfRange: "lengthOutOfRange"; @@ -1064,19 +1168,24 @@ export declare interface HostedFieldsUIOptions { * Some partners displays a complete form and let you configure the text through a JS configuration. We've mapped those * texts and let you override all off them form this single object. During a session, while displaying a form some of * the variables might not be used, it depends on the context. + * + * Every field is plain text and is rendered as such. The only exceptions are `consentText` and + * `loanSimulation`, which accept limited HTML — see their own docs. */ declare interface HostedFormUIOptions { panPlaceholder?: string; panInputLabel?: string; panRequiredError?: string; - panFormatError?: string; + /** @deprecated use panRequiredError */ panCannotBeEmptyError?: string; + panFormatError?: string; panTooltipText?: string; birthdatePlaceholder?: string; birthdateInputLabel?: string; birthdateRequiredError?: string; - birthdateFormatError?: string; + /** @deprecated use birthdateRequiredError */ birthdateCannotBeEmptyError?: string; + birthdateFormatError?: string; birthdateOutOfRangeError?: string; birthdateTooltipText?: string; cvvPlaceholder?: string; @@ -1087,6 +1196,9 @@ declare interface HostedFormUIOptions { * This value is interpolated with {url:string} * use {{variable}} to use interpolation * @example 'Your will agree to the tos ' + * + * Accepts HTML, sanitised by allowlist: a, b, strong, em, i, u, span, br, p, ul, ol, li, + * with href/target/rel/style only. `href` must be an absolute https URL. */ consentText?: string; /** @@ -1098,8 +1210,9 @@ declare interface HostedFormUIOptions { */ cvv4InputLabel?: string; cvvRequiredError?: string; - cvvFormatError?: string; + /** @deprecated use cvvRequiredError */ cvvCannotBeEmptyError?: string; + cvvFormatError?: string; cvvTooltipText?: string; /** * When true, the CVV field is hidden and not required for payment. @@ -1114,30 +1227,35 @@ declare interface HostedFormUIOptions { expirationPlaceholder?: string; expirationInputLabel?: string; expirationRequiredError?: string; - expirationFormatError?: string; + /** @deprecated use expirationRequiredError */ expirationCannotBeEmptyError?: string; + expirationFormatError?: string; expirationOutOfRangeError?: string; expirationTooltipText?: string; holderPlaceholder?: string; holderInputLabel?: string; holderRequiredError?: string; - holderFormatError?: string; + /** @deprecated use holderRequiredError */ holderCannotBeEmptyError?: string; + holderFormatError?: string; ibanPlaceholder?: string; ibanInputLabel?: string; ibanRequiredError?: string; - ibanFormatError?: string; + /** @deprecated use ibanRequiredError */ ibanCannotBeEmptyError?: string; + ibanFormatError?: string; emailPlaceholder?: string; emailInputLabel?: string; emailRequiredError?: string; - emailFormatError?: string; + /** @deprecated use emailRequiredError */ emailCannotBeEmptyError?: string; + emailFormatError?: string; phonePlaceholder?: string; phoneInputLabel?: string; phoneRequiredError?: string; - phoneFormatError?: string; + /** @deprecated use phoneRequiredError */ phoneCannotBeEmptyError?: string; + phoneFormatError?: string; /** * This value is interpolated with: * - first @@ -1147,6 +1265,8 @@ declare interface HostedFormUIOptions { * - cost * - taeg * use {{variable}} to use interpolation + * + * Accepts the same HTML allowlist as `consentText`. */ loanSimulation?: string; /** @@ -1166,16 +1286,13 @@ declare const IntegrationTypes: { readonly xPayButton: "xPayButton"; readonly redirection: "redirection"; }; -export declare interface KRError_2 { - errorCode: string; - errorMessage: string; - detailedErrorCode?: string; - detailedErrorMessage?: string; - field?: string; - children?: Array; - metadata?: Record; -} -export declare type ManagedHostedFieldValue = "valid" | Extract | null; +export declare type Interface = (typeof Interfaces)[keyof typeof Interfaces]; +declare const Interfaces: { + readonly form: "form"; + readonly hostedfields: "hostedfields"; + readonly payButton: "payButton"; +}; +export declare type ManagedHostedFieldValue = "valid" | Extract | null; /** * Options let you override theme configuration and translations on our UIs. */ @@ -1211,6 +1328,173 @@ declare type Options = { */ locale?: string; }; +/** + * SDK-agnostic partner error contract. `events.ts` must not know any specific + * partner SDK's internals — the normalized `message` is the stable contract + * consumers read; the raw partner error is carried opaquely in `originalError`. + */ +export declare interface PartnerErrorPayload { + /** Normalized, contextual message. The stable contract consumers read. */ + message: string; + /** Raw partner SDK error. Opaque — debug only, never introspect its shape or cast it. */ + originalError?: unknown; +} +declare class PartnerUI> extends LitElement { + #private; + private sourceDownloaded; + private plugin; + private extraSources; + private data; + private readonly context; + session: LegacyPaymentSession | undefined; + env: APIEnv | null; + options: Options; + amount?: number; + register: boolean | undefined; + partner: Partner; + method: Method; + interface: Interface; + token?: Token; + /** + * A payzen xpay specific function to trigger onBeforeValidate hook outside the payment process + * This function should throw if the onBeforeValidate fails, allowing us to `return false` in the `onClick` of the smartForm + * Returning false in the onClick prevents to submit + */ + __xpay_payzen_specific_triggerOnBeforeValidate__: (method: Extract) => Promise; + constructor(); + private get envName(); + dispatchUnifiedEvent: (code: K, payload?: UnifiedEventPayload[K] | null) => void; + preValidation(): Promise; + postValidation(data: ValidationResultData, redirectionHandler?: RedirectionHandler): Promise; + /** + * Use this method to run the validation of the UI. It can be useful to trigger a validation of the UI, in a "onsubmit" validation workflow for instance. + * This should not be confused with the "validate" concept which is the API call to validate the payment. + * */ + validateUi(): Promise; + /** + * When detected brands are multiple, use this method to set the selected brand. + * default is detectedBrands[0] + * @param brand the brand to select + * @returns void + **/ + setSelectedBrand(brand: CardScheme): void; + connectedCallback(): void; + /** + * The element has no shadow root and is mounted into merchant-controlled DOM, so anything that + * serialises markup — session replay, error reporters, extension content scripts — captures + * attribute values. `session` alone carries every saved card in the session. These three stay + * supported as attribute *inputs* (`attribute: false` would silently starve callers that still + * pass them, breaking saved-card payments with no error), but nothing is left behind in the DOM. + * SDK-12163. + * + * Called from `connectedCallback`, which is where it is safe: custom-element upgrade replays + * `attributeChangedCallback` for existing attributes *before* `connectedCallback`, so Lit has + * already parsed them, and `connectedCallback` runs synchronously inside `appendChild`, so no + * script can read them in between. + * + * The value has to be re-assigned after removal: `removeAttribute` re-enters Lit's + * `attributeChangedCallback` with `null`, and the default `type: Object` converter maps that + * back to `null` — removing the attribute would otherwise wipe the property it just parsed. + * + * Only ever scrub inputs that callers pass by *property*. Svelte caches the attribute values it + * wrote on the node and skips a same-value rewrite, so an attribute removed behind its back + * never comes back — `amount`, `partner`, `method` and `interface` must stay out of here. + */ + private scrubSecretAttributes; + disconnectedCallback(): void; + performUpdate(): void; + private updateData; + private loadExtraSources; + private getSourceListener; + updated(changedProperties: PropertyValues): void; + willUpdate(changedProperties: PropertyValues): void; + createRenderRoot(): this; + render(): typeof nothing | TemplateResult<1>; +} +/** + * The partner ui context is the piece of data & abilities that we give to the plugins to operate with. + */ +export declare interface PartnerUIContext> { + /** + * The context provides a way to dispatch UnifiedEvent. This is required because our plugins is responsible for + * dispatching key events like fulfillment. + */ + dispatchEvent: (code: K, payload?: UnifiedEventPayload[K] | null) => void; + /** + * This is a reference to the partner ui root node, it is useful for plugins that need to append elements to the DOM + */ + shadowRoot: HTMLElement; + /** + * Data in the context is the "reactive" part, so you can read it through an accessor. + */ + data: () => Data; + /** + * This is the setter for the data. It takes a partial object of Data and patch the current state with it. + * If the references you passed changed the original ones, it will rerender the UI and also call the plugin's onDataChanged + */ + updateData: (data: Partial) => void; + /** + * The current payment session, can be undefined if no session is set. + */ + session?: LegacyPaymentSession; + /** + * This amount could be less than the session total, and plugins might want to perform action when it changes. + * This amount is less than the total when a secondary is involved for instance. + */ + amount?: number; + /** + * The redirection interface allows the plugin to gracefully redirect to another location. + */ + redirection: RedirectionHandler; + /** + * A helper to make calls to the API. We give it the API Root. + */ + api: PaymentAPI; + /** + * The env we're running the plugin in. This is useful for plugins that need to download extra sources. + */ + envName?: EnvironmentTarget; + /** + * Gives the ability to download extra sources at runtime + */ + loadExtraSources: (sources: EnvSource[]) => Promise; + /** + * If the partner UI renders a token, this will be set with the token extracted from session + */ + token?: Token; + /** + * The current partner ui requested interface + */ + ui: Interface; + /** + * The current partner ui's options + */ + options: Options; + /** + * Partner ui theme + */ + theme: Theme; + /** + * If the partners create some sort of client (Securefields, paypal...), its reference can be stored here + */ + client?: Client; + /** + * Current partner ui register attribute. + */ + register?: boolean; + /** + * This is a wild card that can be updated by the plugin to keep references without re-rendering the view + */ + dmz: { + initialStepId: number | null; + } & Record; + /** + * A payzen xpay specific function to trigger onBeforeValidate hook outside the payment process + * This function should throw if the onBeforeValidate fails, allowing us to `return false` in the `onClick` of the smartForm + * Returning false in the onClick prevents to submit + */ + __xpay_payzen_specific_triggerOnBeforeValidate__: (method: Extract) => Promise; +} /** * A theme is carrying all the style variables use across our UIs. * global can be seen as a default values store on which the other scopes will rely on to fall back a value. @@ -1325,6 +1609,13 @@ declare type PartnerUITheme = { helperText: ThemeScope<"helperText">; tooltip: ThemeScope<"tooltip">; }; +declare class PursePartnerUI> extends PartnerUI { +} +export declare interface RedirectionHandler { + submitRedirectionForm(action: string, serializedFormData: string): Promise | void; + submitStringRedirectionForm(id: string, responseFormAsString: string): Promise | void; + redirectToUrl(url: string): Promise | void; +} export declare interface RedirectionUIOptions { title?: string; } @@ -1332,6 +1623,37 @@ export declare type Source = { type: "script" | "link"; attributes: Record; }; +declare class Theme { + static readonly UNEXPOSED: { + "background-color": string; + "brand-select-width": string; + "gap-small": string; + "gap-tiny": string; + "input-height": string; + "divider-margin": string; + "divider-bg": string; + }; + static readonly defaultTheme: PartnerUIOptionsTheme; + private _currentTheme; + private readonly _setProperty; + constructor(_setProperty: Theme["_setProperty"], theme?: PartnerUIOptionsTheme); + setTheme(theme?: PartnerUIOptionsTheme): void; + getValue(scope: Scope, key: keyof PartnerUITheme[Scope], variant?: ThemeScopesVariants): string | undefined; + getGlobalOverrides(): CSSResult; + applyCurrentTheme(): void; + private get currentTheme(); + private set currentTheme(value); + /** + * This method is in charge of creating a complete theme (not partial) using the default theme. + * if the `input['::placeholder']['color']` is not defined + * It will fall back to `input['color']` + * then to `global['::placeholder']['color']` + * and then to `global['color']` + * @param partialThemeFromOptions + * @private + */ + private scaffoldThemeValues; +} declare const THEME_SCOPES: readonly [ "global", "input", @@ -1433,6 +1755,16 @@ export declare type ThemeScopesVariants = ThemeSco export declare type ThemeScopesVariantsMap = { [K in ThemeScopeName]: (typeof THEME_SCOPES_VARIANTS)[K][number]; }; +export declare type Token = { + id: string; + expiration_date: string; + value: string; + selected_network: string; + holder_name: string; + description: { + display_token: string; + }; +}; export declare interface UnifiedEventPayload { clientError: ErrorPayload | null; clientNotFound: ErrorPayload | null; @@ -1485,9 +1817,13 @@ export declare interface UnifiedEventPayload { interface: string; fallBackInterface: string; }; + /** `code`/`reason` carry the rejecting `UnifiedError`, so the log says which check refused. */ pluginIsIncompatibleWithClient: { partner: Partner; method: Method; + code?: ErrorCode; + reason?: string; + stack?: string; }; pluginPostValidation: Record | null; pluginPreValidation: Record | null; @@ -1507,8 +1843,14 @@ export declare interface UnifiedEventPayload { } | null; other: Record | null; partnerError: { - error: Omit; + error: PartnerErrorPayload; method: string; + partner: string; + }; + partnerRecoverableError: { + error: PartnerErrorPayload; + method: string; + partner: string; }; change: HostedFieldsEventPayload | null; focus: HostedFieldsEventPayload | null; @@ -1543,16 +1885,45 @@ declare interface XPayButtonUIOptions { * @example 'fr-FR' */ locale?: string; + /** + * Text preceding the Apple Pay logo, on partners rendering their own button label (e.g. Lyra smart form). + * Native Apple Pay buttons ignore it, they carry their own localized wording. + * Defaults to a locale-based translation. + * @example 'Buy with' + */ + label?: string; }; google?: GooglePayButtonOptions & { height?: number; + /** + * Text preceding the Google Pay logo, on partners rendering their own button label (e.g. Lyra smart form). + * Native Google Pay buttons ignore it, they carry their own localized wording. + * Defaults to a locale-based translation. + * @example 'Buy with' + */ + label?: string; }; paypal?: { + /** + * @deprecated Ignored since the migration to PayPal JS SDK v6: layout was a + * property of the v5 multi-button `paypal.Buttons()` container, and v6 renders + * exactly one button web component per `` element. Kept for + * source compatibility — remove on the next breaking release. + */ layout?: "vertical" | "horizontal"; color?: "silver" | "white" | "black" | "gold" | "blue"; - shape?: "rect" | "pill"; + shape?: "rect" | "pill" | "sharp"; height?: number; - label?: "paypal" | "checkout" | "buynow" | "pay" | "installment"; + /** + * Button wording. `paypal` renders the wordmark alone and is the default; + * `installment` was dropped by the PayPal JS SDK v6 and falls back to it. + */ + label?: "paypal" | "checkout" | "buynow" | "pay" | "subscribe" | "donate" | "installment"; + /** + * Corner radius of the button, in pixels. Takes precedence over `shape`. + * See https://developer.paypal.com/sdk/js/reference/#borderRadius + */ + borderRadius?: number; /** * When true, removes the max-width constraint applied by PayPal on the button container. * See https://developer.paypal.com/sdk/js/reference/#width @@ -1688,15 +2059,22 @@ export interface PurseHeadlessCheckoutPaymentElement { * * @param {PaymentElementEventName} eventName - Name of the event to listen for * @param callback - Function to be called when the event occurs + * @returns A function that unregisters this callback. Call it when the host + * component unmounts: registering again on the same element without it leaves the + * previous callback live, and one event then fans out to every past registration. + * `remove()` does not unregister for you — the element instance is reused across + * remounts, so the callbacks it holds outlive it. * @example * ```typescript * // Listen for the UI ready event - * paymentElement.on('formValid', () => { + * const off = paymentElement.on('formValid', () => { * console.log('Payment UI form is valid'); * }); + * // …later, on teardown + * off(); * ``` */ - on(eventName: K, callback: PaymentElementEventsCallback[K]): void; + on(eventName: K, callback: PaymentElementEventsCallback[K]): () => void; } export interface PurseHeadlessCheckoutHostedFields extends Omit { supportedBrands: Readable; @@ -1737,6 +2115,8 @@ export interface PurseHeadlessCheckoutSecondaryTokenProvider { * If secondary token creation fails * - {@link PurseHeadlessCheckoutError} `FAILED_TO_INIT_SECONDARY_TOKEN` \ * If secondary token initialization fails + * - {@link PurseHeadlessCheckoutError} `LOYALTY_ACCOUNT_NOT_FOUND` \ + * If the customer has no loyalty account on the partner side * - {@link PurseHeadlessCheckoutError} `MISSING_SESSION` \ * If the session is missing * - {@link PurseHeadlessCheckoutError} `PAYMENT_METHOD_NOT_FOUND` \ @@ -1985,6 +2365,11 @@ declare const PurseHeadlessCheckoutErrors: { message: string; documentationLink: string; }; + readonly LOYALTY_ACCOUNT_NOT_FOUND: { + code: string; + message: string; + documentationLink: string; + }; readonly SECONDARY_TOKEN_EMPTY_BALANCE: { code: string; message: string; @@ -2054,6 +2439,7 @@ export interface PurseHeadlessCheckoutErrorsPayload { method: Method; uniqueness_token: string; }; + LOYALTY_ACCOUNT_NOT_FOUND?: never; SECONDARY_TOKEN_EMPTY_BALANCE?: never; USER_ACTION_REQUEST_HAS_EXPIRED: never; USER_ACTION_REQUEST_ABORTED_BY_CUSTOMER: never; @@ -2218,6 +2604,7 @@ export type DisabledStatePayload = { PAYMENT_ELEMENT_RENDER_FAILED: never; PLUGIN_INCOMPATIBLE: never; PARTNER_UI_FATAL_ERROR: never; + PARTNER_UI_RECOVERABLE_ERROR: never; FATAL_ERROR_BEFORE_RENDER: never; MIN_AMOUNT_NOT_MET: { minAmount: number | null; @@ -2257,6 +2644,11 @@ export type DisabledState = { export declare function disabledStateShouldBeFiltered(state: DisabledState | null | undefined): boolean; export declare function isTechnicallyDisabled(state: DisabledState | null | undefined): boolean; export declare function disabledStateRequiresUIClear(state: DisabledState | null | undefined): boolean; +/** + * The partner reported a failure it can come back from: render the method as + * unavailable, but expect it to become usable again without a page reload. + */ +export declare function isRecoverablyDisabled(state: DisabledState | null | undefined): boolean; export interface CardSchemeAsset { url: string; label: string; @@ -2567,6 +2959,15 @@ export type PurseHeadlessCheckoutSecondaryMethod = PurseHeadlessCheckoutMethod & limit?: number; /** Always true for secondary payment methods */ isSecondary: true; + /** + * Secondary methods (gift cards, vouchers, loyalty) have no payment element — they are + * entered via `getSecondaryToken()` / `take()`, not a rendered form. Calling this warns + * (with a documentation link) and returns `undefined` so the integration degrades + * gracefully instead of throwing a `TypeError`. + * + * @see https://docs.purse.tech/docs/integrate/purse-checkout/headless-checkout/advanced-guides/render-payment-methods#secondary-methods + */ + getPaymentElement(): undefined; } & SecondarySpecificConfig; /** * Represents a primary token in the Purse checkout system. @@ -2668,16 +3069,16 @@ export interface PurseHeadlessCheckoutPrimaryToken extends PurseHeadlessCheckout /** Whether this token is the user's favorite payment method. Reflects `IntegratedToken.favorite`. */ isFavorite: Readable; /** - * Mark this token as the user's favorite. Persists the change via the wallet API. + * Toggle this token's favorite status. If it is already a favorite, it will be removed; + * otherwise it will be marked as the user's favorite. Persists the change via the wallet API. * * @throws - {@link PurseHeadlessCheckoutError} `MISSING_WALLET_SESSION` \ * If no wallet session is loaded - * @throws - {@link PurseHeadlessCheckoutError} `MISSING_TOKEN` \ - * If the token is not found in the wallet * * @example * ```typescript - * await token.setAsFavorite(); + * await token.setAsFavorite(); // marks as favorite + * await token.setAsFavorite(); // removes favorite status * ``` */ setAsFavorite(): Promise; @@ -2703,6 +3104,15 @@ export interface PurseHeadlessCheckoutPrimaryToken extends PurseHeadlessCheckout export interface PurseHeadlessCheckoutSecondaryToken extends PurseHeadlessCheckoutToken { type: "token" | "temporary_token"; isSecondary: true; + /** + * Secondary tokens (gift cards, vouchers, loyalty) have no payment element — they are + * applied via `take()`, not a rendered form. Calling this warns (with a documentation + * link) and returns `undefined` so the integration degrades gracefully instead of + * throwing a `TypeError`. + * + * @see https://docs.purse.tech/docs/integrate/purse-checkout/headless-checkout/advanced-guides/render-payment-methods#secondary-methods + */ + getPaymentElement(): undefined; limit: number; expiration_date: string | Date; /** @@ -2798,6 +3208,7 @@ export type RegistrationState = { registered: boolean; name: string | null; }; +export type SaveTokenMode = "ALWAYS" | "PREFER" | "PREFER_NO" | "NEVER"; export interface PurseHeadlessCheckoutRegisterable { /** * Mark this mean of payment for registration upon validate. @@ -2817,10 +3228,20 @@ export interface PurseHeadlessCheckoutRegisterable { name?: string; }): Promise; /** - * This property exposes the saveToken configuration of the method. - * A mean of payment cannot be saved if the method is not registerable. + * Whether the user can choose to register this method. + * True only for `save_token_mode` values `PREFER` and `PREFER_NO`. + * When false, the save-token checkbox must not be shown; the backend + * enforces the registration policy (ALWAYS/NEVER) without a frontend signal. */ canBeRegistered: boolean; + /** + * The effective `save_token_mode` resolved for this mean of payment + * (`ALWAYS` / `PREFER` / `PREFER_NO` / `NEVER`), including the `save_token` + * boolean fallback. Exposed for consumers that need to distinguish *why* + * registration is or isn't user-controllable — e.g. `ALWAYS` forces + * registration silently, with `canBeRegistered` false like `NEVER`. + */ + saveTokenMode: SaveTokenMode; /** * This property exposes if the mean of payment is bound for registration upon validate. */ @@ -2989,12 +3410,39 @@ export interface PurseHeadlessCheckoutHooks { /** Optional argument to handle the redirection */ redirectionHandler?: (redirectionData: RedirectionHandlerParams) => Promise; } +/** + * How a payment submission was triggered. + * + * - `api`: the merchant called `submitPayment()`. + * - `xpay`: an express/wallet plugin (Apple Pay, Google Pay…) requested validation + * itself, without the merchant calling `submitPayment()`. + */ +export type PurseHeadlessCheckoutSubmitTrigger = "api" | "xpay"; +/** + * Per-attempt detail extracted from a `/validate` response. Kept flat and small: it travels with + * every validation event, including the error ones, so that a failed payment can be diagnosed from + * the log alone without correlating several events. + */ +export interface PurseHeadlessCheckoutValidationResultSummary { + method?: string; + partner?: string; + status: { + state?: string; + code?: string; + description?: string; + }; + /** `plugin_result.status`, the partner-side outcome behind the Purse status code. */ + plugin_status?: string; +} export interface PurseHeadlessCheckoutEventPayload { HEADLESS_CHECKOUT_CLEAR_PRIMARY_SPLIT: { partner: string; method: string; }; - HEADLESS_CHECKOUT_CLIENT_HOOK_ERROR: Record; + HEADLESS_CHECKOUT_CLIENT_HOOK_ERROR: { + error: unknown; + message?: string; + }; HEADLESS_CHECKOUT_CREATE: { sessionType: "v1" | "v2"; screen: { @@ -3088,32 +3536,22 @@ export interface PurseHeadlessCheckoutEventPayload { }; HEADLESS_CHECKOUT_VALIDATION_SUCCESS: { global_status: string; - results: { - method: string; - partner: string; - status: { - state: string; - code: string; - description?: string; - }; - }[]; + results: PurseHeadlessCheckoutValidationResultSummary[]; }; HEADLESS_CHECKOUT_VALIDATION_FAILED: { global_status: string; - results: { - method: string; - partner: string; - status: { - state: string; - code: string; - description?: string; - }; - }[]; + results: PurseHeadlessCheckoutValidationResultSummary[]; }; HEADLESS_CHECKOUT_PAYMENT_ERROR: { message?: string; api_status?: string; stack?: string; + /** Why the payment failed, attempt by attempt. Empty array when the API returned no result. */ + results?: PurseHeadlessCheckoutValidationResultSummary[]; + /** Whether the `/validate` response carried a redirection URL of its own. */ + has_redirection?: boolean; + /** Set when the failure came from a thrown error rather than from an API status. */ + error_name?: string; }; HEADLESS_CHECKOUT_PAYMENT_FULFILLED: { isFulfilled: boolean; @@ -3156,8 +3594,18 @@ export interface PurseHeadlessCheckoutEventPayload { message?: string; stack?: string; }; + /** + * A submission was refused by the re-entrancy guard: one was already in flight, or the session + * had already been validated. Counterpart of widget V3's + * `CHECKOUT_PRE_VALIDATION_ALREADY_ROLLING` / `CHECKOUT_CURRENT_SESSION_ALREADY_VALIDATED`. + */ + HEADLESS_CHECKOUT_SUBMIT_BLOCKED: { + state: PurseHeadlessCheckoutSessionState; + trigger: PurseHeadlessCheckoutSubmitTrigger; + }; HEADLESS_CHECKOUT_SUBMIT_PAYMENT: { splits: PaymentSplit[]; + trigger: PurseHeadlessCheckoutSubmitTrigger; }; HEADLESS_CHECKOUT_TOKEN_DELETE: { partner: string; @@ -3169,7 +3617,16 @@ export interface PurseHeadlessCheckoutEventPayload { method: string; tokenId?: string; }; - HEADLESS_CHECKOUT_UNCAUGHT_ERROR: Record; + HEADLESS_CHECKOUT_TOKEN_SET_FAVORITE: { + partner: string; + method: string; + tokenId?: string; + favorite: boolean; + }; + HEADLESS_CHECKOUT_UNCAUGHT_ERROR: { + stack: string; + message?: string; + }; HEADLESS_CHECKOUT_ON_BEFORE_VALIDATE_HOOK_FAILED: { message: string; error: { @@ -3248,6 +3705,10 @@ export interface Payment { methods: Readable; tokens: Readable; walletTokens: Readable; + primaryMethodElementRegistry: Map; + registration: Writable; + }>; paymentSession: Readable; session: PaymentSession; walletSession: WalletSession | null; @@ -3258,6 +3719,94 @@ export interface Payment { deactivateMop(mop: PurseHeadlessCheckoutPaymentItemBase, reason: DisabledState): Promise; isSessionExpired(): boolean; } +declare class PurseHeadlessCheckoutElement { + private readonly manuallyTriggerHook; + private readonly onSessionTerminated?; + protected readonly item: PurseHeadlessCheckoutPaymentItemBase; + protected readonly payment: Payment; + private fulfilled; + private _wasEverReady; + private readonly registration?; + private readonly amount?; + private readonly interface?; + private readonly env; + protected readonly method: Derived; + private storeListeners; + protected readonly options: Writable; + protected UIListeners: (() => void)[]; + private readonly eventsMap; + ui: PursePartnerUI | null; + constructor(item: PurseHeadlessCheckoutPaymentItemBase, data: { + payment: Payment; + env: APIEnv; + registration?: Readable; + amount?: Readable; + interface?: Interface; + }, manuallyTriggerHook: (hookName: keyof PurseHeadlessCheckoutHooks, context?: { + actualMethod?: Method; + sourceId?: string; + }) => Promise, onSessionTerminated?: (() => void) | undefined); + getInterface(): Interface | undefined; + wasEverReady(): boolean; + remove: () => void; + setOptions(options?: Options): void; + hasUI(): boolean; + /** + * This getter is in sync with the "formValid" property of the partner-ui + */ + isFulfilled(): boolean; + /** + * Registers a callback and returns the function that unregisters it. Hosts that + * mount the same element again (dropin remounts its whole list on `setSession`) + * must call it on teardown, otherwise every mount leaves another live callback + * behind and a single `ready` fans out to all of them (SDK-12109). + * + * `remove()` deliberately keeps the map intact: `createPaymentElement` wires the + * splitter and submit plumbing through it once per instance, and the instance is + * cached and reused across remounts, so clearing it here would silently break + * split computation and payment submission. + */ + on(eventName: K, callback: PaymentElementEventsCallback[K]): () => void; + private attachUIListeners; + protected buildUI(): PursePartnerUI; + /** + * Validates the UI of the payment element. + * + * This method triggers the validation process of the UI component associated with the payment element. + * This is purely a form validation function. It can be useful if you want to trigger validation before submitting payment. + * + * @returns {Promise} A promise that resolves with true or the result of the validation + */ + validateUi(): Promise; + private clearListeners; +} +declare class PurseHeadlessCheckoutPaymentElement$1 extends PurseHeadlessCheckoutElement implements PurseHeadlessCheckoutPaymentElement { + appendTo(container: string | Element): void; +} +declare class PurseHeadlessCheckoutHostedFields$1 extends PurseHeadlessCheckoutElement implements PurseHeadlessCheckoutHostedFields { + supportedBrands: Readable; + detectedBrands: Readable; + selectedBrand: Readable; + constructor(item: PurseHeadlessCheckoutPaymentItemBase, data: { + payment: Payment; + env: APIEnv; + registration?: Readable; + amount?: Readable; + interface?: Interface; + }, manuallyTriggerHook: (hookName: keyof PurseHeadlessCheckoutHooks, context?: { + actualMethod?: Method; + sourceId?: string; + }) => Promise, onSessionTerminated?: () => void); + protected buildUI(): PursePartnerUI; + setSelectedBrand(brand: CardScheme): void; + setOptions(options?: Options): void; + render(): void; +} +declare function buildPaymentElementAccessors(create: (inter: I) => I extends "form" ? PurseHeadlessCheckoutPaymentElement$1 : PurseHeadlessCheckoutHostedFields$1): { + getPaymentElement: (options?: Omit) => PurseHeadlessCheckoutPaymentElement$1; + getHostedFields: (options?: HostedFieldsOptions) => PurseHeadlessCheckoutHostedFields$1; + getElementInstance: () => PurseHeadlessCheckoutPaymentElement$1 | PurseHeadlessCheckoutHostedFields$1 | undefined; +}; declare class PursePayment implements Payment { readonly factoriesStaticData: { manuallyTriggerHook: (hookName: keyof PurseHeadlessCheckoutHooks, context?: { @@ -3278,6 +3827,18 @@ declare class PursePayment implements Payment { methods: Readable; tokens: Readable; walletTokens: Readable; + /** + * Primary-method element cache, keyed by the couple's stable `partner-method` + * id. `setSession` rebuilds `mops` with brand-new item objects on every + * refresh; this registry lets `buildPrimaryPaymentMethod` hand back the same + * `` element (and its `registration` store) for a couple + * whose `configurations` are unchanged, instead of tearing it down — see + * `hasSameMethodConfiguration` usage in `setSession` below. + */ + readonly primaryMethodElementRegistry: Map; + registration: Writable; + }>; constructor(env: APIEnv, factoriesStaticData: { manuallyTriggerHook: (hookName: keyof PurseHeadlessCheckoutHooks, context?: { actualMethod?: Method; @@ -3391,9 +3952,11 @@ export declare class PurseHeadlessCheckout { * - 'idle': The session is idle and not yet initialized. * - 'ready': The session is initialized and ready for payment method selection. * - 'submitting': The payment is being submitted. - * - 'submitted': The payment has been successfully submitted. + * - 'submitted': The session was validated successfully and is now spent. Further submissions + * are refused, since the API accepts one validation per session. * - 'expired': The session has expired. - * - 'error': An error occurred with the session. + * - 'error': An error occurred with the session, including a validation the API refused or a + * payment it declined. The session was not consumed, so submitting again is allowed. * @example * ```ts * headlessCheckout.sessionState.subscribe((state: PurseHeadlessCheckoutSessionState) => { @@ -3423,9 +3986,11 @@ export declare class PurseHeadlessCheckout { * @param {PurseHeadlessCheckoutV2Params | PurseHeadlessCheckoutV1Params} checkoutParams - The checkoutParams data for initializing the checkout. * @param {PurseHeadlessCheckoutHooks} hooks - Optional hooks into checkout lifecycle * @param {APIPaths} apiPaths @internal - dev purposes to override the APi URL's - * @param product @internal - dev purposes to override the product name + * @param wrapper @internal - which product embeds this instance. Set by the wrapping + * product (Dropin passes `dropin`); merchants integrating headless directly leave the + * default. Reported on every log line so events can be attributed to the right product. */ - static init(checkoutParams: PurseHeadlessCheckoutV2Params | PurseHeadlessCheckoutV1Params, hooks?: PurseHeadlessCheckoutHooks, apiPaths?: APIPaths): Promise; + static init(checkoutParams: PurseHeadlessCheckoutV2Params | PurseHeadlessCheckoutV1Params, hooks?: PurseHeadlessCheckoutHooks, apiPaths?: APIPaths, wrapper?: MonitoringWrapper): Promise; /** * Get the config from params * @param {PurseHeadlessCheckoutV2Params | PurseHeadlessCheckoutV1Params} checkoutParams - The checkoutParams data for initializing the checkout. @@ -3506,11 +4071,28 @@ export declare class PurseHeadlessCheckout { */ setSession(widgetData: PurseHeadlessCheckoutV2Params): Promise; setSession(widgetData: PurseHeadlessCheckoutV1Params["paymentSession"]): Promise; + /** + * Shared submission path. Both entry points funnel through here — the public + * `submitPayment()` and express/wallet plugins requesting validation themselves. + * + * The `HEADLESS_CHECKOUT_SUBMIT_PAYMENT` event is emitted here rather than on the + * public method because `withMonitoringProxy` only sees calls made *through* the + * returned proxy. Internal callers hold a reference bound in the constructor, + * before the proxy exists, so monitoring attached to the public method never fires + * for them — which silently lost every wallet submission. + * + * It is emitted before the session and fulfilment guards on purpose: the event + * means "a submission was attempted", so guard failures stay visible. Pair it with + * `HEADLESS_CHECKOUT_PAYMENT_ERROR` to see which attempts were rejected. + */ private _submitPayment; /** * Submits the current payment configuration for processing. * This will validate and process all payment methods in the current split configuration. * + * Resolves without doing anything when a submission is already in flight, or when the session + * has already been validated successfully. Watch `sessionState` to tell the two apart. + * * @throws * - {@link PurseHeadlessCheckoutError} `MISSING_SESSION` \ * If the session is missing @@ -3589,7 +4171,12 @@ export type HeadlessCheckout = PurseHeadlessCheckout; * * @returns A promise that resolves to a configured checkout instance */ -export declare const createHeadlessCheckout: (sessionParams: T, hooks?: H, apiPaths?: APIPaths) => Promise; +export declare const createHeadlessCheckout: (sessionParams: T, hooks?: H, apiPaths?: APIPaths, +/** + * @internal Which product embeds this instance — see {@link PurseHeadlessCheckout.init}. + * Merchants integrating headless directly should not pass this. + */ +wrapper?: MonitoringWrapper) => Promise; export { HostedFormUIOptions as PurseHeadlessCheckoutHostedFormUIOptions,