Skip to main content

Payment widget

How to put a Flourish Payments form on your own website for one-time payments, donations, memberships, and subscriptions.

Overview​

The widget renders a multi-step payment form on any page. You describe the screens, fields, pricing, and copy in one configuration object. There's no framework code to write. What the form collects goes to Salesforce through the public connector, and Stripe takes the payment on its own secure form at the widget's checkout step. What happens to the record afterwards is on Payments in Salesforce.

Version 1.4.0. Before any of this works, an admin has to have finished Setup. That's where the connector page's URL comes from.


Quick start​

1. Load the scripts​

The widget loads none of its own dependencies, so the page supplies all four: Vue 3, PrimeVue, the PrimeVue Aura theme, and Stripe.js. Which version of each you run is your decision, and a payment page is the wrong place for a library to change under you.

<!-- Vue 3 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

<!-- PrimeVue and its Aura theme -->
<script src="https://unpkg.com/primevue@4/umd/primevue.min.js"></script>
<script src="https://unpkg.com/@primeuix/themes@2/umd/aura.js"></script>

<!-- Stripe.js -->
<script src="https://js.stripe.com/v3/"></script>

<!-- The Flourish Payments widget -->
<script src="https://cdn.toflourish.org/payments/v1/payment-widget.js"></script>

There's no stylesheet to link. The widget carries its own CSS.

Keep PrimeVue on @4 and its theme on @2 as above. PrimeVue 5 and the version 3 theme need a PrimeUI license key, and without one they put an "Invalid PrimeUI License" badge on your page. Versions 4 and 2 are free.

If one of the four is missing or fails to load, the widget says so instead of rendering:

  • The visitor sees "The payment form couldn't load. Please refresh the page, or contact us if it keeps happening."
  • The browser console names each missing library and the URL that supplies it.
  • create() returns null instead of throwing.

2. Add a place for it​

<div id="pay"></div>

3. Create the form​

<script>
FLPayments.create('#pay', {
text: {
title: 'Make a Donation',
description: 'Your support makes a difference.'
},
pricing: { mode: 'singleAmount', currency: 'usd' },
payload: {
staticValues: { itemName: 'Donation', paymentCurrency: 'usd' }
},
paymentsLwc: { siteUrl: 'https://your-org.my.site.com/yoursite/payment-connector' },
screens: [
{
id: 'amount',
sections: [{
fields: [{
name: 'amount',
type: 'preset-amount',
label: 'Choose an amount',
presets: [25, 50, 100, 250],
allowCustom: true,
customLabel: 'Or enter a custom amount',
defaultSelected: 50
}]
}]
},
{
id: 'donor',
role: 'submit',
sections: [
{
label: 'Your information',
columns: 2,
fields: [
{ name: 'firstName', type: 'text', label: 'First name', required: true },
{ name: 'lastName', type: 'text', label: 'Last name', required: true },
{ name: 'email', type: 'email', label: 'Email', required: true, span: 2 }
]
},
{
type: 'summary',
rows: [{ key: 'amount', label: 'Total', format: 'currency', emphasis: true }]
}
]
},
{ id: 'pay', role: 'checkout', sections: [{ type: 'checkout' }] },
{ id: 'done', sections: [{ type: 'confirmation' }] }
]
});
</script>

Three things in there are required:

  • paymentsLwc.siteUrl is the connector page your admin set up.
  • itemName and paymentCurrency go to Salesforce with every payment that doesn't sell a Stripe price. Salesforce refuses the payment without them, and the widget doesn't add them for you.
  • The amount comes first. Every screen checks that an amount has been chosen, so put it on the first screen or turn the check off (see Validation).

This is the form above on a page of its own:

The donation form's first screen. Under the title Make a Donation and its description, Choose an amount offers $25.00, $50.00 (selected), $100.00, and $250.00. Below are a custom amount box and a Next button. The second screen after Next. First name, last name, and email are filled in under Your information. A summary reads Total $50.00, and Back and Next buttons are below it.

