The Authorize use case allows users to authorize card transactions in a secure manner. The Payment Widget handles the entire transaction process: it creates the transaction, redirects users to their bank’s confirmation page when 3D Secure authentication is required, and continuously monitors the transaction status until completion. Support for Apple Pay and Google Pay transactions is coming soon.

Integration

Here’s how you can leverage the Payment Widget SDK to perform transaction authorization flows:

Create Authorize session

Before initializing the Widget, you must first create a quote for a transaction (e.g. with the help of Select for Deposit or Select for Withdrawal), then create a payment session for the Authorize use case.

Create a Quote

First, create a transaction quote using the Create Quote endpoint. The following example will create a quote for a deposit transaction using an external account obtained from the result of the Select for Deposit use case:
curl -X POST https://api.enterprise.uphold.com/transactions/quote \
  -H "Authorization: Bearer <API_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "X-On-Behalf-Of: user <USER_ID>" \
  -d '{
    "denomination": {
      "amount": "50.00",
      "asset": "GBP",
      "target": "origin"
    },
    "destination": {
      "type": "account",
      "id": "79f52400-5c36-4ea0-94a8-00f406e0d905"
    },
    "origin": {
      "type": "external-account",
      "id": "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7"
    }
  }'

Create Authorize Session

Use the quote ID obtained in the previous step to create a payment session to Authorize:
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": "authorize",
    "data": {
      "quoteId": "<QUOTE_ID>"
    }
  }'

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 Authorize flow:
import { PaymentWidget } from '@uphold/enterprise-payment-widget-web-sdk';

