The Select for Deposit use case allows users to select a deposit method for funding their Uphold account. Users can choose from previously linked external accounts (such as saved cards) or receive an account deposit method for making a deposit (such as a bank transfer).

Available Payment Methods

  • Credit/Debit Cards - Visa and Mastercard
  • FPS (Faster Payment System) - Instant payment service for UK residents

Integration

Here’s how you can leverage the Payment Widget SDK to offer easy deposits:

Create Select for Deposit session

Before initializing the Widget, create a payment session for the Select for Deposit flow using the REST API as shown below.
curl -X POST https://api.enterprise.uphold.com/widgets/payment/sessions \
  -H "Authorization: Bearer <API_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "X-On-Behalf-Of: user <USER_ID>" \
  -d '{
    "flow": "select-for-deposit"
  }'

Setting Up the Widget

The example code below is for web applications. For native applications using a WebView, you’ll still need to implement communication through a bridge to send events to the native side, as highlighted in the Using the SDK page.
Once you have the session, initialize the Payment Widget for the Select for Deposit flow:
import { PaymentWidget } from '@uphold/enterprise-payment-widget-web-sdk';

const initializeDepositWidget = async () => {
  // Create the session
  const sessionData = await createDepositSession();

  // Initialize the Widget with the session
  const widget = new PaymentWidget<'select-for-deposit'>(sessionData.session, { debug: true });

  // Set up event handlers (see sections below)
  setupEventHandlers(widget);

  // Mount the Widget in an iframe
  widget.mountIframe(document.getElementById('payment-container'));
};

Handling the complete event

The complete event is fired when the user successfully selects a deposit method.
widget.on('complete', (event) => {
  const result = event.detail.value;
  console.log('Deposit method selected:', result);

  // result.via indicates the type of selection
  const { via, selection } = result;

  // Process the selected deposit method
  handleDepositMethodSelected(via, selection);

  // Clean up the Widget
  widget.unmount();
});

Event Data

The complete event payload contains two primary properties:
  • via - Specifies the type of deposit method selected:
    • external-account - Previously linked payment method (e.g., saved card)
    • deposit-method - Account deposit method requiring additional setup (e.g., bank transfer)
  • selection - Contains the method data structure
External Account Selection (via: "external-account") When an external account is selected, the selection property contains an external account object with the saved payment method details. Account Deposit Method Selection (via: "deposit-method") When an account deposit method is selected, the selection property contains an object with the following structure:
  • account - The account that will receive the deposited funds
  • depositMethod - The account deposit method configuration for processing the deposit
The following example demonstrates proper handling of both selection types:
const handleDepositMethodSelected = (via, selection) => {
  if (via === 'external-account') {
    // selection is an ExternalAccount
    console.log('Selected external account:', {
      id: selection.id,
      type: selection.type, // 'card'
      label: selection.label, // e.g., "#1111"
      network: selection.network, // e.g., "visa"
      // Additional properties for external accounts
    });

    // Proceed with existing external account
    proceedWithExternalAccount(selection);
  } else if (via === 'deposit-method') {
    // selection contains depositMethod and account
    console.log('Selected account deposit method:', {
      depositMethod: selection.depositMethod,
      account: selection.account
    });

    // Proceed with account deposit method
    proceedWithAccountDepositMethod(selection.depositMethod, selection.account);
  }
};

const proceedWithExternalAccount = (externalAccount) => {
  // For external accounts, proceed to amount selection screen
};

const proceedWithAccountDepositMethod = (depositMethod, account) => {
  if (depositMethod.type === 'bank') {
    console.log('Bank transfer details:', {
        type: depositMethod.type, // 'bank'
        status: depositMethod.status, // 'processing' | 'ok' | 'failed'
        network: depositMethod.details?.network, // 'fps'
        sortCode: depositMethod.details?.sortCode,
        accountNumber: depositMethod.details?.accountNumber,
        reference: depositMethod.details?.reference
    });
  }

  console.log('Target account:', {
    id: account.id,
    label: account.label,
    asset: account.asset,
    balance: account.balance
  });

  // Show a waiting message to the user and start monitoring for the deposit transaction
};

Monitoring for the deposit transaction (FPS only)

When the via property is deposit-method, the user will complete their deposit through a bank transfer. The Payment Widget presents the necessary bank account details to facilitate this transfer, eliminating the need for your application to display them separately. However, the Payment Widget does not monitor for the completion of the bank transfer. Your application may optionally implement transaction monitoring to detect when the deposit has been processed and provide real-time user feedback. This can be achieved by polling the transactions endpoint to identify when the FPS deposit is received by the account specified in the selection.account.id property of the complete event payload. The following example demonstrates how to poll for incoming transactions:
Monitor bank deposit transaction
const monitorForDeposit = async (accountId) => {
  const checkForDeposit = async () => {
    try {
      const response = await fetch('https://api.enterprise.uphold.com/transactions', {
        headers: {
          'Authorization': 'Bearer <API_TOKEN>',
          'Content-Type': 'application/json',
          'X-On-Behalf-Of': 'user <USER_ID>'
        }
      });

      const transactions = await response.json();

      // Look for a transaction where:
      // - origin is an external-account node
      // - destination is the account we're expecting the deposit to
      const depositTransaction = transactions.find(transaction => {
        return transaction.origin.type === 'external-account' &&
               transaction.destination.type === 'account' &&
               transaction.destination.id === accountId;
      });

      return depositTransaction;
    } catch (error) {
      console.error('Error checking for deposit:', error);
      return;
    }
  };

  // Poll every 30 seconds for up to 10 minutes
  const pollInterval = 30000; // 30 seconds
  const maxAttempts = 20; // 10 minutes total
  let attempts = 0;

  const poll = setInterval(async () => {
    attempts++;
    const depositTransaction = await checkForDeposit();

    if (depositTransaction || attempts >= maxAttempts) {
      clearInterval(poll);

      if (depositTransaction) {
        handleDepositReceived(depositTransaction);
        return;
      }

      if (!depositTransaction && attempts >= maxAttempts) {
        console.log('Polling timeout reached without detecting deposit');
        handleDepositTimeout();
      }
    }
  }, pollInterval);
};

