Skip to main content

Documentation Index

Fetch the complete documentation index at: https://setup.despia.com/llms.txt

Use this file to discover all available pages before exploring further.

Native iOS and Android Stripe payments fully bridged to your web layer. Take payments, let returning customers reuse saved cards, manage payment methods through Stripe’s native CustomerSheet, and brand the sheet to match your app using despia(), with no native code and no native Stripe SDK to configure.
Stripe is for physical goods and real-world services only. Marketplaces, delivery, ride-hailing, ticketing, bookings, and similar are allowed. Digital goods consumed inside the app (credits, coins, in-app currency, subscriptions, memberships, premium tiers, ad removal, level unlocks) must use Apple In-App Purchase and Google Play Billing through RevenueCat. Apple and Google reject apps that accept payment for digital goods through Stripe or any other external payment system. The rule of thumb: if what the user buys is delivered to a doorstep, performed by a human, or fulfilled outside the app, Stripe is allowed. If it unlocks anything inside the app, use RevenueCat.

Installation

npm install despia-native
import despia from 'despia-native';

Stripe and Despia setup

Configure Stripe, wire up your backend, then rebuild the Despia app to include the native sheet. The steps go in order, each one feeds the next.
1

Create a Stripe account

Go to stripe.com and sign up, or sign in with an existing account. Stripe is free to set up. You only pay per successful transaction, so account creation does not block early development. New accounts start in test mode by default, which is what you want until you are ready to take real charges.
2

Get your API keys

In the Stripe Dashboard, go to Developers > API keys. Copy your Publishable key (pk_test_...) and your Secret key (sk_test_...). When you activate your account for live charges, repeat for the live keys (pk_live_... and sk_live_...). The publishable key is safe to ship in your web app and gets forwarded into despia() calls. The secret key stays on your server and is the only thing that can create Payment Intents.
3

Enable payment methods

Go to Settings > Payments > Payment methods in the Stripe Dashboard. Toggle on the methods you want to accept (cards, Apple Pay, Google Pay, Link, regional methods like SEPA or iDEAL). The native sheet shows whichever methods you enabled here that are compatible with the Payment Intent’s amount, currency, and customer. There is no client-side switch for this, your dashboard configuration drives what appears.
4

Build your backend endpoints

Stripe payments are server-driven. Your backend creates the Stripe objects, your web app forwards the resulting secrets into despia(). At minimum you need an endpoint that creates a Payment Intent and returns its client_secret alongside your publishable key. If you plan to support saved cards or the Manage saved cards flow, you also need an endpoint that creates an ephemeral key (and optionally a SetupIntent) for the signed-in customer. The native runtime makes no Stripe network calls of its own, so every Stripe object must be created server-side first.
5

Set up webhooks

Go to Developers > Webhooks > Add an endpoint in the Stripe Dashboard. Point it at your backend (e.g. https://yourapp.com/api/stripe/webhook) and subscribe to at least payment_intent.succeeded, payment_intent.payment_failed, and charge.refunded. The completed event you receive in your web app is a client-side signal only. Final order fulfillment, receipts, and dispute records must come from webhooks. See Stripe’s webhook guide for signature verification and event handling.
6

Rebuild your app

Trigger a fresh build from the Despia Editor. The Stripe SDK has to be compiled into the app binary, so this cannot be applied over-the-air. After the build finishes, calls to stripe://payment and stripe://manage will function in production. No keys need to be pasted into the Despia Editor since the publishable key is passed per call in the despia() command.
Skipping the rebuild leaves the Stripe native runtime out of the binary. Calls to stripe://payment and stripe://manage will fail silently with no sheet shown and no event fired. If payments stop working after editing settings, rebuild before opening a support ticket.

How it works

Despia bridges your web layer to the native Stripe SDK. The web app calls despia('stripe://payment?...') with a publishable key and a Payment Intent client secret your backend created. The native runtime shows Stripe’s prebuilt Payment Sheet on top of the WebView, the customer pays through cards, Apple Pay, Google Pay, or Link, and window.stripeEvent fires once with the outcome.
const isDespia = navigator.userAgent.toLowerCase().includes('despia')

window.stripeEvent = function (event) {
    if (event.method !== 'paymentSheet') return
    if (event.status === 'completed') {
        // Confirm via your backend webhook before fulfilling the order.
    }
}

async function pay() {
    const res = await fetch('/api/create-payment-intent', { method: 'POST' })
    const { publishable_key, client_secret } = await res.json()

    if (isDespia) {
        despia(`stripe://payment?publishable_key=${publishable_key}&payment_intent_client_secret=${client_secret}`)
    }
}
For users on the web app who are not in the Despia WebView, fall back to Stripe Checkout or your own hosted Stripe Elements page. The native sheet only runs inside the Despia app.

Payment Sheet

Take a payment, optionally with saved cards. Theme, accent color, corner radius, all the styling options.

Webhooks

Event types, signature verification, and how to reconcile payment_intent.succeeded before fulfilling.

Taking your first payment

The minimum integration is two pieces: a server endpoint that creates the Payment Intent, and a page that defines window.stripeEvent then fires despia('stripe://payment?...'). Amount, currency, and customer are all decided server-side. The web app only forwards what the server returned.
// Your server endpoint, e.g. /api/create-payment-intent
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)

