The @zbdpay/ramp-react-native package is a React Native wrapper for the ZBD Ramp widget that enables Buying Bitcoin with USD directly to the Lightning Network.

Features

  • React Native Optimized: Built specifically for React Native with WebView
  • TypeScript Support: Full type safety with comprehensive TypeScript definitions
  • Cross-Platform: Works on iOS and Android
  • Ref API: Access to WebView methods and ramp instance
  • Hook Support: useZBDRamp hook for programmatic usage

Installation

npm install @zbdpay/ramp-react-native react-native-webview

iOS Setup

1. Install CocoaPods dependencies

cd ios && pod install

2. Add Privacy Permissions

Add the following entries to your ios/{YourAppName}/Info.plist:
<!-- Essential permissions for ZBD Ramp WebView -->
<key>NSCameraUsageDescription</key>
<string>ZBD Ramp needs camera access for document verification and identity verification processes.</string>

<key>NSMicrophoneUsageDescription</key>
<string>ZBD Ramp needs microphone access for liveness detection during identity verification.</string>

<key>NSPhotoLibraryUsageDescription</key>
<string>ZBD Ramp needs photo library access to upload documents for verification purposes.</string>

<key>NSPhotoLibraryAddUsageDescription</key>
<string>ZBD Ramp may save verification photos to your photo library.</string>
These permissions are required for the ZBD Ramp widget to access device features like camera (document verification) and microphone (liveness detection) within the WebView context.

Android Setup

Add the following permissions to your android/app/src/main/AndroidManifest.xml:
<!-- Essential permissions for ZBD Ramp WebView -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
These permissions prevent permissions_unavailable errors when the ZBD Ramp widget tries to access device features.

Quick Start

1. Create Session Token

First, create a session token using the ZBD API:
import { initRampSession, QuoteCurrencyEnum, BaseCurrencyEnum } from '@zbdpay/ramp-react-native';

const response = await initRampSession({
  apikey: 'your-zbd-api-key',
  email: 'user@example.com',
  destination: 'lightning-address@zbd.gg',
  quote_currency: QuoteCurrencyEnum.USD,
  base_currency: BaseCurrencyEnum.BTC,
  webhook_url: 'https://your-webhook-url.com',
});

const sessionToken = response.data.session_token;

2. Use ZBDRamp Component

import React from 'react';
import { View, StyleSheet } from 'react-native';
import { ZBDRamp } from '@zbdpay/ramp-react-native';

function App() {
  return (
    <View style={styles.container}>
      <ZBDRamp
        sessionToken="your-session-token"
        onSuccess={(data) => console.log('Success:', data)}
        onError={(error) => console.error('Error:', error)}
        onStepChange={(step) => console.log('Step:', step)}
        style={styles.webview}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  webview: {
    flex: 1,
  },
});

API Reference

initRampSession

Creates a new session token for the ZBD Ramp widget.
config
InitRampSessionConfig
required
Configuration object for creating a session

Configuration Parameters

apikey
string
required
Your ZBD API key
email
string
required
User’s email address
destination
string
required
Lightning address or Bitcoin address
quote_currency
QuoteCurrencyEnum
required
Quote currency (e.g., USD)
base_currency
BaseCurrencyEnum
required
Base currency (e.g., BTC)
webhook_url
string
Webhook URL for notifications
reference_id
string
Your internal reference ID
metadata
Record<string, any>
Additional metadata to attach to the session

ZBDRamp Component

React Native component that renders the ZBD Ramp widget using WebView.

Props

sessionToken
string
required
Session token from ZBD API
style
WebViewProps['style']
WebView style
webViewProps
Omit<WebViewProps, 'source' | 'onMessage' | 'style'>
Additional WebView props

Callback Props

onSuccess
(data: any) => void
Called when payment is successful
onError
(error: RampError) => void
Called when an error occurs
onStepChange
(step: string) => void
Called when user navigates to a different step
onLog
(log: RampLog) => void
Debug/info logging callback
onReady
() => void
Called when widget is fully loaded
onClose
() => void
Called when user closes the widget

Ref API

interface ZBDRampRef {
  mount: (container?: HTMLElement | string) => void;
  unmount: () => void;
  destroy: () => void;
}

useZBDRamp Hook

Hook for managing ZBD Ramp instances programmatically.
const { rampRef, sendMessage, updateConfig, reload } = useZBDRamp(options);

