fluveo.js reports two kinds of error, and they never mix.
- A payment error is about the buyer's payment, such as a declined card.
confirmPayment()resolves with it as{ error }: the API's own error object, withtype,code,decline_code,messageanddoc_url. Whentypeiscard_errororvalidation_error,messageis written for the buyer. Errors lists the codes. - An integration error is a mistake in the code that calls
fluveo.js. The script throws it at the call, and the buyer never sees it. This page lists every one.
An integration error is an Error whose name is FluveoIntegrationError. It has a stable code and a doc_url, a link that opens this page at the section with the same name. Its message always has the same form: what is wrong, how to fix it, then the doc_url.
FluveoIntegrationError: elements() needs { clientSecret }, or { mode: "payment", amount, currency }. Pass the client_secret of a PaymentIntent your server made (POST /v1/payment_intents), or the amount and currency of the payment your server will make when the buyer presses Pay. https://docs.fluveo.com/payments/fluveo-js-errors/#client_secret_missingsecret_key_in_browser#
Fluveo() got a secret key (sk_...), a restricted key (rk_...) or a webhook secret (whsec_...). Those stay on your server. The message names only the kind of key, never the key.
Fix: pass your publishable key (pk_test_...). The secret reached a browser, so treat it as leaked: rotate it in the Dashboard under Settings, API keys, with the expiration Now. See Authentication.
publishable_key_missing#
Fluveo() got no key, an empty one, or the text undefined or null.
Fix: pass your publishable key, Fluveo("pk_test_..."), and check that your server has it to write.
not_a_fluveo_key#
The first argument of Fluveo() is not a publishable key. A publishable key is pk_test_ or pk_live_ followed by 43 characters. Usually it was cut short or is another service's key.
Fix: copy the publishable key from the Dashboard under Settings, API keys, and check that your server or your build writes it into the page.
client_secret_missing#
fluveo.elements() was called with neither clientSecret nor mode, or with a clientSecret that is empty. This also happens when the request to your server failed and the page passed on undefined.
Fix: create a PaymentIntent on your server with POST /v1/payment_intents, send its client_secret to the page and pass it as fluveo.elements({ clientSecret }). Or mount the form before the PaymentIntent exists, with fluveo.elements({ mode: "payment", amount, currency }).
option_not_supported#
A call got an option, an event name or a value that fluveo.js does not have. The message names the call as you wrote it and lists what the call takes.
Fix: keep only what the message lists. fluveo.js shows every call with its options, and Form options what each value of an option does.
| Call | What it takes |
|---|---|
Fluveo(publishableKey, options) | locale |
fluveo.elements(options) | clientSecret, or mode, amount and currency; appearance, fonts, locale, loader |
elements.create("payment", options) | layout, fields, defaultValues |
elements.update(options) | appearance, fonts, locale, loader, and on elements made with mode also amount and currency |
element.update(options) | layout, fields, defaultValues |
element.on(event, handler) | "ready", "change", "focus", "blur", "loaderror" |
fluveo.confirmPayment(options) | elements, clientSecret (only with mode), confirmParams, redirect |
confirmParams | return_url, payment_method_data with billing_details only |
redirect | "always" or "if_required" |
appearance | theme, variables, labels, inputs, disableAnimations |
appearance.theme | "fluveo", "night", "flat" |
appearance.labels | "above", "floating", "auto" |
appearance.inputs | "spaced", "condensed" |
appearance.disableAnimations | true or false |
appearance.variables | colorPrimary, colorBackground, colorText, colorTextSecondary, colorTextPlaceholder, colorDanger, fontFamily, fontSizeBase, spacingUnit, borderRadius |
locale | "auto", "en", "pt-BR" |
loader | "auto", "always", "never" |
layout | "tabs", "accordion", "auto", or an object with type, defaultCollapsed, radios, spacedAccordionItems |
layout.radios | "always", "never", "if_multiple", "auto", true or false |
layout.defaultCollapsed, layout.spacedAccordionItems | true or false |
fields | billingDetails, which is "auto", "never" or an object with address; address is "auto", "never" or an object with country and postalCode, each "auto" or "never" |
defaultValues | billingDetails.address.country, the two-letter code of a country the form offers, and billingDetails.address.postal_code, a text |
The message lists every name the call knows. Of those names, fonts throws not_available_yet, and so do locale in elements.update() and fields and defaultValues in element.update().
script_not_from_fluveo#
The script that is running was not loaded from https://elements.fluveo.com/v1/. It was copied into a bundle, served from another host, or loaded from /v1 without the last slash.
Fix: put <script src="https://elements.fluveo.com/v1/"></script> in the page, or add that same tag from your code. Never save a copy of the script: a copy misses every fix. A query string after /v1/ is fine.
insecure_page#
A live key (pk_live_...) was used on a page that is not served over https.
Fix: serve the page over https. While you build on http://localhost, use your test key (pk_test_...).
container_not_found#
element.mount(target) found no element. The selector matches nothing, the element is not in the page when mount() runs, or target is neither a selector nor an element.
Fix: call mount() after the container exists, with its selector ("#payment-element") or the element itself. In a component, mount in the effect that runs after the first render.
element_not_supported#
elements.create() got a type other than "payment", such as "card" or "cardNumber". The payment element is the only element: it holds the card number, the expiration date and the security code in one form.
Fix: use elements.create("payment").
client_secret_wrong_account#
The form asked the API for the PaymentIntent, and the API does not know this client secret for this publishable key. The two belong to different accounts or different modes, or the client secret is not one your server got from the API. The form cannot load, so this error does not come from a call: the element's loaderror event carries it, and the console prints it.
Fix: use the pk_... and the sk_... of the same account, both test or both live, and pass the client_secret exactly as POST /v1/payment_intents answered it.
client_secret_invalid#
fluveo.retrievePaymentIntent() got something that is not the client secret of a PaymentIntent. The usual cause is a return page opened without its query, where payment_intent_client_secret reads as null.
Fix: pass the payment_intent_client_secret parameter of the return page's address, or the client_secret your server got. When the parameter is missing, send the buyer back to the checkout.
elements_missing#
fluveo.confirmPayment() got no elements, an elements object made by another Fluveo(), or one whose payment element is not mounted in the page.
Fix: pass the object that fluveo.elements() returned, after elements.create("payment").mount("#payment-element"), and confirm with the same fluveo that made it.
return_url_missing#
fluveo.confirmPayment() needs confirmParams.return_url, and it must be an absolute http or https address. A path such as "/complete" is refused.
Fix: pass confirmParams: { return_url: location.origin + "/complete" }. The buyer lands there when the payment is done, and after 3D Secure.
confirm_client_secret_missing#
fluveo.confirmPayment() got no clientSecret, or one that is not a PaymentIntent's client secret, for elements made with { mode, amount, currency }.
Fix: at the press of Pay, call elements.submit(), create the PaymentIntent on your server and pass its client_secret as confirmPayment({ elements, clientSecret, confirmParams }).
submit_not_mounted#
elements.submit() ran while the payment element was not mounted in the page: before mount(), or after unmount() or destroy().
Fix: call it after elements.create("payment").mount("#payment-element"), when the buyer presses Pay.
submit_required#
fluveo.confirmPayment() of elements made with { mode, amount, currency } ran with no elements.submit() called before it. One call serves until a confirm ends in anything but a field's validation_error, so call it at each press of Pay.
Fix: in the Pay handler, run const { error } = await elements.submit() before any other async work, and stop on error. Then create the PaymentIntent and confirm. Let one press end before the next starts.
currency_mismatch#
fluveo.confirmPayment() of elements made with { mode, amount, currency } got the client secret of a PaymentIntent in another currency than the form's. The form's currency is the one fluveo.elements() got, or the last one elements.update({ currency }) set; "USD" and "usd" are the same currency. Nothing is charged: the form sends no card, and the PaymentIntent stays requires_payment_method. A PaymentIntent of another amount is no error: it is paid, at its own amount.
This error is not thrown. confirmPayment() resolves with it as { error }: its type is invalid_request_error, its code is currency_mismatch, its param is currency, its payment_intent is the PaymentIntent, and its message names the PaymentIntent's currency. The form shows the buyer nothing for it. Like every confirm that ends in an error other than a field's, it uses the elements.submit() up.
Fix: create the PaymentIntent in the form's currency. When the buyer picks another currency on your page, call elements.update({ currency }) first; at the next press, call elements.submit() and then confirmPayment().
not_available_yet#
The call or the option is part of fluveo.js, and this build of the script does not act on it. It throws so that nothing is ignored in silence. Today these throw it: the option fonts of fluveo.elements(), rules inside appearance, and an option an update() cannot change yet, which is fonts or locale on elements.update() and fields or defaultValues on element.update().
Fix: remove the option. The form works without it. Set the language with fluveo.elements({ locale }), and fields and defaultValues with elements.create("payment"): an update() changes none of the three. fluveo.js lists what each update() takes.
billing_details_missing#
fluveo.confirmPayment() was called for a form that does not ask the buyer for the country or the postal code, and confirmParams has no value for it. elements.create("payment", { fields }) hides a field with "never", and the form sends nothing for a hidden field, so your page has to. The message names the option that hid the field and the value that is missing. Nothing is sent to the API.
fields sets to "never" | confirmParams must hold |
|---|---|
fields.billingDetails.address.country | payment_method_data.billing_details.address.country |
fields.billingDetails.address.postalCode | payment_method_data.billing_details.address.postal_code |
"never" on billingDetails or on address hides both. A hidden field needs its value on every confirm, whatever the country, and a value that is empty or only spaces counts as missing.
Fix: pass what your page knows of the buyer, such as confirmParams: { return_url, payment_method_data: { billing_details: { address: { country: "US", postal_code: "94107" } } } }, or set the field to "auto" so the form asks for it.
appearance_value_invalid#
fluveo.elements({ appearance }) or elements.update({ appearance }) got a variable whose value is not of its kind. The message names the variable, the value it got, the kind it takes and an example. Each value is a text, written as CSS writes it: a number such as 4 is refused, and "4px" is taken.
Variable of appearance.variables | Kind | What it takes |
|---|---|---|
colorPrimary, colorBackground, colorText, colorTextSecondary, colorTextPlaceholder, colorDanger | color | A hex color, a color name, or rgb(), rgba(), hsl(), hsla(), hwb(), lab(), lch(), oklab() or oklch() with no other function inside, such as "#2961b2". Never var(), url(), inherit, initial, unset, revert or currentColor. |
fontFamily | font family | A list of font names, up to 200 characters, such as "Inter, sans-serif". |
fontSizeBase, spacingUnit, borderRadius | length | "0", or a number of up to three digits and two decimals in px, rem or em, such as "4px". |
A name that appearance or appearance.variables does not have throws option_not_supported, and rules throws not_available_yet.
Fix: pass a value of the variable's kind, as the example in the message shows. Write a color of your stylesheet out in full: the form is a frame, and it cannot read a variable of your page.
payment_intent_unexpected_state#
The form asked the API for the PaymentIntent, and its status is succeeded, processing, requires_capture or canceled. Such a PaymentIntent takes no card, so the form shows no card field: the buyer reads one line that sends them to the store. The message names the status. This error does not come from a call: the element's loaderror event carries it, and the console prints it.
It happens when a buyer reloads the checkout, or comes back to it, after they paid: an Idempotency-Key built from the order's id gives your server the same PaymentIntent again.
Fix: read the PaymentIntent's status on your server before you send its client_secret to the page. For one of the four, show the buyer how the order stands in place of the form. In the page, hide your Pay button when loaderror carries this code.
A PaymentIntent that reaches one of these after the form mounted is the API's to refuse: confirmPayment() resolves with the API's own error of the same code, and the form shows no message for it.
wrong_prop#
A component of @fluveo/react-fluveo-js got a prop it does not take, or <Elements> got no fluveo prop. The message names the component and the prop.
| Component | Props it takes |
|---|---|
<Elements> | fluveo, options, children |
<PaymentElement> | id, className, options, onReady, onChange, onFocus, onBlur, onLoadError |
<Elements> also raises it when its fluveo prop is not what loadFluveo() gives. That one comes after the promise resolves, so the console prints it as a rejected promise, the page keeps rendering and the form never shows.
Fix: pass only the props of the table. <Elements> takes what loadFluveo("pk_test_...") returns as fluveo, the promise itself, and { clientSecret } as options.
elements_provider_missing#
<PaymentElement>, useFluveo() or useElements() of @fluveo/react-fluveo-js ran in a component that is not inside <Elements>. The message names which of the three. Each reads the Fluveo object and the elements group from the <Elements> above it, so the component that renders <Elements> is itself outside it.
Fix: render the component that uses them inside <Elements fluveo={fluveoPromise} options={{ clientSecret }}>. Keep two components: one renders <Elements>, and one inside it renders <PaymentElement /> and calls the two hooks.