Skip to main content

Documentation Index

Fetch the complete documentation index at: https://developer.uphold.com/llms.txt

Use this file to discover all available pages before exploring further.

The Payment Widget handles card linking and selection for deposits. Users can add a new card or pick an existing one directly in the widget. If the resulting quote requires card authorization, a second widget session handles it and creates the transaction. If authorization is not required, your backend creates the transaction directly.
The Payment Widget handles card linking, selection, and authorization. When authorization is not required, your backend must create the transaction via the REST API.

Prerequisites

Walkthrough


Select a card account

The widget session lets the user pick an existing card or add a new one.

Create a payment session

Call Create payment session to start the select-for-deposit flow.
POST /widgets/payment/sessions
{
  "flow": "select-for-deposit"
}
{
  "session": {
    "flow": "select-for-deposit",
    "url": "https://payment.enterprise.uphold.com/",
    "token": "GEbRxBN...edjnXbL"
  }
}

Set up the widget

import { PaymentWidget } from '@uphold/enterprise-payment-widget-web-sdk';

const initializeDepositWidget = async (session) => {
  const widget = new PaymentWidget<'select-for-deposit'>(session, { debug: true });

  widget.on('complete', (event) => {
    console.log('Complete', JSON.stringify(event.detail.value));
  });

  widget.on('cancel', () => {
    console.log('Cancelled');
    widget.unmount();
  });

  widget.on('error', (event) => {
    console.error('Error', event.detail.error);
    widget.unmount();
  });

  widget.mountIframe(document.getElementById('payment-container'));
};
For native apps using a WebView, you’ll need a bridge for events as outlined in Installation & setup.

Handle the complete event

The complete event fires after the user selects a card. The event payload includes the selected card external account.
widget.on('complete', (event) => {
  const { via, selection } = event.detail.value;

  if (via === 'external-account') {
    // selection is the selected card external account
    // use selection.id as the origin in the quote
    handleCardSelected(selection);
  }

  widget.unmount();
});
Once you have the selected card, prompt the user to select a destination account, then create a quote.

Handle cancellations

widget.on('cancel', () => {
  widget.unmount();
  // Return the user to the previous screen
});

Handle errors

The error event fires for critical unrecoverable errors. Card-specific errors (duplicate card, country mismatch, card limits) are handled by the widget internally.
widget.on('error', (event) => {
  console.error('Widget error:', event.detail.error);
  widget.unmount();
  // Show a user-friendly error message
});
The Payment Widget handles most errors internally. For unrecoverable errors, the widget fires an error event. It is the host application’s responsibility to handle these events, present an error message to the user, and unmount the widget.

Select destination account

Card deposits can target any account. If the selected account is not in the card’s currency, the amount will be converted at settlement using Uphold’s prevailing rate. Make sure the destination asset has the necessary features enabled.

Find an existing account

Call List accounts to retrieve the user’s accounts and let them pick the one they want to fund.
GET /core/accounts
{
  "accounts": [
    {
      "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
      "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a",
      "label": "My GBP account",
      "asset": "GBP",
      "balance": {
        "total": "500.00",
        "available": "500.00"
      }
    }
  ]
}

Create a new account

If the user has no accounts, create one with Create account before proceeding.
POST /core/accounts
{
  "label": "My GBP account",
  "asset": "GBP"
}
{
  "account": {
    "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
    "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a",
    "label": "My GBP account",
    "asset": "GBP",
    "balance": {
      "total": "0",
      "available": "0"
    }
  }
}

Create a quote

Call Create quote with the selected card external account as origin and the destination account.
POST /core/transactions/quote
{
  "origin": {
    "type": "external-account",
    "id": "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7"
  },
  "destination": {
    "type": "account",
    "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8"
  },
  "denomination": {
    "asset": "GBP",
    "amount": "250.00",
    "target": "origin"
  }
}
Check the origin node in the response for confirmationUrl. If present, card authorization is required — see Handle quote requirements below.
{
  "quote": {
    "id": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0",
    "origin": {
      "amount": "250.00",
      "asset": "GBP",
      "node": {
        "type": "external-account",
        "id": "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      },
      "rate": "1"
    },
    "destination": {
      "amount": "250.00",
      "asset": "GBP",
      "node": {
        "type": "account",
        "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      },
      "rate": "1"
    },
    "denomination": {
      "asset": "GBP",
      "amount": "250.00",
      "target": "origin",
      "rate": "1"
    },
    "fees": [],
    "expiresAt": "2024-07-24T15:22:39Z"
  }
}
Quotes typically expire quickly. Prompt for user confirmation within the expiry window and requote if needed.

Handle quote requirements

If confirmationUrl is present on the origin node, card authorization is required. Use the Authorize widget flow — the widget creates the transaction, handles the authentication challenge, and polls until a terminal status is reached.

Create an authorize session

Call Create payment session with flow: "authorize" and the quoteId.
POST /widgets/payment/sessions
{
  "flow": "authorize",
  "data": {
    "quoteId": "<quoteId>"
  }
}
{
  "session": {
    "flow": "authorize",
    "url": "https://payment.enterprise.uphold.com/",
    "token": "GEbRxBN...edjnXbL"
  }
}

Set up the widget

Initialize the widget with the session. The widget creates the transaction, handles the 3DS redirect, and polls until a terminal status is reached.
import { PaymentWidget } from '@uphold/enterprise-payment-widget-web-sdk';

const initializeAuthorizeWidget = async (session) => {
  const widget = new PaymentWidget<'authorize'>(session, { debug: true });

  widget.on('complete', (event) => {
    const { transaction, trigger } = event.detail.value;
    console.log('Complete', transaction.status, trigger.reason);
    widget.unmount();
  });

  widget.on('cancel', () => {
    console.log('Cancelled');
    widget.unmount();
  });

  widget.on('error', (event) => {
    console.error('Error', event.detail.error);
    widget.unmount();
  });

  widget.mountIframe(document.getElementById('payment-container'));
};
For native apps using a WebView, you’ll need a bridge for events as outlined in Installation & setup.

Handle the complete event

The complete event does not guarantee success. Always check transaction.status and trigger.reason.
widget.on('complete', (event) => {
  const { transaction, trigger } = event.detail.value;

  if (trigger.reason === 'transaction-status-changed') {
    if (transaction.status === 'completed') {
      // Show success
    } else if (transaction.status === 'failed') {
      // Map transaction.statusDetails.reason to a user-facing message
    }
  } else if (trigger.reason === 'max-retries-reached') {
    // Widget stopped polling — continue monitoring via webhooks or polling
  }

  widget.unmount();
});
Failure reasons in transaction.statusDetails.reason:
ReasonDescription
card-declined-by-bankThe card was declined by the issuing bank
card-expiredThe card has expired
card-permanently-declined-by-bankThe card was permanently declined
card-unauthorizedThe card authorization was not completed
card-unsupportedThe card is not supported for this operation
insufficient-fundsThe origin account has insufficient funds
provider-maximum-limit-exceededThe transaction exceeds provider limits
velocityThe transaction was blocked by velocity rules

Handle cancellations

The cancel event fires when the user navigates back without completing authorization.
widget.on('cancel', () => {
  widget.unmount();
  // Return the user to the previous screen
});

Handle errors

The error event fires for critical unrecoverable errors. Card-specific errors (duplicate card, country mismatch, card limits) are handled by the widget internally.
widget.on('error', (event) => {
  const { code, details } = event.detail.error;
  console.error('Widget error:', code, details);
  widget.unmount();
  // Show a user-friendly error message
});
Error codes in event.detail.error.code:
CodeDescription
entity_not_foundThe quote was not found or has expired
insufficient_balanceThe origin account has insufficient balance
operation_not_allowedThe operation is not permitted (e.g. duplicate withdrawal, card unauthorized)
user_capability_failureThe user lacks the required capability for this operation
The Payment Widget handles most errors internally. For unrecoverable errors, the widget fires an error event. It is the host application’s responsibility to handle these events, present an error message to the user, and unmount the widget.

Complete implementation example

import { PaymentWidget } from '@uphold/enterprise-payment-widget-web-sdk';

const initializeAuthorizeWidget = async (session) => {
  const widget = new PaymentWidget<'authorize'>(session, { debug: true });

  widget.on('complete', (event) => {
    const { transaction, trigger } = event.detail.value;
    handleAuthorizeComplete(transaction, trigger);
    widget.unmount();
  });

  widget.on('cancel', () => {
    widget.unmount();
    // Return the user to the previous screen
  });

  widget.on('error', (event) => {
    const { code, details } = event.detail.error;
    handleAuthorizeError(code, details);
    widget.unmount();
  });

  widget.mountIframe(document.getElementById('payment-container'));
};

const handleAuthorizeComplete = (transaction, trigger) => {
  if (trigger.reason === 'transaction-status-changed') {
    if (transaction.status === 'completed') {
      // Show success — transaction is settled
    } else if (transaction.status === 'failed') {
      handleTransactionFailure(transaction.statusDetails.reason);
    }
  } else if (trigger.reason === 'max-retries-reached') {
    // Widget stopped polling — continue monitoring via webhooks or polling
  }
};

const handleTransactionFailure = (reason) => {
  switch (reason) {
    case 'card-declined-by-bank':
    case 'card-permanently-declined-by-bank':
      // Prompt the user to try a different card
      break;
    case 'card-expired':
      // Prompt the user to update their card
      break;
    case 'card-unauthorized':
      // 3DS authentication was not completed
      break;
    case 'card-unsupported':
      // Card type is not supported for this operation
      break;
    case 'insufficient-funds':
      // User does not have enough funds
      break;
    case 'provider-maximum-limit-exceeded':
    case 'velocity':
      // Transaction blocked by limits — inform the user
      break;
    default:
      // Unhandled reason — show a generic error message
      break;
  }
};

const handleAuthorizeError = (code, details) => {
  switch (code) {
    case 'entity_not_found':
      // Quote expired — prompt user to start over
      break;
    case 'insufficient_balance':
      // Origin account has insufficient balance
      break;
    case 'operation_not_allowed':
      // Operation not permitted (e.g. duplicate withdrawal, card unauthorized)
      break;
    case 'user_capability_failure':
      // User lacks the required capability
      break;
    default:
      // Unexpected error — show a generic error message
      break;
  }
};

Confirm and create transaction

If card authorization is not required, call Create transaction with the quote ID after the user confirms.
POST /core/transactions
{
  "quoteId": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0"
}
In a successful card deposit, the origin is the external-account representing the card and the destination is the user’s account.
{
  "transaction": {
    "id": "f5a6b7c8-3d4e-4f7a-b00c-9d8e7f6a5b4c",
    "origin": {
      "asset": "GBP",
      "amount": "250.00",
      "node": {
        "type": "external-account",
        "id": "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      }
    },
    "destination": {
      "asset": "GBP",
      "amount": "250.00",
      "node": {
        "type": "account",
        "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      }
    },
    "status": "completed",
    "quotedAt": "2025-01-10T11:02:39Z",
    "createdAt": "2025-01-10T11:12:39Z",
    "updatedAt": "2025-01-10T11:13:08Z",
    "denomination": {
      "asset": "GBP",
      "amount": "250.00",
      "target": "origin"
    }
  }
}

Monitor for settlement

Card deposit transactions may remain in processing while the payment settles. Monitor until the transaction reaches a terminal state.

Notify the user

Display an in-app confirmation when the transaction is completed, and send an email if applicable.
You now support card deposits via the Payment Widget.