const handleDepositReceived = (transaction) => {
  console.log('FPS deposit received:', transaction);

  // Check transaction status to determine outcome
  if (transaction.status === 'completed') {
    // Update UI to show successful deposit
    showDepositSuccess(transaction);
  } else if (transaction.status === 'failed') {
    // Update UI to show failed deposit
    showDepositFailed(transaction);
  } else if (transaction.status === 'processing') {
    // Update UI to show deposit is still being processed
    showDepositProcessing(transaction);
  }
};

const handleDepositTimeout = () => {
  console.log('Deposit monitoring timed out');
  // Show message that deposit is taking longer than expected
  showDepositPending();
};

Handling the cancel event

The cancel event is fired when the user closes the Widget without selecting an external account.
widget.on('cancel', () => {
  console.log('User cancelled deposit method selection');

  // Handle cancellation
  handleDepositCancelled();

  // Clean up the Widget
  widget.unmount();
});

const handleDepositCancelled = () => {
  // Redirect back to the previous page or main deposit page
};

Handling the error event

The error event is fired when an error occurs during the external account selection process.
widget.on('error', (event) => {
  const error = event.detail.error;
  console.error('Deposit Widget error:', error);

  // Handle the error
  handleDepositError(error);

  // Clean up the Widget
  widget.unmount();
});

const handleDepositError = (error) => {
  console.error('Error details:', {
    code: error.code,
    message: error.message
  });

  // Show user-friendly error message like 'An error occurred. Please try again later.'
};
The Payment Widget does not offer user-facing error handling. It’s the responsibility of the host application to present an error message to the user and unmount the Widget.

Complete Implementation Example

Here’s a complete example combining all the event handlers:
import { PaymentWidget } from '@uphold/enterprise-payment-widget-web-sdk';

class DepositMethodSelector {
  constructor() {
    this.widget = null;
  }

  async initialize() {
    try {
      // Create session for deposit flow
      const sessionData = await this.createDepositSession();

      // Initialize Widget
      this.widget = new PaymentWidget<'select-for-deposit'>(sessionData.session, { debug: true });

      // Set up all event handlers
      this.setupEventHandlers();

      // Mount Widget in an iframe
      this.widget.mountIframe(document.getElementById('payment-container'));
    } catch (error) {
      console.error('Failed to initialize Deposit Widget:', error);
      this.handleInitializationError(error);
    }
  }

  async createDepositSession() {
    const response = await fetch('https://api.enterprise.uphold.com/widgets/payment/sessions', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer <API_TOKEN>',
        'Content-Type': 'application/json',
        'X-On-Behalf-Of': 'user <USER_ID>'
      },
      body: JSON.stringify({
        flow: 'select-for-deposit'
      })
    });

    if (!response.ok) {
      throw new Error('Failed to create deposit session');
    }

    return await response.json();
  }

  setupEventHandlers() {
    // Handle successful selection
    this.widget.on('complete', (event) => {
      const { via, selection } = event.detail.value;
      console.log('Deposit method selected:', { via, selection });

      this.handleMethodSelected(via, selection);
      this.widget.unmount();
    });

    // Handle cancellation
    this.widget.on('cancel', () => {
      console.log('Selection cancelled');
      this.handleCancellation();
      this.widget.unmount();
    });

    // Handle errors
    this.widget.on('error', (event) => {
      console.error('Widget error:', event.detail.error);
      this.handleError(event.detail.error);
      this.widget.unmount();
    });
  }

  handleMethodSelected(via, selection) {
    if (via === 'external-account') {
      // This is an ExternalAccount (saved card)
      console.log('External account selected:', selection);
    } else if (via === 'deposit-method') {
      // This contains depositMethod and account
      console.log('Deposit method selected:', selection);

      const { depositMethod, account } = selection;
      console.log('Deposit method details:', depositMethod);
      console.log('Target account:', account);
    }
  }

  handleCancellation() {
    // Show cancellation message
    alert('Deposit method selection was cancelled');
  }

  handleError(error) {
    alert('An error occurred. Please try again later.');
  }

  handleInitializationError(error) {
    console.error('Initialization failed:', error);
    alert('Failed to load deposit methods. Please try again later.');
  }
}

// Usage
const depositSelector = new DepositMethodSelector();
depositSelector.initialize();

Next Steps

After the user selects a deposit method, the next steps depend on what type of method was chosen:

For External Accounts (Saved Cards)

  1. Collect deposit details - Collect remaining instructions from the user (e.g. amount, destination account)
  2. Create and authorize transaction - Use the selected external account to create the actual deposit transaction using the Authorize flow

For Account Deposit Methods (FPS)

The following steps are optional. Most applications can simply close the trading screen after the user receives the bank transfer details, as the deposit will be processed automatically by the banking system.
  1. Monitor for payment (Optional) - Set up monitoring to detect when the bank transfer is received and provide real-time feedback to users.
  2. Provide deposit confirmation (Optional) - Notify the user when the deposit has been successfully processed and credited to their account.

Additional Resources