Skip to main content
Follow these steps to integrate using our pre-built React Native component.

Step 1: Obtain Access Token

Before using the Phoenix component, you need to obtain an access token from your backend.
// Example of fetching the access token from your backend
const [accessToken, setAccessToken] = useState('');

useEffect(() => {
  // Replace with your actual backend endpoint that implements the token generation
  fetch('https://your-backend.com/api/token')
    .then(response => response.json())
    .then(data => setAccessToken(data.access_token))
    .catch(error => console.error('Error fetching access token:', error));
}, []);
See the Obtaining Access Token page for detailed instructions on setting up the backend endpoint.
This is example code for obtaining an access token. You will need to adapt the endpoint URL and request format based on your specific backend implementation. Refer to the Obtaining Access Token page for a complete example of setting up a backend service.

Step 2: Install Required Dependencies

# Install the WebView package
npm install react-native-webview

# For iOS, install pods
cd ios && pod install && cd ..

Step 3: Download the Component

You can view the component code or download it directly:
PhoenixPaymentNative.tsx
import React, { useRef, useEffect } from 'react';
import { View, StyleSheet, TouchableOpacity, Text, Modal, Platform, Linking } from 'react-native';
import { WebView } from 'react-native-webview';

interface PhoenixPaymentNativeProps {
  walletAddress: string;
  amount: string;
  accessToken: string; // Access token from your backend
  onClose: () => void;
  onComplete: (success: boolean, transactionId?: string, data?: any) => void;
  onDone?: () => void; // New callback for handling done events
  onOrderCreated?: (data: any) => void; // Callback for handling order creation
  theme?: 'light' | 'dark'; // Optional theme parameter
}

export const PhoenixPaymentNative: React.FC<PhoenixPaymentNativeProps> = ({
  walletAddress,
  amount,
  accessToken,
  onClose,
  onComplete,
  onDone,
  onOrderCreated,
  theme,
}) => {
  const webViewRef = useRef<WebView>(null);

  // Handle messages from WebView
  const handleWebViewMessage = (event: any) => {
    try {
      const data = JSON.parse(event.nativeEvent.data);
      
      // Handle transaction success message
      if (data?.type === 'TRANSACTION_SUCCESS') {
        const { amount, token, chain, walletAddress, sendAmount, transactionHash, basescanLink } = data.data;
        onComplete(true, transactionHash, { 
          amount, token, chain, walletAddress, sendAmount, transactionHash, basescanLink 
        });
      }
      // Handle order created message
      else if (data?.type === 'ORDER_CREATED') {
        const { order_id, ...orderData } = data.data;
        console.log('Order created:', data.data);
        
        if (onOrderCreated) {
          onOrderCreated({ order_id, data: orderData });
        }
      }
      // Handle cancel message
      else if (data?.type === 'CANCEL') {
        console.log('Payment canceled by user');
        onClose();
      }
      // Handle open explorer URL message
      else if (data?.type === 'OPEN_EXPLORER_URL') {
        const url = data.url;
        console.log('Opening explorer URL:', url);
        
        // Always open in browser using Linking
        Linking.canOpenURL(url).then(supported => {
          if (supported) {
            Linking.openURL(url);
          } else {
            console.log("Don't know how to open URI: " + url);
          }
        });
      }
      // Handle done message
      else if (data?.type === 'USER_DONE') {
        console.log('Payment process completed');
        
        if (onDone) {
          onDone();
        } else {
          // Default behavior: close the window
          onClose();
        }
      }
    } catch (error) {
      console.error('Error parsing WebView message:', error);
    }
  };

  // Build WebView URL with all parameters
  const getWebViewUrl = () => {
    let url = `https://app-partner.phoenix.market/?address=${walletAddress}&amount=${amount}&access_token=${accessToken}`;
    if (theme) {
      url += `&theme=${theme}`;
    }
    return url;
  };

  // Inject JavaScript to listen for postMessage events and forward them to React Native
  const injectedJavaScript = `
    window.addEventListener('message', function(event) {
      if (event.origin === 'https://app-partner.phoenix.market') {
        window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
      }
    });
    true; // note: this is required, or you'll sometimes get silent failures
  `;

  return (
    <Modal
      visible={true}
      animationType="slide"
      transparent={true}
      onRequestClose={onClose}
    >
      <View style={styles.container}>
        <View style={styles.paymentModal}>
          <TouchableOpacity 
            style={styles.closeButton} 
            onPress={onClose}
            accessibilityLabel="Close payment window"
          >
            <Text style={styles.closeButtonText}></Text>
          </TouchableOpacity>
          <WebView
            ref={webViewRef}
            source={{ uri: getWebViewUrl() }}
            style={styles.webView}
            javaScriptEnabled={true}
            domStorageEnabled={true}
            originWhitelist={['*']}
            injectedJavaScript={injectedJavaScript}
            onMessage={handleWebViewMessage}
            incognito={false}
            thirdPartyCookiesEnabled={true}
            allowsInlineMediaPlayback={true}
            androidLayerType="hardware"
          />
        </View>
      </View>
    </Modal>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  paymentModal: {
    backgroundColor: 'transparent',
    borderRadius: 10,
    width: 350,
    height: 600,
    overflow: 'visible',
    position: 'relative',
    ...Platform.select({
      ios: {
        shadowColor: 'transparent',
        shadowOffset: { width: 0, height: 0 },
        shadowOpacity: 0,
        shadowRadius: 0,
      },
      android: {
        elevation: 0,
      },
    }),
  },
  closeButton: {
    position: 'absolute',
    top: -15,
    right: -15,
    zIndex: 10,
    backgroundColor: '#333',
    width: 30,
    height: 30,
    borderRadius: 15,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 0,
    ...Platform.select({
      ios: {
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.25,
        shadowRadius: 4,
      },
      android: {
        elevation: 4,
      },
    }),
  },
  closeButtonText: {
    fontSize: 18,
    fontWeight: 'bold',
    color: 'white',
    lineHeight: 18,
    textAlign: 'center',
  },
  webView: {
    flex: 1,
    borderRadius: 8,
    overflow: 'hidden',
    backgroundColor: 'transparent',
    opacity: 0.99, // Fix for black borders on Android
  },
});