const initializeAuthorizeWidget = async (transactionData) => {
  // Create the quote and session
  const session = await createAuthorizeSession(transactionData);

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

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

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

Handling the complete event

When the complete event is raised, you will receive the transaction alongside a trigger property containing the reason why the event was raised.
The complete event being raised does NOT guarantee that the transaction was completed successfully. You MUST check the transaction status and trigger reason to determine the actual outcome and handle the event correctly.
widget.on('complete', (event) => {
  const result = event.detail.value;
  console.log('Authorize flow completed:', result);

  // result contains transaction and trigger information
  const { transaction, trigger } = result;

  // ALWAYS check the trigger reason and transaction status
  handleAuthorizeComplete(transaction, trigger);

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

Understanding Trigger Reasons

The trigger.reason indicates why the complete event was fired:
const handleAuthorizeComplete = (transaction, trigger) => {
  console.log('Trigger reason:', trigger.reason);
  console.log('Transaction status:', transaction.status);

  if (trigger.reason === 'transaction-status-changed') {
    // Transaction status has changed - check if it succeeded or failed
    handleTransactionStatusChange(transaction);
  } else if (trigger.reason === 'max-retries-reached') {
    // Widget stopped polling - transaction may still be processing
    handleProcessingTransaction(transaction);
  }
};
When the trigger reason is transaction-status-changed, examine the transaction status:
const handleTransactionStatusChange = (transaction) => {
  switch (transaction.status) {
    case 'completed':
      // ✅ Transaction was successful
      handleTransactionSuccess(transaction);
      break;

    case 'failed':
      // ❌ Transaction failed
      handleTransactionFailure(transaction);
      break;

    default:
      break;
  }
};

const handleTransactionSuccess = (transaction) => {
  console.log('✅ Transaction completed successfully:', transaction.id);

  // Update UI, redirect to success page, etc.
  navigateToSuccessPage(transaction);
};

const handleTransactionFailure = (transaction) => {
  console.error('❌ Transaction failed:', transaction.id);

  // Present a specific error message (See next section)
};
When the transaction status is failed, you can obtain more information in the transaction’s statusDetails field. This will help you return a more specific message to the user:
const handleTransactionFailure = (transaction) => {
  switch(transaction.statusDetails.reason) {
    // Transaction amount outside boundaries
    case 'amount-limit-exceed':
      break;
    // Card declined by bank
    case 'card-declined-by-bank':
      break;
    // Card has expired
    case 'card-expired':
      break;
    // Card is permanently declined by bank
    case 'card-permanently-declined-by-bank':
      break;
    // Card was not authorized
    case 'card-unauthorized':
      break;
    // Card is not supported
    case 'card-unsupported':
      break;
    // The specified account does not have sufficient funds to carry the transaction
    case 'insufficient-funds':
      break;
    // Too many transactions in a short period of time
    case 'velocity':
      break;
    // Error not specified
    case 'unspecified-error':
      break;
    // Handle unmatched reason (when new reasons are added)
    default:
      break;
  }
};
When the trigger reason is max-retries-reached, the Widget has stopped polling but the transaction may still be processing. This occurs when the transaction requires more time than usual to complete and will reach a final state at a later time:
const handleProcessingTransaction = (transaction) => {
  console.log('⏰ Polling timeout reached for transaction:', transaction.id);

  // Transaction is still processing - continue monitoring
  showProcessingMessage('Your transaction is being processed. Check again later.');
};

Handling the cancel event

The cancel event is fired when the user cancels the authorization process.
widget.on('cancel', () => {
  console.log('User cancelled transaction authorization');

  // Handle cancellation
  handleAuthorizeCancelled();

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

const handleAuthorizeCancelled = () => {
  // Show a message to the user
  showMessage('Transaction authorization was cancelled');

  // Redirect back to the transaction setup page
  navigateBack();
};

Handling the error event

The error event is fired when an error occurs during the authorization process. An error can occur while:
  • Creating the transaction through the Create Transaction endpoint
  • Another unrecoverable situation like no networking connection or server is down
You can use the error’s code and details properties to distinguish between those kinds of errors.
widget.on('error', (event) => {
  const error = event.detail.error;
  console.error('Authorization Widget error:', error);

  // Handle the error
  handleAuthorizeError(error);

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

const handleAuthorizeError = (error) => {
  const { code, details } = error;

  switch(code) {
    // Quote has expired
    case 'entity_not_found':
      break;

    // No sufficient funds in the account
    case 'insufficient_balance':
      break;

    case 'operation_not_allowed':
      // Duplicate withdrawal in 2 minutes
      if(details.reasons.includes('duplicate-withdrawal')) {
      }
      // Card was unauthorized by bank
      else if(details.reasons.includes('card-unauthorized')) {
      }
      // An unknown error happened
      else if(details.reasons.includes('unspecified-error')) {
      }
      break;

    // User capability does not allow the operation
    case 'user_capability_failure':
      break;

    // Handle other reasons like no network connection or server is down
    default:
      break;
  }
};

Complete Implementation Example

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

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

  async authorize(transactionData) {
    try {
      // Create quote and session for authorization
      const session = await this.createAuthorizeSession(transactionData);

      // Initialize Widget
      this.widget = new PaymentWidget<'authorize'>(session.session, { debug: true });

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

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

  async createAuthorizeSession(transactionData) {
    // Create quote
    const quoteResponse = await fetch('https://api.enterprise.uphold.com/transactions/quote', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer <API_TOKEN>',
        'Content-Type': 'application/json',
        'X-On-Behalf-Of': 'user <USER_ID>'
      },
      body: JSON.stringify(transactionData)
    });

    if (!quoteResponse.ok) {
      throw new Error('Failed to create quote');
    }

    const quote = await quoteResponse.json();

    // Create authorize session
    const sessionResponse = 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: 'authorize',
        data: { quoteId: quote.id }
      })
    });

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

    const session = await sessionResponse.json();
    return session;
  }

  setupEventHandlers() {
    // Handle authorization completion
    this.widget.on('complete', (event) => {
      const { transaction, trigger } = event.detail.value;
      console.log('Authorize completed:', { transaction, trigger });

      this.handleAuthorizeComplete(transaction, trigger);
      this.widget.unmount();
    });

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

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

  handleAuthorizeComplete(transaction, trigger) {
    if (trigger.reason === 'transaction-status-changed') {
      this.handleTransactionStatusChange(transaction);
    } else if (trigger.reason === 'max-retries-reached') {
      this.handleProcessingTransaction(transaction);
    }
  }

  handleTransactionStatusChange(transaction) {
    switch (transaction.status) {
      case 'completed':
        // Redirect user to your app's transaction completed page
        window.location.href = `/transaction/success?id=${transaction.id}`;
        break;

      case 'failed':
        handleTransactionFailure(transaction);
        break;

      default:
        break;
    }
  }

  handleTransactionFailure(transaction) {
    switch(transaction.statusDetails.reason) {
      // Transaction amount outside boundaries
      case 'amount-limit-exceed':
        break;
      // Card declined by bank
      case 'card-declined-by-bank':
        break;
      // Card has expired
      case 'card-expired':
        break;
      // Card is permanently declined by bank
      case 'card-permanently-declined-by-bank':
        break;
      // Card was not authorized
      case 'card-unauthorized':
        break;
      // Card is not supported
      case 'card-unsupported':
        break;
      // The specified account does not have sufficient funds to carry the transaction
      case 'insufficient-funds':
        break;
      // Too many transactions in a short period of time
      case 'velocity':
        break;
      // Error not specified
      case 'unspecified-error':
        break;
      // Handle unmatched reason (when new reasons are added)
      default:
        break;
    }
  };

  handleProcessingTransaction(transaction) {
    alert('Your transaction is being processed. Please, check again later.');
    window.location.href = `/transaction/status?id=${transaction.id}`;
  }

  handleCancellation() {
    alert('Transaction authorization was cancelled');
    window.history.back();
  }

  handleError(error) {
    const { code, details } = error;

    switch(code) {
      // Quote has expired
      case 'entity_not_found':
        break;

      // No sufficient funds in the account
      case 'insufficient_balance':
        break;

      case 'operation_not_allowed':
        // Duplicate withdrawal in 2 minutes
        if(details.reasons.includes('duplicate-withdrawal')) {
        }
        // Card was unauthorized by bank
        else if(details.reasons.includes('card-unauthorized')) {
        }
        // An unknown error happened
        else if(details.reasons.includes('unspecified-error')) {
        }
        break;

      // User capability does not allow the operation
      case 'user_capability_failure':
        break;

      // Handle other reasons like no network connection or server is down
      default:
        break;
    }
  }

  handleInitializationError(error) {
    console.error('Authorization initialization failed:', error);
    alert('Failed to initialize transaction authorization. Please try again later.');
  }
}

// Usage
const authorizer = new TransactionAuthorizer();

// Example transaction data
const transactionData = {
  denomination: {
    amount: "100.00",
    asset: "USD",
    target: "origin"
  },
  destination: {
    type: "account",
    id: "79f52400-5c36-4ea0-94a8-00f406e0d905"
  },
  origin: {
    type: "external-account",
    id: "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7"
  }
};

authorizer.authorize(transactionData);

Additional Resources