> ## Documentation Index
> Fetch the complete documentation index at: https://razorpay-881012b3.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Web JS Integration (Dynamic Cart Value)

> Integrate Apple Pay using the headless Razorpay JS SDK when the cart total is only known at the moment the customer clicks pay, in cases of dynamic pricing and deferred calculations.

<div style={{display:"flex",flexWrap:"wrap",alignItems:"center",gap:"0.35rem 0.9rem",border:"1px solid rgba(128,128,128,0.28)",borderRadius:"0.5rem",padding:"0.45rem 0.75rem",margin:"0 0 1.25rem",fontSize:"0.875rem"}}>
  <span style={{fontWeight:600}}>Available in</span>
  <span>🇮🇳 India</span>
</div>

<Warning>
  **Internal Use Only**

  This flow (setting amount/currency after the button click) is meant for businesses where the cart amount is not known at init time. For example, dynamic pricing resolved at click time or deferred shipping/tax calculation. Businesses must refer to the [Web JS Integration](/docs/payments/payment-methods/apple-pay/custom-integration/web-sdk/web-js-integration) by default.
</Warning>

The headless JS integration with amount after click is for checkouts where the cart total is only known or finalised at the moment the customer clicks pay. You initialise the Razorpay JS object without amount or currency, then set them right before the payment starts. Know more about [Apple Pay](https://www.apple.com/apple-pay/).

<AccordionGroup>
  <Accordion title="When to Use This Flow">
    Use this integration only when your checkout cannot know the cart total at initialisation time. Common scenarios include:

    * **Dynamic pricing**: The final price is resolved at click time based on real-time inventory, demand or promotions.
    * **Deferred shipping/tax calculation**: Shipping costs and taxes are calculated after the customer selects their address.
    * **Multi-step checkout**: The cart total changes based on selections made late in the checkout flow.

    For most merchants, the [Web JS Integration](/docs/payments/payment-methods/apple-pay/custom-integration/web-sdk/web-js-integration) is simpler and recommended.
  </Accordion>
</AccordionGroup>

## Prerequisites

Before starting the integration, ensure you have the following:

* A Razorpay account with Apple Pay enabled.
* An existing Razorpay Custom Checkout integration.
* International Payments enabled on your Razorpay account.
* Your API Key Id available. Know how to generate [API Keys from the Dashboard](/docs/payments/dashboard/account-settings/api-keys#generate-api-keys).
* An HTTPS-enabled domain (TLS 1.2 or higher). Apple Pay requires a secure context and will not function over HTTP.
* Server-side capability to create orders via the Razorpay Orders API.
* Domains verified in the Dashboard for Apple Pay.

## Integration Steps

Follow the steps given below.

<AccordionGroup>
  <Accordion title="Step 1: Domain Verification">
    Verify your checkout domain(s) for Apple Pay before you go live. To find the list of these domains, please log in to the [dashboard](https://dashboard.razorpay.com/app/payment-methods/apple-pay).

    <Info>
      **Handy Tip**

      Only domains whitelisted with the accounts service will be visible here. Similar domains are visible as well. Please contact our [Support team](https://razorpay.com/support/) if you cannot find your Apple Pay showing domain here. You need to whitelist your URL.
    </Info>

    Dashboard Configuration and Verification

    * Log in to the Dashboard and navigate to **Account & Settings** → **International payments** (under Payment methods). Click [**Apple Pay**](https://dashboard.razorpay.com/app/payment-methods/apple-pay).

          <img src="https://razorpay.com/docs/build/browser/assets/images/click-apple-pay.jpg" alt="Click Apple Pay on the Dashboard" width="800" />

          <Warning>
            **Important**

            This will only be visible if the business has International payments activated. If you do not have international payments active, you will not see Apple Pay.
          </Warning>
    * You will see a list of domains associated with your business account:
      * **Verified domains**: Ready for Apple Pay.
      * **Unverified domains**: Need to be verified.
    * Click **Verify domains** for any unverified domains.
  </Accordion>

  <Accordion title="Step 2: Load the Script">
    Include the Razorpay Custom Checkout script in your page's `<head>` tag.

    ```html HTML theme={null}
    <head>
        <script src="https://checkout.razorpay.com/v1/razorpay.js"></script>
    </head>
    ```

    <Info>
      **Handy Tip**

      Load this script on every page where you intend to use the Apple Pay integration. Existing Custom Checkout merchants already load this script.
    </Info>
  </Accordion>

  <Accordion title="Running Alongside Standard Checkout">
    Both `razorpay.js` (headless Apple Pay) and `checkout.js` (Standard Checkout) register their constructor on the same `window.Razorpay` global. If your page uses both, whichever script loads last overwrites the other's constructor on `window.Razorpay`. Capture the headless constructor as soon as `razorpay.js` loads, and let `checkout.js` load after it so `window.Razorpay` is restored to Standard Checkout's constructor for the rest of your page.

    ```html HTML theme={null}
    <head>
        <script src="https://checkout.razorpay.com/v1/razorpay.js" onload="window.__rzpApplePay = window.Razorpay;"></script>
        <script src="https://checkout.razorpay.com/v1/checkout.js"></script>
    </head>
    ```

    If you cannot guarantee this load order (for example, scripts injected dynamically or loaded in parallel), queue the captures instead and resolve them once both scripts have loaded:

    ```js JavaScript theme={null}
    window.__rzpQueue = window.__rzpQueue || [];

    function enqueueRazorpayCtor(name) {
      window.__rzpQueue.push({ name, ctor: window.Razorpay });
      if (window.__rzpQueue.length === 2) {
        // Both scripts have loaded — headless SDK loaded first, Standard Checkout loaded second.
        const [applePay, standard] = window.__rzpQueue;
        window.__rzpApplePay = applePay.ctor;
        window.Razorpay = standard.ctor;
      }
    }
    ```

    Use `window.__rzpApplePay` (instead of `window.Razorpay`) to initialise the headless Apple Pay object in Step 3, and leave `window.Razorpay` untouched for your existing Standard Checkout code.

    ```js JavaScript theme={null}
    const razorpay = new window.__rzpApplePay({ ... });
    ```
  </Accordion>

  <Accordion title="Step 3: Initialise Without Amount and Currency">
    Initialise Razorpay without passing amount or currency. Use the `on_payment_initiate_create_order` callback to create the order on your server right before the payment sheet opens.

    ```js JavaScript theme={null}
    const razorpay = new Razorpay({
      key: 'rzp_test_XXXXXXXXXX',
      prefill: {
        contact: '+919876543210', // This is your customer's contact number.
      },
      on_payment_initiate_create_order: async () => {
        // Called right before the payment sheet opens.
        // Create the order on your server here, then:
        razorpay.set('order_id', orderIdFromYourServer);
      },
    });
    ```

    <Warning>
      **Watch Out!**

      `on_payment_initiate_create_order` is where you create the Razorpay order. You must call `razorpay.set('order_id', ...)` inside it before it resolves — the payment cannot be authorised without an order id.
    </Warning>
  </Accordion>

  <Accordion title="Step 4: Listen for the Result">
    Register event listeners for payment success and failure.

    ```js JavaScript theme={null}
    razorpay.on('payment.success', (response) => {
      // response.paymentData.razorpay_payment_id
      // response.paymentData.razorpay_order_id
      // response.paymentData.razorpay_signature
    });

    razorpay.on('payment.failure', (response) => {
      // response.error -> { code, description, source, reason }
    });
    ```
  </Accordion>

  <Accordion title="Step 5: Check Availability and Set Amount/Currency on Click">
    Check whether the customer's device supports Apple Pay using `canMakePayment()`, then set the amount and currency right before triggering the payment using one of the following options.

    <Tabs>
      <Tab title="Option 1: mount() with onClick (Recommended)">
        Use `mount()` to have Razorpay render an Apple Pay button into a container element you provide. This is the recommended default. Pass an `onClick` callback to set the amount and currency right before the payment sheet opens.

        ```js JavaScript theme={null}
        razorpay.mount({
          method: 'card',
          app: { name: 'apple_pay' },
          container: document.getElementById('apple-pay-container'),
          buttonLabel: 'pay',
          buttonTheme: 'dark',
          buttonWidth: '148px',
          buttonHeight: '32px',
          onClick: async () => {
            // Runs right before the sheet opens. Resolve the cart total here.
            razorpay.set('amount', amount);
            razorpay.set('currency', currency);
          },
        });
        ```

        `mount()` creates the button, appends it to the container and wires up the click to payment lifecycle for you, calling your `onClick` first. The result still comes via the `payment.success` / `payment.failure` events from Step 3.

        <Warning>
          **Watch Out!**

          Call `canMakePayment()` before `mount()` if you want to avoid mounting a button the customer cannot actually use (for example, hide the container on `available: false`).
        </Warning>
      </Tab>

      <Tab title="Option 2: Your Own Button + createPayment()">
        Use this option only if you need full control over the button's markup and rendering. Use `canMakePayment()` to check eligibility, then call `razorpay.set('amount', ...)` and `razorpay.set('currency', ...)` inside your button's click handler before calling `createPayment()`.

        ```js JavaScript theme={null}
        const { available, reason } = await razorpay.canMakePayment({
          method: 'card',
          app: { name: 'apple_pay' },
        });

        if (!available) {
          // hide your Apple Pay button, show alternatives
        } else {
          // show your button
        }

        myButton.addEventListener('click', () => {
          razorpay.set('amount', amount);      // resolve cart total here
          razorpay.set('currency', currency);
          razorpay.createPayment({
            method: 'card',
            app: { name: 'apple_pay' },
          });
        });
        ```

        <Info>
          **Handy Tip**

          `createPayment()` is fire-and-forget — the outcome always comes through the `payment.success` / `payment.failure` events registered in Step 3.
        </Info>
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="Step 6: Verify the Payment Signature on Your Server">
    After a successful payment (the `payment.success` event fires), verify the payment signature on your server before fulfilling the order.

    Send the following fields to your backend:

    * `razorpay_payment_id`
    * `razorpay_order_id`
    * `razorpay_signature`

    Verify them using the standard [Razorpay signature verification process](/docs/payments/server-integration/python/integration-steps#14-verify-payment-signature).

    <Warning>
      **Watch Out!**

      Never fulfil an order based solely on the client-side `payment.success` event. Signature verification ensures the payment was genuinely processed by Razorpay and has not been tampered with.
    </Warning>
  </Accordion>
</AccordionGroup>

## Error Handling Reference

Every failure — from `payment.failure` events or a caught exception — carries the same shape:

```js JavaScript theme={null}
{
  code: 'PAYMENT_CANCELLED' | 'PAYMENT_FAILED' | 'INTERNAL_ERROR',
  description: string,  // safe to show to the customer
  source: 'customer' | 'merchant' | 'bank' | 'internal',
  reason: string,        // machine-readable, for logging
}
```

| Code                | Meaning                                                      | Suggested Handling                              |
| ------------------- | ------------------------------------------------------------ | ----------------------------------------------- |
| `PAYMENT_CANCELLED` | Customer closed the sheet, or you called `abort()`           | Return to the product/cart page                 |
| `PAYMENT_FAILED`    | Payment was attempted but declined/failed                    | Show description, offer retry or another method |
| `INTERNAL_ERROR`    | Something went wrong on our end (network, script load, etc.) | Show a generic "try again later" message        |

## Quick Checklist

* Load `https://checkout.razorpay.com/v1/razorpay.js`
* Initialise `new Razorpay({ ... })` without amount/currency
* Implement order creation inside `on_payment_initiate_create_order`
* Call `razorpay.set('amount', ...)` / `razorpay.set('currency', ...)` right before payment starts (click handler or `mount()`'s `onClick`)
* Wire up `payment.success` / `payment.failure` handling
* Test `canMakePayment()` handling for devices/browsers where Apple Pay is not available
* Verify the payment signature on your server before fulfilling the order

### Related Information

* [Web Component Integration](/docs/payments/payment-methods/apple-pay/custom-integration/web-sdk/web-component)
* [Web JS Integration](/docs/payments/payment-methods/apple-pay/custom-integration/web-sdk/web-js-integration)
* [Apple Pay Standard Checkout](/docs/payments/payment-methods/apple-pay)