export default PhoenixPaymentNative;
Save this file to your components directory, for example: components/PhoenixPaymentNative.tsx

Step 4: Implement the Component

Follow these steps to implement the component in your application:

Step 4.1: Import the Component

import { PhoenixPaymentNative } from './components/PhoenixPaymentNative';

Step 4.2: Prepare Required and Optional Parameters

Before adding the component, you need to collect the following information:

Required Parameters

  • walletAddress: The Ethereum wallet address where USDC will be sent
  • amount: The amount in USD to convert to USDC
  • accessToken: The access token retrieved from your backend
  • onClose: Function to handle when the user closes the payment modal
  • onComplete: Callback function to handle payment completion events

Optional Parameters

  • onDone: Callback function to handle when the payment process is complete
  • onOrderCreated: Callback function to handle order creation events
  • theme: Optional UI theme, can be set to either ‘light’ or ‘dark’
You should create form inputs in your UI to collect the required information:
// Example of collecting user input
const [walletAddress, setWalletAddress] = useState('');
const [amount, setAmount] = useState('');
const [showPayment, setShowPayment] = useState(false);
const [accessToken, setAccessToken] = useState('');

useEffect(() => {
  // Fetch the access token from your backend
  fetch('https://your-backend.com/api/token')
    .then(response => response.json())
    .then(data => setAccessToken(data.access_token))
    .catch(error => console.error('Error fetching access token:', error));
}, []);

Step 4.3: Add the Component to Your JSX

Once you have collected these values, you can pass them to the PhoenixPaymentNative component:
<PhoenixPaymentNative
  walletAddress={walletAddress}
  amount={amount}
  accessToken={accessToken}
  onClose={() => setShowPayment(false)}
  onComplete={handlePaymentComplete}
  onDone={handlePaymentDone}
  onOrderCreated={handleOrderCreated}
  theme="dark" // Optional: specify 'light' or 'dark' theme
/>

Step 4.4: Handle Order Created Events

The onOrderCreated callback is triggered when a new order is created in the Phoenix system. This happens before the payment process begins. The callback receives an object containing:
  • order_id: Unique identifier for the order
  • data: Object containing additional order details:
    • created_at: Timestamp of order creation
    • buyer_address: The buyer’s wallet address
    • seller_address: The seller’s wallet address
    • send_amount: The amount to be sent
    • receive_amount: The amount to be received
    • uniqueness_amount: The amount of cents added to the order for uniqueness
    • receiving_chain_id: The chain ID where the payment will be received
    • zelle_address: The Zelle address for payment
const handleOrderCreated = ({ order_id, data }) => {
  console.log('Order created:', { order_id, data });
  // You can store the order details for reference
};

Step 4.5: Handle Payment Completion

The onComplete callback receives three parameters:
  1. success: Boolean indicating if the operation succeeded
  2. transactionId: Transaction hash (only for successful payments)
  3. data: Object containing additional information

Transaction Success Data

When a transaction is successful, the data object will contain:
  • amount: The USD amount
  • token: The token type (e.g., “USDC”)
  • chain: The blockchain network
  • walletAddress: The recipient wallet
  • sendAmount: The amount of tokens sent
  • transactionHash: The blockchain transaction hash
  • basescanLink: Link to view the transaction
const handlePaymentComplete = (success, transactionId, data) => {
  if (success) {
    // For transaction success
    if (data?.transactionHash) {
      console.log('Transaction completed:', data.transactionHash);
      
      // Uncomment the following line if you want to close the window after transaction success
      // setShowPayment(false);
    }
  } else {
    console.log('Payment failed or cancelled');
    
    // Uncomment the following line if you want to close the window after payment failure/cancellation
    // setShowPayment(false);
  }
};

Step 4.6: Handle Done Events

const handlePaymentDone = () => {
  // Close the payment window
  setShowPayment(false);
};

Platform Configuration

iOS

Add to your Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>app-partner.phoenix.market</key>
    <dict>
      <key>NSExceptionAllowsInsecureHTTPLoads</key>
      <false/>
      <key>NSIncludesSubdomains</key>
      <true/>
    </dict>
  </dict>
</dict>

Android

Add to your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />

Complete Implementation Example

Here’s a complete example showing how to implement the Phoenix payment in a React Native app:
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button, StyleSheet, Linking } from 'react-native';
import { PhoenixPaymentNative } from './components/PhoenixPaymentNative';

export default function PaymentScreen() {
  const [walletAddress, setWalletAddress] = useState('');
  const [amount, setAmount] = useState('');
  const [showPayment, setShowPayment] = useState(false);
  const [accessToken, setAccessToken] = useState('');
  
  useEffect(() => {
    // Fetch the access token from your backend
    fetch('https://your-backend.com/api/token')
      .then(response => response.json())
      .then(data => setAccessToken(data.access_token))
      .catch(error => console.error('Error fetching access token:', error));
  }, []);
  
  const handlePaymentComplete = (success, transactionId, data) => {
    if (success) {
      // For transaction success
      if (data?.transactionHash) {
        console.log('Transaction completed:', data.transactionHash);
        
        // Uncomment the following line if you want to close the window after transaction success
        // setShowPayment(false);
      }
    } else {
      console.log('Payment failed or cancelled');
      
      // Uncomment the following line if you want to close the window after payment failure/cancellation
      // setShowPayment(false);
    }
  };
  
  const handlePaymentDone = () => {
    // Close the payment window
    setShowPayment(false);
  };

  const handleOrderCreated = ({ order_id, data }) => {
    console.log('Order created:', { order_id, data });
    // You can store the order details for reference
  };

  return (
    <View style={styles.container}>
      <Text style={styles.label}>Wallet Address:</Text>
      <TextInput
        style={styles.input}
        value={walletAddress}
        onChangeText={setWalletAddress}
        placeholder="0x..."
      />
      
      <Text style={styles.label}>Amount (USD):</Text>
      <TextInput
        style={styles.input}
        value={amount}
        onChangeText={setAmount}
        keyboardType="numeric"
        placeholder="100"
      />
      
      <Button
        title="Make Payment"
        onPress={() => setShowPayment(true)}
        disabled={!accessToken} // Disable if access token isn't loaded
      />
      
      {showPayment && accessToken && (
        <PhoenixPaymentNative
          walletAddress={walletAddress}
          amount={amount}
          accessToken={accessToken}
          onClose={() => setShowPayment(false)}
          onComplete={handlePaymentComplete}
          onDone={handlePaymentDone}
          onOrderCreated={handleOrderCreated}
          theme="dark" // Optional: specify 'light' or 'dark' theme
        />
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  label: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 4,
    padding: 8,
    marginBottom: 16,
  },
});
I