Build a fully customized crypto stablecoin onramp experience using Privy embedded wallets and Stripe Embedded Components for the Crypto Onramp SDK
Privy’s embedded wallets integrate with Stripe’s Embedded Components for the Crypto Onramp SDK. Stripe handles payment processing, KYC, and crypto delivery. This recipe shows how to build a fully customized onramp experience where your app controls the UI.Using this integration, your app can:
Create a seamless branded onramp experience with app-controlled UI
Leverage Stripe’s payment infrastructure for card, bank, and Apple Pay
Delivers crypto to users’ Privy embedded wallets through Stripe
Handle KYC verification through Stripe’s identity services
Stripe’s Embedded Components for Crypto Onramp is currently in private beta. Your app must be
approved for crypto onramp and enrolled in Stripe’s verified apps program before using this
integration.
Check if the user has a Link account and authenticate or register them:
async function handleLinkAuth(email: string) { // Check if user already has a Link account const result = await hasLinkAccount(email); if (result.hasLinkAccount) { // Authenticate existing user - presents Stripe UI for OTP verification const authResult = await authenticateUser(); if (authResult.customerId) { setCryptoCustomerId(authResult.customerId); return authResult.customerId; } } else { // Register new Link user const regResult = await registerLinkUser({ email, phone: '+1234567890', // Collect from user in E.164 format country: 'US', // Two-letter country code fullName: 'John Doe' // Recommended for non-US users }); if (regResult.customerId) { setCryptoCustomerId(regResult.customerId); return regResult.customerId; } } return null;}
Here’s a complete component that ties together the entire flow (you can find the repo example here):
import {useState, useEffect} from 'react';import {View, Text, Button, TextInput} from 'react-native';import {usePrivy, useEmbeddedEthereumWallet} from '@privy-io/expo';import {useOnramp} from '@stripe/stripe-react-native';type OnrampStep = 'auth' | 'kyc' | 'wallet' | 'payment' | 'checkout' | 'complete';export function HeadlessOnramp() { const {user} = usePrivy(); const {wallets} = useEmbeddedEthereumWallet(); const { configure, hasLinkAccount, registerLinkUser, authenticateUser, attachKycInfo, registerWalletAddress, collectPaymentMethod, createCryptoPaymentToken, performCheckout } = useOnramp(); const [step, setStep] = useState<OnrampStep>('auth'); const [cryptoCustomerId, setCryptoCustomerId] = useState<string | null>(null); const [amount, setAmount] = useState('100'); useEffect(() => { configure({ appearance: {colors: {primary: '#6366F1'}} }); }, []); // Step 1: Link Authentication async function handleAuth() { const email = user?.email?.address; if (!email) return; const lookupResult = await hasLinkAccount(email); if (lookupResult.hasLinkAccount) { const authResult = await authenticateUser(); if (authResult.customerId) { setCryptoCustomerId(authResult.customerId); await checkAndAdvance(authResult.customerId); } } else { // Show registration form... } } // Check customer status and advance to appropriate step async function checkAndAdvance(customerId: string) { const status = await fetch(`/api/crypto/customer/${customerId}`).then((r) => r.json()); if (status.kycStatus !== 'verified') { setStep('kyc'); } else if (!status.hasWallet) { setStep('wallet'); } else if (!status.hasPaymentMethod) { setStep('payment'); } else { setStep('checkout'); } } // Step 2: Register Privy wallet async function handleWalletRegistration() { const wallet = wallets[0]; if (!wallet) return; const result = await registerWalletAddress(wallet.address, 'base'); if (!result.error) { setStep('payment'); } } // Step 3: Collect payment method async function handlePaymentMethod() { const result = await collectPaymentMethod('Card'); if (result.paymentDisplayData) { setStep('checkout'); } } // Step 4: Execute checkout async function handleCheckout() { // Create a payment token for the selected method const tokenResult = await createCryptoPaymentToken(); if (!tokenResult.cryptoPaymentToken) return; const session = await fetch('/api/crypto/onramp', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ cryptoCustomerId, paymentToken: tokenResult.cryptoPaymentToken, sourceAmount: amount, destinationCurrency: 'usdc.base' }) }).then((r) => r.json()); const result = await performCheckout(session.id, async () => { const checkout = await fetch(`/api/crypto/onramp/${session.id}/checkout`, { method: 'POST' }).then((r) => r.json()); return checkout.client_secret; }); if (!result.error) { setStep('complete'); } } return ( <View> {step === 'auth' && <Button title="Connect with Link" onPress={handleAuth} />} {step === 'wallet' && ( <View> <Text>Register your wallet to receive crypto</Text> <Text>Wallet: {wallets[0]?.address}</Text> <Button title="Register Wallet" onPress={handleWalletRegistration} /> </View> )} {step === 'payment' && <Button title="Add Payment Method" onPress={handlePaymentMethod} />} {step === 'checkout' && ( <View> <TextInput value={amount} onChangeText={setAmount} keyboardType="numeric" placeholder="Amount in USD" /> <Button title={`Buy $${amount} of USDC`} onPress={handleCheckout} /> </View> )} {step === 'complete' && <Text>Transaction complete! Check your wallet for USDC.</Text>} </View> );}
Stripe Crypto onramp currently only supports stablecoin as onramp currencies. If you wish to
onramp other currencies such as ETH or SOL, you can make use of our card-based
funding offerings.