@flopay/js

中文文档
1.14.11发布于 2026/11/14TypeScriptESM + CJS近 30 天下载 8020

Browser-side FloPay SDK. loadFloPay() initializes a FloPay instance for hosted card capture and session access, PaymentAPI is the billing API client, and createCheckoutSession() creates a checkout session and redirects to the hosted checkout page.

原始使用文档
文档目录22 节
  1. Installation
  2. Choosing and upgrading a release
  3. Usage
  4. Initialize the SDK
  5. Retrieve a session
  6. Call the billing API directly
  7. Create a hosted checkout session
  8. Checkout metadata
  9. Authorise now, capture later
  10. Idempotent checkout creation
  11. Detached session creation (default)
  12. Server-side card gateways (Worldpay)
  13. Hosted card capture
  14. Privacy-safe operational telemetry
  15. Filter third-party errors in your own Sentry client
  16. Examples
  17. Public API
  18. Exports
  19. FloPay methods
  20. PaymentAPI methods
  21. PciVaultCardCapture methods
  22. Errors

@flopay/js

Browser-side FloPay SDK. loadFloPay() initializes a FloPay instance for hosted card capture and session access, PaymentAPI is the billing API client, and createCheckoutSession() creates a checkout session and redirects to the hosted checkout page.

Providers sit behind the PaymentProviderAdapter interface (implemented today by StripeAdapter), so checkout code does not change when a session is routed to a different gateway.

Installation

pnpm add @flopay/js

@stripe/stripe-js and @flopay/shared install automatically as dependencies; you do not need to add them. Choose the billing API environment once at startup with configureFlopay() from @flopay/shared, or the NEXT_PUBLIC_FLOPAY_ENV environment variable.

For React applications, @flopay/react builds on this package and renders the complete checkout.

Choosing and upgrading a release

Read the SDK changelog for the target release's changes and integration guidance before upgrading. When installing more than one @flopay/* package directly, choose the same released version for each and commit the resulting lockfile. You do not need to install packages that are already supplied as dependencies.

Confirm that the chosen version is available on npm for every package you are updating. A merged GitHub pull request alone does not mean publication has finished. If that exact version is not yet available, wait for publication rather than mixing versions or installing a repository branch.

Usage

Initialize the SDK

import { loadFloPay } from '@flopay/js';

const flopay = await loadFloPay('pk_test_...', {
  billingApiUrl: 'https://billing.example.com', // enables retrieveSession() and retrieveUnifiedSession()
});

The optional second argument accepts every FloPayConfig field except publishableKey: billingApiUrl, locale, appearance, and telemetry. loadFloPay caches by publishable key and behavior-affecting options: concurrent calls with the same key and options share one in-flight provider initialization and resolve to the same FloPay instance. A failed initialization is evicted, so a later call can retry.

Retrieve a session

flopay.retrieveSession(sessionId) returns the normalized CheckoutSession (amount, currency, status, customer). flopay.retrieveUnifiedSession(sessionId) returns a NormalizedCheckoutSession: every gateway the session advertises is listed in providers, and gateway data such as the Stripe client secret is under data. Both use the billingApiUrl from loadFloPay, or a URL you pass as the second argument.

retrieveUnifiedSession() exposes provider-specific session data without mounting provider UI. Use FloPayCheckout or SplitCardForm from @flopay/react for hosted-vault card, wallet, and APM rendering.

Call the billing API directly

