iOS Integration

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


Use Razorpay Apple Pay to add Apple Pay to your iOS app. Razorpay handles payment processing; you build your own UI around the Apple Pay button.

SDK Version

The Apple Pay feature is available in SDK version 2.2.0 and later. As this is a pre-release build, you must pin the exact beta version in Swift Package Manager (SPM's "Up to Next Major Version" rule does not resolve pre-release tags).

Complete this setup once before writing any integration code.

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.

    Watch Out!

    Certificates expire after 25 months. Set a reminder to renew before expiry.

  4. Share Certificate with Razorpay — Send the apple_pay.cer file back to your Razorpay point of contact for configuration.

Xcode Steps

  1. Open your .xcodeproj.
  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:

<key>com.apple.developer.in-app-payments</key>
<array>
<string>merchant.com.yourcompany.app</string>
</array>

The SDK is distributed as a Swift package at https://github.com/razorpay/razorpay-customui-pod.

  1. In Xcode, select File → Add Package Dependencies…
  2. Paste the package URL: https://github.com/razorpay/razorpay-customui-pod
  3. Under Dependency Rule, choose Exact Version and enter 2.2.0.
  4. Click Add Package.
  5. On the product selection screen, add the RazorpayApplePay library to your app target, then click Add Package.

RazorpayApplePay automatically pulls in its dependencies (RazorpayCustom, RazorpayCore) — you do not need to add those separately.

In your checkout view controller:

import UIKit
import WebKit
import Razorpay
import RazorpayApplePay
class CheckoutVC: UIViewController, RazorpayPaymentCompletionProtocol {
// ...
}

private var razorpay: RazorpayCheckout?
// Apple Pay runs through PassKit and requires a WKWebView.
// Pass a throwaway instance the SDK holds but never navigates.
private let dummyWebView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
razorpay = RazorpayCheckout.initWithKey(
"rzp_live_XXXXXXXXXX",
andDelegate: self,
withPaymentWebView: dummyWebView,
ApplePay: ApplePay.pluginInstance()
)
}

Initialisation Parameters

Call this before showing your Apple Pay button. It checks whether the user can pay via Apple Pay on their device.

applePayButton.isHidden = true
razorpay?.applePay?.canMakePayment { [weak self] canPay in
DispatchQueue.main.async {
self?.applePayButton.isHidden = !canPay
}
}

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

curl -X POST https://api.razorpay.com/v1/orders \
-u rzp_test_XXXXXXXXXX:your_secret \
-H "Content-Type: application/json" \
-d '{
"amount": 50000,
"currency": "INR"
}'

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

Order Parameters

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

import PassKit
private lazy var applePayButton: PKPaymentButton = {
let btn = PKPaymentButton(
paymentButtonType: .buy,
paymentButtonStyle: .automatic
)
btn.addTarget(self,
action: #selector(applePayTapped),
for: .touchUpInside)
return btn
}()

Button constraints:

  • Use PKPaymentButton, not a generic UIButton with an Apple Pay icon.
  • Do not place text or icons inside it.
  • Respect Apple's minimum height requirement.
  • Use .automatic style on iOS 14+ for automatic light/dark mode adaptation.

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.

This call must be made in response to a user gesture (such as a button tap). Use the existing razorpay.authorize(options) API with an Apple Pay payload.

@objc func applePayTapped() {
let options: [AnyHashable: Any] = [
"amount": 50000, // in currency subunits (for example, paise)
"currency": "INR",
"email": "test@example.com",
"contact": "+919898989898",
"order_id": "order_xxxxxxxxx",
"method": "card",
"app": [
"name": "apple_pay",
"apple_pay": [
"merchant_identifier": "merchant.com.yourcompany.app"
]
]
]
razorpay?.authorize(options)
}

Payment Parameters

extension CheckoutVC: RazorpayPaymentCompletionProtocol {
func onPaymentSuccess(_ payment_id: String,
andData response: [AnyHashable: Any]) {
let orderId = response["razorpay_order_id"] as? String ?? ""
let signature = response["razorpay_signature"] as? String ?? ""
// Send payment_id, orderId, signature to your server
// for signature verification.
}
func onPaymentError(_ code: Int32,
description str: String,
andData response: [AnyHashable: Any]) {
// Handle failure or cancellation
}
}

