# Implementation

Complete guide to integrating the Paze component into your application.

## Overview

The Paze component provides a fast, secure payment experience using the Paze digital wallet. The component follows a simple three-step lifecycle:

1. **Initialise:** Configure the SDK with your session and transaction data.
2. **Create and mount:** Build the Paze button with your configuration and render it to your page.
3. **Handle callbacks:** Respond to payment success, errors, and other events.


The component automatically handles Paze wallet presentment, checkout, token decryption, and transaction processing.

Backend verification is mandatory. Always verify payments on your backend before fulfilling orders. Frontend callbacks can be manipulated by malicious users.

## Before you start

Complete [Paze onboarding](/guides/checkout/components/web/paze/onboarding) in the Unity Portal before integrating the component. You will need:

* Paze enabled at merchant group and site level, with your Paze client ID saved in **Paze Account Settings**
* PXP API credentials (client ID and authentication token) for session creation on your backend
* HTTPS on your site (required for Paze; `localhost` is exempt for development)
* A Paze merchant account linked to the Unity Portal


Set the SDK `environment` to `test` for UAT or `live` for production. The Web SDK loads the Paze Digital Wallet SDK for you; the script URL depends on your environment:

| Environment | Paze SDK URL |
|  --- | --- |
| `test` | `https://checkout.wallet.uat.earlywarning.io/web/resources/js/digitalwallet-sdk.js` |
| `live` | `https://checkout.paze.com/web/resources/js/digitalwallet-sdk.js` |


Paze also requires `USD` currency and a card intent in your session and SDK config. We also recommend an `onGetShopper` callback on SDK initialisation. These are covered in the steps below.

