Documentation
Connectors
Available

Stripe

Connect Stripe to FloPay and configure payment methods and webhooks.

What you can do

  • Accept cards and eligible wallets for one-time purchases, and use Stripe subscriptions for recurring billing.
  • Control which eligible Stripe payment methods appear in FloPay Checkout.
  • Verify Stripe events and receive provider-neutral FloPay webhook events for payment and subscription changes.

Who this is for

This connector is for merchants that process payments with a Stripe account and want to present those payment methods through FloPay Checkout. FloPay client owners and admins can configure the gateway. Developers can use the same connection through the SDK and REST API.

Prerequisites

  • A Stripe account with access to its API keys and webhook settings.
  • A FloPay client where you are an owner or admin.
  • An active, user-owned FloPay API token if you configure the gateway through the REST API.
  • Access to each checkout hostname if you plan to enable Apple Pay or Google Pay.

Setup

FloPay stores your Stripe connection as a gateway record. One gateway represents one provider account in one environment. Use a stage gateway for Stripe test mode and a production gateway for Stripe live mode. Moving from test to live means creating a separate gateway record, not changing an existing record in place.

Client owners and admins can manage gateways in Settings > Gateways or through the settings/gateways REST API. Members cannot manage gateway settings. Client Basic authentication uses the client identifier as the username and an active user-owned API token as the password. Access is always scoped to the authenticated client.

The setup sequence uses POST /v1/settings/gateways, PATCH /v1/settings/gateways/:id, POST /v1/settings/gateways/:id/test, and POST /v1/settings/gateways/:id/activate.

Gateway lifecycle

StateMeaningAvailable actions
draftNew or incomplete. It is not used for payments.Edit, test, activate
activeComplete, validated, and eligible for payment processing.Edit settings, replace credentials, test, deactivate
inactiveComplete but deactivated.Edit, test, reactivate

Every new gateway starts as a draft. Activation requires all three credential fields and a live provider check. Deactivation stops the gateway from handling new payments but keeps its configuration. Reactivation validates the connection again. Gateways are never deleted.

FloPay captures the canonical Stripe account ID (acct_...) during the first successful connection validation. The provider, environment, and connected account are immutable after validation. Connecting the same Stripe account twice in the same environment returns 409.

Credentials and secret handling

Open Stripe Dashboard > Developers > API keys and copy:

  • publishableKey: pk_test_... for stage or pk_live_... for production.
  • secretKey: sk_test_..., sk_live_..., or a restricted rk_... key with account read access.
  • webhookSigningSecret: the endpoint signing secret beginning with whsec_.

The environment must match the key mode. FloPay rejects test keys on a production gateway and live keys on a stage gateway.

publishableKey is returned in read responses. secretKey and webhookSigningSecret are write-only. Read responses expose only secretKeyConfigured, webhookSigningSecretConfigured, and timestamps. There is no way to retrieve a submitted secret through the API, Dashboard, or support.

To rotate a credential, submit its replacement in a normal update. Omitted credential fields keep their stored values. FloPay validates the complete resulting credential set before it saves anything, so a failed validation leaves the previous configuration untouched.

Never put real keys or signing secrets in code samples, screenshots, URLs, logs, or support tickets.

1. Create a draft

In the Dashboard, open Settings > Gateways > Add gateway, choose Stripe and the environment, then create the draft. Credentials are optional until you are ready to validate the connection.

curl -X POST https://api.flopay.com/v1/settings/gateways \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "UK Ltd Stripe (live)",
    "providerType": "stripe",
    "environment": "production",
    "currency": "GBP",
    "countries": ["GB", "IE"],
    "priority": 0
  }'

2. Register the FloPay webhook

FloPay does not create the Stripe webhook automatically. In the matching test or live mode, open Developers > Webhooks, add a destination, and use the callback URL shown on the gateway's Webhook setup card:

https://<flopay-webhook-host>/webhooks/stripe

The URL is per provider, not per gateway. FloPay matches deliveries to the gateway using the account identity in the event. The host differs between FloPay stage and production, so copy the Dashboard value rather than constructing it. Subscribe to the events in Webhook events, then save the endpoint's whsec_... value as webhookSigningSecret.

3. Save and test credentials

Save publishableKey, secretKey, and webhookSigningSecret on the draft. A credential-bearing PATCH validates the resulting set and captures the Stripe account ID.