Usage Examples

Basic Component

import React from 'react';
import { View, StyleSheet, Alert } from 'react-native';
import { ZBDRamp } from '@zbdpay/ramp-react-native';

function PaymentScreen() {
  const handleSuccess = (data: any) => {
    Alert.alert('Success', 'Payment completed successfully!');
    console.log('Payment data:', data);
  };

  const handleError = (error: any) => {
    Alert.alert('Error', error.message);
    console.error('Payment error:', error);
  };

  return (
    <View style={styles.container}>
      <ZBDRamp
        sessionToken="your-session-token"
        onSuccess={handleSuccess}
        onError={handleError}
        style={styles.webview}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  webview: {
    flex: 1,
    backgroundColor: 'transparent',
  },
});

With Ref for Control

import React, { useRef } from 'react';
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { ZBDRamp } from '@zbdpay/ramp-react-native';
import type { ZBDRampRef } from '@zbdpay/ramp-react-native';

function ControlledPayment() {
  const rampRef = useRef<ZBDRampRef>(null);

  const unmountWidget = () => {
    rampRef.current?.unmount();
  };

  const destroyWidget = () => {
    rampRef.current?.destroy();
  };

  return (
    <View style={styles.container}>
      <View style={styles.controls}>
        <TouchableOpacity style={styles.button} onPress={unmountWidget}>
          <Text style={styles.buttonText}>Unmount</Text>
        </TouchableOpacity>

        <TouchableOpacity style={styles.button} onPress={destroyWidget}>
          <Text style={styles.buttonText}>Destroy</Text>
        </TouchableOpacity>
      </View>

      <ZBDRamp
        ref={rampRef}
        sessionToken="your-session-token"
        onSuccess={(data) => console.log('Success:', data)}
        onError={(error) => console.error('Error:', error)}
        style={styles.webview}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  controls: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    padding: 10,
    backgroundColor: '#fff',
  },
  button: {
    backgroundColor: '#ff6b35',
    paddingHorizontal: 15,
    paddingVertical: 8,
    borderRadius: 4,
  },
  buttonText: {
    color: 'white',
    fontSize: 12,
    fontWeight: 'bold',
  },
  webview: {
    flex: 1,
  },
});

Using the Hook

import React, { useState } from 'react';
import { View, TouchableOpacity, Text, StyleSheet, Modal } from 'react-native';
import { ZBDRamp, useZBDRamp } from '@zbdpay/ramp-react-native';