PaymentAPI wraps the billing API routes the checkout uses. Session reads and continuation calls take the session nonce, the session-bound checkout token returned when the session was created, and send it as x-checkout-session-token.

  • getUnifiedCheckoutSession(sessionId, nonce) reads and normalizes a session.
  • createSessionIntent(sessionId, nonce, request) creates a Stripe wallet/APM or direct-PayPal intent. The request is a discriminated union of provider, payment-method category, type, and id, and intent kind; direct-card requests are not representable. If the session is still processing an earlier payment (for example a PayPal capture under review), the billing API answers 202 checkout_processing. The SDK then polls the session status within the same time limit processPayment uses. It resolves to a settled result, { settled: true, response }, instead of an intent. response is the Response processPayment would give for that outcome: 204 succeeded, 200 authorized, 400 declined. Check for it with isSettledSessionIntent from @flopay/shared. If the time limit runs out first, it rejects with checkout_processing_timeout, statusCode: 202 and the sessionId.
  • reportSessionIntentDecline(sessionId, nonce, request) reports a client-observed non-card decline using a classification code only. Messages, identifiers, card data, credentials, and personal data are rejected before sending.
  • processPayment(userId, params) submits a saved or approved non-card payment to the session's /process route. When the billing API answers 202, the SDK follows its authenticated status endpoint until the latest transaction attempt succeeds, authorizes, declines, requires buyer action, fails, or times out.
  • getPaymentsByEmail(email) looks up prior payments for a saved-payment experience.

Create a hosted checkout session

createCheckoutSession(options) posts to POST /v1/checkouts/sessions and redirects the browser:

  • On 201, the browser goes to the hosted checkout page and the result is { status: 201, redirectUrl, nonce }. A matching flopay_checkout_token cookie is written to the same parent domain as checkout_data, so the hosted page can read the token without putting it in the URL, history, or Referer.
  • On 204, a payment method is already on file and the browser goes to successUrl.

createCheckoutSessionWithRetries(options) adds automatic retry with jittered exponential backoff on transient transport, timeout, and idempotency-in-progress failures: at most two retries (three attempts), all inside one 12-second total deadline you can change with timeoutMs.

Checkout metadata

Pass checkoutMetadata?: Record<string, string> | null to redirect, retrying, one-shot inline, or detached creation. A present map, an explicit null, and {} are sent unchanged; omitting the field leaves the request body unchanged. Session reads expose a returned snapshot as normalized.checkoutMetadata and normalized.data.session.checkoutMetadata; an omitted or null create value is absent from reads, while {} is returned as {}. The SDK performs no duplicate size validation: billing API rejections keep their code, message, and HTTP statusCode.

Authorise now, capture later

For an item-only card checkout that should place a hold instead of capturing immediately, add captureMethod: 'manual' to createCheckoutSession, createCheckoutSessionWithRetries, PaymentAPI.createAndFetchSession, or PaymentAPI.createDetachedSession. An accepted hold surfaces as status: 'authorized' on normalized sessions and payment results, with paymentId, sessionId, and authorizationExpiresAt. Treat it as checkout-terminal but not paid. Capture is a merchant-authenticated REST operation, never available to browser code; see the payments and authorisations reference.

Idempotent checkout creation

Every checkout-session create sends a stable Idempotency-Key header when a secure random source is available, so a timeout or lost response cannot mint a second session. Existing integrations need no changes.

  • Automatic (default): the SDK generates one cryptographically random key per logical createCheckoutSession call and reuses it for every retry of that operation. Independent calls get different keys even when their bodies are identical. The key is never derived from customer data and is never logged or returned. Without crypto.randomUUID or crypto.getRandomValues the header is omitted; supply your own key to stay idempotent there.
  • Merchant-supplied: pass idempotencyKey to keep the key stable across remounts, SDK instances, or your own server retries. It identifies exactly one logical checkout and must never be reused for a new purchase.

createCheckoutSessionWithRetries resolves the key once, before its retry loop, so timeouts, lost responses, and in-progress replies all replay the same key; a 409 payload conflict is surfaced without retrying. PaymentAPI.createAndFetchSession (used by @flopay/react) follows the same rules, with one shared three-attempt budget across network failures and in-progress replies.

Detached session creation (default)

Inline session creation is detached by default. Instead of one request that resolves buyer identity and the catalog together, the SDK issues two:

  1. POST /v1/checkouts/sessions with deferDataAttachment: true returns a lightweight shell. It runs no catalog validation and takes none of the buyer-identity locks that serialise concurrent checkouts for the same customer, yet already carries gateways and the hosted vault block: the card form can mount from it. Checkout metadata is sent on this request.
  2. PATCH /v1/checkouts/sessions/{id}/claim attaches products and coupons in the background. When the draft has an email it also attaches the buyer; an email-less draft omits the buyer, and the returned session has its full catalog, totals, and methods while buyerIdentified stays false.