curl -X PATCH https://api.flopay.com/v1/settings/gateways/GATEWAY_ID \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "publishableKey": "pk_live_PLACEHOLDER",
    "secretKey": "sk_live_PLACEHOLDER",
    "webhookSigningSecret": "whsec_PLACEHOLDER"
  }'

curl -X POST https://api.flopay.com/v1/settings/gateways/GATEWAY_ID/test \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN"

The connection test probes Stripe without changing the gateway. Failures return a sanitized error and never echo key material.

4. Activate

Activation checks completeness, validates the stored credentials again, and moves the gateway to active.

curl -X POST https://api.flopay.com/v1/settings/gateways/GATEWAY_ID/activate \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN"

Use the matching /deactivate operation to stop an active gateway. Calling /activate on an inactive gateway revalidates and reactivates it.

Supported features

FeatureSupport
One-time paymentsCards and eligible Stripe-rendered payment methods
Recurring billingStripe-managed subscriptions and renewal events
WalletsApple Pay and Google Pay on eligible devices and registered domains
PayPalStripe-rendered fallback when the direct PayPal connector is absent
WebhooksVerified Stripe events normalized to FloPay event families

Payment methods

The Stripe gateway is dynamic. FloPay asks Stripe to render the methods enabled on the connected account and eligible for the current buyer. Enabling a supported method in Stripe does not require an SDK upgrade or frontend change.

showStripe controls the entire Stripe gateway. Its default value is true. Setting it to false hides cards, wallets, and all other Stripe-rendered methods. It does not select individual methods.

<FloPayCheckout sessionId={sessionId} onComplete={handleSuccess} />

<FloPayCheckout
  sessionId={sessionId}
  showStripe={false}
  onComplete={handleSuccess}
/>

The methods shown to a buyer depend on all of these conditions:

  1. The method is enabled for the connected account in Stripe.
  2. The buyer's locale, currency, and amount meet Stripe's rules.
  3. The browser and device support the method.
  4. Any required checkout domain is registered.

Stripe can provide cards, Apple Pay, Google Pay, Link, buy-now-pay-later methods, bank redirects, bank debits, and cash or voucher methods. Stripe's payment-method catalog is the current source of truth.

Narrowing the rendered methods

The session provides its eligible list as gateways.stripe.enabledPaymentMethods. Pass enabledPaymentMethods to render a subset. It narrows, never widens: naming a method the session does not advertise cannot enable it.

<FloPayCheckout
  sessionId={sessionId}
  enabledPaymentMethods={['apple_pay']}
  onComplete={handleSuccess}
/>

<FloPayCheckout
  sessionId={sessionId}
  enabledPaymentMethods={[]}
  showPayPal={false}
  onComplete={handleSuccess}
/>

An empty array means no optional methods and produces a card-only checkout. It does not fall back to the session list. Disable a single method account-wide in Stripe rather than setting showStripe={false}. Use the debug prop during local development to see which methods resolved for the current session.

Apple Pay

Apple Pay requires all of the following:

  1. Apple Pay enabled for the connected Stripe account.
  2. A Stripe-provided domain association file served from /.well-known/apple-developer-merchantid-domain-association on every checkout hostname.
  3. Each hostname registered in the matching Stripe account and mode.
  4. HTTPS and a supported Apple device with Apple Pay configured.

Place the downloaded association file at public/.well-known/apple-developer-merchantid-domain-association. Next.js serves it from the root. Register production, staging, regional, and subdomains separately. Repeat the registration for every Stripe account that can serve the checkout.

Apple Pay appears in Safari on supported iPhone, iPad, and Mac devices. It does not appear on Android or Windows, and Chrome on Mac is not a supported Apple Pay surface. Stripe test mode still requires a real card in Wallet, although the card is not charged.

If the button does not appear, verify the file returns successfully over HTTPS, confirm the exact hostname is registered in the current Stripe mode, check the device and Wallet setup, and inspect the browser console for Stripe errors.

Google Pay

Google Pay uses Stripe's ExpressCheckoutElement. Register every checkout hostname under Stripe Dashboard > Settings > Payments > Payment method domains in the matching test and live environments. Unlike Apple Pay, Google Pay does not use the Apple domain association file.

