> ## 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.

# React Native Integration

> Accept Apple Pay payments in your React Native app using Razorpay's Custom Checkout SDK. Build your own UI around the Apple Pay button with Razorpay handling payment processing.

<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>

Use Razorpay Apple Pay to add Apple Pay to your React Native app. Razorpay handles payment processing while you build your own UI around the Apple Pay button.

<Info>
  **SDK Version**

  The Apple Pay feature is available in `react-native-customui` version `2.3.0` and later. Apple Pay runs on iOS only.
</Info>

## Prerequisites

| Requirement | Details |
| - | - |
| Razorpay account | With Apple Pay enabled (**Dashboard → Account & Settings → Payment Methods**) |
| Razorpay API key | `rzp_live_*` (your live key) |
| Apple Developer account | Required to configure an Apple Pay Merchant ID |
| Apple Pay Merchant ID | For example, `merchant.com.yourcompany.app`, created in the Apple Developer Portal |
| Merchant ID certificate | Uploaded to the Razorpay Dashboard so the backend can decrypt Apple Pay tokens |
| React Native project | With the `react-native-customui` package installed |
| Xcode 14+ and CocoaPods | iOS deployment target 12.0+ |
| Physical iOS device | Transactions can only complete on real devices |
| Server-side order | Created via the Razorpay Orders API |

<Info>
  **iOS Only**

  Apple Pay is available on iOS only. On Android, `canMakePayment` always returns `false`, so hide the Apple Pay button there.
</Info>

## One-Time Setup (Before Code)

Complete this setup once before writing any integration code.

<AccordionGroup>
  <Accordion title="Apple Developer Portal Steps">
    1. **Create Apple Pay Merchant ID:** In the Apple Developer Portal, go to **Identifiers → + → Merchant IDs**. Use the format `merchant.com.yourcompany.app` and register.
    2. **Share Merchant ID with Razorpay:** Send your Merchant ID to your Razorpay point of contact. The team will share a CSR file with you.
    3. **Generate Certificate on Apple:** In the Apple Developer Portal, open your Merchant ID → **Create Certificate** under Apple Pay Payment Processing. Upload the `.csr`, then download the `apple_pay.cer`.

    <Warning>
      **Watch Out!**

      Certificates expire after 25 months. Set a reminder to renew before expiry.
    </Warning>

    4. **Share Certificate with Razorpay:** Send the `apple_pay.cer` file back to your Razorpay point of contact for configuration.
  </Accordion>

  <Accordion title="Xcode Steps">
    1. Open `ios/YourApp.xcworkspace` in Xcode.
    2. Select your app target → **Signing & Capabilities**.
    3. Click **+ Capability → Apple Pay**.
    4. Tick the Merchant ID created above.

    This generates a `.entitlements` file. Sample output:

    ```xml Entitlements theme={null}
    <key>com.apple.developer.in-app-payments</key>
    <array>
        <string>merchant.com.yourcompany.app</string>
    </array>
    ```

    The merchant identifier in this entitlement must be the same one you pass as `apple_pay.merchant_identifier` at runtime.
  </Accordion>
</AccordionGroup>

## Integration Steps

### Step 1: Install the SDK

Install the package from npm:

```sh Terminal theme={null}
npm install react-native-customui@^2.3.0
```

### Step 2: Add the Apple Pay Plugin Pod

The Apple Pay plugin is opt-in. Add it to your `ios/Podfile`, inside your app target:

```ruby Podfile theme={null}
pod 'RazorpayApplePay',
    :podspec => '../node_modules/react-native-customui/RazorpayApplePay.podspec'
```

Then install the pods:

```sh Terminal theme={null}
cd ios && pod install
```

<AccordionGroup>
  <Accordion title="Why is the plugin opt-in?">
    `RazorpayApplePay` is not published to the CocoaPods trunk. It ships as a Swift Package binary target and GitHub release asset. Declaring it as a hard dependency would break `pod install` for every existing app using `react-native-customui`, so the package ships a podspec that you add explicitly.

    The SDK resolves the plugin at runtime. If the pod is absent, your app still builds and Apple Pay degrades cleanly: `canMakePayment` returns `false` and `open()` rejects with `DEVICE_NOT_SUPPORTED`.
  </Accordion>
</AccordionGroup>

### Step 3: Check if the Customer Can Pay

Call this before showing your Apple Pay button. It checks whether the user can pay via Apple Pay with your merchant account.

```js JavaScript theme={null}
import Razorpay from 'react-native-customui';

const eligible = await Razorpay.canMakePayment('rzp_live_XXXXXXXXXX');
```

