Payments
AppFunnel provides payment components that handle Stripe and Paddle integrations out of the box. You select a product with useProducts(), render a payment form, and the SDK takes care of intent creation, event tracking, and post-purchase flow.
StripePaymentForm
import { StripePaymentForm } from '@appfunnel-dev/sdk'A drop-in Stripe payment form that supports both inline card collection and full Stripe Embedded Checkout.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'elements' | 'embedded' | 'elements' | elements: inline card form (PaymentElement). embedded: Stripe Embedded Checkout (iframe). |
mode | 'checkout' | 'validate-only' | 'checkout' | checkout: charge or create subscription. validate-only: collect card without charging. |
productId | string | — | Product ID override. Defaults to the currently selected product from useProducts(). |
successPageKey | string | — | Page key to redirect to after embedded checkout completes. Required for embedded variant. |
automaticTax | boolean | false | Enable Stripe automatic tax calculation. Embedded variant only. |
managedPayments | boolean | false | Enable Stripe managed payments — Stripe controls tax, payment methods, and compliance. Embedded variant only. |
allowPromotionCodes | boolean | false | Allow promotion/coupon codes in checkout. Embedded variant only. |
onSuccess | () => void | — | Called on successful payment. Elements variant only. |
onError | (error: string) => void | — | Called on failure. Elements variant only. |
onReady | () => void | — | Called when the form is ready for interaction. Elements variant only. |
appearance | Appearance | — | Stripe Appearance override. Elements variant only. |
layout | 'tabs' | 'accordion' | 'auto' | 'tabs' | PaymentElement layout. Elements variant only. |
ref | Ref<StripePaymentHandle> | — | Imperative handle for programmatic control. Elements variant only. |
Ref handle
Pass a ref to get access to the StripePaymentHandle (elements variant only):
import type { StripePaymentHandle } from '@appfunnel-dev/sdk'| Method | Description |
|---|---|
submit() | Programmatically submit the form. Equivalent to the user clicking a submit button inside the Stripe form. |
Behavior
- Automatic intent detection — The component auto-detects whether to create a SetupIntent or a PaymentIntent based on whether the selected product has a trial period.
- Product binding — Reads the currently selected product from
useProducts(). Make sure a product is selected before rendering the form. - Tracking events — Fires
checkout.startwhen the form mounts,checkout.payment_addedwhen the user enters valid payment details, andpurchase.completeon successful payment. - Error toasts — Shows a toast notification automatically on payment errors. Disable with
settings.disableToastsin your config.
Elements Variant
The default variant renders an inline card form using Stripe PaymentElement. You control the submit button and handle the success callback.
import { useRef } from 'react'
import { definePage, StripePaymentForm, useProducts, usePayment, useNavigation } from '@appfunnel-dev/sdk'
import type { StripePaymentHandle } from '@appfunnel-dev/sdk'
export const page = definePage({
name: 'Checkout',
type: 'checkout',
routes: [{ to: 'success' }],
})
export default function Checkout() {
const formRef = useRef<StripePaymentHandle>(null)
const { selected } = useProducts()
const { loading, error } = usePayment()
const { goToNextPage } = useNavigation()
return (
<div>
<h1>Checkout</h1>
<p>{selected?.displayName} — {selected?.price}/{selected?.period}</p>
<StripePaymentForm ref={formRef} onSuccess={() => goToNextPage()} />
{error && <p className="text-red-500">{error}</p>}
<button onClick={() => formRef.current?.submit()} disabled={loading}>
{loading ? 'Processing...' : 'Pay Now'}
</button>
</div>
)
}Navigation after payment is not automatic. Call goToNextPage() in the onSuccess callback to advance to the next page.
Embedded Variant
The embedded variant renders Stripe Embedded Checkout inside an iframe. Stripe controls the entire payment UI including payment method selection, form validation, and the submit button.
After checkout completes, Stripe redirects the user to the page specified by successPageKey. The SDK automatically picks up the page from the URL — no onSuccess callback is needed.
<StripePaymentForm
variant="embedded"
successPageKey="download"
/>With managed payments and automatic tax
<StripePaymentForm
variant="embedded"
successPageKey="download"
managedPayments
automaticTax
allowPromotionCodes
/>Embedded Checkout does not save the card for future off-session charges. If you need upsells after the initial purchase, use the elements variant instead.
Upsells with purchase()
After the initial payment via the elements variant, the user’s card is saved. You can charge it again without showing another payment form using the purchase() function from usePayment().
import { useProducts, usePayment, useNavigation } from '@appfunnel-dev/sdk'
export default function Upsell() {
const { products } = useProducts()
const { loading, purchase } = usePayment()
const { goToNextPage } = useNavigation()
const upsellProduct = products.find((p) => p.id.startsWith('upsell'))
const handleAdd = () => {
if (!upsellProduct) return
purchase(upsellProduct.id, {
onSuccess: () => goToNextPage(),
onError: (error) => console.error(error),
})
}
return (
<button onClick={handleAdd} disabled={loading}>
{loading ? 'Processing...' : 'Add to my plan'}
</button>
)
}The purchase() function:
- Charges the card already on file for the given product
- Handles 3DS authentication automatically if required
- Tracks
purchase.completeandsubscription.createdevents - Sets
loadinganderrorstate during the operation - Shows a toast notification on errors (disable with
settings.disableToasts) - In dev mode, simulates a successful charge after a short delay
| Parameter | Type | Description |
|---|---|---|
productId | string | The product to purchase |
options.onSuccess | () => void | Called on successful purchase |
options.onError | (error: string) => void | Called with error message on failure |
Returns Promise<boolean> — true on success, false on failure.
Upsells require the initial payment to use the elements variant. Embedded Checkout does not save cards for future off-session charges.
PaddleCheckout
import { PaddleCheckout } from '@appfunnel-dev/sdk'A Paddle payment component that supports overlay and inline modes.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
mode | 'overlay' | 'inline' | — | overlay: opens the Paddle checkout in a modal overlay. inline: renders the checkout inline on the page. |
onSuccess | () => void | — | Called on successful payment. |
onError | (error: Error) => void | — | Called on failure. |
Paddle integration is in alpha. The API surface may change in future releases.
Payment Hooks
Two hooks are commonly used alongside payment components. See the Hooks page for full documentation.
const { products, selected, select } = useProducts()
const { loading, error, purchase } = usePayment()useProducts()— Returns the list of available products, the currently selected product, and aselectfunction to change the selection. Always select a product before rendering a payment form.usePayment()— Returnsloading,error, and thepurchase()function for off-session charges.