Test on HTTPS with Chrome or another supported Chromium browser and a Google Pay-enabled profile or device. Register the hostname in every Stripe account whose publishable key may initialize FloPay. If the button works in one environment but not another, confirm the exact hostname and connected Stripe account in that environment.

PayPal via Stripe

FloPay supports a Stripe-rendered PayPal fallback alongside the direct PayPal connector. The session selects exactly one path:

  • When gateways.paypal is present, direct PayPal renders.
  • When gateways.paypal is absent or null, PayPal renders through Stripe's ExpressCheckoutElement using gateways.stripe.publishableKey.

The paths are mutually exclusive per session, so PayPal never renders twice. FloPayCheckout handles selection, redirect state, and resume behavior automatically.

Stripe requires the PayPal ExpressCheckoutElement to use an Elements instance without paymentMethodCreation: 'manual', while card tokenization requires that setting. SplitCardForm therefore mounts separate card and PayPal Elements groups. The PayPal group receives amount and currency from the session.

The Stripe-rendered path does not work reliably inside Facebook, Meta, and Instagram in-app browsers. Merchants serving those buyers should configure the direct PayPal connector. Existing Stripe-only integrations keep using the fallback until a PayPal gateway becomes active.

Webhook events

FloPay verifies Stripe deliveries and normalizes them into the provider-neutral outbound events documented under Webhooks. Merchant handlers do not need to branch on the gateway.

Subscribed Stripe Events

FloPay subscribes to the following 30 Stripe webhook events.

Stripe eventPurpose
charge.refundedA charge was refunded, fully or partially.
charge.dispute.createdA dispute opened; it records no amount when the notification contains no balance movement.
charge.dispute.funds_withdrawnDisputed funds left the merchant balance.
charge.dispute.funds_reinstatedDisputed funds returned after a win.
charge.dispute.closedThe dispute reached its final won or lost outcome.
customer.createdA Stripe customer was created.
customer.updatedA Stripe customer's details changed.
customer.deletedA Stripe customer was deleted.
customer.subscription.createdA Stripe subscription was created.
customer.subscription.updatedSubscription details changed.
customer.subscription.deletedA Stripe subscription ended.
customer.subscription.pausedA subscription was paused.
customer.subscription.resumedA subscription resumed.
invoice.createdA draft invoice was created.
invoice.updatedInvoice fields changed.
invoice.deletedA draft invoice was deleted.
invoice.finalizedAn invoice became collectible.
invoice.sentAn invoice was sent to the customer.
invoice.upcomingStripe rendered an upcoming invoice.
invoice.will_be_dueAn invoice is about to be due.
invoice.payment_action_requiredInvoice payment requires SCA or 3DS action.
invoice.payment_failedAn invoice payment attempt failed.
invoice.payment_succeededAn invoice payment attempt succeeded; invoice.paid remains authoritative.
invoice.paidAn invoice was paid in full.
invoice.overdueAn invoice passed its due date.
invoice.voidedAn invoice was voided.
invoice.marked_uncollectibleAn invoice was marked uncollectible.
payment_intent.succeededA one-off PaymentIntent succeeded.
payment_intent.payment_failedA PaymentIntent attempt failed.
refund.createdA refund object was created.

Signature Verification

FloPay reads the Stripe-Signature header and verifies the signed raw payload with Stripe's official Stripe.webhooks.constructEvent method and the HMAC SHA-256 signing secret stored on the gateway. Missing, malformed, mismatched, or stale signatures are rejected without retry.

Inbound → Outbound Mapping

Stripe eventOutbound Flo eventNotes
charge.refundeditem.refundedMaps to the refunded charge's line.
charge.dispute.createditem.charged_back, invoice.updated, or (no outbound money event)The dispute is opened. When it has no balance movement, Flo records no amount and emits no outbound money event. A previously unseen withdrawal is emitted once.
charge.dispute.funds_withdrawnitem.charged_back, invoice.updated, or (no new outbound)A previously unseen provider balance movement records the positive chargeback and updates the invoice. If already recorded, Flo emits nothing new.
charge.dispute.funds_reinstatedinvoice.updated or (no new outbound)A previously unseen reinstatement records the negative movement and restores the invoice. A duplicate or movement-free notification emits nothing new, no second item.charged_back, and no item.refunded.
charge.dispute.closeditem.charged_back, invoice.updated, or (no new outbound)FloPay projects any previously unseen balance movement. A withdrawal emits item.charged_back and invoice.updated; a reinstatement emits only invoice.updated. An already-recorded or movement-free close emits nothing new.
customer.created(no outbound)FloPay associates the Stripe customer ID with the user gateway record.
customer.updated(no outbound)Stored on the gateway record.
customer.deleted(no outbound)Internal gateway-record cleanup.
customer.subscription.createdsubscription.createdA subscription was created.
customer.subscription.updatedsubscription.updated, subscription.cancelled, subscription.reactivated, subscription.paused, or subscription.resumedFloPay compares the current subscription with previous_attributes to select the transition.
customer.subscription.deletedsubscription.expiredStripe's deleted subscription becomes Flo's terminal expired state.
customer.subscription.pausedsubscription.pausedThe subscription paused.
customer.subscription.resumedsubscription.resumedThe subscription resumed.
invoice.createdinvoice.createdA draft invoice was created.
invoice.updatedinvoice.updatedInvoice fields changed.
invoice.deletedinvoice.deletedA draft invoice was deleted.
invoice.finalized(no outbound)The corresponding merchant activity arrives with payment state.
invoice.sent(no outbound)Stripe email activity is not a Flo lifecycle event.
invoice.upcoming(no outbound)Flo waits for the real billing cycle.
invoice.will_be_due(no outbound)A pre-due signal is not surfaced.
invoice.payment_action_required(no outbound)Checkout handles the SCA or 3DS prompt.
invoice.payment_failedinvoice.payment_failedEmitted for each failed attempt.
invoice.payment_succeeded(no outbound)Suppressed because invoice.paid is authoritative.
invoice.paidinvoice.paid, plus subscription.renewed and/or item.purchasedFlo emits the invoice event, a renewal for subscription billing reasons, and purchases for non-subscription lines.
invoice.overdueinvoice.overdueThe invoice remains collectible.
invoice.voided(no outbound)No paid event is emitted.
invoice.marked_uncollectible(no outbound)Stripe-side state only.
payment_intent.succeededitem.purchasedSuppressed for invoice-backed intents. A catalog identity must resolve from metadata or the checkout item.
payment_intent.payment_failed(no outbound)FloPay records the attempt and mapped decline reason internally.
refund.createditem.refundedFloPay loads the parent charge and emits an item-level refund event.

FloPay derives specific subscription transitions from the Stripe state change rather than relying only on the inbound event name.

Dispute balance movements

Stripe can repeat the same movement across overlapping lifecycle notifications. FloPay keys ledger entries and outbound events to the provider balance-movement identity, so the same withdrawal converges on one result.

The notification name does not imply another movement. Any dispute event can carry a previously unseen withdrawal or reinstatement. A lost dispute confirms its existing withdrawal and adds no new event when that movement was already recorded.

Recurring billing behavior

Stripe drives the recurring billing cycle for Stripe subscriptions. Renewals arrive through invoice.paid with a subscription billing reason, and FloPay emits subscription.renewed with invoice.paid. Stripe Smart Retries owns off-session retries. FloPay emits invoice.payment_failed for each reported failure and does not issue another retry.

Limitations

  • Stripe decides method eligibility at runtime. An enabled method can still be absent for a buyer whose device, browser, locale, currency, or amount is ineligible.
  • Apple Pay and Google Pay require HTTPS and exact hostname registration. Apple Pay also requires the association file.
  • enabledPaymentMethods can reduce the session list but cannot enable a method that Stripe or FloPay did not offer.
  • showStripe={false} removes the whole Stripe panel, including hosted card fields.
  • The Stripe-rendered PayPal path is unreliable in Facebook, Meta, and Instagram in-app browsers. Use direct PayPal for those surfaces.
  • Gateway provider, environment, and account identity cannot change after validation.

Troubleshooting

ProblemFix
Stripe rejects the keyConfirm the key belongs to this account and environment and has account read access.
Environment mismatchUse test keys on stage and live keys on production. Create a new gateway to move environments.
Activation reports missing fieldsSave the publishable key, secret key, and Stripe signing secret, then test again.
A wallet is missingCheck device support, HTTPS, payment-method enablement, and exact domain registration.
A method is missingConfirm Stripe eligibility and inspect debug output for the session.
A credential update returns 422Correct the replacement and resubmit. The previous credentials remain active.

On this page