`canMakePayment` returns a boolean. It is merchant-aware, not a device probe: it fetches the card networks enabled for your Razorpay account and resolves `true` only when Apple Pay is live for your account and the customer's Wallet holds a card on an accepted network. It returns `false` on Android. Hide the Apple Pay button when it is `false`.

### Step 4: Create an Order on Your Server

Create a Razorpay order via the Orders API on your backend and send the `order_id` to your app.

```bash Request theme={null}
curl -X POST https://api.razorpay.com/v1/orders \
  -u rzp_live_XXXXXXXXXX:your_secret \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 50000,
    "currency": "INR",
    "receipt": "YOUR_ORDER_REF",
    "notes": { "orderId": "YOUR_ORDER_REF" }
  }'
```

The response returns an `order_id` such as `order_CuEzONfnOI86Ab`. Pass this to your app.

To carry your own order reference through an Apple Pay payment, set `receipt` and `notes` on the **order** (as shown above). See [Attaching Your Own Order Reference](#attaching-your-own-order-reference).

<Warning>
  **Watch Out!**

  Never embed your API secret in the mobile app. Orders must be created by your own backend. A decompiled app bundle exposes any secret it contains, and with it your merchant account.
</Warning>

<AccordionGroup>
  <Accordion title="Order Parameters">
    | Key | Required | Description |
    | - | - | - |
    | `amount` | Yes | Payment amount in the smallest currency subunit. For example, for ₹299, pass `29900`. For three-decimal currencies such as KWD, BHD and OMR, to accept 295.991, pass `295990`. For zero-decimal currencies such as JPY, to accept 295, pass `295`. |
    | `currency` | Yes | The currency in which the transaction should be made. |
    | `receipt` | Optional | Your internal order reference. Returned as `order_receipt` in settlement recon and reports. Use this to map an Apple Pay payment back to your own order. |
    | `notes` | Optional | Key-value notes stored on the order. Must be a nested JSON object, such as `{ "orderId": "YOUR_ORDER_REF" }`. Readable later via the Orders API. |
  </Accordion>
</AccordionGroup>

### Step 5: Load the Payment Button

Apple's Human Interface Guidelines require the use of `PKPaymentButton` (or a visually compliant variant). Custom buttons will lead to App Store rejection.

React Native has no built-in Apple Pay button. Expose `PKPaymentButton` to JavaScript with a small native view module (a `UIViewRepresentable`-style wrapper using `RCTViewManager`), or use a community package that does the same. Whichever you use, it must render Apple's `PKPaymentButton` and call your payment handler on tap.

**Button constraints:**

* Use `PKPaymentButton`, not a generic button with an Apple Pay icon.
* Do not place text or icons inside it.
* Respect Apple's minimum height requirement.
* Use the `automatic` style on iOS 14+ for automatic light and dark mode adaptation.
* Disable the button while a payment is in flight (see Step 6).

<Info>
  **Test on a Real Device**

  Run on a physical iOS device. Transactions cannot complete on the simulator. Tap your Apple Pay button, authenticate with Face ID or Touch ID, then confirm the payment record appears in the Razorpay Dashboard.
</Info>

### Step 6: Trigger the Payment on User Tap

This call must be made in response to a user gesture (such as a button tap). Do not call it on mount or from a timer. Use the existing `Razorpay.open(options)` API with an Apple Pay payload.

```js JavaScript theme={null}
import Razorpay from 'react-native-customui';

function onApplePayTapped() {
  Razorpay.open({
    key_id: 'rzp_live_XXXXXXXXXX',
    order_id: 'order_CuEzONfnOI86Ab',
    amount: '50000',          // in currency subunits (for example, paise)
    currency: 'INR',
    email: 'gaurav.kumar@example.com',
    contact: '9876543210',
    method: 'card',
    app: {
      name: 'apple_pay',
      apple_pay: {
        merchant_identifier: 'merchant.com.yourcompany.app',
      },
    },
  })
    .then(data => console.log(data.razorpay_payment_id))
    .catch(err => console.log(err.error));
}
```

<Warning>
  **Guard Against Double Taps**

  Apple Pay will not present a second payment sheet while one is pending. If `open()` is called twice, both calls fail with `SHEET_PRESENTATION_FAILED`. Disable the Apple Pay button as soon as it is tapped and re-enable it only after the promise settles.
</Warning>

<AccordionGroup>
  <Accordion title="Payment Parameters">
    | Key | Required | Description |
    | - | - | - |
    | `key_id` | Yes | Your Razorpay key ID. For example, `rzp_live_*xxxx`. |
    | `amount` | Yes | Payment amount in the smallest currency subunit. For example, for ₹299, pass `29900`. |
    | `currency` | Yes | The currency for the transaction. |
    | `contact` | Yes | Customer phone number. Maximum length 15 characters, inclusive of country code. |
    | `email` | Optional | Customer email address. Maximum length 40 characters. |
    | `order_id` | Yes | Unique identifier of the order created in Step 4. |
    | `method` | Yes | Must be `"card"`. |
    | `app.name` | Yes | Must be `"apple_pay"`. |
    | `apple_pay.merchant_identifier` | Yes | Your Apple Pay Merchant ID from the Apple Developer Portal. |
    | `notes` | No | **Not carried through on the Apple Pay flow.** Any `notes` passed in these options are dropped and will not appear on the payment object. To attach your own order reference, set `receipt` or `notes` on the **order** instead (see [Step 4](#step-4-create-an-order-on-your-server) and [Attaching Your Own Order Reference](#attaching-your-own-order-reference)). |
  </Accordion>
</AccordionGroup>

### Step 7: Handle the Payment Result

`open()` returns a promise. It resolves with the payment payload on success and rejects with an error object on failure or cancellation.

```js JavaScript theme={null}
Razorpay.open(options)
  .then(data => {
    const { razorpay_payment_id, razorpay_order_id, razorpay_signature } = data;
    // Send payment_id, order_id and signature to your server
    // for signature verification.
  })
  .catch(err => {
    const { code, description } = err.error;
    // Handle failure or cancellation
  });
```

**Success response (promise resolves with):**

```json Response theme={null}
{
    "razorpay_payment_id": "pay_XXXXXXXXXX",
    "razorpay_order_id":   "order_XXXXXXXXXX",
    "razorpay_signature":  "9ef4dffbfd84f1318f6739a3ce19f9d85851857ae648f114332d8401e0949a3d"
}
```

**Failure response (promise rejects with):**

```json Response theme={null}
{
    "error": {
        "code":        "PAYMENT_CANCELLED",
        "description": "Customer dismissed the payment sheet.",
        "metadata": {
            "payment_id": "pay_XXXXXXXXXX"
        }
    }
}
```

`metadata.payment_id` is present only when a payment was created before the failure.

### Step 8: Verify Payment Signature on Your Server

Always verify the signature on your server before fulfilling the order. A successful `open()` is not a fulfilled order. Send `razorpay_payment_id`, `razorpay_order_id` and `razorpay_signature` to your backend for HMAC verification.

<Warning>
  **Watch Out!**

  Never fulfil an order based solely on the client-side result. Always verify the payment signature server-side.
</Warning>

See the [signature verification guide](/docs/payments/server-integration/nodejs/integration-steps) for Node.js and other language samples.

## Attaching Your Own Order Reference

`notes` passed in the `Razorpay.open()` options are **not** carried onto the payment for Apple Pay. If your system maps payments back to your own order ID, set the reference on the **order** instead.

Set it at order creation (Step 4):

```json Order theme={null}
{
  "amount": 50000,
  "currency": "INR",
  "receipt": "YOUR_ORDER_REF",
  "notes": { "orderId": "YOUR_ORDER_REF" }
}
```

Then read it back:

| Where you set it | Where it comes back |
| - | - |
| Order `receipt` | `order_receipt` in settlement recon and reports |
| Order `notes` | `notes` on the order, via the Orders API |

<Warning>
  **This Differs From Other Flows on the Same SDK**

  On UPI and standard card payments, `notes` passed at payment time **do** reach the payment object. On Apple Pay they do **not**. If you reuse payment-creation code across methods, this is the one field that will not behave the same way.
</Warning>

In this SDK's `Razorpay.open()` options, `notes` must be a nested JSON object. Flat bracket keys such as `"notes[orderId]"` are not parsed as notes here.

## Error Codes

Apple Pay uses the **same** `Razorpay.open()` promise, the same success and error events and the same error envelope as any other payment method in this SDK. If your app already handles card payments, your existing `.then()` and `.catch()` handle Apple Pay with no changes.

On failure, read the error code as a string from `err.error.code` in the rejected promise. (The top-level `err.code` is a numeric `0` for failures and `1` for cancellation. This is the SDK-wide envelope, not something Apple Pay introduces.)

| Error | When It Fires |
| - | - |
| `INVALID_OPTIONS` | Missing `apple_pay.merchant_identifier`, missing or invalid amount or a merchant key so malformed the request URL cannot be built. |
| `NO_SUPPORTED_CARD` | Device supports Apple Pay, but the Wallet has no card for the merchant's supported networks. |
| `DEVICE_NOT_SUPPORTED` | Apple Pay unavailable on the device: hardware, OS, region or restrictions. Also returned on iOS when the `RazorpayApplePay` pod is not installed, and on Android. |
| `SHEET_PRESENTATION_FAILED` | PassKit declined to present the payment sheet, including when a sheet is already pending. |
| `PAYMENT_CANCELLED` | Customer dismissed the sheet or cancelled at the Face ID or Touch ID prompt. |
| `NETWORK_ERROR` | Connection loss, timeout or any transport-level failure. |
| `PAYMENT_FAILED` | Internal or processing failure (serialise failure, empty or unparseable response) and the normalised code for any backend decline. |

## Currency and Card Networks

American Express is accepted for INR payments only. For any other currency the SDK removes American Express from the payment sheet automatically. You do not need to do anything.

## Complete Working Example

```js JavaScript theme={null}
import React, { useEffect, useState } from 'react';
import { Platform, Text, View } from 'react-native';
import Razorpay from 'react-native-customui';
import ApplePayButton from './ApplePayButton'; // your PKPaymentButton bridge

const KEY_ID = 'rzp_live_XXXXXXXXXX';
const MERCHANT_IDENTIFIER = 'merchant.com.yourcompany.app';

export default function CheckoutScreen({ orderId, amount, currency }) {
  const [eligible, setEligible] = useState(false);
  const [busy, setBusy] = useState(false);
  const [status, setStatus] = useState('Ready');

  useEffect(() => {
    if (Platform.OS !== 'ios') return;
    Razorpay.canMakePayment(KEY_ID).then(setEligible);
  }, []);

  function onApplePayTapped() {
    if (busy) return;
    setBusy(true);
    Razorpay.open({
      key_id: KEY_ID,
      order_id: orderId,
      amount: String(amount),
      currency,
      email: 'gaurav.kumar@example.com',
      contact: '9876543210',
      method: 'card',
      app: {
        name: 'apple_pay',
        apple_pay: { merchant_identifier: MERCHANT_IDENTIFIER },
      },
    })
      .then(data => setStatus(`Success: ${data.razorpay_payment_id}`))
      .catch(err => setStatus(`Error: ${err.error.code}`))
      .finally(() => setBusy(false));
  }

  return (
    <View>
      <Text>{status}</Text>
      {eligible && (
        <ApplePayButton onPress={onApplePayTapped} disabled={busy} />
      )}
    </View>
  );
}
```

## Frequently Asked Questions

<AccordionGroup>
  <Accordion title="Can I test Apple Pay on the iOS Simulator or in Razorpay test mode?">
    No. The Simulator has no Secure Element, so the Apple Pay token it produces is empty and the payment fails. Razorpay test mode does not process Apple Pay tokens. Test on a physical iOS device with a real card in Wallet, using your `rzp_live_*` key.
  </Accordion>

  <Accordion title="I show payment methods in a list where each expands on selection. How does Apple Pay fit?">
    The SDK separates detection from payment. Use `canMakePayment` to decide whether to include the Apple Pay row at all. When the user selects that row, show the Apple Pay button. When they tap it, call `Razorpay.open(options)` with the Apple Pay payload.
  </Accordion>

  <Accordion title="The Apple Pay sheet appears but the transaction always fails. What should I check?">
    * **Merchant ID alignment:** Does the Merchant ID you passed in `app.apple_pay.merchant_identifier` match the one in your `.entitlements` and the one uploaded to the Razorpay Dashboard?
    * **Certificate uploaded:** Is the CSR-signed certificate for that Merchant ID present in the Razorpay Dashboard?
    * **Network match:** Are you testing with a card whose network is enabled for your Razorpay account?
    * **Backend support:** Is Apple Pay enabled for your account in the Razorpay Dashboard?
    * **Real device:** Are you running on a physical device, not the Simulator?

    If all of these are correct, check `err.error.description` in the rejected promise for the specific failure reason from the backend.
  </Accordion>

  <Accordion title="canMakePayment returns false but the device has cards in Wallet. Why?">
    `canMakePayment` checks your merchant account as well as the device. It returns `false` when Apple Pay is not enabled for your Razorpay account, when none of the cards in Wallet are on a network enabled for your account, when the `RazorpayApplePay` pod is not installed or on Android.
  </Accordion>
</AccordionGroup>

### Related Information

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