If portal or session setup fails, see [Portal and session setup](/guides/checkout/components/web/paze/troubleshooting#portal-and-session-setup).

### Browser and device compatibility

Paze for Web has specific requirements for optimal functionality.

#### Supported browsers

- Chrome 80+ on all platforms
- Safari 13.1+ on macOS and iOS
- Firefox 75+ on all platforms
- Edge 80+ on Windows and macOS


#### Device requirements

- Android devices: Android 7.0 (Nougat) or later
- iOS devices: iOS 13.1 or later
- Desktop: Windows 10+, macOS 10.14+, or Linux


#### Configuration requirements

- The customer must have a Paze wallet account with saved payment methods.
- The customer's email address must match their Paze wallet registration.


## Step 1: Install the Web SDK library

Install the latest version of the Web SDK from the npm public registry. You'll need to have Node.js 22.x or higher.

```shell
npm i @pxpio/web-components-sdk
```

The Paze component is part of the main SDK package. Import `PxpCheckout` directly from `@pxpio/web-components-sdk`.

## Step 2: Create a session on your backend

The Paze component requires a session from the PXP Sessions API. This must be done on your backend using HMAC authentication to keep your credentials secure.

### Understanding HMAC authentication

Our platform uses HMAC (Hash-based Message Authentication Code) with SHA256 for authentication to ensure secure communication and data integrity. This method involves creating a signature by hashing your request data with a secret key, which must then be included in the HTTP headers of your API request.

### Create an HMAC signature

To create the HMAC signature, you need to prepare a string that includes four parts concatenated together:

- **Timestamp**: The current time in Unix seconds (UTC) (e.g., `1754701373`)
- **Request ID**: A unique GUID for this request (e.g., `ce244054-b372-42c2-9102-f0d976db69f6`)
- **Request path**: The API endpoint path: `api/v1/sessions`
- **Request body**: The complete JSON request body as a minified string


Example request body to minify:

```json
{
  "merchant": "MERCHANT-1",
  "site": "SITE-1",
  "sessionTimeout": 120,
  "merchantTransactionId": "txn-123",
  "transactionMethod": {
    "intent": {
      "card": "Authorisation"
    }
  },
  "amounts": {
    "currencyCode": "USD",
    "transactionValue": 99.99
  },
  "allowTransaction": true
}
```

When creating the HMAC signature, the request body must be minified (no whitespace or formatting). The formatted JSON above is for readability only. Paze requires `currencyCode` to be `USD`.

### Session request parameters

The session request accepts the following parameters:

| Parameter | Description |
|  --- | --- |
| `merchant`string (≤ 20 characters) | Your unique merchant identifier, as assigned by PXP. You can find it in the Unity Portal, by going to **Merchant setup > Merchants** and checking the *Merchant ID* column. |
| `site`string (≤ 20 characters) | Your unique site identifier, as assigned by PXP. You can find it in the Unity Portal, by going to **Merchant setup > Sites** and checking the *Site ID* column. |
| `merchantTransactionId`string (≤ 50 characters) | A unique identifier of your choice that represents this transaction. |
| `sessionTimeout`number | The duration of the session, in minutes. |
| `transactionMethod`object | Details about the transaction method, including the intent for each payment type. |
| `transactionMethod.intent`object | The transaction intent for each payment method. |
| `transactionMethod.intent.card`string | The intent for Paze transactions.Possible values:`Authorisation``Purchase``Verification``EstimatedAuthorisation``Payout` |
| `amounts`object | Details about the transaction amount. |
| `amounts.currencyCode`string (3 characters) | The currency code associated with the transaction, in ISO 4217 format. Paze supports `USD` only. |
| `amounts.transactionValue`number | The transaction amount. The numbers after the decimal will be zero padded if they are less than the expected `currencyCode` exponent. For example, USD 1 = USD 1.00. The transaction will be rejected if numbers after the decimal are greater than the expected `currencyCode` exponent (e.g., USD 1.234). |
| `allowTransaction`boolean | Whether or not to proceed with the transaction. |


### Prepare and send your request

The HMAC signature requires combining your timestamp, request ID, request path, and request body. Here's the format:

**String to hash**: `{timestamp}{requestId}{requestPath}{requestBody}`

For example:

```
1754701373ce244054-b372-42c2-9102-f0d976db69f6api/v1/sessions{"merchant":"MERCHANT-1","site":"SITE-1","sessionTimeout":120,"merchantTransactionId":"txn-123","transactionMethod":{"intent":{"card":"Authorisation"}},"amounts":{"currencyCode":"USD","transactionValue":99.99},"allowTransaction":true}
```

Use your **token value** as the secret key to create an HMAC SHA256 hash of this string. The result will be a hex-encoded signature like:

```
1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6
```

You can now put together your `Authorization` header. It follows this format: `PXP-UST1 {tokenId}:{timestamp}:{signature}`. For example:

```
PXP-UST1 9aac6071-38d0-4545-9d2f-15b936af6d7f:1754701373:1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6
```

Lastly, send your request to the Sessions API. You'll need to add a request ID of your choice and include your client ID, which you can find in the Unity Portal.

Here's a full example of what your request might look like:

```curl
curl -i -X POST \
  'https://api-services.dev.pxp.io/api/v1/sessions' \
  -H 'Authorization: PXP-UST1 9aac6071-38d0-4545-9d2f-15b936af6d7f:1754701373:1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6' \
  -H 'X-Request-Id: ce244054-b372-42c2-9102-f0d976db69f6' \
  -H 'X-Client-Id: f47ac10b-58cc-4372-a567-0e02b2c3d479' \
  -H 'Content-Type: application/json' \
  -d '{
  "merchant": "MERCHANT-1",
  "site": "SITE-1",
  "sessionTimeout": 120,
  "merchantTransactionId": "txn-123",
  "transactionMethod": {
    "intent": {
      "card": "Authorisation"
    }
  },
  "amounts": {
    "currencyCode": "USD",
    "transactionValue": 99.99
  },
  "allowTransaction": true
}'
```

### Session response

If your request is successful, you'll receive a `200` response containing the session data. When Paze is enabled for your site, the response includes Paze configuration:

```json
{
  "sessionId": "c5f0799b-0839-43ce-abc5-5b462a98f250",
  "hmacKey": "904bc42395d4af634e2fd48ee8c2c7f52955a1da97a3aa3d82957ff12980a7bb",
  "encryptionKey": "20d175a669ad3f8c195c9c283fc86155",
  "sessionExpiry": "2025-05-19T13:39:20.3843454Z",
  "allowedFundingTypes": {
    "cardSchemes": ["Visa", "Mastercard", "AmericanExpress"],
    "cards": [],
    "wallets": {
      "paze": {
        "clientId": "your-paze-client-id",
        "profileId": "optional-profile-id",
        "merchantCategoryCode": "category-code"
      }
    }
  }
}
```

Return the session response to your frontend together with the `merchantTransactionId` you sent in the session request. Your frontend must pass the same value to `transactionData.merchantTransactionId` when initialising the SDK.

The Paze configuration is automatically included in the session response when Paze is configured for your site in the Unity Portal. See [Onboarding](/guides/checkout/components/web/paze/onboarding) for portal setup steps.

## Step 3: Initialise the SDK on your frontend

Import `PxpCheckout` from the SDK and initialise with your configuration.

```typescript
import { PxpCheckout, IntentType } from '@pxpio/web-components-sdk';

// Get session data from your backend
const sessionData = await fetch('/api/sessions', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
}).then(response => response.json());

// Initialise the SDK
const merchantTransactionId = sessionData.merchantTransactionId ?? crypto.randomUUID();

const pxpSdk = PxpCheckout.initialize({
  environment: 'test',
  session: sessionData,
  ownerId: 'MERCHANT-1',
  ownerType: 'MerchantGroup',
  clientName: 'Your Merchant',
  siteName: 'Your Store',
  transactionData: {
    currency: 'USD',
    amount: 99.99,
    entryType: 'Ecom',
    intent: {
      card: IntentType.Authorisation
    },
    merchantTransactionId,
    merchantTransactionDate: () => new Date().toISOString()
  },
  kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
  onGetShopper: () => Promise.resolve({ 
    id: 'shopper-123',
    email: 'customer@example.com',
    firstName: 'John',
    lastName: 'Doe'
  })
});
```

Use the same `merchantTransactionId` in your session request and in `transactionData.merchantTransactionId`. If these values differ, reconciliation, callbacks, and transaction lookups will not align.

## Step 4: Create and configure the Paze button

Use the SDK's `create()` method to build the Paze button with your desired configuration:

```typescript
import { PxpCheckout, IntentType, PazeCompleteResult } from '@pxpio/web-components-sdk';

const pazeButton = pxpSdk.create('paze-button', {
  buttonRenderMode: 'static',
  emailAddress: 'customer@example.com',
  phoneNumber: '15551234567',
  style: {
    color: 'pazeblue',
    shape: 'pill',
    label: 'check out with'
  },
  onPresentmentResolved: (isVisible) => {
    console.log('Paze button visible:', isVisible);
  },
  onCheckoutComplete: async (result) => {
    console.log('Paze checkout complete:', result);
    return true;
  },
  onComplete: async (result: PazeCompleteResult) => {
    console.log('Paze complete success', result);
  },
  onPreDecryption: async () => true,
  onPostDecryption: async (decryptedPayload) => {
    console.log('Decryption complete:', decryptedPayload.fundingData.cardNetwork);
  },
  onPostAuthorisation: async (data) => {
    // CRITICAL: Verify on backend before fulfilling order
    await verifyPaymentOnBackend(data.systemTransactionId);
  },
  onError: (error) => {
    console.error('Paze payment failed:', error);
  }
});
```

### Configuration options

| Parameter | Description |
|  --- | --- |
| `buttonRenderMode`enum | Controls button presentment. Defaults to `"static"`.Possible values:`"static"``"dynamic"` |
| `emailAddress`string | Optional shopper email address for identity and presentment. Required when `buttonRenderMode` is `"dynamic"`. |
| `phoneNumber`string | Optional  phone number in US E.164 format without `+`. Required when `buttonRenderMode` is `"dynamic"`. |
| `paymentDescription`string | A statement descriptor forwarded to Paze. Maximum 25 characters. |
| `billingPreference`enum | Billing address collection in the Paze popup. Defaults to `"ALL"`.Possible values:`"ALL"``"ZIP_COUNTRY"``"NONE"` |
| `confirmLaunch`boolean | When `true`, Paze may show a confirmation popup before checkout. |
| `sessionId`string | Optional session ID echoed back by Paze. Defaults to the SDK session ID. |
| `acceptedShippingCountries`string[] | The list of accepted shipping countries, in ISO 3166-1 alpha-2 format. |
| `acceptedPaymentCardNetworks`string[] | Accepted card networks. Defaults to `VISA`, `MASTERCARD`, and `DISCOVER`. |
| `cobrand`PazeCobrandConfig[] | Optional cobrand entries for partner branding in the Paze wallet. |
| `enhancedTransactionData`object | Optional checkout metadata forwarded to Paze. See [Configuration](/guides/checkout/components/web/paze/configuration#enhanced-transaction-data). |
| `style.color`enum | The button colour theme.Possible values:`white``whitewithoutline``midnightblack``pazeblue` |
| `style.shape`enum | The button shape.Possible values:`default``rectangle``pill` |
| `style.label`enum | The button label text.Possible values:`checkout``check out with``Donate with` |
| `style.disableMaxHeight`boolean | When `true`, disables the native max-height so the button fills its container. |
| `performAVS`boolean | When `true`, includes billing address from decrypted payload in AVS fields when the SDK submits the transaction. Defaults to `false`. Not used when `onPreDecryption` returns `false`. |
| `onCustomValidation`() => Promise<boolean> | Called before checkout opens. Return `false` to prevent checkout. |


For the full list of component callbacks, see [Events](/guides/checkout/components/web/paze/events). For additional configuration examples, see [Configuration](/guides/checkout/components/web/paze/configuration).

## Step 5: Mount the button to your page

Add a container element to your page where the Paze button will be rendered:

```html
<div id="paze-container"></div>
```

Then call the `mount()` method to render the button:

```typescript
pazeButton.mount('paze-container');
```

## Step 6: Unmount the component

When your component unmounts (e.g., when navigating away or cleaning up), call the `unmount()` method:

```typescript
return () => {
  pazeButton.unmount();
};
```

## Step 7: Handle callbacks

Most Paze callbacks are optional. Configure them on the `paze-button` component to customise presentment, checkout approval, decryption, and authorisation handling.

We recommend implementing `onGetShopper` on `PxpCheckout.initialize()` (not on the button component). The SDK calls it when building the transaction request after decryption. The callback is optional in code, but most integrations need shopper data for authorisation:

```typescript
const pxpSdk = PxpCheckout.initialize({
  // ... session and transactionData
  onGetShopper: () => Promise.resolve({
    id: 'shopper-123',
    email: 'customer@example.com',
    firstName: 'John',
    lastName: 'Doe'
  })
});
```

If you implement `onCheckoutComplete`, return `true` to approve checkout and proceed to `complete()`, decryption, and authorisation. Return `false` to cancel.

For all component callbacks, including `onPresentmentResolved`, `onCheckoutComplete`, `onPreDecryption`, `onPostDecryption`, and `onPostAuthorisation`, see [Events](/guides/checkout/components/web/paze/events).

Never trust frontend callbacks for order fulfilment. Always verify payments on your backend using webhooks or the [Get transaction details API](/apis/transaction/other/get-transaction-details) before fulfilling orders.

## Step 8: Backend verification (critical)

Frontend callbacks can be manipulated by malicious users. You must verify all payments on your backend before fulfilling orders.

Use the same webhook and API verification patterns described in the [card implementation guide](/guides/checkout/components/web/card/implementation#step-8-backend-verification-critical).

## Understanding the Paze payment flow

The Paze payment flow consists of several stages:

1. **Presentment:** The SDK checks if the customer has a Paze wallet for the provided email address.
2. **Button click:** The customer clicks the Paze button to initiate payment.
3. **Checkout:** The customer completes checkout within the Paze wallet.
4. **Decryption:** The Paze token is decrypted (SDK-managed or manual).
5. **Authorisation:** The payment is authorised with the decrypted card data.


```typescript
// Example showing all stages
const pazeButton = pxpSdk.create('paze-button', {
  buttonRenderMode: 'static',
  emailAddress: 'customer@example.com',
  
  onPresentmentResolved: (isVisible) => {
    console.log('Stage 1: Presentment resolved, visible:', isVisible);
  },
  
  onPazeButtonClicked: () => {
    console.log('Stage 2: Paze button clicked');
    trackEvent('paze_button_clicked');
  },
  
  onCheckoutComplete: async (result) => {
    console.log('Stage 3: Checkout complete');
    return true;
  },
  
  onPreDecryption: async () => {
    console.log('Stage 4a: Starting decryption');
    return true;
  },
  
  onPostDecryption: async (decryptedPayload) => {
    console.log('Stage 4b: Decryption complete');
  },
  
  onPreAuthorisation: async () => {
    console.log('Stage 5a: Preparing authorisation');
    return {
      riskScreeningData: {
        performRiskScreening: true,
        userIp: '192.168.1.100'
      }
    };
  },
  
  onPostAuthorisation: async (data) => {
    console.log('Stage 5b: Payment authorised:', data.systemTransactionId);
    await verifyPaymentOnBackend(data.systemTransactionId);
  }
});
```

## Backend decryption

When you return `false` from `onPreDecryption`, the SDK skips built-in decryption and authorisation. Your backend must decrypt the secured payload and submit the scheme token transaction.

### 1. Configure the frontend

Return `false` from `onPreDecryption` and forward the secured payload to your backend in `onComplete`:

```typescript
const pazeButton = pxpSdk.create('paze-button', {
  onCheckoutComplete: async () => true,

  onPreDecryption: async () => false,

  onComplete: async (result) => {
    const securedPayload = result.completeDecodedResponse?.securedPayload;

    await fetch('/api/paze/decrypt', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ securedPayload })
    });
  }
});
```

The `onComplete` callback receives a `PazeCompleteResult` like this:

```json
{
  "completeResponse": "eyJhdWQiOi...",
  "completeDecodedResponse": {
    "payloadId": "5C9190F52C004d7cf1a2-d4a0-4d4d-0463-15f90ad0d801",
    "sessionId": "3247d172-1232-4175-9fa2-eabbec62950d",
    "securedPayload": "eyJhdWQHBmaW..."
  }
}
```

Pass `completeDecodedResponse.securedPayload` to your backend. Never call the PXP decrypt API directly from frontend code.

### 2. Decrypt the secured payload

From your backend, call the PXP decrypt-token endpoint using HMAC authentication. Use the same HMAC pattern as [session creation](#step-3-create-a-session-on-your-backend).

This is the merchant backend endpoint (PXP-UST1). When `onPreDecryption` returns `true` or is omitted, the Web SDK decrypts internally using `POST /api/v1/paze/decrypt-token` with `{ "securedPayload": "..." }` and session authentication. Use the wallets endpoint below only for manual backend decryption.

| Environment | Endpoint |
|  --- | --- |
| Test | `POST https://api-services.dev.pxp.io/api/v1/wallets/Paze/decrypt-token` |
| Live | `POST https://api-services.pxp.io/api/v1/wallets/Paze/decrypt-token` |


**Headers:**

* `Content-Type: application/json`
* `Authorization: PXP-UST1 <tokenId>:<timestamp>:<hmac>`
* `X-Request-Id: <uuid>`
* `X-Client-Id: <clientId>`


**Request body:**

```json
{
  "site": "your-site-id",
  "token": "eyJhdWQHBmaW..."
}
```

The `token` value is the `securedPayload` from `onComplete`.

Here's a sample response:

```json
{
  "shopper": {
    "firstName": "Test",
    "lastName": "Wallet",
    "fullName": "Test Wallet",
    "emailAddress": "customer@example.com",
    "phoneCountryCode": "1",
    "phoneNumber": "6264645283",
    "countryCode": "US",
    "languageCode": "EN"
  },
  "billingAddress": {
    "addressLine1": null,
    "city": null,
    "state": null,
    "countryCode": "US",
    "postalCode": "67051"
  },
  "fundingData": {
    "networkToken": "<networkToken>",
    "expirationMonth": "08",
    "expirationYear": "2032",
    "accountReference": "V0010013023181487240991060052",
    "cardNetwork": "VISA",
    "tokenUsageType": "PURCHASE",
    "dynamicDataExpiration": "1781938231",
    "eci": "07",
    "schemeTokenCryptogram": "<cryptogram>"
  }
}
```

### 3. Submit the scheme token transaction

Use the decrypted `fundingData` to submit an authorisation through the PXP [Create transaction API](/apis/transaction) (`POST /api/v1/transactions`) from your backend. Authenticate with the same PXP-UST1 HMAC pattern as session creation.

#### Cryptogram and ECI handling

| Decrypt path | Where cryptogram / ECI go |
|  --- | --- |
| **SDK-managed** (`/api/v1/paze/decrypt-token`) | The gateway stores `schemeTokenCryptogram` and ECI in the **session** before the SDK submits the transaction. You do not pass them in the frontend transaction request. |
| **Manual backend** (`/api/v1/wallets/Paze/decrypt-token`) | Map fields from the decrypt response into your transaction request: `fundingData.schemeTokenCryptogram` → `fundingData.card.schemeTokenCryptogram`, and `fundingData.eci` → `threeDSecureData.electronicCommerceIndicator` when required by your integration. |


#### Sample transaction request

Map decrypted Paze token data to a scheme-token card authorisation. Align `merchantTransactionId` with the value used at session creation and in SDK `transactionData`:

```json
{
  "merchant": "MERCHANT-1",
  "site": "SITE-1",
  "merchantTransactionId": "txn-123",
  "merchantTransactionDate": "2025-06-17T10:30:00.000Z",
  "transactionMethod": {
    "intent": "Authorisation",
    "entryType": "Ecom",
    "fundingType": "Card"
  },
  "fundingData": {
    "card": {
      "schemeTokenNumber": "<networkToken from decrypt response>",
      "expiryMonth": "08",
      "expiryYear": "2032",
      "walletType": "Paze",
      "schemeTokenCryptogram": "<schemeTokenCryptogram from decrypt response>"
    }
  },
  "amounts": {
    "transaction": 99.99,
    "currencyCode": "USD"
  },
  "threeDSecureData": {
    "electronicCommerceIndicator": "07"
  },
  "shopper": {
    "id": "shopper-123",
    "email": "customer@example.com",
    "firstName": "John",
    "lastName": "Doe"
  },
  "addressVerification": {
    "countryCode": "US",
    "houseNumberOrName": "123 Main St",
    "postalCode": "67051"
  }
}
```

Include `addressVerification` only when you perform AVS on the backend. Populate `shopper` from your customer records or from the decrypt response `shopper` object.

After submission, verify the transaction on your backend before fulfilling the order. See [Backend verification](#step-8-backend-verification-critical).

When using manual decryption, you are responsible for securely calling the decrypt API and submitting the transaction from your backend. The SDK doesn't proceed with authorisation.

## Manual decryption example

End-to-end frontend example that delegates decryption and payment submission to your backend:

```typescript
const pazeButton = pxpSdk.create('paze-button', {
  buttonRenderMode: 'static',
  emailAddress: 'customer@example.com',

  onCheckoutComplete: async () => true,

  onPreDecryption: async () => false,

  onComplete: async (result) => {
    const securedPayload = result.completeDecodedResponse?.securedPayload;

    try {
      const paymentResult = await fetch('/api/paze/decrypt-and-pay', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ securedPayload })
      }).then(res => res.json());

      if (paymentResult.success) {
        window.location.href = '/order-confirmation';
      } else {
        showError('Unable to complete payment');
      }
    } catch (error) {
      console.error('Manual decryption failed:', error);
      showError('Unable to complete payment');
    }
  }
});
```

Your `/api/paze/decrypt-and-pay` backend route should call the [decrypt-token endpoint](#2-decrypt-the-secured-payload), then submit the [scheme token transaction](#3-submit-the-scheme-token-transaction) using the decrypted `fundingData`.

## Complete example

Here's a complete example showing Paze integration in a React component:

```typescript
import { PxpCheckout, IntentType } from '@pxpio/web-components-sdk';
import { useEffect, useState } from 'react';

export default function PazePage() {
  const [sessionData, setSessionData] = useState<any>(null);
  const [customerEmail, setCustomerEmail] = useState('customer@example.com');

  const createSession = async () => {
    const merchantTransactionId = crypto.randomUUID();
    const createdSession = await fetch('/api/sessions', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        merchant: "MERCHANT-1",
        site: "SITE-1",
        sessionTimeout: 120,
        merchantTransactionId,
        transactionMethod: {
          intent: {
            card: "Authorisation"
          }
        },
        amounts: {
          currencyCode: "USD",
          transactionValue: 99.99
        },
        allowTransaction: true
      })
    }).then((response) => response.json());
    setSessionData({ ...createdSession, merchantTransactionId });
  };

  useEffect(() => {
    createSession();
  }, []);

  useEffect(() => {
    if (!sessionData) {
      return;
    }

    const pxpSdk = PxpCheckout.initialize({
      environment: 'test',
      session: sessionData,
      ownerId: 'MERCHANT_GROUP_1',
      ownerType: 'MerchantGroup',
      clientName: 'Your Merchant',
      siteName: 'Your Store',
      transactionData: {
        currency: 'USD',
        amount: 99.99,
        entryType: 'Ecom',
        intent: {
          card: IntentType.Authorisation,
        },
        merchantTransactionId: sessionData.merchantTransactionId,
        merchantTransactionDate: () => new Date().toISOString(),
      },
      kountDisabled: false,
      onGetShopper: async () => {
        return {
          id: 'shopper-123',
          email: customerEmail,
          firstName: 'John',
          lastName: 'Doe',
        };
      },
    });

    const pazeButton = pxpSdk.create('paze-button', {
      buttonRenderMode: 'dynamic',
      emailAddress: customerEmail,
      phoneNumber: '15551234567',
      billingPreference: 'ALL',
      acceptedPaymentCardNetworks: ['VISA', 'MASTERCARD'],
      performAVS: true,
      style: {
        color: 'pazeblue',
        shape: 'pill',
        label: 'check out with'
      },
      onInit: () => {
        console.log('Paze component initialised');
      },
      onPresentmentResolved: (isVisible) => {
        console.log('Paze button visible:', isVisible);
        if (!isVisible) {
          console.log('Shopper not eligible for Paze');
        }
      },
      onPazeButtonClicked: () => {
        console.log('Paze button clicked');
        trackEvent('paze_button_clicked');
      },
      onCheckoutComplete: async (result) => {
        console.log('Paze checkout complete');
        return true;
      },
      onCheckoutIncomplete: async (result) => {
        console.log('Paze checkout cancelled:', result.reason);
      },
      onPreDecryption: async () => true,
      onPostDecryption: async (decryptedPayload) => {
        console.log('Token decrypted:', decryptedPayload.fundingData.cardNetwork);
      },
      onPreAuthorisation: async () => ({
        riskScreeningData: {
          performRiskScreening: true,
          userIp: '192.168.1.100',
          account: {
            id: 'shopper-123',
            creationDateTime: '2024-01-15T10:30:00.000Z'
          }
        }
      }),
      onPostAuthorisation: async (data) => {
        if (data.state === 'Authorised') {
          const verified = await fetch('/api/verify-payment', {
            method: 'POST',
            body: JSON.stringify({
              systemTransactionId: data.systemTransactionId,
              merchantTransactionId: data.merchantTransactionId
            })
          }).then(r => r.json());
          
          if (verified.success) {
            window.location.href = `/success?orderId=${verified.orderId}`;
          }
        } else {
          console.error('Payment not authorised:', data.state);
        }
      },
      onSubmitError: async (submitError) => {
        console.error('Payment submission error:', submitError);
      },
      onError: (error) => {
        console.error('Paze error:', error);
      }
    });

    pazeButton.mount('paze-container');

    return () => {
      pazeButton.unmount();
    };
  }, [sessionData, customerEmail]);

  return (
    <div>
      <h1>Checkout with Paze</h1>
      <div>
        <label htmlFor="email">Email address:</label>
        <input
          id="email"
          type="email"
          value={customerEmail}
          onChange={(e) => setCustomerEmail(e.target.value)}
          placeholder="customer@example.com"
        />
      </div>
      <div id="paze-container"></div>
    </div>
  );
}
```

## What's next?

Now that you've integrated Paze, here are some recommended next steps:

- **[Configuration](/guides/checkout/components/web/paze/configuration):** Learn how to customise the Paze button and behaviour.
- **[Events](/guides/checkout/components/web/paze/events):** Explore all available callbacks and event handling.
- **[Analytics](/guides/checkout/components/web/paze/analytics):** Track Paze payment metrics and performance.
- **[Configure webhooks](/guides/get-started/about-webhooks):** Set up server-side webhook handling for reliable payment verification.