For non-React frameworks or custom implementations, follow this HTML integration guide.

Step 1: Obtain Access Token

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: Add the iframe to your HTML

<div id="payment-container" style="display: none;">
  <div class="payment-overlay">
    <div class="payment-modal">
      <button 
        id="cancel-payment-button"
        class="close-button"
        aria-label="Close payment window"
      >
        &times;
      </button>
      <iframe
        id="phoenix-payment-iframe"
        src="https://app-partner.phoenix.market/"
        class="teller-iframe"
        title="Phoenix Payment"
        allow="clipboard-write"
      ></iframe>
    </div>
  </div>
</div>

Step 3: Add Essential CSS

Add this CSS to style the payment container and iframe:

/* Phoenix Payment Styles */
#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;
  box-shadow: none;
  overflow: visible;
}

.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);
}

Step 4: Add JavaScript for Communication

You need to add JavaScript functions to handle the integration. Here’s a breakdown of the required parameters and functions:

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 from your backend
  • onClose: Function to handle when the user closes the payment modal
  • handlePaymentMessage: Function to process payment response messages
  • onDone: Function to handle when the payment process is complete (new)

Core Functions

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

Order Created Data

When an order is created, the data object will contain:

  • 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
// Function to get access token from your backend
async function getAccessToken() {
  try {
    const response = await fetch('/api/token');
    const data = await response.json();
    return data.access_token;
  } catch (error) {
    console.error('Error fetching access token:', error);
    return null;
  }
}

// Function to show payment iframe with parameters
async function showPayment(walletAddress, amount, theme) {
  const iframe = document.getElementById('phoenix-payment-iframe');
  const container = document.getElementById('payment-container');
  
  // Get the access token from your backend
  const accessToken = await getAccessToken();
  
  if (!accessToken) {
    alert('Unable to obtain access token. Please try again later.');
    return;
  }
  
  // Build iframe URL with all parameters
  let url = `https://app-partner.phoenix.market/?address=${walletAddress}&amount=${amount}&access_token=${accessToken}`;
  if (theme) {
    url += `&theme=${theme}`;
  }
  
  // Set iframe src
  iframe.src = url;
  
  // Display the container that holds the iframe
  container.style.display = 'block';
  
  // Set up event listener for messages from the iframe
  window.addEventListener('message', handlePaymentMessage);
  
  // Set up cancel button functionality
  document.getElementById('cancel-payment-button').onclick = hidePayment;
}

// Function to handle messages received from the iframe
function handlePaymentMessage(event) {
  // IMPORTANT: Verify origin for security
  if (event.origin !== 'https://app-partner.phoenix.market') return;
  
  // Handle order created message
  if (event.data?.type === 'ORDER_CREATED') {
    const { order_id, ...data } = event.data.data;
    console.log('Order created:', event.data.data);
    
    // Handle the order creation event as needed
    // You should store order details in your database, especially the order_id
  }
  // Handle transaction success message
  else if (event.data?.type === 'TRANSACTION_SUCCESS') {
    const { amount, token, chain, walletAddress, sendAmount, transactionHash, basescanLink } = event.data.data;
    console.log('Transaction successful:', transactionHash);
    
    // You should store transaction details in your database
    // and update your UI to show the payment was successful
    
    // Uncomment the following line if you want to close the window after transaction success
    // hidePayment();
  }
  // Handle cancel message
  else if (event.data?.type === 'CANCEL') {
    console.log('Payment canceled by user');
    hidePayment();
  }
  // Handle open explorer URL message
  else if (event.data?.type === 'OPEN_EXPLORER_URL') {
    const url = event.data.url;
    console.log('Opening explorer URL:', url);
    
    // Open the URL in a new tab
    window.open(url, '_blank');
  }
  // Handle done message
  else if (event.data?.type === 'USER_DONE') {
    console.log('Payment process completed');
    hidePayment();
  }
  // Handle payment failure or cancellation
  else if (event.data?.type === 'PAYMENT_FAILED' || event.data?.type === 'PAYMENT_CANCELLED') {
    console.log('Payment failed or cancelled');
    
    // Uncomment the following line if you want to close the window after payment failure/cancellation
    // hidePayment();
  }
}

// Function to hide the payment iframe
function hidePayment() {
  document.getElementById('payment-container').style.display = 'none';
  // IMPORTANT: Remove event listener to prevent memory leaks
  window.removeEventListener('message', handlePaymentMessage);
}

Connecting Everything

Add this script to connect your form to the payment flow:

// Set up the button click handler to start the payment process
document.getElementById('start-payment').addEventListener('click', function() {
  // Get values from form inputs
  const walletAddress = document.getElementById('wallet').value;
  const amount = document.getElementById('amount').value;
  const theme = document.getElementById('theme').value; // Get selected theme
  
  // Validate inputs (basic validation)
  if (!walletAddress || !amount) {
    alert('Please enter both wallet address and amount');
    return;
  }
  
  // Start payment process with collected values
  showPayment(walletAddress, amount, theme);
});

Complete Implementation Example