React Native Integration ​
Official Client Library Available! 🎉
Bridge Payments now has an official client library integrated into @pubflow/react-native! This provides a type-safe, easy-to-use interface for all payment operations.
This guide shows how to use the official BridgePaymentClient in your React Native apps.
📚 Quick Links ​
- Official @pubflow/react-native Package - Full documentation with TypeScript support
- API Reference - Complete REST API documentation
- Starter Kits - Production-ready Expo templates with Bridge Payments
Installation ​
# Install Pubflow React Native (includes Bridge Payment Client)
npm install @pubflow/react-native @pubflow/core
# Install Stripe React Native (required for payment UI)
npm install @stripe/stripe-react-native
# Install AsyncStorage for session management
npm install @react-native-async-storage/async-storage
# Install dependencies
npx pod-install # iOS onlyOfficial Client Library
The BridgePaymentClient is now part of @pubflow/react-native, providing:
- ✅ Type-safe TypeScript interfaces
- ✅ Automatic authentication (session-based and guest tokens)
- ✅ Multi-tenant support (organizations)
- ✅ Consistent API across React, React Native, and Next.js
Configuration ​
1. Environment Variables ​
Create or update your .env file:
# Bridge Payments instance URL
EXPO_PUBLIC_BRIDGE_BASE_PAYMENT_URL=https://your-instance.pubflow.com
# Stripe publishable key
EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
# Optional: Enable debug logging
EXPO_PUBLIC_LOG_MODE=development2. Initialize Stripe Provider ​
// App.tsx
import { StripeProvider } from '@stripe/stripe-react-native';
export default function App() {
return (
<StripeProvider publishableKey={process.env.EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY!}>
{/* Your app content */}
</StripeProvider>
);
}Basic Usage ​
Create Payment Client ​
The BridgePaymentClient automatically handles authentication using the session stored in AsyncStorage:
import { BridgePaymentClient } from '@pubflow/react-native';
// Initialize client (automatically uses session from AsyncStorage)
const client = new BridgePaymentClient({
baseUrl: process.env.EXPO_PUBLIC_BRIDGE_URL!
});Automatic Session Management
The client automatically retrieves the session ID from AsyncStorage using the key pubflow_session_id. No need to manually pass the session ID!
Authentication Methods ​
The client supports three authentication methods:
1. Authenticated Users (Automatic) ​
// Session ID is automatically retrieved from AsyncStorage
const client = new BridgePaymentClient({
baseUrl: process.env.EXPO_PUBLIC_BRIDGE_URL!
});2. Guest Users (Token-Based) ​
// For guest checkout, provide a guest token
const client = new BridgePaymentClient({
baseUrl: process.env.EXPO_PUBLIC_BRIDGE_URL!,
guestToken: 'guest_abc123' // Uses X-Guest-Token header
});3. Organization Context (Multi-Tenant) ​
// For organization-scoped operations
const client = new BridgePaymentClient({
baseUrl: process.env.EXPO_PUBLIC_BRIDGE_URL!,
organizationId: 'org_123' // Works with Subscriptions and Customers
});Complete Payment Flow ​
import { useState } from 'react';
import { View, Button, Alert } from 'react-native';
import { CardField, useStripe } from '@stripe/stripe-react-native';
import { BridgePaymentClient } from '@pubflow/react-native';
function CheckoutScreen() {
const stripe = useStripe();
const [loading, setLoading] = useState(false);
const handlePayment = async () => {
setLoading(true);
try {
// 1. Initialize client (automatically uses session)
const client = new BridgePaymentClient({
baseUrl: process.env.EXPO_PUBLIC_BRIDGE_URL!
});
// 2. Create payment intent
const intent = await client.createPaymentIntent({
subtotal_cents: 1800,
tax_cents: 200,
total_cents: 2000,
currency: 'USD',
concept: 'Premium Subscription',
description: 'Monthly premium plan',
provider_id: 'stripe'
});
// 4. Confirm payment with Stripe
const { error, paymentIntent } = await stripe.confirmPayment(
intent.client_secret,
{
paymentMethodType: 'Card'
}
);
if (error) {
Alert.alert('Payment Failed', error.message);
return;
}
if (paymentIntent.status === 'Succeeded') {
Alert.alert('Success', 'Payment completed successfully!');
}
} catch (error) {
console.error('Payment error:', error);
Alert.alert('Error', 'An error occurred. Please try again.');
} finally {
setLoading(false);
}
};
return (
<View style={{ padding: 20 }}>
<CardField
postalCodeEnabled={false}
style={{ height: 50, marginBottom: 20 }}
/>
<Button
title={loading ? 'Processing...' : 'Pay $20.00'}
onPress={handlePayment}
disabled={loading}
/>
</View>
);
}Payment Methods ​
List Saved Payment Methods ​
const client = new BridgePaymentClient({
baseUrl: process.env.EXPO_PUBLIC_BRIDGE_URL!
});
const methods = await client.listPaymentMethods();
// Display payment methods
methods.forEach(method => {
console.log(`${method.brand} ending in ${method.last4}`);
});Get a Specific Payment Method ​
const method = await client.getPaymentMethod('pm_123');
console.log(`Card: ${method.brand} **** ${method.last4}`);Update Payment Method ​
// Set as default
await client.updatePaymentMethod('pm_123', {
is_default: true,
alias: 'My Visa Card'
});Delete Payment Method ​
await client.deletePaymentMethod('pm_123');Save Payment Method During Payment ​
const intent = await client.createPaymentIntent({
total_cents: 2000,
currency: 'USD',
description: 'First Purchase',
provider_id: 'stripe',
save_payment_method: true // Save for future use
});Use Saved Payment Method ​
const intent = await client.createPaymentIntent({
total_cents: 2000,
currency: 'USD',
description: 'Subscription Renewal',
provider_id: 'stripe',
payment_method_id: 'pm_123' // Use saved method
});Guest Checkout with Token Authentication ​
Token-Based Authentication
Bridge Payments uses token-based authentication for guest checkout. The token is sent via email/SMS and validated to create a session.
This is more secure than traditional guest checkout as it verifies the user's email/phone before allowing payment.
Step 1: Request Verification Token ​
First, request a verification token from Flowless:
const requestToken = async (email: string) => {
const response = await fetch(`${process.env.EXPO_PUBLIC_FLOWLESS_URL}/auth/token/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Bridge-Secret': process.env.EXPO_PUBLIC_BRIDGE_SECRET! // Required for security
},
body: JSON.stringify({
identifier: email,
type: 'email',
token_type: 'verification'
})
});
const data = await response.json();
if (data.success) {
Alert.alert('Check Your Email', 'We sent you a verification code.');
return true;
} else {
Alert.alert('Error', data.message || 'Failed to send verification code');
return false;
}
};Step 2: Validate Token and Create Session ​
After the user receives the token via email, validate it:
const validateToken = async (token: string) => {
const response = await fetch(`${process.env.EXPO_PUBLIC_FLOWLESS_URL}/auth/token/validate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Bridge-Secret': process.env.EXPO_PUBLIC_BRIDGE_SECRET!
},
body: JSON.stringify({ token })
});
const data = await response.json();
if (data.success) {
// Store session ID
await AsyncStorage.setItem('pubflow_session_id', data.sessionId);
return data.sessionId;
} else {
Alert.alert('Error', data.message || 'Invalid verification code');
return null;
}
};Step 3: Complete Payment Flow ​
import { useState } from 'react';
import { View, TextInput, Button, Alert } from 'react-native';
import { CardField, useStripe } from '@stripe/stripe-react-native';
import { BridgePaymentClient } from '@pubflow/react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
function GuestDonationScreen() {
const stripe = useStripe();
const [email, setEmail] = useState('');
const [verificationCode, setVerificationCode] = useState('');
const [amount, setAmount] = useState(50);
const [step, setStep] = useState<'email' | 'verify' | 'payment'>('email');
const [sessionId, setSessionId] = useState<string | null>(null);
const handleRequestCode = async () => {
const success = await requestToken(email);
if (success) {
setStep('verify');
}
};
const handleVerifyCode = async () => {
const session = await validateToken(verificationCode);
if (session) {
setSessionId(session);
setStep('payment');
}
};
const handleDonation = async () => {
if (!sessionId) {
Alert.alert('Error', 'Please verify your email first');
return;
}
try {
// Initialize client (automatically uses session from AsyncStorage)
const client = new BridgePaymentClient({
baseUrl: process.env.EXPO_PUBLIC_BRIDGE_URL!
});
const intent = await client.createPaymentIntent({
total_cents: amount * 100,
currency: 'USD',
description: 'Donation',
provider_id: 'stripe'
});
const { error } = await stripe.confirmPayment(
intent.client_secret,
{
paymentMethodType: 'Card'
}
);
if (!error) {
Alert.alert('Thank You!', 'Your donation was successful.');
// Clear session after successful payment
await AsyncStorage.removeItem('pubflow_session_id');
setStep('email');
setEmail('');
setVerificationCode('');
}
} catch (error) {
console.error('Donation error:', error);
Alert.alert('Error', 'Payment failed. Please try again.');
}
};
if (step === 'email') {
return (
<View style={{ padding: 20 }}>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
style={{ borderWidth: 1, padding: 10, marginBottom: 10 }}
/>
<Button title="Send Verification Code" onPress={handleRequestCode} />
</View>
);
}
if (step === 'verify') {
return (
<View style={{ padding: 20 }}>
<TextInput
placeholder="Verification Code"
value={verificationCode}
onChangeText={setVerificationCode}
style={{ borderWidth: 1, padding: 10, marginBottom: 10 }}
/>
<Button title="Verify Code" onPress={handleVerifyCode} />
</View>
);
}
return (
<View style={{ padding: 20 }}>
<TextInput
placeholder="Amount"
value={String(amount)}
onChangeText={(text) => setAmount(Number(text))}
keyboardType="numeric"
style={{ borderWidth: 1, padding: 10, marginBottom: 10 }}
/>
<CardField style={{ height: 50, marginVertical: 20 }} />
<Button title={`Donate $${amount}`} onPress={handleDonation} />
</View>
);
}Environment Variables
Make sure to set these environment variables:
EXPO_PUBLIC_FLOWLESS_URL- Your Flowless instance URL (e.g.,https://your-instance.flowless.dev)EXPO_PUBLIC_BRIDGE_URL- Your Bridge Payments instance URLEXPO_PUBLIC_BRIDGE_SECRET- Bridge secret for token authentication (required for security)
Bridge Secret Security
The X-Bridge-Secret is designed to be used in client applications (React Native, React, Next.js, etc.) as an additional security layer to prevent unauthorized access to token authentication endpoints.
How it works:
- ✅ Safe to include in your client app (React Native, web apps)
- ✅ Provides rate limiting and abuse prevention
- ✅ Different from API keys - it's a shared secret for your instance
- ✅ Can be rotated if compromised
This is different from traditional API secrets - it's meant to be distributed with your app to minimize risks while maintaining security.
Addresses ​
Create Address ​
const address = await client.createAddress({
type: 'billing', // or 'shipping'
line1: '123 Main St',
line2: 'Apt 4B',
city: 'New York',
state: 'NY',
postal_code: '10001',
country: 'US',
is_default: true
});List Addresses ​
const addresses = await client.listAddresses();Get a Specific Address ​
const address = await client.getAddress('addr_123');Update Address ​
await client.updateAddress('addr_123', {
line1: '456 Oak Ave',
is_default: true
});Delete Address ​
await client.deleteAddress('addr_123');Subscriptions ​
Organization Support
Subscriptions support organization context. Pass organizationId in the client config to create organization-scoped subscriptions.
Create Subscription ​
const subscription = await client.createSubscription({
plan_id: 'plan_premium_monthly',
payment_method_id: 'pm_123',
trial_period_days: 14
});List Subscriptions ​
const subscriptions = await client.listSubscriptions();Get a Specific Subscription ​
const subscription = await client.getSubscription('sub_123');Cancel Subscription ​
// Cancel immediately
await client.cancelSubscription('sub_123');
// Cancel at period end
await client.cancelSubscription('sub_123', {
cancel_at_period_end: true
});Organizations (Multi-Tenant) ​
Organization Support
Organizations are supported for Subscriptions and Customers only. Other endpoints (Addresses, Payments, Payment Methods) currently have organization_id hardcoded to null in the backend.
Create Organization ​
const org = await client.createOrganization({
name: 'Acme Corp',
email: '[email protected]',
tax_id: '12-3456789'
});List Organizations ​
const orgs = await client.listOrganizations();Add Organization Member ​
const member = await client.addOrganizationMember('org_123', {
user_id: 'user_456',
role: 'admin' // owner, admin, billing, member
});Update Member Role ​
await client.updateOrganizationMemberRole('org_123', 'member_789', {
role: 'billing'
});Remove Member ​
await client.removeOrganizationMember('org_123', 'member_789');Leave Organization ​
await client.leaveOrganization('org_123');Error Handling ​
try {
const intent = await client.createPaymentIntent({
total_cents: 2000,
currency: 'USD',
description: 'Test Payment',
provider_id: 'stripe'
});
} catch (error: any) {
if (error.message?.includes('401')) {
// Session expired, redirect to login
await AsyncStorage.removeItem('pubflow_session_id');
navigation.navigate('Login');
} else if (error.message?.includes('400')) {
// Validation error
Alert.alert('Error', 'Invalid payment data');
} else {
// Network or server error
Alert.alert('Error', 'An unexpected error occurred');
console.error('Payment error:', error);
}
}TypeScript Support ​
Full TypeScript support with comprehensive type definitions:
import type {
// Client & Config
BridgePaymentClient,
BridgePaymentConfig,
PaymentRequestOptions,
// Payment Intents
PaymentIntent,
CreatePaymentIntentRequest,
Payment,
// Payment Methods
PaymentMethod,
PaymentMethodType,
UpdatePaymentMethodRequest,
// Addresses
Address,
AddressType,
CreateAddressRequest,
UpdateAddressRequest,
// Customers
Customer,
CreateCustomerRequest,
UpdateCustomerRequest,
// Subscriptions
Subscription,
SubscriptionStatus,
CreateSubscriptionRequest,
CancelSubscriptionRequest,
// Organizations
Organization,
OrganizationRole,
OrganizationMember,
CreateOrganizationRequest,
UpdateOrganizationRequest,
AddOrganizationMemberRequest,
UpdateOrganizationMemberRoleRequest,
// Utilities
PaginationParams,
ListResponse
} from '@pubflow/react-native';
// Example usage
const request: CreatePaymentIntentRequest = {
total_cents: 2000,
currency: 'USD',
description: 'Premium Plan',
provider_id: 'stripe'
};
const intent: PaymentIntent = await client.createPaymentIntent(request);API Reference ​
Constructor ​
new BridgePaymentClient(config: BridgePaymentConfig)Config Options:
baseUrl(required): Bridge Payments instance URLstorage(optional): Custom storage adapter (defaults to AsyncStorage)instanceId(optional): Instance identifier for multi-instance supportheaders(optional): Custom headers to include in all requestsguestToken(optional): Guest token for guest checkoutorganizationId(optional): Organization ID for multi-tenant operations
Payment Intents ​
// Create payment intent
createPaymentIntent(data: CreatePaymentIntentRequest, options?: PaymentRequestOptions): Promise<PaymentIntent>
// Get payment intent
getPaymentIntent(id: string, options?: PaymentRequestOptions): Promise<PaymentIntent>
// List payments
listPayments(params?: PaginationParams, options?: PaymentRequestOptions): Promise<Payment[]>
// Get payment
getPayment(id: string, options?: PaymentRequestOptions): Promise<Payment>Payment Methods ​
// List payment methods
listPaymentMethods(params?: PaginationParams, options?: PaymentRequestOptions): Promise<PaymentMethod[]>
// Get payment method
getPaymentMethod(id: string, options?: PaymentRequestOptions): Promise<PaymentMethod>
// Update payment method
updatePaymentMethod(id: string, data: UpdatePaymentMethodRequest, options?: PaymentRequestOptions): Promise<PaymentMethod>
// Delete payment method
deletePaymentMethod(id: string, options?: PaymentRequestOptions): Promise<void>Addresses ​
// Create address
createAddress(data: CreateAddressRequest, options?: PaymentRequestOptions): Promise<Address>
// List addresses
listAddresses(params?: PaginationParams, options?: PaymentRequestOptions): Promise<Address[]>
// Get address
getAddress(id: string, options?: PaymentRequestOptions): Promise<Address>
// Update address
updateAddress(id: string, data: UpdateAddressRequest, options?: PaymentRequestOptions): Promise<Address>
// Delete address
deleteAddress(id: string, options?: PaymentRequestOptions): Promise<void>Customers ​
// Create customer
createCustomer(data: CreateCustomerRequest, options?: PaymentRequestOptions): Promise<Customer>
// List customers
listCustomers(params?: PaginationParams, options?: PaymentRequestOptions): Promise<Customer[]>
// Get customer
getCustomer(id: string, options?: PaymentRequestOptions): Promise<Customer>
// Update customer
updateCustomer(id: string, data: UpdateCustomerRequest, options?: PaymentRequestOptions): Promise<Customer>
// Delete customer
deleteCustomer(id: string, options?: PaymentRequestOptions): Promise<void>Subscriptions ​
// Create subscription
createSubscription(data: CreateSubscriptionRequest, options?: PaymentRequestOptions): Promise<Subscription>
// List subscriptions
listSubscriptions(params?: PaginationParams, options?: PaymentRequestOptions): Promise<Subscription[]>
// Get subscription
getSubscription(id: string, options?: PaymentRequestOptions): Promise<Subscription>
// Cancel subscription
cancelSubscription(id: string, data?: CancelSubscriptionRequest, options?: PaymentRequestOptions): Promise<Subscription>Organizations ​
// Create organization
createOrganization(data: CreateOrganizationRequest, options?: PaymentRequestOptions): Promise<Organization>
// List organizations
listOrganizations(params?: PaginationParams, options?: PaymentRequestOptions): Promise<Organization[]>
// Get organization
getOrganization(id: string, options?: PaymentRequestOptions): Promise<Organization>
// Update organization
updateOrganization(id: string, data: UpdateOrganizationRequest, options?: PaymentRequestOptions): Promise<Organization>
// Delete organization
deleteOrganization(id: string, options?: PaymentRequestOptions): Promise<void>
// List organization members
listOrganizationMembers(organizationId: string, params?: PaginationParams, options?: PaymentRequestOptions): Promise<OrganizationMember[]>
// Add organization member
addOrganizationMember(organizationId: string, data: AddOrganizationMemberRequest, options?: PaymentRequestOptions): Promise<OrganizationMember>
// Update organization member role
updateOrganizationMemberRole(organizationId: string, memberId: string, data: UpdateOrganizationMemberRoleRequest, options?: PaymentRequestOptions): Promise<OrganizationMember>
// Remove organization member
removeOrganizationMember(organizationId: string, memberId: string, options?: PaymentRequestOptions): Promise<void>
// Leave organization
leaveOrganization(organizationId: string, options?: PaymentRequestOptions): Promise<void>Guest Conversion ​
// Convert guest to user
convertGuestToUser(data: ConvertGuestToUserRequest, options?: PaymentRequestOptions): Promise<ConvertGuestToUserResponse>Next Steps ​
- Next.js Client - Next.js integration
- API Reference - Complete API documentation
- Guest Checkout Guide - Implement guest payments
- Testing - Test your integration