Success response keys (in andData):

{
"razorpay_payment_id": "pay_XXXXXXXXXX",
"razorpay_order_id": "order_XXXXXXXXXX",
"razorpay_signature": "9ef4dffbfd84f1318f6739a3ce19f9d85851857ae648f114332d8401e0949a3d"
}

Failure response keys (in andData):

{
"error": {
"code": "PAYMENT_CANCELLED",
"description": "Customer dismissed the payment sheet.",
"reason": "payment_cancelled"
}
}

Always verify the signature on your server before fulfilling the order. Send razorpay_payment_id, razorpay_order_id, and razorpay_signature to your backend for HMAC verification.

Watch Out!

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

See the

for Node.js and other language samples.

On failure, read the error code as a String from response["error"]["code"] inside the andData dictionary passed to onPaymentError.

Handy Tips

The code: Int32 parameter of onPaymentError is always 0 for Apple Pay — the actionable code is the String inside andData.

import UIKit
import WebKit
import PassKit
import Razorpay
import RazorpayApplePay
class CheckoutVC: UIViewController, RazorpayPaymentCompletionProtocol {
private var razorpay: RazorpayCheckout?
private let dummyWebView = WKWebView()
@IBOutlet weak var statusLabel: UILabel!
private lazy var applePayButton: PKPaymentButton = {
let btn = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .automatic)
btn.cornerRadius = 8
btn.translatesAutoresizingMaskIntoConstraints = false
btn.addTarget(self, action: #selector(applePayTapped), for: .touchUpInside)
return btn
}()
override func viewDidLoad() {
super.viewDidLoad()
razorpay = RazorpayCheckout.initWithKey(
"rzp_live_XXXXXXXXXX",
andDelegate: self,
withPaymentWebView: dummyWebView,
ApplePay: ApplePay.pluginInstance()
)
view.addSubview(applePayButton)
NSLayoutConstraint.activate([
applePayButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
applePayButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
applePayButton.widthAnchor.constraint(equalToConstant: 250),
applePayButton.heightAnchor.constraint(equalToConstant: 48)
])
applePayButton.isHidden = true
razorpay?.applePay?.canMakePayment { [weak self] canPay in
DispatchQueue.main.async { self?.applePayButton.isHidden = !canPay }
}
}
@objc func applePayTapped() {
let options: [AnyHashable: Any] = [
"amount": 50000, "currency": "INR",
"email": "gaurav.kumar@example.com", "contact": "+919876543210",
"order_id": "order_CuEzONfnOI86Ab", "method": "card",
"app": [
"name": "apple_pay",
"apple_pay": ["merchant_identifier": "merchant.com.yourcompany.app"]
]
]
razorpay?.authorize(options)
}
func onPaymentSuccess(_ payment_id: String, andData response: [AnyHashable: Any]) {
statusLabel.text = "Success: \(payment_id)"
}
func onPaymentError(_ code: Int32, description str: String, andData response: [AnyHashable: Any]) {
statusLabel.text = "Error: \(str)"
}
}

1. I use a table view or collection view where payment methods expand on selection. How does the plugin fit?

The plugin 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 your CTA, call razorpay.authorize(options) with the Apple Pay payload.

// On view setup
razorpay?.applePay?.canMakePayment { [weak self] canPay in
DispatchQueue.main.async {
if canPay { self?.paymentMethods.append(.applePay) }
self?.tableView.reloadData()
}
}
// When user taps the CTA inside the expanded row
@IBAction func payTapped() {
let options: [AnyHashable: Any] = [ /* full Apple Pay payload */ ]
razorpay?.authorize(options)
}

2. Do I need to import PassKit?

Yes. PassKit provides Apple's PKPaymentButton for the button UI. It is part of iOS, so it adds no dependency weight. You will also need to import WebKit since Custom Checkout init requires a WKWebView instance.

3. The Apple Pay sheet appears but the transaction always fails. What should I check?

  • Merchant ID alignment — Does the Merchant ID you passed in options["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?

If all four are correct, check the response in onPaymentError's andData for the specific failure reason from the backend.


Was this page helpful?


apple pay
ios
custom checkout
swift
native sdk