What the widget does to your page​

The form renders inside its own shadow DOM, so your page's CSS doesn't reach its inputs and buttons. A few things do cross that line:

  • Head styles are copied in. The widget copies in every <style> tag in your page's <head>. It also copies ones added later. That's how PrimeVue's styles reach it, and your own head styles come along, too.
  • Stripe's step sits outside it. Stripe's payment form goes in a <div id="fl-checkout-portal"> placed right after your element (fl-checkout-portal-2 for a second form on the page, and so on). Stripe's bank-account flow needs a normal page to work. Its look comes from a style the widget adds to your page's <head>.
  • The viewport tag changes. It gains minimum-scale=1, or one is created if the page has none. Stripe's bank-account flow needs it on phones.

How the flow works​

A flow is an ordered list of screens. Each screen holds sections, and field sections hold fields. The visitor moves through the screens with Next and Back. The last screen has no Next button and usually shows a confirmation.

A screen's role decides what its Next button does:

RoleWhat Next does
(none)Checks the screen's visible fields and moves on.
submitChecks the fields, sends everything to Salesforce, and moves on once Salesforce answers with a payment session.
checkoutShows Stripe's payment form. Next stays off until the payment completes.

A few rules worth knowing:

  • The checkout screen needs both parts: role: 'checkout' and a section of type checkout. Stripe's form appears only with both.
  • Only Stripe's form shows on the checkout screen. The title, other sections, and messages are hidden there.
  • The Finish button: the Next button on the second-to-last screen reads text.finishButton. Every other screen's reads text.nextButton. In the usual flow, the second-to-last screen is the checkout screen.

Scrolling​

  • Changing screens: the widget scrolls to its own top. On the checkout screen it scrolls to Stripe's form.
  • A field appears: when a field appears because of showWhen and isn't fully in view, the widget scrolls just enough to show it.

Configuration reference​

All options go in the second argument to FLPayments.create(selector, config).

FLPayments.create('#pay', {
text: { /* labels and copy */ },
features: { allowBackNavigation: true, autoFinishCheckout: false },
pricing: { mode: 'singleAmount', currency: 'usd', amountFieldName: 'amount', adjustments: [] },
validation: { requireAmount: true },
state: { defaults: {}, pricingDefaults: {} },
payload: { method: 'createCheckoutSession', staticValues: {} },
stripe: { mountSelector: '#fl-checkout' },
paymentsLwc: { siteUrl: '...', iframeId: 'lwc-iframe', wrapperSelector: '.lwc-iframe-wrapper' },
connector: { timeoutMs: 10000, submitTimeoutMs: 30000, disabled: false, debug: false },
theme: { css: '', stylesheetUrl: '' },
hooks: { /* lifecycle callbacks */ },
screens: [ /* required */ ]
});

Smaller options:

OptionNotes
paymentsLwc.siteUrlThe connector page. It's always loaded over https, and your page's own URL parameters are passed on to it (see URL parameters).
connector.timeoutMsHow long to wait for the connector before showing text.connectorTimeout. Defaults to 10 seconds.
connector.submitTimeoutMsHow long Next waits for Salesforce to answer before showing text.submitTimeout. Defaults to 30 seconds.
connector.disabledtrue previews a layout without connecting. Nothing is sent to Salesforce, and submitting says payments are turned off.
connector.debugtrue logs every message to and from the connector in the browser console with an [FLPayments] prefix. Useful when a page won't connect.
stripe.mountSelectorWhere Stripe's form goes. Must be an #id. Defaults to #fl-checkout (#fl-checkout-2 and up for further forms on the page).
stripe.publishableKeySupplied by the connector. Set it yourself only with connector.disabled.
paymentsLwc.iframeId, paymentsLwc.wrapperSelectorLeave as they are unless they clash with your page. The wrapper must be a class selector.
testDataAn object copied into the form after everything else. Use it for trying a page out. Don't ship it.