PaymentAPI.createDetachedSession(params) exposes both phases as { shell, sessionId, nonce, claimed, claim }. claimed settles when the catalog is attached; claim(account) attaches a buyer identity later. Both are independently replay-safe: repeated valid claim(account) calls return the same promise, and a local invalid-email rejection does not consume the operation. createAndFetchSession also accepts an email-less draft and resolves with the catalog-attached anonymous session.

Nothing may be charged before claimed settles: the billing API rejects process, intent, and decline calls on an unclaimed session with 409 checkout_session_data_attachment_required, and holds an unclaimed hosted card charge with a retryable 503. @flopay/react gates the card form's submit button for exactly this window.

For detached creation, timeoutMs is a fresh per-phase budget: the shell POST gets the full value, and after it succeeds the initial claim PATCH gets the full value again under a different AbortSignal (12 seconds each by default), so time spent creating a usable shell never starves its catalog claim. Detached creation requires billing API v1.7.12 or newer and checkoutMode: 'full', and is skipped when tokenizedData is supplied. There is no fallback to the one-shot create; pass deferDataAttachment: false to force it. Because catalog validation moves to the claim, an invalid product or coupon surfaces as the same 422, later in the flow. The claim body is fingerprinted by the billing API, so the SDK replays a byte-identical payload across retries, and a materially different claim for an already-claimed session returns 409 without retrying.

Server-side card gateways (Worldpay)

The billing API can process hosted-vault and eligible saved cards through an approved Worldpay Corporate Gateway entirely server-side. Whether a session can use it is the billing API's decision, made from the merchant's active gateways and the saved method's eligibility. PaymentAPI sends no gateway hint, provider selection, eligibility assertion, merchant code, or scheme reference. Everything stays provider-neutral, so existing integrations change nothing:

  • Cards still go through the hosted card form, from the embedded vault block or getVaultCapture(). There is no Worldpay browser SDK, key, or credential: gateways.worldpayCorporateGateway has a null publishable key and no enabled methods, and it never appears in the normalized providers list.
  • Outcomes arrive as the same complete, decline, and error events. Declines use FloPay's decline taxonomy, never a raw processor code. An ambiguous submission stays pending until the billing API reconciles it; the hosted form reports that as an error, never a success, and a later session read returns the reconciled status.
  • Cardholder authentication (3DS) resumes through the same provider-neutral continuation as every other card gateway. The hosted form reports action_required with a Flo-hosted challenge URL, the adapter renders that URL in its own modal overlay, and the outcome is resolved server-side before the hosted form reports the terminal complete, decline, or error. No Worldpay or authentication-provider script, merchant code, ACS URL, or token reaches the browser.
  • Eligible saved-card Auto, Confirm, returning-buyer, and subscription checkouts call the same provider-neutral /process route without tokenizedData. Immediate success and authorization use the existing Flo shapes; a 202 checkout_processing response is reconciled through its nonce-protected status URL, including decline, buyer action, and terminal error states.
  • When a saved method cannot be used, stored_payment_method_unsupported is preserved as an actionable Flo error with the instruction to choose another payment method. The SDK never silently switches to a different charging behavior. Scheduled subscription rebills, retries, dunning, and stored-credential eligibility remain billing-API responsibilities.

PaymentAPI Auto creates advertise only the generic async-auto-checkout-v1 response capability so the billing API may return the asynchronous status envelope. It is not a Worldpay capability or routing request; Confirm and Full creates send no capability header. Do not add gateway selection or provider-specific branches to a create request, whether through this SDK or a request you build yourself: the billing API owns that decision.

Hosted card capture

flopay.cardCapture() returns a CardCaptureAdapter that injects the billing API's self-contained hosted card form. The form owns the PCI card fields, its own submit button, tokenization, the charge (created and confirmed server-side), 3DS, and the result, so no Stripe.js runs on the card path and card numbers never enter the SDK runtime. The SDK injects the form and relays its terminal outcome.

  • Use the session's embedded vault block when present. Otherwise fetch one with PaymentAPI.getVaultCapture(sessionId, nonce) for a session whose Stripe enabledPaymentMethods explicitly includes card, or that advertises gateways.worldpayCorporateGateway. getVaultCapture() shares one active request between concurrent callers, aborts a stalled attempt after ten seconds, and retries transient network failures on the idempotent POST /v1/checkouts/sessions/{id}/vault/capture route.
  • Outcome events are ready, complete, decline, and error; challenge_dismissed is a non-terminal notification that the buyer closed the authentication challenge. The form is injected same-window, so the adapter rejects terminal outcomes that are not bound to the mounted session, that mismatch the block's messageToken, or that arrive from a different expectedOrigin. Pass both from the vault block to mount().
  • When the buyer's bank asks for authentication, the hosted form hands the challenge up to the adapter, which renders the provider-hosted URL in a full-page modal overlay (role="dialog", aria-modal, focus moved into the challenge and restored on teardown) above any processing backdrop. The challenge is not an outcome: it reaches no listener, and the challenge URL never appears in an event or in telemetry. The hosted form resolves the outcome and reports it as the usual complete, decline, or error; if nothing comes back at all, the overlay closes itself after the hosted form's own authentication window and reports an error. Closing the overlay abandons the challenge and reports the non-terminal challenge_dismissed event, so the host can take down its processing state and leave the form retryable straight away; it is not an outcome, and a late complete, decline, or error from the hosted form still reaches your listeners.
  • A complete event's intentId is the processing gateway's own reference. It is a Stripe PaymentIntent (or SetupIntent) id only when Stripe took the card; for a server-side card gateway it may be an opaque order reference. Reconcile by paymentId and the session instead.
  • For a merchant-server-created no-charge setup session, create the adapter with operation: 'card_setup'. A complete event may then carry a display-only paymentMethod (FloPay id, brand, last four, expiry, and status).
  • capture.applyTheme(theme) re-skins the mounted form without remounting; applyTheme(null) is the explicit classic reset. Theming is cosmetic and never blocks capture.
  • capture.setSubmitLabel(label) sets the hosted submit button's wording live (billing API v1.9.35+), without remounting or clearing entered card data. Pass final, already-localized plain text; the hosted form shows its session default (CONFIRM PAYMENT, or ADD CARD for card setup) for blank or invalid text. capture.setSubmitLabel(null) is the explicit reset to that default, and a submitLabel mount option sets the initial wording.

The adapter retains the latest label and replays it every time the widget reports an authenticated ready, so a late or remounted form still receives it. Because the directive carries merchant text, it is posted only when the vault block supplied both messageToken and expectedOrigin, bound to the mounted sessionId, with the exact expectedOrigin as the postMessage target — never '*':

{ source: 'flopay-vault-host', type: 'submitLabel', sessionId, messageToken, label }

A form mounted without those fields (an older billing API) keeps its default. The label is never added to telemetry. The other host directives (theme, submit gate, and field order) also target the exact expectedOrigin when one was supplied, falling back to '*' only for a widget mounted without it. There is no host directive for authentication: the hosted form accepts no completion signal from the page, because the outcome of a challenge is resolved server-side.

Privacy-safe operational telemetry

Browser telemetry is on by default and best-effort. It reports only the closed SDK error, lifecycle, and performance taxonomy to a FloPay-owned API; it never bundles Sentry, reads Resource Timing, persists a queue, or transmits payment, customer, or form data, credentials, URLs, provider payloads, arbitrary messages, or stacks. Opt out with telemetry: false on loadFloPay, PaymentAPI, PciVaultCardCapture, createCheckoutSession, and createCheckoutSessionWithRetries. Merchant callbacks keep their arguments and invocation order; exceptions they throw are contained and logged only to your console.

