# Express Checkout

Express checkout lets you collect shipping and billing addresses directly from Apple Pay, Google Pay, or PayPal wallet UIs — no separate address form needed.

Available on [`PaymentRequest`](https://docs.monei.com/monei-js/reference/.md#paymentrequest-component) (Apple Pay / Google Pay) and [`PayPal`](https://docs.monei.com/monei-js/reference/.md#paypal-component) components.

## Before you begin[​](#before-you-begin "Direct link to Before you begin")

* You'll need a MONEI account and your API keys (test or live). Find them in your [MONEI Dashboard](https://dashboard.monei.com/settings/api).
* Use your [test mode keys](https://docs.monei.com/testing/.md) for integration testing.
* Apple Pay requires [domain verification](https://docs.monei.com/payment-methods/apple-pay/.md#register-your-domain-with-apple-pay) in both development and production.
* Google Pay requires at least one configured [card processor](https://dashboard.monei.com/settings/payment-methods/card).
* PayPal requires a connected [PayPal business account](https://dashboard.monei.com/settings/payment-methods).

## How It Works[​](#how-it-works "Direct link to How It Works")

<!-- -->

1. The **browser** opens the wallet UI (Apple Pay, Google Pay, or PayPal) with your account ID and initial amount
2. The customer selects shipping/billing options — your callbacks update amounts dynamically
3. After approval, the **token and details** are sent to your backend
4. Your **backend** creates the payment with the token — no pre-created payment needed
5. MONEI sends the final payment status via webhook

## Express checkout with shipping[​](#express-checkout-with-shipping "Direct link to Express checkout with shipping")

With shipping, the wallet-authorized amount may differ from the initial amount when shipping costs change. This flow uses the **accountId** approach — no pre-created payment needed. The payment is created server-side after the customer confirms in the wallet UI.

### 1. Include monei.js (Client-side)[​](#1-include-moneijs-client-side "Direct link to 1. Include monei.js (Client-side)")

Add the script tag to the `head` of your HTML file.

checkout.html

```
<head>

  <script src="https://js.monei.com/v3/monei.js"></script>

</head>

<body>

  <!-- Use "payment_request" for Apple Pay / Google Pay, "paypal_container" for PayPal -->

  <div id="payment_request"></div>

  <div id="paypal_container"></div>

  <script src="client.js"></script>

</body>
```

### 2. Initialize Component with express checkout (Client-side)[​](#2-initialize-component-with-express-checkout-client-side "Direct link to 2. Initialize Component with express checkout (Client-side)")

* Apple Pay / Google Pay
* PayPal

client.js

```
const paymentRequest = monei.PaymentRequest({

  accountId: 'your_account_id',

  sessionId: 'unique_session_id',

  amount: 1099, // base amount in minor units (before shipping)

  currency: 'EUR',

  requestShipping: true,

  requestBilling: true,

  shippingOptions: [

    {id: 'standard', label: 'Standard Shipping', amount: 500},

    {id: 'express', label: 'Express Shipping', amount: 1200, description: '1-2 business days'}

  ],

  onShippingAddressChange(address) {

    // Called when the customer changes their shipping address.

    // address contains: country, city, state, zip (no street-level data mid-flow).

    // Return updated shipping options and/or amount based on the address.

    if (address.country !== 'ES') {

      return {

        shippingOptions: [{id: 'intl', label: 'International', amount: 2000}],

        amount: 3099 // product (1099) + intl shipping (2000)

      };

    }

    return {shippingOptions: [{id: 'standard', label: 'Standard', amount: 500}], amount: 1599};

  },

  onShippingOptionChange(option) {

    // Called when the customer selects a different shipping option.

    // Return updated total amount.

    return {amount: 1099 + option.amount};

  },

  async onSubmit(result) {

    // Called when the customer approves the payment in the wallet UI.

    // Create the payment server-side (see Step 3).

    await fetch('/create-payment', {

      method: 'POST',

      headers: {'Content-Type': 'application/json'},

      body: JSON.stringify({

        amount: result.finalAmount,

        currency: 'EUR',

        sessionId: 'unique_session_id',

        paymentToken: result.token,

        billingDetails: result.billingDetails,

        shippingDetails: result.shippingDetails

      })

    });

  },

  onError(error) {

    console.error(error);

  }

});



paymentRequest.render('#payment_request');
```

client.js

```
const paypal = monei.PayPal({

  accountId: 'your_account_id',

  sessionId: 'unique_session_id',

  amount: 1099,

  currency: 'EUR',

  requestShipping: true,

  shippingOptions: [

    {id: 'standard', label: 'Standard Shipping', amount: 500, selected: true},

    {id: 'express', label: 'Express Shipping', amount: 1200}

  ],

  onShippingAddressChange(address) {

    return {

      shippingOptions: [{id: 'standard', label: 'Standard', amount: 500, selected: true}],

      amount: 1599

    };

  },

  onShippingOptionChange(option) {

    return {amount: 1099 + option.amount};

  },

  async onSubmit(result) {

    // PayPal always returns payer info (name, email) in billingDetails.

    // Create the payment server-side (see Step 3).

    await fetch('/create-payment', {

      method: 'POST',

      headers: {'Content-Type': 'application/json'},

      body: JSON.stringify({

        amount: result.finalAmount,

        currency: 'EUR',

        sessionId: 'unique_session_id',

        paymentToken: result.token,

        billingDetails: result.billingDetails,

        shippingDetails: result.shippingDetails

      })

    });

  },

  onError(error) {

    console.error(error);

  }

});



paypal.render('#paypal_container');
```

note

PayPal always returns payer information (name, email) regardless of configuration — there is no separate `requestBilling` prop.

**Key options:**

* **accountId** `string`: Your MONEI account ID.
* **sessionId** `string`: A unique session identifier. Must match the `sessionId` passed when [creating the payment server-side](#3-create-payment-server-side).
* **amount** `number`: Initial amount in the smallest currency unit (e.g., 1099 = 10.99 EUR).
* **requestShipping** `boolean`: Enable shipping address collection in the wallet UI.
* **requestBilling** `boolean`: Enable billing address collection (PaymentRequest only — PayPal always returns billing info).
* **shippingOptions** [`ShippingOption[]`](https://docs.monei.com/monei-js/reference/.md#shippingoption): Available shipping options.

See [`PaymentRequest` options](https://docs.monei.com/monei-js/reference/.md#paymentrequest-options) and [`PayPal` options](https://docs.monei.com/monei-js/reference/.md#paypal-options) for the full list.

### 3. Create Payment (Server-side)[​](#3-create-payment-server-side "Direct link to 3. Create Payment (Server-side)")

When the customer confirms in the wallet UI, your `onSubmit` handler sends the result to your server. Create the payment passing the `paymentToken` — this confirms the payment automatically.

important

You must pass the same `sessionId` used to initialize the component on the frontend. This ensures the token-generating customer matches the paying customer.

* cURL
* Node.js
* PHP
* Python

POST https\://api.monei.com/v1/payments

```
curl --request POST 'https://api.monei.com/v1/payments' \

--header 'Authorization: YOUR_API_KEY' \

--header 'Content-Type: application/json' \

--data-raw '{

  "amount": 1599,

  "currency": "EUR",

  "orderId": "14379133960355",

  "description": "Test Shop - #14379133960355",

  "sessionId": "unique_session_id",

  "paymentToken": "token_from_client",

  "callbackUrl": "https://example.com/checkout/callback",

  "customer": {

    "email": "email@example.com"

  },

  "billingDetails": {},

  "shippingDetails": {}

}'
```

server.js

```
import {Monei} from '@monei-js/node-sdk';



const monei = new Monei('YOUR_API_KEY');



// In your /create-payment route handler:

app.post('/create-payment', async (req, res) => {

  const {amount, currency, sessionId, paymentToken, billingDetails, shippingDetails} = req.body;



  const payment = await monei.payments.create({

    amount,

    currency,

    orderId: '14379133960355',

    description: 'Test Shop - #14379133960355',

    sessionId,

    paymentToken,

    callbackUrl: 'https://example.com/checkout/callback',

    customer: {email: billingDetails?.email},

    billingDetails,

    shippingDetails

  });



  res.json({status: payment.status});

});
```

server.php

```
<?php

require_once 'vendor/autoload.php';



use Monei\Model\CreatePaymentRequest;

use Monei\MoneiClient;



$monei = new MoneiClient('YOUR_API_KEY');



$body = json_decode(file_get_contents('php://input'), true);



$payment = $monei->payments->create(

  new CreatePaymentRequest([

    'amount' => $body['amount'],

    'currency' => $body['currency'],

    'order_id' => '14379133960355',

    'description' => 'Test Shop - #14379133960355',

    'session_id' => $body['sessionId'],

    'payment_token' => $body['paymentToken'],

    'callback_url' => 'https://example.com/checkout/callback',

    'billing_details' => $body['billingDetails'],

    'shipping_details' => $body['shippingDetails']

  ])

);



echo json_encode(['status' => $payment->getStatus()]);

?>
```

server.py

```
import Monei

from Monei import CreatePaymentRequest



monei = Monei.MoneiClient(api_key="YOUR_API_KEY")



# In your /create-payment route handler:

payment = monei.payments.create(

    CreatePaymentRequest(

        amount=body["amount"],

        currency=body["currency"],

        order_id="14379133960355",

        description="Test Shop - #14379133960355",

        session_id=body["sessionId"],

        payment_token=body["paymentToken"],

        callback_url="https://example.com/checkout/callback",

        billing_details=body["billingDetails"],

        shipping_details=body["shippingDetails"]

    )

)
```

Check all available [request parameters](https://docs.monei.com/apis/rest/payments-create/.md).

### 4. Process Webhook Notification (Server-side)[​](#4-process-webhook-notification-server-side "Direct link to 4. Process Webhook Notification (Server-side)")

MONEI sends the final payment status via an asynchronous HTTP POST request to the `callbackUrl` you provided in Step 3. The request body contains the full [Payment object](https://docs.monei.com/apis/rest/schemas/payment/.md) in JSON format.

This webhook is the **only reliable way** to confirm the definitive payment outcome.

1. **Verify the `MONEI-Signature` header** to confirm the webhook genuinely came from MONEI. See the [Verify Signatures guide](https://docs.monei.com/guides/verify-signature/.md) for implementation details.
2. **Return a `200 OK` HTTP status code** immediately to acknowledge receipt.

If MONEI doesn't receive a `200 OK`, it will retry sending the webhook.

## Billing-only checkout[​](#billing-only-checkout "Direct link to Billing-only checkout")

If you only need the billing address (no shipping), the flow is simpler — use `paymentId` instead of `accountId` and confirm client-side with `confirmPayment`. Follow the same steps as [Build a custom checkout](https://docs.monei.com/integrations/build-custom-checkout/.md), but add `requestBilling: true` to collect the billing address from the wallet:

client.js

```
const paymentRequest = monei.PaymentRequest({

  paymentId: '{{payment_id}}',

  requestBilling: true,

  async onSubmit(result) {

    await monei.confirmPayment({

      paymentId: '{{payment_id}}',

      paymentToken: result.token,

      billingDetails: result.billingDetails

    });

  },

  onError(error) {

    console.error(error);

  }

});



paymentRequest.render('#payment_request');
```

## Type reference[​](#type-reference "Direct link to Type reference")

See the full type definitions in the [Reference](https://docs.monei.com/monei-js/reference/.md#type-reference):

* [`SubmitResult`](https://docs.monei.com/monei-js/reference/.md#submitresult) — returned by all `onSubmit` callbacks
* [`ShippingOption`](https://docs.monei.com/monei-js/reference/.md#shippingoption) — shipping option configuration
* [`ShippingAddressChangeResult`](https://docs.monei.com/monei-js/reference/.md#shippingaddresschangeresult) — return type for `onShippingAddressChange`
* [`ShippingOptionChangeResult`](https://docs.monei.com/monei-js/reference/.md#shippingoptionchangeresult) — return type for `onShippingOptionChange`
* [`BillingDetails`](https://docs.monei.com/monei-js/reference/.md#billingdetails) — billing/shipping details
* [`Address`](https://docs.monei.com/monei-js/reference/.md#address) — postal address

## Before you go live[​](#before-you-go-live "Direct link to Before you go live")

* Make sure you are using [live (production) mode](https://docs.monei.com/testing/.md) Account ID and API Key.
* Make sure you have the relevant [payment methods configured and enabled](https://dashboard.monei.com/settings/payment-methods) in live mode.
* For Apple Pay, verify your [production domain](https://docs.monei.com/payment-methods/apple-pay/.md#register-your-domain-with-apple-pay).
