# `paymentRequest()`

Creates a Payment Request button that enables customers to pay with Apple Pay or Google Pay using the W3C Payment Request API. The SDK automatically detects available payment methods based on the user's device and browser.

**Key features:**

- Automatic detection of Apple Pay/Google Pay availability
- Native payment sheet experience
- Built-in shipping address and options collection
- Customisable button styles
- Real-time shipping cost calculation

:::info
For a complete implementation guide with examples, see: [Accept payments via Apple Pay and Google Pay](/docs/guides/merchant/accept-payments/online-payments/apple-pay-google-pay/introduction)
:::

## Prerequisites

This payment method requires payments module initialisation. See: [Payments module initialisation](/docs/sdks/merchant-web-sdk/initialisation/payments-module)

## Type signature

```typescript
PaymentsInstance.paymentRequest(
  target: HTMLElement,
  options: PaymentRequestOptions
): PaymentRequestInstance

interface PaymentRequestOptions {
  amount: number
  currency: string
  createOrder: () => Promise<{ publicId: string }>
  onSuccess?: () => void
  onError?: (error: RevolutCheckoutError) => void
  onCancel?: () => void
  validate?: (payload: PaymentValidationPayload) => void | Promise<void>
  preferredPaymentMethod?: PaymentRequestPaymentMethod | PaymentRequestPaymentMethod[]
  buttonStyle?: ButtonStyle
  requestShipping?: boolean
  shippingOptions?: ShippingOption[]
  onShippingOptionChange?: (option: ShippingOption) => Promise<ShippingChangeResult>
  onShippingAddressChange?: (address: Address) => Promise<ShippingAddressChangeResult>
}

interface PaymentRequestInstance {
  render: () => Promise<void>
  canMakePayment: () => Promise<PaymentRequestPaymentMethod | null>
  destroy: () => void
}

type PaymentRequestPaymentMethod = 'applePay' | 'googlePay'
```

## Parameters

