CallDocumentation Index
Fetch the complete documentation index at: https://setup.despia.com/llms.txt
Use this file to discover all available pages before exploring further.
despia('stripe://payment?...') to show the native Stripe Payment Sheet with a publishable key and a Payment Intent client secret. The sheet handles card entry, 3DS, Link, and any other payment methods enabled on the Payment Intent, then the Despia runtime fires window.stripeEvent once with the outcome.
A second action, despia('stripe://manage?...'), opens Stripe’s native CustomerSheet so signed-in customers can add, remove, and pick a default among their saved payment methods. Both actions share the same window.stripeEvent callback. See Manage saved cards.
Do not use this for digital goods inside mobile apps. Credits, coins, in-app currency, memberships, subscriptions, premium tiers, ad removal, unlocking levels, and any virtual content consumed inside the app must use RevenueCat, the store-compliant native in-app purchase path backed by Apple StoreKit and Google Play Billing. Apple and Google will reject your app on submission if you accept payment for digital goods through Stripe or any other external payment system. See in-app purchase rejections for the full policy and rejection language.Stripe Payment is for physical goods and real-world services that are delivered or consumed outside the app. This is the same category Apple permits in section 3.1.3 of the App Review Guidelines and Google permits in the Play Store payments policy. Apps like Amazon (physical products), Uber (rides), DoorDash (food delivery), and Angie’s List (service marketplace) all fall under this allowance. The rule of thumb: if what the user buys arrives at a doorstep, is performed by a human, or is fulfilled outside the app, Stripe is allowed. If it unlocks anything inside the app, use RevenueCat instead.
Your backend creates the Payment Intent with your Stripe secret key and returns the client secret to the page. Amount, currency, customer, allowed payment methods, Stripe Connect routing, and metadata are all decided server-side. The web app only passes the publishable key and the client secret into the native action.
Installation
- Bundle
- CDN
Create a Payment Intent on your server
The Payment Intent must be created server-side with your Stripe secret key, never from the web app. Your server returns theclient_secret to the page, which then passes it into the despia() call.
amount is an integer in the smallest currency unit, so 1999 charges $19.99 in USD or ¥1999 in JPY. currency is a lowercase three-letter ISO code. automatic_payment_methods[enabled]=true lets Stripe choose which methods to show based on what is enabled for your account and what is compatible with the Payment Intent. Amount, currency, customer, metadata, Stripe Connect routing, and the allowed methods are all decided here, not on the client.
A successful call returns the Payment Intent object as JSON:
client_secret from this response, and your matching publishable_key from your environment variables. Returning both together guarantees the keys never cross modes, since the server is the only place that knows for sure whether it is running with sk_test_... or sk_live_... (see Test versus live keys below). id and status stay server-side for webhook reconciliation, the client_secret already encodes the ID so the page never needs the bare pi_... value. status starts as requires_payment_method and transitions to succeeded once the sheet completes and the payment captures.
For the full parameter list, lifecycle, and error codes, see the Payment Intents API reference, the Create a Payment Intent endpoint, and the Payment Intents guide.
How it works
This is the fire-and-listen pattern. Definewindow.stripeEvent once at page load. The Despia runtime calls it with the outcome when the Payment Sheet closes. Then fire the action with despia().
stripe://payment. The two required params are publishable_key (pk_live_xxx or pk_test_xxx) and payment_intent_client_secret (the full pi_..._secret_... string from the Payment Intent, not the bare pi_... id). Four optional styling params (theme, accent_color, corner_radius, action_corner_radius) control the sheet’s appearance and are covered in the Styling section. An optional saved-card pair (customer_id, ephemeral_key_secret) attaches the Stripe customer so the sheet lists their saved cards and saves the new one, covered in Saved cards on payment. Do not fire a second stripe://payment while one sheet is open.
Styling
The Payment Sheet accepts two optional query params that control its appearance:theme and accent_color. Both are independent, both are safe to omit if Stripe’s default styling is fine.
Theme
Thetheme param controls the sheet’s color scheme. Accepted values are light, dark, and automatic. Anything else (a typo, an empty value, or omitting the param) falls back to automatic, which follows the device’s system light/dark setting.
stripe, night, flat). The only theme switch available here is light, dark, or automatic.
Accent color
Theaccent_color param sets the color of the primary Pay button and the sheet’s primary accent (selected option highlights and similar). Use it to match your brand.
| Form | Example | Notes |
|---|---|---|
| 6-digit hex | 1A73E8 | most common, recommended |
| 3-digit shorthand | F0A | expands to FF00AA |
| 8-digit with alpha | 1A73E8CC | RRGGBBAA, last byte is opacity |
With # prefix | #1A73E8 | works but see below |
Percent-encoded # | %231A73E8 | output of encodeURIComponent('#1A73E8') |
Drop the
#. In a stripe:// command, a literal # would normally be treated as a fragment delimiter, which truncates everything that follows. The Despia runtime tolerates a raw # in any position of a native feature command, but omitting it (or percent-encoding it as %23) is the portable habit and keeps the command string unambiguous if you ever log it, parse it, or copy it into another system.accent_color=blue, accent_color=zzz, an empty string) are silently ignored. The sheet renders with Stripe’s default styling and the payment still completes normally, no failed event is emitted. Guard against typos by validating the hex on the page before firing the action, since the runtime will not surface the mistake.
Corner radius
Thecorner_radius param sets the general corner radius applied to the sheet’s input fields and buttons together, including the secondary back button. The value is a non-negative number of points, with no units and no # prefix.
corner_radius=12.5 is valid. Invalid, negative, or non-finite values (corner_radius=round, corner_radius=-4, an empty string) are silently ignored and the sheet renders with Stripe’s default corner radius.
Action corner radius
Theaction_corner_radius param overrides the corner radius of the primary Pay button independently of the general radius. Same format as corner_radius: a non-negative number of points.
The inheritance rules are the most useful part:
- Only
corner_radiusset. Inputs and the Pay button both use it. The Pay button inherits automatically, you do not need to setaction_corner_radiusat all. - Both set. Everything uses
corner_radiusexcept the Pay button, which usesaction_corner_radiusas an independent override. - Only
action_corner_radiusset. Just the Pay button is rounded, the rest of the sheet stays at Stripe’s default.
action_corner_radius values fall back to inheriting from corner_radius (or to Stripe’s default if neither is set). No failed event is emitted for a bad value.
Combining styling options
All four styling params are independent and may be combined freely. Use any subset, or omit them all for Stripe’s default appearance.stripe://manage action covered below.
Result events
The Despia runtime fireswindow.stripeEvent exactly once per despia('stripe://payment?...') call, or zero times if the listener was missing when the result arrived. There are no intermediate presented, processing, or dismissing events. The method field on the payload is always the string paymentSheet, even though the action is payment, so always match on status.
"missing param" is the only stable matchable error value. It fires in two cases: when publishable_key or payment_intent_client_secret is missing or empty, and when the optional saved-card pair (customer_id, ephemeral_key_secret) is half-supplied with exactly one of the two values rather than both or neither. Every other failure (bad client secret, test and live mode mismatch, expired ephemeral key, API version mismatch, network failure, terminal decline) returns Stripe’s localized SDK message. Log or display these messages, but never branch on their contents since they are locale-dependent.
Confirm on your backend before fulfilling
Thecompleted status is the client-side signal that the user finished the flow. Always confirm the final Payment Intent status through a Stripe webhook on your backend before unlocking the order. Network drops mid-confirmation, refunds initiated immediately after capture, and asynchronous payment methods all mean the client signal on its own is not sufficient.
Test versus live keys
Apk_test_... publishable key requires a test-mode client secret, and pk_live_... requires a live-mode secret. Mismatched modes resolve to a failed event with an SDK message about an invalid client secret, so the keys must always match.
The reliable pattern is to let the server return the publishable key alongside the client secret. The server already knows which mode it is in, since it holds the secret key, so it can hand back the publishable key that pairs with it. Both keys come from the same environment, so they cannot be crossed.
STRIPE_SECRET_KEY and STRIPE_PUBLISHABLE_KEY to the test pair in your dev environment and the live pair in production. The web app never needs to know which is which, it just forwards whatever the server returned into the despia('stripe://payment?...') call.
Saved cards on payment
By default,stripe://payment is a guest checkout. The Payment Sheet shows a blank card form, and the card the customer enters is not saved to any Stripe customer. To let a returning customer pick a previously saved card, and to save the new one for next time, pass the optional saved-card pair on the same stripe://payment command: customer_id and ephemeral_key_secret. This is a different flow from stripe://manage, which opens a dedicated management UI without taking a payment.
| Param | Description |
|---|---|
customer_id | The Stripe customer (cus_...) the saved cards belong to. |
ephemeral_key_secret | Server-created customer ephemeral key (ek_...) for that customer. API-version-pinned and short-lived, about one hour. |
The Payment Intent must be created with the same
customer id on the server, not just the ephemeral key. Without it, the saved card is reusable but the payment will not be attached to that customer for receipts, invoicing, dispute records, or revenue analytics. The customer attachment is what makes the saved card actually saveable in the first place.Stripe-Version pinning gotcha applies here as on stripe://manage. A mismatch between the ephemeral key’s API version and the mobile SDK’s expected version returns a failed event with an SDK message about the API version. Ask the mobile team for the exact version, or pin one centrally on the server and update it in lockstep with SDK upgrades.
Manage saved cards
Calldespia('stripe://manage?...') to open Stripe’s native CustomerSheet. The signed-in customer can add new cards, remove existing ones, and pick which card is their default. Use this for a “Payment methods” or “Wallet” screen, and only on screens where Stripe is the actual payment rail.
Backend setup
For the signed-in customer, your server must produce three values and return them to the page:- The Stripe customer id (
cus_...). - An ephemeral key for that customer, created with the Stripe API version your mobile SDK expects. Returns
ephemeral_key_secret(ek_...). The key is short-lived (about an hour), so generate it on demand right before opening the sheet, not at app start. - (Recommended) A SetupIntent for that customer. Returns
client_secret(seti_..._secret_...). Without it, the sheet still lists and selects existing cards but the customer cannot add a new one.
stripe://manage command. The native runtime makes no Stripe network calls of its own. See the Ephemeral Keys API reference and the SetupIntents API reference for the full parameter list.
How it works
Same fire-and-listen pattern as the payment action. Both sharewindow.stripeEvent, so route on event.method: paymentSheet for charges, customerSheet for card management.
Parameters
| Param | Required | Notes |
|---|---|---|
publishable_key | Yes | pk_live_xxx or pk_test_xxx. Must match the mode of the ephemeral key. |
customer_id | Yes | The Stripe customer (cus_...). |
ephemeral_key_secret | Yes | From your server (ek_...). API-version-pinned and short-lived. |
setup_intent_client_secret | No | From your server (seti_..._secret_...). Omit to disable the “add card” flow. |
theme, accent_color, corner_radius, action_corner_radius | No | Same styling as the payment action. |
publishable_key, customer_id, or ephemeral_key_secret returns { method: 'customerSheet', status: 'failed', error: 'missing param' } and the sheet is not shown.
CustomerSheet result events
The shape mirrors PaymentSheet, withmethod: 'customerSheet' and a different status set. There is no completed status, since managing cards is not a payment, and no selected status on paymentSheet either, so a strict method check is enough to route correctly.
selected event fires when the customer confirms a card selection, including immediately after adding a new card via the SetupIntent flow. The chosen card’s id, brand, and last four digits are intentionally not included in the event. Read the customer’s saved methods from your backend if you need to display the current default.
Resources
NPM Package
despia-native