app.post('/api/create-payment-intent', async (req, res) => {
    const intent = await stripe.paymentIntents.create({
        amount: 1999,
        currency: 'usd',
        automatic_payment_methods: { enabled: true }
    })

    res.json({
        publishable_key: process.env.STRIPE_PUBLISHABLE_KEY,
        client_secret:   intent.client_secret
    })
})
Set STRIPE_SECRET_KEY and STRIPE_PUBLISHABLE_KEY to the test pair in development and the live pair in production. Returning the publishable key from the server alongside the client secret keeps the two keys in sync, since the server is the only place that knows for certain which mode it is running in.
Do not branch on window.location.hostname to choose between test and live keys. Despia’s local server serves the production web build from http://localhost on the device, so a hostname === 'localhost' shortcut classifies every production install as test mode and the Payment Sheet never succeeds in the wild. Resolve the keys on the server, where the environment is unambiguous.

Saved cards

Returning customers can reuse a previously saved card on stripe://payment itself by passing the optional customer_id and ephemeral_key_secret pair. Both values come from your server. The pair is all-or-nothing: pass both for saved cards, pass neither for guest checkout (the default). Passing exactly one returns a missing param failed event with no sheet.
if (isDespia) {
    despia(`stripe://payment?publishable_key=${publishable_key}&payment_intent_client_secret=${client_secret}&customer_id=${customer_id}&ephemeral_key_secret=${ephemeral_key_secret}`)
}
For a dedicated “Payment methods” or wallet screen where customers can add, remove, and pick a default card without taking a payment, use stripe://manage to open Stripe’s native CustomerSheet. The shared window.stripeEvent callback handles both flows. Route on event.method: paymentSheet for charges, customerSheet for management. See Saved cards on payment and Manage saved cards for the full integration.
The stripe://manage flow governs Stripe-stored cards only. App Store and Google Play subscriptions managed through RevenueCat are not shown or controlled here. Surface “Manage cards” only on screens where Stripe is the payment rail.

Reconcile via webhooks before fulfilling

The completed status on window.stripeEvent is a client-side success signal. Network drops mid-confirmation, asynchronous payment methods, and refunds initiated immediately after capture all mean the client event alone is not sufficient to safely unlock the order. The source of truth is your backend, listening to Stripe webhooks.
// Your webhook endpoint, e.g. /api/stripe/webhook
app.post('/api/stripe/webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const event = stripe.webhooks.constructEvent(
        req.body,
        req.headers['stripe-signature'],
        process.env.STRIPE_WEBHOOK_SECRET
    )

    if (event.type === 'payment_intent.succeeded') {
        const intent = event.data.object
        // Unlock the order keyed off intent.id or intent.metadata.
    }

    res.json({ received: true })
})
On the page, treat completed as a signal to ask your backend whether fulfillment is ready, not as the green light itself.
window.stripeEvent = function (event) {
    if (event.method !== 'paymentSheet') return
    if (event.status === 'completed') {
        fetch('/api/confirm-payment', { method: 'POST' })
    }
}

Resources

NPM Package

despia-native

Stripe Dashboard

Configure API keys, payment methods, and webhooks

Stripe Docs

Payment Intents API reference and lifecycle