Genuine technical failures carry one bounded category: server_error for billing API 5xx responses, transport_error when no usable response arrives, invalid_response for malformed responses, and provider_runtime for provider SDK or hosted-form runtime failures. Payment declines and request validation failures are reported as non-error payment_declined and validation_rejected outcomes.

When telemetry is enabled, a Stripe loader rejection is reported only with the bounded provider_runtime category, while a null Stripe loader result uses the distinct invalid_response category. Neither changes the thrown error or the failed-load retry behavior, and neither transmits the publishable key, rejection, provider message, stack, URL, or provider payload.

FloPayCheckout in @flopay/react also classifies failures around the session and provider boundary without exposing raw errors. An existing session-read transport failure uses transport_error; malformed JSON uses invalid_response.

Filter third-party errors in your own Sentry client

import * as Sentry from '@sentry/browser';
import { dropThirdPartyOnlyError } from '@flopay/js';

Sentry.init({ beforeSend: dropThirdPartyOnlyError });

This pure predicate drops only complete exception stacks with no app or @flopay/* frame and at least one browser-extension URL or Stripe dahlia/stripe.js controller frame. Mixed, unrelated, incomplete, and anonymous-only errors are returned unchanged, and Sentry's in_app flag is ignored.

Examples

Initialize the SDK and read a session's gateways:

import { loadFloPay } from '@flopay/js';

const flopay = await loadFloPay('pk_test_...', { billingApiUrl: 'https://billing.example.com' });

const session = await flopay.retrieveSession('session_uuid');
console.log(session.amount, session.currency, session.status);

const unified = await flopay.retrieveUnifiedSession('session_uuid');
if (unified.providers.includes('stripe')) {
  console.log('Stripe publishable key:', unified.data.stripe?.publishableKey);
}

Create a hosted checkout session with metadata and a merchant-controlled idempotency key:

import { createCheckoutSessionWithRetries } from '@flopay/js';
import { FloPayError } from '@flopay/shared';

try {
  const result = await createCheckoutSessionWithRetries({
    billingApiUrl: 'https://billing.example.com',
    checkoutBaseUrl: 'https://checkout.example.com',
    clientId: 'client_123',
    currency: 'USD',
    items: [{ providerItemId: 'prod_abc', providerItemName: 'Pro Plan', totalAmount: 49.99 }],
    account: { userId: 'user_1', email: '[email protected]' },
    successUrl: '/success',
    cancelUrl: '/cancel',
    couponCodes: ['SAVE10'],
    checkoutMetadata: { merchantOrderId: 'order-123', campaign: 'summer' },
    idempotencyKey: 'checkout:order-123',
  });
  console.log('Checkout session created with status', result.status);
} catch (error) {
  if (error instanceof FloPayError && error.code === 'CouponLimitExceeded') {
    console.warn('Too many coupon codes.');
  } else if (error instanceof FloPayError && error.code === 'CouponCurrencyUnsupported') {
    console.warn('A coupon is not valid for this currency.');
  } else {
    throw error;
  }
}

Create a detached session, mount the card form from the shell, and identify the buyer later:

import { PaymentAPI } from '@flopay/js';

const api = new PaymentAPI('https://billing.example.com');

const detached = await api.createDetachedSession({
  clientId: 'client_123',
  currency: 'EUR',
  items: [{ providerItemId: 'prod_abc', providerItemName: 'Widget', totalAmount: 29.99 }],
  account: { userId: 'buyer_123' },
  successUrl: '/success',
  cancelUrl: '/cancel',
});

console.log('Card form can mount from', detached.shell.data.session?.vault);
const anonymous = await detached.claimed; // catalog, totals, and methods; buyerIdentified is false
const identified = await detached.claim({ email: '[email protected]', firstName: 'Jane', lastName: 'Doe' });
console.log(anonymous.data.session?.buyerIdentified, identified.data.session?.buyerIdentified);

Mount the hosted card form and relay its outcome:

import { loadFloPay, PaymentAPI } from '@flopay/js';
import type { PaymentResult } from '@flopay/shared';

const billingApiUrl = 'https://billing.example.com';
const sessionId = 'session_uuid';
const nonce = 'session_nonce';

function onComplete(result: PaymentResult) {
  console.log('Checkout finished:', result.status, result.paymentId);
}

const flopay = await loadFloPay('pk_test_...', { billingApiUrl });
const session = await flopay.retrieveSession(sessionId);
const vault = session.vault ?? (await new PaymentAPI(billingApiUrl).getVaultCapture(sessionId, nonce));

const capture = flopay.cardCapture({ sessionId, captureMethod: session.captureMethod });
capture.on('complete', (e) => {
  if (e.outcome === 'authorized' || session.captureMethod === 'manual') {
    onComplete({
      status: 'authorized',
      paymentId: e.paymentId,
      sessionId: e.sessionId,
      authorizationExpiresAt: e.authorizationExpiresAt,
    });
    return;
  }
  onComplete({ status: 'succeeded', paymentId: e.paymentId, sessionId: e.sessionId });
});
capture.on('decline', (e) => console.warn('Card declined:', e.declineReason));
capture.on('error', (e) => console.error('Card form failed:', e.message));

const container = document.getElementById('card-form');
if (container && vault.html) {
  await capture.mount(container, {
    html: vault.html,
    messageToken: vault.messageToken,
    expectedOrigin: vault.expectedOrigin,
    submitLabel: 'Pay €29.99',
  });
}

Create a wallet intent and report a client-observed decline:

import { PaymentAPI } from '@flopay/js';
import { isSettledSessionIntent } from '@flopay/shared';

const api = new PaymentAPI('https://billing.example.com', { telemetry: false });

const intent = await api.createSessionIntent('session_uuid', 'session_nonce', {
  provider: 'stripe',
  paymentMethodCategory: 'wallet',
  paymentMethodType: 'apple_pay',
  paymentMethodId: null,
  intentKind: 'payment',
});
if (isSettledSessionIntent(intent)) {
  // An earlier payment on this session already reached its outcome: there is nothing to confirm.
  console.log('Session settled with', intent.response.status);
} else if (intent.provider === 'stripe') {
  console.log('Confirm the wallet payment with', intent.clientSecret);
}

await api.reportSessionIntentDecline('session_uuid', 'session_nonce', {
  provider: 'stripe',
  paymentMethodCategory: 'wallet',
  paymentMethodType: 'apple_pay',
  providerDeclineReason: 'payment_failed',
});

Public API

Exports

ExportDescription
loadFloPay(publishableKey, options?)Initializes the SDK and resolves a FloPay. Shares successful and in-flight work by key and options; failed work stays retryable.
FloPayMain SDK class, created by loadFloPay()
PaymentAPIBilling API client: new PaymentAPI(billingApiUrl, { telemetry })
createCheckoutSession(options)Creates a checkout session and redirects. Resolves a CheckoutSessionResult.
createCheckoutSessionWithRetries(options)Like createCheckoutSession, with up to two retries (three attempts) inside one total deadline, using jittered exponential backoff
PciVaultCardCaptureCardCaptureAdapter implementation that injects the billing API's hosted card form
CardCaptureOperation'checkout' / 'card_setup'
PciVaultCardCaptureConfigAdapter configuration: sessionId?, captureMethod?, operation?, expectedOrigin?, telemetry?
StripeAdapterPaymentProviderAdapter implementation for Stripe
toStripeAppearance(appearance)Converts a FloPayAppearance into a StripeSafeAppearance, mapping FloPay's theme token ('default', 'flat', 'night', 'none') onto Stripe's accepted set. Use it wherever you hand an appearance to Stripe Elements yourself.
toStripeAppearanceTheme(theme)Lower-level helper that maps a single FloPay theme token to Stripe's
StripeSafeAppearanceAn appearance Stripe Elements accepts
cacheSessionDisplayData(sessionId, data, options?)Stashes display-only session data client-side so it survives the redirect to a checkout page
getSessionDisplayData(sessionId)Reads cached display data, or null when none is cached or it expired
clearSessionDisplayData(sessionId)Drops cached display data, for example on the success page
SessionDisplayCacheDataDisplay-only data stored by cacheSessionDisplayData()
dropThirdPartyOnlyError(event)Sentry beforeSend predicate that drops recognized third-party-only exception stacks and otherwise returns the original event

FloPay methods

MethodReturnsDescription
cardCapture(options?)CardCaptureAdapterCreates a hosted card-form adapter bound to sessionId and captureMethod
retrieveSession(sessionId, billingApiUrl?)Promise<CheckoutSession>Reads a session through GET /v1/checkouts/sessions/{id}
retrieveUnifiedSession(sessionId, billingApiUrl?)Promise<NormalizedCheckoutSession>Reads and normalizes a session, including gateway data
getRawProvider()unknownThe underlying provider instance, such as the Stripe object
destroy()voidTears down telemetry and the provider

PaymentAPI methods

MethodReturnsDescription
getUnifiedCheckoutSession(sessionId, nonce?)Promise<NormalizedCheckoutSession>Reads and normalizes a session
getVaultCapture(sessionId, nonce?)Promise<VaultCaptureBlock>Fetches the hosted card form when the session has no embedded vault block
createAndFetchSession(params)Promise<NormalizedCheckoutSession>Creates an inline session and resolves once its catalog (and any supplied buyer) is attached
createDetachedSession(params)Promise<DetachedCheckoutSession>Creates an inline session in two phases and exposes both
claimCheckoutSession(sessionId, nonce, payload)Promise<NormalizedCheckoutSession>Sends a PATCH /v1/checkouts/sessions/{id}/claim
createSessionIntent(sessionId, nonce, request, options?)Promise<SessionIntentResult>Creates a Stripe wallet/APM or direct-PayPal intent. If the session is still processing an earlier payment, resolves to { settled: true, response } with that payment's outcome.
reportSessionIntentDecline(sessionId, nonce, request, options?)Promise<void>Reports a non-card decline classification
processPayment(userId, params, options?)Promise<Response>Submits a saved or approved non-card payment to /process; a 202 follows the advertised authenticated status endpoint and maps terminal attempt outcomes back to the existing process response shapes
waitForCheckoutSessionCompletion(sessionId, options?)Promise<NormalizedCheckoutSession>Waits for terminal state; pass the session nonce and a processing response's statusUrl to reconcile attempt-level success, authorization, decline, buyer action, and failure. timeoutMs (15 seconds by default) stops normal polling. A read already in flight may still land during a grace of up to 15 more seconds; if it returns a terminal status, the SDK may start one follow-up session read to normalize the result. The entire operation remains capped by that grace
reportThreeDsAbandoned(sessionId)voidRecords a saved-card 3DS challenge the buyer never finished as a buyer-abandonment outcome in Flo's privacy-safe telemetry, not as a technical failure. No-op when telemetry is off. The automatic payment button calls it for you
getPaymentsByEmail(email, options?)Promise<{ data, total, page, limit }>Lists prior payments for an email
destroy()voidTears down the client's telemetry

PciVaultCardCapture methods

MethodDescription
mount(container, { html, messageToken?, expectedOrigin?, submitLabel? })Injects the hosted card form
on(event, handler)Subscribes to ready, complete, decline, or error
unmount()Removes the form and its listeners
applyTheme(theme)Re-skins the mounted form; null is the classic reset
setSubmitGate(blocked)While blocked, the hosted form cancels its next submit and emits blocked so the host can validate its own fields (such as AVS) first
setCardFieldOrder(order, autoFocus)Reorders the hosted card rows (name, number, expiry); the first row is focused unless autoFocus is false
setSubmitLabel(label)Sets the hosted submit wording live; null restores the session default

Errors

Failures reject with FloPayError from @flopay/shared. Branch on type and code, never on message. When the billing API returns a structured error body, the SDK keeps its safe message, code, and HTTP statusCode; a body-less response falls back to code: 'http_<status>'.

CodeTypeWhen
CurrencyRequiredvalidation_errorSession creation could not resolve a currency from the session, items, subscriptions, or products. Thrown before any request.
CouponLimitExceededvalidation_error or api_errorMore than five coupon codes were supplied: a validation_error before the request, or an api_error from the billing API.
CouponCurrencyUnsupportedapi_errorAn amount-based coupon has no price in the cart currency.
CaptureMethodUnsupportedForSubscriptionvalidation_errorManual capture was requested for a cart with a subscription.
InvalidIdempotencyKeyvalidation_errorA supplied idempotencyKey is blank or longer than 255 characters. Thrown before any request.
IdempotencyKeyInProgressapi_errorThe same key is still being processed. Retried automatically with the same key within the deadline.
MissingCheckoutSessionTokenvalidation_error or api_errorA continuation call was made without the session nonce, or the billing API created a session without returning one.
BuyerEmailCollectionRequiredvalidation_errorA buyer email supplied for creation or a late claim is not a valid email address.
checkout_session_data_attachment_requiredapi_errorA process, intent, or decline call was made before the detached session's catalog claim settled (HTTP 409). Wait for claimed.
InvalidSessionIntentRequestvalidation_errorAn intent request is not a supported non-card or PayPal request.
InvalidAuthorizationAttemptIdvalidation_errorAn intent request's authorization attempt id is not a valid UUID.
InvalidSessionIntentDeclineRequestvalidation_errorA decline report carries anything other than safe classification codes.
InvalidSessionIntentResponseapi_errorThe billing API returned an intent that does not match the request.
InvalidCheckoutSessionResponseapi_errorThe billing API returned a malformed session or no session id.
session_auto_completedapi_errorInline creation found a payment method already on file and completed the session without a checkout.
checkout_processing_timeoutapi_errorThe SDK could not get a terminal answer in time, so the payment's outcome is not known yet and it may still complete. Do not resubmit or offer the buyer a retry: reconcile from payment.* webhooks or read the session again. The buyer-facing message says the payment is still being confirmed. When createSessionIntent raises it because the session was still processing an earlier payment, it also carries statusCode: 202 and the sessionId.
stored_payment_method_unsupportedapi_errorThe backend-selected saved method cannot be used for this transaction. Ask the buyer to choose another payment method; the SDK does not silently change payment behavior.
vault_capture_timeoutapi_errorgetVaultCapture() did not return the hosted card form within ten seconds.
card_capture_no_windowapi_errorPciVaultCardCapture.mount() ran outside a browser.
card_capture_no_widget_htmlapi_errorPciVaultCardCapture.mount() was called without hosted form HTML.

Card declines are not thrown: they arrive as the card form's decline event with a declineReason, and technical failures as its error event. A product or coupon rejected during the detached catalog claim surfaces as the billing API's 422 error from claimed.

版本与发布记录

@flopay/js 共发布 175 个版本,首次发布于 2026/03/31;过去 12 个月发布了 175 个版本。

版本发布日期距今
1.14.11latest2026/11/141 天前
1.14.102026/11/141 天前
1.14.92026/11/132 天前
1.14.82026/11/123 天前
1.14.72026/11/114 天前
1.14.62026/11/114 天前

关于 @flopay/js 的常见问题

@flopay/js 是什么?

Browser-side FloPay SDK. loadFloPay() initializes a FloPay instance for hosted card capture and session access, PaymentAPI is the billing API client, and createCheckoutSession() creates a checkout session and redirects to the hosted checkout page.

如何安装 @flopay/js?

在项目目录运行 npm install @flopay/js,或使用 pnpm add / yarn add / bun add。

@flopay/js 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@flopay/js 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@flopay/js 可以商用吗?

@flopay/js 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@flopay/js 的下载量有多少?

根据 npm 官方下载统计,@flopay/js 在 2026/10/15 – 2026/11/13 这 30 天内被下载 8,020 次,日均约 267 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@flopay/js 还在维护吗?

最新版本 1.14.11 发布于 2026/11/14(1 天前);至今共发布 175 个版本,其中过去 12 个月发布了 175 个;首次发布于 2026/03/31。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。