| Parameter | Description                                            | Type                                                          | Required |
| --------- | ------------------------------------------------------ | ------------------------------------------------------------- | -------- |
| `target`  | DOM element where the payment button should be mounted | `HTMLElement`                                                 | Yes      |
| `options` | Configuration object for the payment request           | [`PaymentRequestOptions`](#payment-request-options-interface) | Yes      |

### `PaymentRequestOptions` interface

| Parameter                 | Description                                                                                                                                                                                                             | Type                                                                          | Required |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -------- |
| `amount`                  | Payment amount in lowest currency denomination (e.g., cents)                                                                                                                                                            | `number`                                                                      | Yes      |
| `currency`                | [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code (uppercase). See: [Supported currencies](https://help.revolut.com/business/help/merchant-accounts/payments/in-which-currencies-can-i-accept-payments/) | `string`                                                                      | Yes      |
| `createOrder`             | Async function that calls your backend to [create an order](/docs/api/merchant#create-order) and returns the order token                                                                                                | `() => Promise<{publicId: string}>`                                           | Yes      |
| `onSuccess`               | Callback triggered when payment completes successfully                                                                                                                                                                  | `() => void`                                                                  | No       |
| `onError`                 | Callback triggered when payment fails. Receives [`RevolutCheckoutError`](/docs/sdks/merchant-web-sdk/types/revolut-checkout-error)                                                                                      | `(error: RevolutCheckoutError) => void`                                       | No       |
| `onCancel`                | Callback triggered when user cancels payment                                                                                                                                                                            | `() => void`                                                                  | No       |
| `validate`                | Async validation function executed before payment processing. Should throw error if validation fails                                                                                                                    | <code>(payload: PaymentValidationPayload) => void \| Promise&lt;void></code>  | No       |
| `preferredPaymentMethod`  | Preferred payment method or array of methods in order of preference                                                                                                                                                     | <code>'applePay' \| 'googlePay' \| Array&lt;'applePay' \| 'googlePay'></code> | No       |
| `buttonStyle`             | Button appearance customisation                                                                                                                                                                                         | [`ButtonStyle`](#buttonstyle)                                                 | No       |
| `requestShipping`         | Enable shipping address and delivery method collection                                                                                                                                                                  | `boolean` (default: `false`)                                                  | No       |
| `shippingOptions`         | Available shipping options for the customer                                                                                                                                                                             | [`ShippingOption[]`](#shippingoption)                                         | No       |
| `onShippingOptionChange`  | Callback triggered when customer selects a different shipping option                                                                                                                                                    | `(option: ShippingOption) => Promise<ShippingChangeResult>`                   | No       |
| `onShippingAddressChange` | Callback triggered when customer changes shipping address                                                                                                                                                               | `(address: Address) => Promise<ShippingAddressChangeResult>`                  | No       |

#### `ButtonStyle`

```typescript
type ButtonStyle = {
  action?: 'subscribe' | 'donate' | 'pay' | 'buy'
  size?: 'small' | 'large'
  radius?: 'none' | 'small' | 'large' | 'round'
  variant?: 'light' | 'dark' | 'light-outlined'
  height?: string
}
```

Customises the payment button appearance.

| Property  | Description                                                     | Type                                        | Default   |
| --------- | --------------------------------------------------------------- | ------------------------------------------- | --------- |
| `action`  | Button action text (e.g., "Buy with Apple Pay")                 | `'subscribe' \| 'donate' \| 'pay' \| 'buy'` | `null`    |
| `size`    | Button width: `'large'` fills container, `'small'` fits content | `'small' \| 'large'`                        | `'large'` |
| `radius`  | Border radius size                                              | `'none' \| 'small' \| 'large' \| 'round'`   | `'small'` |
| `variant` | Colour theme                                                    | `'light' \| 'dark' \| 'light-outlined'`     | `'dark'`  |
| `height`  | Custom height (CSS value, e.g., `'48px'`)                       | `string`                                    | -         |

#### `ShippingOption`

```typescript
interface ShippingOption {
  id: string
  label: string
  amount: number
  description?: string
}
```

Defines a shipping option available to the customer.

| Property      | Description                                       | Type     | Required |
| ------------- | ------------------------------------------------- | -------- | -------- |
| `id`          | Unique identifier for the shipping option         | `string` | Yes      |
| `label`       | Display name (e.g., "Standard Shipping")          | `string` | Yes      |
| `amount`      | Shipping cost in lowest currency denomination     | `number` | Yes      |
| `description` | Additional details (e.g., "Delivery in 5-7 days") | `string` | No       |

#### `PaymentValidationPayload`

```typescript
type PaymentValidationPayload = {
  email?: string
  shippingAddress?: W3CPaymentAddress
  billingAddress?: W3CPaymentAddress
}
```

Customer data passed to the `validate` callback for pre-payment validation.

#### `ShippingChangeResult`

```typescript
type ShippingChangeResult = {
  status: 'success' | 'fail'
  total: {
    amount: number
    label?: string
  }
}
```

Response from `onShippingOptionChange` callback with updated total.

#### `ShippingAddressChangeResult`

```typescript
type ShippingAddressChangeResult = {
  status: 'success' | 'fail'
  shippingOptions?: ShippingOption[]
  total: {
    amount: number
    label?: string
  }
}
```

Response from `onShippingAddressChange` callback with updated shipping options and total.

## Return value

```typescript
PaymentRequestInstance

interface PaymentRequestInstance {
  render: () => Promise<void>
  canMakePayment: () => Promise<PaymentRequestPaymentMethod | null>
  destroy: () => void
}
```

The method returns a `PaymentRequestInstance` object containing:

| Method           | Description                                                          | Type                                                             |
| ---------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `render`         | Render the payment button after confirming availability              | `() => Promise<void>`                                            |
| `canMakePayment` | Check if Apple Pay or Google Pay is available on this device/browser | <code>() => Promise&lt;'applePay' \| 'googlePay' \| null></code> |
| `destroy`        | Remove the button and clean up resources                             | `() => void`                                                     |

### Check payment method availability

Always call `canMakePayment()` before rendering the button:

```typescript
const instance = paymentRequest(target, options)
const method = await instance.canMakePayment()

if (method) {
  // Apple Pay or Google Pay is available
  instance.render()
} else {
  // No supported payment method - clean up
  instance.destroy()
}
```

## Callback events

The payment request provides callback functions for handling payment lifecycle events.

:::warning
Widget callbacks are not guaranteed to fire due to network issues, browser closures, or ad-blockers. Always use [webhooks](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks) for critical backend operations like order fulfilment.
:::

### `onSuccess`

```typescript
() => void
```

Triggered when the payment completes successfully.

**Use cases:**

- Display success message to the customer
- Redirect to order confirmation page
- Update UI to reflect successful payment

**Example:**

```typescript
onSuccess: () => {
  console.log('Payment successful!')
  window.location.href = '/confirmation'
}
```

### `onError`

```typescript
(error: RevolutCheckoutError) => void
```

Triggered when the payment fails. The `error` parameter is a [`RevolutCheckoutError`](/docs/sdks/merchant-web-sdk/types/revolut-checkout-error) object containing error details.

**Use cases:**

- Display error message to the customer
- Log error for debugging
- Re-enable checkout form

**Example:**

```typescript
onError: (error) => {
  console.error('Payment failed:', error.message)
  alert(`Payment failed: ${error.message}`)
}
```

### `onCancel`

```typescript
() => void
```

Triggered when the user cancels the payment (e.g., closes the payment sheet).

**Use cases:**

- Display cancellation message
- Re-enable checkout form
- Track abandonment analytics

**Example:**

```typescript
onCancel: () => {
  console.log('Payment cancelled')
  alert('Payment was cancelled. You can try again.')
}
```

## Usage examples

- ![With async/await]

  ```typescript
  import RevolutCheckout from '@revolut/checkout'

  // Initialise payments module
  const { paymentRequest } = await RevolutCheckout.payments({
    publicToken: process.env.REVOLUT_PUBLIC_KEY,
    mode: 'prod',
  })

  // Create payment request button
  const target = document.getElementById('payment-request')
  const instance = paymentRequest(target, {
    amount: 1000,
    currency: 'USD',

    createOrder: async () => {
      const response = await fetch('/api/create-order', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ amount: 1000, currency: 'USD' }),
      })
      const order = await response.json()
      return { publicId: order.token }
    },

    onSuccess: () => {
      console.log('Payment successful!')
      window.location.href = '/confirmation'
    },

    onError: (error) => {
      console.error('Payment failed:', error.message)
      alert(`Payment failed: ${error.message}`)
    },

    onCancel: () => {
      console.log('Payment cancelled')
    },
  })

  // Check availability and render
  const method = await instance.canMakePayment()
  if (method) {
    console.log(`${method} is available`)
    instance.render()
  } else {
    console.log('Apple Pay / Google Pay not available')
    instance.destroy()
  }
  ```

- ![Without async/await]

  ```typescript
  import RevolutCheckout from '@revolut/checkout'

  // Initialise payments module
  RevolutCheckout.payments({
    publicToken: process.env.REVOLUT_PUBLIC_KEY,
    mode: 'prod',
  }).then(({ paymentRequest }) => {
    // Create payment request button
    const target = document.getElementById('payment-request')
    const instance = paymentRequest(target, {
      amount: 1000,
      currency: 'USD',

      createOrder: () => {
        return fetch('/api/create-order', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ amount: 1000, currency: 'USD' }),
        })
          .then((response) => response.json())
          .then((order) => ({ publicId: order.token }))
      },

      onSuccess: () => {
        console.log('Payment successful!')
        window.location.href = '/confirmation'
      },

      onError: (error) => {
        console.error('Payment failed:', error.message)
        alert(`Payment failed: ${error.message}`)
      },

      onCancel: () => {
        console.log('Payment cancelled')
      },
    })

    // Check availability and render
    instance.canMakePayment().then((method) => {
      if (method) {
        console.log(`${method} is available`)
        instance.render()
      } else {
        console.log('Apple Pay / Google Pay not available')
        instance.destroy()
      }
    })
  })
  ```

### Custom button styling

Customise the payment button appearance to match your design:

```typescript
const instance = paymentRequest(target, {
  amount: 1000,
  currency: 'USD',
  createOrder: async () => {
    /* ... */
  },

  buttonStyle: {
    action: 'buy',
    size: 'large',
    radius: 'small',
    variant: 'dark',
    height: '48px',
  },

  onSuccess: () => {
    window.location.href = '/confirmation'
  },

  onError: (error) => {
    alert(`Payment failed: ${error.message}`)
  },
})
```

### With shipping options

Collect shipping address and offer delivery options:

```typescript
const shippingOptions = [
  {
    id: 'standard',
    label: 'Standard Shipping',
    amount: 500,
    description: 'Delivery in 5-7 business days',
  },
  {
    id: 'express',
    label: 'Express Shipping',
    amount: 1000,
    description: 'Delivery in 1-2 business days',
  },
]

const instance = paymentRequest(target, {
  amount: 5000,
  currency: 'USD',
  createOrder: async () => {
    /* ... */
  },

  requestShipping: true,
  shippingOptions,

  onShippingOptionChange: async (selectedOption) => {
    // Recalculate total with new shipping cost
    return {
      status: 'success',
      total: {
        amount: 5000 + selectedOption.amount,
        label: 'Total',
      },
    }
  },

  onShippingAddressChange: async (address) => {
    // Validate address and potentially update shipping options
    const isValidRegion = address.country === 'US'

    if (!isValidRegion) {
      return {
        status: 'fail',
        total: { amount: 5000 },
      }
    }

    // Offer express shipping for nearby regions
    const updatedOptions =
      address.region === 'CA'
        ? [
            ...shippingOptions,
            {
              id: 'same-day',
              label: 'Same-Day Delivery',
              amount: 2000,
              description: 'Delivery today',
            },
          ]
        : shippingOptions

    return {
      status: 'success',
      shippingOptions: updatedOptions,
      total: {
        amount: 5000 + shippingOptions[0].amount,
      },
    }
  },

  onSuccess: () => {
    window.location.href = '/confirmation'
  },

  onError: (error) => {
    alert(`Payment failed: ${error.message}`)
  },
})
```

### Pre-payment validation

Validate customer data before processing payment:

```typescript
const instance = paymentRequest(target, {
  amount: 1000,
  currency: 'USD',
  createOrder: async () => {
    /* ... */
  },

  validate: async (payload) => {
    // Validate email format
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    if (payload.email && !emailRegex.test(payload.email)) {
      throw new Error('Invalid email address')
    }

    // Validate shipping address with backend
    if (payload.shippingAddress) {
      const response = await fetch('/api/validate-address', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload.shippingAddress),
      })

      if (!response.ok) {
        throw new Error('Invalid shipping address')
      }
    }
  },

  onSuccess: () => {
    window.location.href = '/confirmation'
  },

  onError: (error) => {
    alert(`Payment failed: ${error.message}`)
  },
})
```

## See also

- [Accept payments via Apple Pay and Google Pay - Web](/docs/guides/merchant/accept-payments/online-payments/apple-pay-google-pay/web)
- [Payments module initialisation](/docs/sdks/merchant-web-sdk/initialisation/payments-module)
- [Address type reference](/docs/sdks/merchant-web-sdk/types/address)
- [RevolutCheckoutError type reference](/docs/sdks/merchant-web-sdk/types/revolut-checkout-error)
- [Use webhooks to keep track of the payment lifecycle](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks)