Skip to content

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.

Installation ​

bash
# 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 only

Official 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:

bash
# 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=development

2. Initialize Stripe Provider ​

tsx
// 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:

typescript
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) ​

typescript
// Session ID is automatically retrieved from AsyncStorage
const client = new BridgePaymentClient({
  baseUrl: process.env.EXPO_PUBLIC_BRIDGE_URL!
});

2. Guest Users (Token-Based) ​

typescript
// 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) ​

typescript
// 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 ​

tsx
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 ​

typescript
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 ​

typescript
const method = await client.getPaymentMethod('pm_123');
console.log(`Card: ${method.brand} **** ${method.last4}`);

Update Payment Method ​

typescript
// Set as default
await client.updatePaymentMethod('pm_123', {
  is_default: true,
  alias: 'My Visa Card'
});

Delete Payment Method ​

typescript
await client.deletePaymentMethod('pm_123');

Save Payment Method During Payment ​

typescript
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 ​

typescript
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:

typescript
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:

typescript
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 ​

tsx
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 URL
  • EXPO_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 ​

typescript
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 ​

typescript
const addresses = await client.listAddresses();

Get a Specific Address ​

typescript
const address = await client.getAddress('addr_123');

Update Address ​

typescript
await client.updateAddress('addr_123', {
  line1: '456 Oak Ave',
  is_default: true
});

Delete Address ​

typescript
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 ​

typescript
const subscription = await client.createSubscription({
  plan_id: 'plan_premium_monthly',
  payment_method_id: 'pm_123',
  trial_period_days: 14
});

List Subscriptions ​

typescript
const subscriptions = await client.listSubscriptions();

Get a Specific Subscription ​

typescript
const subscription = await client.getSubscription('sub_123');

Cancel Subscription ​

typescript
// 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 ​

typescript
const org = await client.createOrganization({
  name: 'Acme Corp',
  email: '[email protected]',
  tax_id: '12-3456789'
});

List Organizations ​

typescript
const orgs = await client.listOrganizations();

Add Organization Member ​

typescript
const member = await client.addOrganizationMember('org_123', {
  user_id: 'user_456',
  role: 'admin' // owner, admin, billing, member
});

Update Member Role ​

typescript
await client.updateOrganizationMemberRole('org_123', 'member_789', {
  role: 'billing'
});

Remove Member ​

typescript
await client.removeOrganizationMember('org_123', 'member_789');

Leave Organization ​

typescript
await client.leaveOrganization('org_123');

Error Handling ​

typescript
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:

typescript
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 ​

typescript
new BridgePaymentClient(config: BridgePaymentConfig)

Config Options:

  • baseUrl (required): Bridge Payments instance URL
  • storage (optional): Custom storage adapter (defaults to AsyncStorage)
  • instanceId (optional): Instance identifier for multi-instance support
  • headers (optional): Custom headers to include in all requests
  • guestToken (optional): Guest token for guest checkout
  • organizationId (optional): Organization ID for multi-tenant operations

Payment Intents ​

typescript
// 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 ​

typescript
// 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 ​

typescript
// 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 ​

typescript
// 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 ​

typescript
// 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 ​

typescript
// 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 ​

typescript
// Convert guest to user
convertGuestToUser(data: ConvertGuestToUserRequest, options?: PaymentRequestOptions): Promise<ConvertGuestToUserResponse>

Next Steps ​