text​

KeyDefault
title''
description''
paymentDescription''. Shown on the checkout screen.
confirmationImageA built-in success image. Set '' for none.
confirmationHeading'Payment complete'
confirmationDescription'Thank you for your payment.'
nextButton, finishButton, backButton'Next', 'Finish', 'Back'
connectorLoading'Loading the secure payment form…'
connectorTimeout"The payment form isn't available right now. Please refresh the page or try again later."
connectorMissingSettings"This payment form isn't set up yet. Please contact the site's administrator." Shown when the connector answers without a Stripe key.
submitTimeout"The payment system didn't respond. Please try again." Shown when Salesforce doesn't answer within connector.submitTimeoutMs.
paymentNotOpen"There's nothing to pay here. This payment may already be paid or canceled. Please contact us if you think that's a mistake." Shown instead of the form when a pay link names a payment that isn't open.

Some wording can't be changed here:

  • Payments section labels: "Amount Due," "Total," and "Balance:"
  • The select placeholder: "Select..." (a field's own placeholder replaces it).
  • Validation messages (except through errorMessage).
  • During checkout: "Completing your payment…" and "Something went wrong. Please try again."
  • Salesforce errors: an error from Salesforce is shown as written.

features​

KeyDefaultWhat it does
allowBackNavigationtrueShows the Back button on screens after the first.
autoFinishCheckoutfalseWhen true, the flow moves on by itself as soon as the payment completes. A brief "Completing your payment…" message shows, and there's no Finish button. Keep a screen after checkout, or the visitor is left on an empty card. When false, the visitor clicks Finish.

pricing​

pricing: {
mode: 'singleAmount', // or 'external'
currency: 'usd', // for display; see below
amountFieldName: 'amount', // must match the preset-amount field's name
adjustments: [
{
enabled: true, // required; an adjustment without it is ignored
type: 'percentage', // the only type
baseKey: 'baseAmount', // the default
targetKey: 'feeAmount', // required
percent: 0.029, // a 2.9% processing fee
applyWhen: (form) => form.coverFees === true // optional
}
]
}

Modes

  • singleAmount: the amount comes from the chosen preset or the custom amount, the enabled adjustments run, and the total is the amount plus the fee.
  • external: your code supplies the figures, and the widget only rounds them. Adjustments don't run.
    • Set the starting figures in state.pricingDefaults. To change them later, write to ctx.vm.pricingState from a hook.
    • With no amount, the total is baseAmount alone.

These pricing keys work in summary rows, in showWhen and applyWhen checks, and in hooks:

KeyMeaning
baseAmountThe chosen or entered amount. In external mode, the value your code set.
feeAmountWritten by adjustments. Each one overwrites its target, so the last enabled adjustment aimed at feeAmount wins.
amountThe total of baseAmount and feeAmount.

An adjustment can write to a key of its own through targetKey. A summary row can show it, but it isn't added to the total and isn't sent to Salesforce.

Currency. pricing.currency only formats amounts on screen. Stripe charges in paymentCurrency from the payload, so set payload.staticValues.paymentCurrency to match.

Sections​

Each screen has an id (required), an optional role, and a list of sections. Set a section's type, or leave it out for fields. Every section accepts id, label, description, and showWhen (a function (form, pricing) => true or false).

TypeWhat it shows
fieldsA grid of fields. Set columns: 2 for two columns.
summaryRead-only rows from the pricing keys. Each row takes key, label, format: 'currency', and emphasis.
contentA block of text or of html you control. class is optional.
imageAn image set by src and alt. caption and class are optional.
dividerA horizontal rule.
alertA callout with text or html. severity is 'info' (the default), 'success', 'warning', or 'error'.
paymentsThe payments of an existing Opportunity. Shows only with a payment model.
checkoutStripe's payment form. Goes on the role: 'checkout' screen.
confirmationThe success image, heading, and description from text.
customA component you registered. See Custom components.

Fields​

Every field takes name (required), label, required (true or a function), showWhen, defaultValue, errorMessage, and tooltip. Text, email, currency, long text, and select fields also take a placeholder. In a two-column section, span: 2 makes a field fill the row.

TypeNotes
preset-amountSee below.
textA text box.
emailA text box that checks the address format.
currencyA number formatted as money. min sets the floor (default 0).
textareaSeveral lines of text. rows defaults to 3.
selectA dropdown. Pass options: [{label, value}], or name other keys with optionLabel and optionValue.
radioA set of choices. Each option can carry a description.
checkboxA single checkbox that starts unticked. checkboxLabel sets the text beside it. A required checkbox must be ticked. It shows no tooltip.

preset-amount fields:

  • Buttons and custom amount: presets: [25, 50, ...] makes the buttons. allowCustom adds a box for another amount. defaultSelected picks a button to start with.
  • Copy: presetLabels renames individual buttons. For example, { "100": "$100 Sustainer" } renames the $100 button. customLabel and customPlaceholder set the custom box's copy.
  • min: the lowest amount accepted (default 1).
  • required: a chosen button or a typed amount both count. The amount check already covers this, so it mostly matters when that check is off.

Only the first preset-amount field on the form counts. It's the one defaultSelected and the amount check use, and its error shows above the buttons.

Showing fields conditionally​

required, showWhen, and applyWhen can all be functions of the form:

{ name: 'company', type: 'text', label: 'Company',
showWhen: (form) => form.donorType === 'business',
required: (form) => form.donorType === 'business' }

A hidden field is never required or checked, but its value is still sent.

URL parameters​

Pre-filling the form. A URL parameter named flpaypre_ plus a field name fills that field when the page loads:

https://yoursite.org/donate?flpaypre_email=jane@example.com&flpaypre_firstName=Jane
  • Values arrive as text. flpaypre_coverFees=true sets the text "true". A check like form.coverFees === true won't match it.
  • Amounts fill the custom box. flpaypre_amount=100 fills the custom amount. It doesn't select the $100 button.
  • Any name is accepted, not just your fields, and goes to Salesforce with everything else. Names Salesforce acts on, such as priceId and mode, are included. On a page where those matter, set them in payload.staticValues. A URL can't override those.

Paying an existing Opportunity. Your page's URL parameters are passed on to the connector. It reads two of its own: flpayoid (the Opportunity to pay) and flpaypid (a payment on it). See Payment models.

state​

state: {
defaults: { // starting field values, by field name
frequency: 'once',
coverFees: false
},
pricingDefaults: { // starting pricing, for 'external' mode
baseAmount: 0,
amount: 0
}
}

A field's starting value is decided in this order. Later steps win over earlier ones:

  1. Its defaultValue (only when state.defaults doesn't name the field).
  2. state.defaults.
  3. A flpaypre_ URL value.
  4. testData.

theme​

There are two layers, applied in order. The later one wins:

  1. theme.stylesheetUrl: the address of a CSS file.
  2. theme.css: CSS as text.

Both apply inside the widget, so set the variables below rather than restyling its internals.

theme: {
css: `
:host { --flpay-brand: #c2185b; --flpay-brand-hover: #880e4f; }
`
}
VariableDefaultUsed for
--flpay-brand#0176d3Main buttons, links, and selected states
--flpay-brand-hover#014486The Back button and preset buttons on hover
--flpay-brand-active#032d60The selected preset button's text
--flpay-focus#1b96ffThe focus ring
--flpay-surface#ffffffThe card's background
--flpay-surface-alt#fafaf9Summary rows
--flpay-border#c9c9c9Borders
--flpay-border-weak#dddbdaLight borders
--flpay-text#181818Text
--flpay-text-muted#5c5c5cSecondary text
--flpay-text-label#3e3e3cField labels
--flpay-radius-sm4pxRounding on inputs and buttons
--flpay-radius-md6pxRounding on cards
--flpay-shadow-card0 2px 3px rgba(0, 0, 0, 0.08)The card's shadow
--flpay-shadow-focus0 0 0 2px var(--flpay-focus)The focus ring's shadow
--flpay-widget-padding24pxSpace inside the card

Stripe's step isn't styled by these. It sits outside the widget (see What the widget does to your page). To restyle it, target #fl-checkout-portal (or .fl-checkout-portal for every form on the page) from your own page's stylesheet and use !important.

hooks​

The widget calls these functions as the visitor moves through the form:

hooks: {
beforeValidate: (ctx) => { /* ... */ },
afterValidate: (ctx) => { /* ... */ },
beforePayloadBuild: (ctx) => { /* ... */ },
transformPayload: (payload, ctx) => ({ ...payload, source: 'web' }),
onPaymentResponse: (response, ctx) => { /* ... */ },
onError: (error, ctx) => { /* ... */ }
}
HookWhen it runs
beforeValidateBefore each screen is checked. Throw an error to stop the visitor with your own message.
afterValidateAfter a screen passes its checks.
beforePayloadBuildOn submit, once the payload is built and just before transformPayload. Changing the form here doesn't change what's sent.
transformPayloadLast, with the payload. Return what to send. If it returns nothing, the payload as it now stands is sent.
onPaymentResponseAfter Salesforce answers and Stripe's form is showing.
onErrorWhen a screen fails its checks, Salesforce returns an error, or Salesforce doesn't answer in time. Not called when the connector fails to load.

Hooks run straight through and aren't waited on, so an async hook can't hold the form.

What ctx holds:

PropertyDescription
configThe whole configuration.
currentScreenThe current screen's id.
formStateField values, by name.
pricingStateA copy of the pricing keys. To change pricing, write to ctx.vm.pricingState.
customDataA shared place to keep data between hooks, screens, and custom components.
selectedPresetAmountThe selected preset, if any.
vmFor advanced cases, the widget's Vue instance.

payload​

The widget sends this to Salesforce on submit, in this order. Later entries win:

  1. Every field's value. That includes hidden fields and any flpaypre_ values.
  2. amount, baseAmount, and feeAmount from pricing (in dollars).
  3. Any payment model selections.
  4. payload.staticValues.
  5. Whatever transformPayload returns.

Leave payload.method as 'createCheckoutSession'. It's the only one the connector answers. With any other name, the form waits forever.

Names Salesforce acts on:

NameWhat it does
itemName, paymentCurrencyRequired unless you send priceId. Name what's being paid for, and set the currency Stripe charges in.
itemDescriptionA description on Stripe's receipt line.
email, firstName, lastName, companyNameFind or create the payer and their company.
amount, feeAmount, baseAmountThe payment amount.
mode, priceId, interval, intervalCountStart a subscription. See Subscriptions.
setupFutureUsage'off_session' saves the card so Salesforce can charge it later.
quantity, collectPhoneNumber, customerId, metadata, clientReferenceId, returnUrlPassed on to Stripe.

Every other name is kept on the Opportunity in Payment Custom Data for your Flows to read. See Payments in Salesforce.

The amount the form shows is a preview. Salesforce decides what's charged before calling Stripe. See Server-side pricing.


Payment models​

A payment model page collects against an existing Opportunity: an invoice, a balance due, or a set of installments. The page is opened with ?flpayoid=<Opportunity ID>. When that Opportunity has a Payment Model, a payments section shows what's owed:

Payment ModelWhat the visitor sees
Opportunity OnlyA read-only "Amount Due".
Summed PaymentsRead-only payment lines and their total.
Selective PaymentsA checkbox per payment. All start ticked, and the total follows the ticks.
Dynamic PaymentsAn amount box per payment that starts at 0 and is capped at its balance. Salesforce refuses an amount that's 0 or less or more than the balance.

Summed Payments charges every payment the Opportunity still owes, even if one was added after the page loaded. Once the payment goes through, Salesforce marks each payment it charged as paid.

Salesforce charges only payments the Opportunity still owes. If one is paid, written off, or canceled after the page loads, the visitor sees "One of these payments isn't open anymore. Please refresh the page and try again."

Paying one payment​

A pay link pays a single payment record. Add flpaypid=<payment ID> beside flpayoid:

https://yoursite.org/pay?flpayoid=006xx000000abcd&flpaypid=a01xx000000efgh

The payments section shows a read-only "Amount Due" of what's still owed on that payment, whatever the Opportunity's payment model. Salesforce charges that amount and marks the payment paid once the payment goes through.

If the Opportunity doesn't owe that payment, the form doesn't show. That covers a payment that's paid, written off, or canceled and one on another Opportunity. The visitor reads text.paymentNotOpen instead, and the page never charges the whole Opportunity in its place.

Build these pages with validation: { requireAmount: false }. The payments total isn't part of the widget's pricing, so with the amount check on, submit fails with "Please choose or enter a valid amount." For the same reason, a summary row can't show the payments total.

Setting up payment models on records is on Payments in Salesforce.


Subscriptions​

A page starts a subscription instead of a one-time payment by sending mode: 'subscription'. The payer checks out on the same Stripe form. The price comes from one of two places:

SendWhat Stripe charges
priceIdA recurring Stripe price. It can be one you made on a Product's record page. The price sets the amount, currency, and billing period, so the page's own amount is ignored.
amount with intervalThe form's amount every interval: day, week, month (the default), or year. Add intervalCount for longer periods. For example, month with intervalCount: 3 bills quarterly. Like any payment without a price, it needs itemName and paymentCurrency.

Send itemName with a priceId, too. It names the Opportunity.

A fixed price. The form only collects the payer, so turn the amount check off:

FLPayments.create('#pay', {
validation: { requireAmount: false },
payload: {
staticValues: {
mode: 'subscription',
priceId: 'price_1QxYz...',
itemName: 'Monthly Giving Circle'
}
},
paymentsLwc: { siteUrl: '...' },
screens: [ /* payer fields on a submit screen, then checkout and confirmation */ ]
});

One-time or monthly, the visitor's choice. A value in staticValues can't follow the form, so decide in transformPayload:

hooks: {
transformPayload: (payload, ctx) => ctx.formState.frequency === 'monthly'
? Object.assign(payload, { mode: 'subscription', interval: 'month' })
: payload
}

Products, invoices, and subscriptions covers what a subscription leaves on your records and how renewals and cancellations work.


Custom components​

Register a Vue component before calling create(). Then use it in any flow:

FLPayments.registerComponent('donor-disclaimer', {
props: ['section', 'formState', 'pricingState', 'customData', 'config'],
template: '<p class="muted"><strong>Note:</strong> {{ section.note }}</p>'
});

// Then in your screens:
{ type: 'custom', component: 'donor-disclaimer', note: 'All donations are tax-deductible.' }
  • Props: a custom component receives section, formState, pricingState, customData, and config. It also receives anything in section.props.
  • Names: use lowercase names with hyphens, and don't start them with p-.

Validation​

  • When: fields are checked when the visitor clicks Next. Hidden fields are skipped.
  • The amount check: with validation.requireAmount: true (the default), every screen's Next also checks the amount. It must be at least the first preset-amount field's min (1 if that isn't set). Turn it off on pages where the amount comes from a Stripe price or a payment model.
  • Errors:
    • Each error shows under its field. The amount error shows above the preset buttons.
    • The first problem scrolls into view.
    • A message at the bottom repeats a single error or says "Please complete the highlighted fields."
  • Messages: a field's errorMessage replaces "This field is required."

A complete example​

This donation page has an optional processing fee:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Donate</title>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/primevue@4/umd/primevue.min.js"></script>
<script src="https://unpkg.com/@primeuix/themes@2/umd/aura.js"></script>
<script src="https://js.stripe.com/v3/"></script>
<script src="https://cdn.toflourish.org/payments/v1/payment-widget.js"></script>
</head>
<body>

<div id="pay"></div>

<script>
FLPayments.create('#pay', {
text: {
title: 'Support Our Mission',
description: 'Choose an amount and complete your donation.',
confirmationHeading: 'Thank You!',
confirmationDescription: 'Your donation has been received.'
},

screens: [
{
id: 'form',
sections: [
{
label: 'Donation Amount',
fields: [{
name: 'amount',
type: 'preset-amount',
label: 'Select an amount',
presets: [25, 50, 100, 250, 500],
defaultSelected: 50,
allowCustom: true,
customLabel: 'Or enter a custom amount',
min: 5
}]
},
{
label: 'Your Information',
columns: 2,
fields: [
{ name: 'firstName', type: 'text', label: 'First Name', required: true },
{ name: 'lastName', type: 'text', label: 'Last Name', required: true },
{ name: 'email', type: 'email', label: 'Email', required: true, span: 2 }
]
},
{
fields: [
{ name: 'coverFee', type: 'checkbox', checkboxLabel: 'Cover the 3% processing fee' }
]
}
]
},
{
id: 'review',
role: 'submit',
sections: [{
type: 'summary',
rows: [
{ key: 'baseAmount', label: 'Donation', format: 'currency' },
{ key: 'feeAmount', label: 'Processing Fee', format: 'currency' },
{ key: 'amount', label: 'Total', format: 'currency', emphasis: true }
]
}]
},
{ id: 'checkout', role: 'checkout', sections: [{ type: 'checkout' }] },
{ id: 'confirmation', sections: [{ type: 'confirmation' }] }
],

pricing: {
mode: 'singleAmount',
currency: 'usd',
adjustments: [{
enabled: true,
type: 'percentage',
targetKey: 'feeAmount',
percent: 0.03,
applyWhen: function (form) { return form.coverFee === true; }
}]
},

payload: {
staticValues: {
itemName: 'Donation',
paymentCurrency: 'usd',
campaignId: '701xx000000abcd'
}
},

paymentsLwc: { siteUrl: 'https://your-org.my.site.com/yoursite/payment-connector' },

theme: {
css: ':host { --flpay-brand: #2d6a4f; }'
}
});
</script>

</body>
</html>

campaignId isn't a name Salesforce acts on, so it lands in Payment Custom Data. A Flow can read it there.


Public API​

MemberWhat it does
FLPayments.create(selector, config)Builds the form in the element selector matches and returns the Vue app. Call unmount() on it to remove the form. Returns null when a dependency is missing. Throws when nothing matches the selector or the element already holds a form.
FLPayments.registerComponent(name, definition)Registers a component for custom sections. Call it before create().
FLPayments.versionThe widget's version (for example, '1.4.0').
FLPayments.buildA short fingerprint of the exact file the page loaded. It tells a cached copy from a fresh one.
FLPayments.debug()Logs and returns what's on the page: the version, build, missing dependencies, and how many forms are showing. Useful when a page misbehaves.

The element you build into also carries data-fl-state (ready or error). On error, it carries a data-fl-error that says what went wrong.

A page can hold several forms if each has its own element. Only one Stripe payment form can be open at a time, so opening it in one form closes it in another. To remove a form, call unmount() on the app create() returned. It takes its Stripe area and page styles with it.


Versioning​

AddressWhat it serves
https://cdn.toflourish.org/payments/v1/payment-widget.jsThe latest 1.x release. Use this one. A breaking change would come out under /v2/ and leave it alone.
https://cdn.toflourish.org/payments/v1/payment-widget.min.jsA minified copy of the same file.

Browsers and the CDN keep a copy for about five minutes, so a new release reaches visitors shortly after it's published. If you host your own copy instead, put the success image beside it at images/pay_img_successSplash.png, or set text.confirmationImage.