function HookExample() {
  const [isVisible, setIsVisible] = useState(false);

  const { rampRef, sendMessage, updateConfig, reload } = useZBDRamp({
    sessionToken: 'your-session-token',
    onSuccess: (data) => {
      console.log('Payment successful:', data);
      setIsVisible(false);
    },
    onClose: () => {
      setIsVisible(false);
    },
  });

  const openPayment = () => {
    setIsVisible(true);
  };

  const closePayment = () => {
    setIsVisible(false);
  };

  return (
    <View style={styles.container}>
      <TouchableOpacity style={styles.openButton} onPress={openPayment}>
        <Text style={styles.buttonText}>Open Payment</Text>
      </TouchableOpacity>

      <Modal visible={isVisible} animationType="slide">
        <View style={styles.modalContainer}>
          <View style={styles.modalHeader}>
            <TouchableOpacity onPress={closePayment}>
              <Text style={styles.closeButton}>Close</Text>
            </TouchableOpacity>
          </View>

          <ZBDRamp
            ref={rampRef}
            sessionToken="your-session-token"
            onSuccess={(data) => {
              console.log('Success:', data);
              setIsVisible(false);
            }}
            style={styles.webview}
          />
        </View>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  openButton: {
    backgroundColor: '#ff6b35',
    paddingHorizontal: 20,
    paddingVertical: 10,
    borderRadius: 8,
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
  modalContainer: {
    flex: 1,
  },
  modalHeader: {
    flexDirection: 'row',
    justifyContent: 'flex-end',
    padding: 15,
    backgroundColor: '#f8f8f8',
  },
  closeButton: {
    fontSize: 16,
    color: '#007AFF',
  },
  webview: {
    flex: 1,
  },
});

Custom WebView Configuration

import React from 'react';
import { View, StyleSheet } from 'react-native';
import { ZBDRamp } from '@zbdpay/ramp-react-native';

function CustomWebViewRamp() {
  return (
    <View style={styles.container}>
      <ZBDRamp
        sessionToken="your-session-token"
        style={styles.webview}
        webViewProps={{
          bounces: false,
          scrollEnabled: false,
          showsHorizontalScrollIndicator: false,
          showsVerticalScrollIndicator: false,
          allowsBackForwardNavigationGestures: false,
          userAgent: 'MyApp/1.0',
        }}
        onSuccess={(data) => console.log('Success:', data)}
        onError={(error) => console.error('Error:', error)}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  webview: {
    flex: 1,
    backgroundColor: '#ffffff',
  },
});

Error Handling

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import { ZBDRamp } from '@zbdpay/ramp-react-native';
import type { RampError } from '@zbdpay/ramp-react-native';

function PaymentWithErrorHandling() {
  const [error, setError] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  const handleError = (error: RampError) => {
    setIsLoading(false);
    let errorMessage = 'An unexpected error occurred.';

    switch (error.code) {
      case 'INVALID_CONFIG':
        errorMessage = 'Configuration error. Please check your settings.';
        break;
      case 'NETWORK_ERROR':
        errorMessage = 'Network error. Please check your connection.';
        break;
      case 'PAYMENT_FAILED':
        errorMessage = 'Payment failed. Please try again.';
        break;
    }

    setError(errorMessage);
    Alert.alert('Payment Error', errorMessage);
  };

  const handleReady = () => {
    setIsLoading(false);
    setError(null);
  };

  const retry = () => {
    setError(null);
    setIsLoading(true);
  };

  return (
    <View style={styles.container}>
      {isLoading && (
        <View style={styles.loadingContainer}>
          <Text>Loading payment widget...</Text>
        </View>
      )}

      {error && (
        <View style={styles.errorContainer}>
          <Text style={styles.errorText}>{error}</Text>
          <TouchableOpacity style={styles.retryButton} onPress={retry}>
            <Text style={styles.retryButtonText}>Retry</Text>
          </TouchableOpacity>
        </View>
      )}

      {!error && (
        <ZBDRamp
          sessionToken="your-session-token"
          onReady={handleReady}
          onError={handleError}
          onSuccess={() => {
            setError(null);
            Alert.alert('Success', 'Payment completed!');
          }}
          style={styles.webview}
        />
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  errorContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  errorText: {
    color: 'red',
    textAlign: 'center',
    marginBottom: 20,
    fontSize: 16,
  },
  retryButton: {
    backgroundColor: '#ff6b35',
    paddingHorizontal: 20,
    paddingVertical: 10,
    borderRadius: 8,
  },
  retryButtonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
  webview: {
    flex: 1,
  },
});

TypeScript Support

The package includes comprehensive TypeScript definitions:
import type {
  ZBDRampProps,
  ZBDRampRef,
  RampConfig,
  RampCallbacks,
  RampOptions,
  RampError,
  RampLog,
  PostMessageData,
  InitRampSessionConfig,
  InitRampSessionData,
  InitRampSessionResponse,
  QuoteCurrencyEnum,
  BaseCurrencyEnum,
} from '@zbdpay/ramp-react-native';

Type Examples

// Define typed configuration
const config: InitRampSessionConfig = {
  apikey: process.env.ZBD_API_KEY!,
  email: 'user@example.com',
  destination: 'lightning-address@zbd.gg',
  quote_currency: QuoteCurrencyEnum.USD,
  base_currency: BaseCurrencyEnum.BTC,
};

// Handle typed responses
const handleSuccess = (data: any): void => {
  console.log('Payment completed:', data);
};

const handleError = (error: RampError): void => {
  console.error(`Error ${error.code}: ${error.message}`);
};

// Create typed instance
const ramp: ZBDRampRef = useRef<ZBDRampRef>(null);

Development Setup

Running the Example App

The repository includes a complete React Native example app:
# Clone the repository
git clone https://github.com/zbdpay/ramp-react-native.git
cd ramp-react-native

# Install dependencies
npm install

# Navigate to example app
cd example
npm install

# For iOS
cd ios && pod install && cd ..
npm run ios

# For Android
npm run android
The example app includes:
  • Session token creation
  • Debug logging
  • Full integration with all event handlers
  • Error handling examples

Resources

Support

For support and questions: