Skip to main content
Follow these steps to integrate using our pre-built React 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('/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: Download the Component

You can view the component code or download it directly:
PhoenixPayment.tsx
import React, { useEffect, useRef } from 'react';

interface PhoenixPaymentProps {
  // Required parameters
  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 PhoenixPayment: React.FC<PhoenixPaymentProps> = ({
  walletAddress,
  amount,
  accessToken,
  onClose,
  onComplete,
  onDone,
  onOrderCreated,
  theme,
}) => {
  const iframeRef = useRef<HTMLIFrameElement>(null);

  useEffect(() => {
    // Handle messages from the iframe
    const handleMessage = (event: MessageEvent) => {
      // Verify origin for security
      if (event.origin !== 'https://app-partner.phoenix.market') return;
      
      // Handle transaction success message
      if (event.data?.type === 'TRANSACTION_SUCCESS') {
        const { amount, token, chain, walletAddress, sendAmount, transactionHash, basescanLink } = event.data.data;
        onComplete(true, transactionHash, { 
          amount, token, chain, walletAddress, sendAmount, transactionHash, basescanLink 
        });
      }
      // Handle order created message
      else if (event.data?.type === 'ORDER_CREATED') {
        const { order_id, ...data } = event.data.data;
        console.log('Order created:', event.data.data);
        
        if (onOrderCreated) {
          onOrderCreated({ order_id, data });
        }
      }
      // Handle cancel message
      else if (event.data?.type === 'CANCEL') {
        console.log('Payment canceled by user');
        onClose();
      }
      // Handle open explorer URL message
      else if (event.data?.type === 'OPEN_EXPLORER_URL') {
        const url = event.data.url;
        console.log('Opening explorer URL:', url);
        
        // Always open in a new tab
        window.open(url, '_blank');
      }
      // Handle done message
      else if (event.data?.type === 'USER_DONE') {
        console.log('Payment process completed');
        
        if (onDone) {
          onDone();
        } else {
          // Default behavior: close the window
          onClose();
        }
      }
    };

    window.addEventListener('message', handleMessage);
    
    // Clean up
    return () => {
      window.removeEventListener('message', handleMessage);
    };
  }, [onComplete, onClose, onDone, onOrderCreated]);

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

  return (
    <div className="phoenix-payment-container">
      <div className="payment-overlay">
        <div className="payment-modal">
          <button 
            className="close-button" 
            onClick={onClose}
            aria-label="Close payment window"
          >
            &times;
          </button>
          <iframe
            ref={iframeRef}
            src={getIframeUrl()}
            className="teller-iframe"
            title="Phoenix Payment"
            allow="clipboard-write"
          />
        </div>
      </div>
      <style>{`
        .phoenix-payment-container {
          position: fixed;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          z-index: 1000;
        }
        
        .payment-overlay {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          background-color: rgba(0, 0, 0, 0.5);
          display: flex;
          justify-content: center;
          align-items: center;
        }
        
        .payment-modal {
          position: relative;
          background-color: transparent;
          border-radius: 12px;
          padding: 0;
          overflow: visible;
          box-shadow: none;
        }
        
        .close-button {
          position: absolute;
          top: -15px;
          right: -15px;
          background-color: #333;
          color: white;
          border: none;
          border-radius: 50%;
          width: 30px;
          height: 30px;
          font-size: 18px;
          cursor: pointer;
          display: flex;
          align-items: center;
          justify-content: center;
          z-index: 10;
          padding: 0;
          line-height: 1;
          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
        }
        
        .teller-iframe {
          width: 350px;
          height: 600px;
          border: none;
          border-radius: 8px;
          overflow: hidden;
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
        }
      `}</style>
    </div>
  );
};

export default PhoenixPayment;
Save this file to your project’s component directory, for example: /src/components/PhoenixPayment.tsx
The component includes all necessary styling internally via the <style> tag, so you don’t need to add any additional CSS.

Step 3: Implement the Component

Follow these steps to implement the component in your application:

Step 3.1: Import the Component

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

Step 3.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: Optional callback function to handle when an order is created
  • 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 [accessToken, setAccessToken] = useState('');

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

Step 3.3: Add the Component to Your JSX

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

Step 3.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 3.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 received (e.g., “USDC”)
  • chain: The blockchain (e.g., “Base”)
  • walletAddress: The receiving wallet address
  • sendAmount: The amount of tokens sent
  • transactionHash: The blockchain transaction hash
  • basescanLink: Link to view the transaction on Basescan
const handlePaymentComplete = (success, transactionId, data) => {
  if (success) {
    // For transaction success
    if (data?.transactionHash) {
      console.log('Transaction successful:', data);
      
      // You might want to redirect to a success page or show transaction details
      
      // 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 3.6: Handle Done Events

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

Full Implementation Example

Here’s a complete example showing how all the pieces fit together:
import { useState, useEffect } from 'react';
import { PhoenixPayment } from './components/PhoenixPayment';

function PaymentPage() {
  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('/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 successful:', data);
        
        // You might want to redirect to a success page or show transaction details
        
        // 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 (
    <>
      {/* Form to collect wallet address and amount */}
      <div>
        <label htmlFor="wallet">Wallet Address:</label>
        <input
          id="wallet"
          type="text"
          value={walletAddress}
          onChange={(e) => setWalletAddress(e.target.value)}
          placeholder="0x..."
        />
      </div>
      <div>
        <label htmlFor="amount">Amount (USD):</label>
        <input
          id="amount"
          type="number"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
          placeholder="100"
        />
      </div>
      
      <button 
        onClick={() => setShowPayment(true)} 
        disabled={!accessToken} // Disable if access token isn't loaded
      >
        Make Payment
      </button>
      
      {showPayment && accessToken && (
        <PhoenixPayment 
          walletAddress={walletAddress}
          amount={amount}
          accessToken={accessToken}
          onClose={() => setShowPayment(false)}
          onComplete={handlePaymentComplete}
          onDone={handlePaymentDone}
          onOrderCreated={handleOrderCreated}
          theme="dark" // Optional: specify 'light' or 'dark' theme
        />
      )}
    </>
  );
}
I