> ## Documentation Index
> Fetch the complete documentation index at: https://www.cashfree.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS Element Custom Card Component (Non-PCI)

> Integrate the Cashfree Custom Card Component into your iOS application to accept card payments without handling raw card data.

The Cashfree Custom Card Component lets you embed a secure, SDK-managed card input field directly into your iOS application UI. Because the `CFCardComponent` captures and processes the card number entirely within the SDK, your application never receives the raw card number. This makes the integration suitable for non-PCI merchants, that is, merchants who are not certified to store, process, or transmit raw cardholder data, and who rely on the SDK to handle card data securely on their behalf.

The component formats the card number into groups of four digits (up to 16 digits), detects the card network in real time and displays its icon, and runs a Luhn check on every keystroke. After the customer enters 8 digits, the component looks up the card BIN (Bank Identification Number) and TDR (Transaction Discount Rate) automatically. Your application receives only metadata through `CFCardListener`: the digit count, the Luhn result, BIN information, and TDR information.

<Note>
  This page covers the custom card component integration only. For the standard iOS payment gateway integration, see [iOS Integration](/docs/payments/online/mobile/ios).
</Note>

<Note>
  If you are PCI-DSS certified and have access to the raw card number, call `.setCardNumber()` on `CFCard.CFCardBuilder()` directly instead of using `CFCardComponent`. That path follows the standard card payment flow.
</Note>

## Prerequisites

Complete the following tasks before you start the integration:

* Create a [Cashfree Merchant Account](https://merchant.cashfree.com/merchants/signup).
* Log in to the [Merchant Dashboard](https://merchant.cashfree.com/auth/login) and generate an **App ID** and **Secret Key**. Learn how to [generate API keys](/docs/api-reference/authentication#generate-api-keys).
* Use Cashfree iOS SDK version **2.2.6** or above. The latest version is recommended.
* Set your application's minimum deployment target to **iOS 13.0** or higher.

The integration consists of three steps:

<CardGroup cols={3}>
  <Card title="Step 1" icon="money-bill-wave" href="/docs/payments/online/element/custom-card/ios-custom-card#step-1-create-an-order-server-side">
    Create an order
  </Card>

  <Card title="Step 2" icon="desktop" href="/docs/payments/online/element/custom-card/ios-custom-card#step-2-open-the-payment-page-client-side">
    Open the payment page
  </Card>

  <Card title="Step 3" icon="circle-check" href="/docs/payments/online/element/custom-card/ios-custom-card#step-3-confirm-the-payment-server-side">
    Confirm the payment
  </Card>
</CardGroup>

## Step 1: Create an order <Badge color="green">Server-side</Badge>

Create an order from your backend server before you process any payment.

<Note>This API requires your secret key. Create orders through your server only. Do not call this API directly from your mobile application.</Note>

##### API request for creating an order

Here's a sample request for creating an order using your desired backend language. Cashfree offers backend [SDKs](/docs/api-reference/payments/sdk#server-sdk) to simplify the integration process.

<CodeGroup>
  ```bash curl theme={"dark"}
  curl --location 'https://sandbox.cashfree.com/pg/orders' \
  --header 'X-Client-Secret: {{clientKey}}' \
  --header 'X-Client-Id: {{clientId}}' \
  --header 'x-api-version: 2025-01-01' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data-raw '{
    "order_amount": 10.10,
    "order_currency": "INR",
    "customer_details": {
      "customer_id": "USER123",
      "customer_name": "joe",
      "customer_email": "joe.s@cashfree.com",
      "customer_phone": "+919876543210"
    },
    "order_meta": {
      "return_url": "https://b8af79f41056.eu.ngrok.io?order_id=order_123"
    }
  }'
  ```

  ```javascript nodejs theme={"dark"}
  import { Cashfree, CFEnvironment } from "cashfree-pg";

  const cashfree = new Cashfree(
  	CFEnvironment.PRODUCTION,
  	"{Client ID}",
  	"{Client Secret Key}"
  );

  function createOrder() {
  	var request = {
  		order_amount: "1",
  		order_currency: "INR",
  		customer_details: {
  			customer_id: "node_sdk_test",
  			customer_name: "",
  			customer_email: "example@gmail.com",
  			customer_phone: "9999999999",
  		},
  		order_meta: {
  			return_url:
  				"https://test.cashfree.com/pgappsdemos/return.php?order_id=order_123",
  		},
  		order_note: "",
  	};

  	cashfree
  		.PGCreateOrder(request)
  		.then((response) => {
  			console.log("Order created successfully:", response.data);
  		})
  		.catch((error) => {
  			console.error("Error setting up order request:", error.response.data);
  		});
  }
  ```

  ```python python theme={"dark"}
  from cashfree_pg.models.create_order_request import CreateOrderRequest
  from cashfree_pg.api_client import Cashfree
  from cashfree_pg.models.customer_details import CustomerDetails


  Cashfree.XClientId = {Client ID}
  Cashfree.XClientSecret = {Client Secret Key}
  Cashfree.XEnvironment = Cashfree.XSandbox
  x_api_version = "2023-08-01"

  def create_order():
          customerDetails = CustomerDetails(customer_id="123", customer_phone="9999999999")
          createOrderRequest = CreateOrderRequest(order_amount=1, order_currency="INR", customer_details=customerDetails)
          try:
              api_response = Cashfree().PGCreateOrder(x_api_version, createOrderRequest, None, None)
              print(api_response.data)
          except Exception as e:
              print(e)
  ```

  ```java java theme={"dark"}
  import com.cashfree.*;

  Cashfree.XClientId = {Client Key};
  Cashfree.XClientSecret = {Client Secret Key};
  Cashfree.XEnvironment = Cashfree.SANDBOX;

  static void createOrder() {
    CustomerDetails customerDetails = new CustomerDetails();
    customerDetails.setCustomerId("123");
    customerDetails.setCustomerPhone("9999999999");

    CreateOrderRequest request = new CreateOrderRequest();
    request.setOrderAmount(1.0);
    request.setOrderCurrency("INR");
    request.setCustomerDetails(customerDetails);
    try {
      Cashfree cashfree = new Cashfree();
      ApiResponse<OrderEntity> response = cashfree.PGCreateOrder("2023-08-01", request, null, null, null);
      System.out.println(response.getData().getOrderId());

    } catch (ApiException e) {
      throw new RuntimeException(e);
    }
  }
  ```

  ```go go theme={"dark"}
  import (
    cashfree "github.com/cashfree/cashfree-pg/v3"
  )

  func createOrder() {

  clientId := {Client ID}
  clientSecret := {Client Secret Key}
  cashfree.XClientId = &clientId
  cashfree.XClientSecret = &clientSecret
  cashfree.XEnvironment = cashfree.SANDBOX

  request := cashfree.CreateOrderRequest{
  		OrderAmount: 1,
  		CustomerDetails: cashfree.CustomerDetails{
  			CustomerId:    "1",
  			CustomerPhone: "9999999999",
  		},
  		OrderCurrency: "INR",
  		OrderSplits:   []cashfree.VendorSplit{},
  	}
  	version := "2023-08-01"
  	response, httpResponse, err := cashfree.PGCreateOrder(&version, &request, nil, nil, nil)
  	if err != nil {
  		fmt.Println(err.Error())
  	} else {
  		fmt.Println(httpResponse.StatusCode)
  		fmt.Println(response)
      }
  }
  ```

  ```csharp .net theme={"dark"}
  using cashfree_pg.Client;
  using cashfree_pg.Model;

  Cashfree.XClientId = {Client ID};
  Cashfree.XClientSecret = {Client Secret Key};
  Cashfree.XEnvironment = Cashfree.PRODUCTION;
  var cashfree = new Cashfree();
  var xApiVersion = "2023-08-01";

  void CreateOrder() {
      var customerDetails = new CustomerDetails("123", null, "9999999999");
      var createOrdersRequest = new CreateOrderRequest(null, 1.0, "INR", customerDetails);
      try {
          // Create Order
          var result = cashfree.PGCreateOrder(xApiVersion, createOrdersRequest, null, null, null);
          Console.WriteLine(result);
          Console.WriteLine(result.StatusCode);
          Console.WriteLine((result.Content as OrderEntity));
      } catch (ApiException e) {
          Console.WriteLine("Exception when calling PGCreateOrder: " + e.Message);
          Console.WriteLine("Status Code: " + e.ErrorCode);
          Console.WriteLine(e.StackTrace);
      }
  }
  ```

  ```php php theme={"dark"}
  \Cashfree\Cashfree::$XClientId = "<x-client-id>";
  \Cashfree\Cashfree::$XClientSecret = "<x-client-secret>";
  \Cashfree\Cashfree::$XEnvironment = Cashfree\Cashfree::$SANDBOX;

  $cashfree = new \Cashfree\Cashfree();

  $x_api_version = "2023-08-01";
  $create_orders_request = new \Cashfree\Model\CreateOrdersRequest();
  $create_orders_request->setOrderAmount(1.0);
  $create_orders_request->setOrderCurrency("INR");
  $customer_details = new \Cashfree\Model\CustomerDetails();
  $customer_details->setCustomerId("123");
  $customer_details->setCustomerPhone("9999999999");
  $create_orders_request->setCustomerDetails($customer_details);

  try {
      $result = $cashfree->PGCreateOrder($x_api_version, $create_orders_request);
      print_r($result);
  } catch (Exception $e) {
      echo 'Exception when calling PGCreateOrder: ', $e->getMessage(), PHP_EOL;
  }
  ```
</CodeGroup>

After successfully creating an order, you will receive a unique `order_id` and `payment_session_id` that you need for subsequent steps.

You can view the complete API request and response for `/orders` in the [Create Order API](/docs/api-reference/payments/latest/orders/create-order).

## Step 2: Open the payment page <Badge color="orange">Client-side</Badge>

After you create the order, set up the card component and open the payment page so the customer can provide their card details.

### 1. Set up the SDK

The Cashfree iOS SDK is available via CocoaPods. The custom card component requires SDK version **2.2.6** or above and iOS **13.0** or higher.

Add the following to your `Podfile`:

```ruby theme={"dark"}
platform :ios, '13.0'

target 'YourAppTarget' do
  use_frameworks!
  pod 'CashfreePG', '2.4.0'
end
```

Run `pod install` to install the dependency, then import the SDK modules in the file where you build the checkout:

```swift theme={"dark"}
import CashfreePGCoreSDK
import CashfreePG
```

### 2. Complete the payment

To complete the payment, follow these steps:

1. Add the `CFCardComponent` to your view.
2. Create a `CFSession` object.
3. Initialise the card component after the session is ready.
4. Conform to `CFCardListener` to receive card metadata.
5. Set up the payment callback.
6. Build the payment object and initiate the payment.

#### Add the card component to your view

`CFCardComponent` is a `UIView` that you can add in Interface Builder or programmatically. In Interface Builder, set the custom class of a `UIView` to `CFCardComponent` (module: `CashfreePGCoreSDK`) and connect it as an outlet:

```swift theme={"dark"}
@IBOutlet weak var cfCardComponent: CFCardComponent!
```

To add the component programmatically, create it with a frame and add it to your view hierarchy:

```swift theme={"dark"}
let cfCardComponent = CFCardComponent(frame: CGRect(x: 16, y: 100, width: view.bounds.width - 32, height: 48))
view.addSubview(cfCardComponent)
```

Apply standard `UIView` styling as needed:

```swift theme={"dark"}
cfCardComponent.layer.borderColor = UIColor.systemGray4.cgColor
cfCardComponent.layer.borderWidth = 1.0
cfCardComponent.layer.cornerRadius = 8.0
```

The component manages only the card number field. Collect the cardholder name, expiry month (`MM` format, for example `12`), expiry year (`YY` format, for example `29` for 2029), and CVV in separate input fields in your checkout UI.

#### Create a session

The `CFSession` object holds the session context for the payment. It accepts the `payment_session_id` and `order_id` obtained from [Step 1](#step-1-create-an-order-server-side), and the Cashfree environment (`.SANDBOX` or `.PRODUCTION`).

The `CFSession.CFSessionBuilder()` supports the following methods:

| Method | Required | Description |
| - | - | - |
| `.setOrderID(_ id: String)` | Yes | Order ID returned by the Create Order API. |
| `.setPaymentSessionId(_ id: String)` | Yes | `payment_session_id` returned by the Create Order API. |
| `.setEnvironment(_ env: CFENVIRONMENT)` | Yes | `.SANDBOX` for testing or `.PRODUCTION` for live payments. |

The following example shows how to create the session object:

```swift theme={"dark"}
do {
    let session = try CFSession.CFSessionBuilder()
        .setOrderID("<ORDER_ID>")
        .setPaymentSessionId("<PAYMENT_SESSION_ID>")
        .setEnvironment(.SANDBOX) // or .PRODUCTION
        .build()
} catch {
    print(error.localizedDescription)
}
```

#### Initialise the card component

Call `initializeCardComponent` only after you have a valid `CFSession`. The component uses the session to authenticate the BIN and TDR lookup requests.

The `initializeCardComponent` method accepts the following parameters:

| Parameter | Type | Required | Description |
| - | - | - | - |
| `session` | `CFSession` | Yes | Session built from your backend order. |
| `card_listener` | `CFCardListener` | Yes | Receives metadata on every keystroke. |
| `hint_text` | `String` | Yes | Placeholder text. Pass `""` for the default `XXXX XXXX XXXX XXXX` format. |
| `font` | `UIFont?` | No | Custom font. Pass `nil` for the system default. |
| `textColor` | `UIColor?` | No | Text colour. Pass `nil` for the system default. |
| `enable_pasting` | `Bool` | Yes | Whether the customer can paste from the clipboard. |

The following example initialises the component with a custom font and text colour:

```swift theme={"dark"}
cfCardComponent.initializeCardComponent(
    session: session,
    card_listener: self,
    hint_text: "", // Pass "" for the default "XXXX XXXX XXXX XXXX"
    font: UIFont.systemFont(ofSize: 16),
    textColor: UIColor.label,
    enable_pasting: false
)
```

#### Set up the CFCardListener callback

Conform your view controller to `CFCardListener` and implement `cardMetaData(card_listener_response:)`. The SDK calls this method each time the card number in `CFCardComponent` changes.

<Tip>
  If `cardMetaData` updates your UI, such as enabling the pay button, wrap those updates in `DispatchQueue.main.async`.
</Tip>

The following example enables the pay button only when the card number passes the Luhn check:

```swift theme={"dark"}
class CheckoutViewController: UIViewController, CFCardListener {

    func cardMetaData(card_listener_response: CFCardListenerResponse) {
        let charCount = card_listener_response.numberOfCharacters ?? 0
        let meta = card_listener_response.meta_data ?? [:]

        let isLuhnValid = meta["luhn_check"] as? Bool ?? false
        let binInfo = meta["card_bin_info"] as? [String: Any]
        let tdrInfo = meta["tdr_info"] as? [String: Any]

        DispatchQueue.main.async {
            // Enable the pay button only when the card number is valid
            self.payButton.isEnabled = isLuhnValid
        }

        // BIN info is available once 8 digits are entered
        if let scheme = binInfo?["scheme"] as? String {
            print("Card scheme: \(scheme)")
        }
    }
}
```

The callback delivers a `CFCardListenerResponse` with the following top-level fields:

| Field | Type | Description |
| - | - | - |
| `numberOfCharacters` | `Int?` | Number of digits entered, not counting spaces. |
| `message` | `String?` | Informational message about the response. |
| `type` | `String?` | Always `"card_info"`. |
| `meta_data` | `[String: Any]?` | Card validation, BIN, and TDR data (see below). |

The `meta_data` dictionary contains the following keys:

| Key | Type | Available from | Description |
| - | - | - | - |
| `card_length` | `Int` | First digit | Same value as `numberOfCharacters`: the total digits entered. |
| `luhn_check` | `Bool` | First digit | `true` if the card number passes the Luhn algorithm; `false` otherwise. |
| `card_bin_info` | `[String: Any]` | 8th digit | BIN lookup data from the server, such as card network, card type, and issuing bank. `nil` when fewer than 8 digits are entered. |
| `tdr_info` | `[String: Any]` | 8th digit | The TDR for this BIN on the current order. `nil` when fewer than 8 digits are entered. |

The `card_bin_info` dictionary returned by the BIN lookup contains the following fields:

| Field | Description |
| - | - |
| `card_bin` | Bank Identification Number. |
| `logo` | Card network logo reference. |
| `sub_type` | Card sub-type classification. |
| `type` | Card type classification. |
| `scheme` | Card network scheme (for example, `visa`, `mastercard`). |
| `brand` | Card brand classification. |
| `bank_name` | Issuing bank name. |

<Warning>
  `card_bin_info` and `tdr_info` are only present after the customer has entered at least 8 digits. If the customer deletes digits and the count falls below 8, the SDK clears both keys. If the BIN or TDR lookup fails, the matching key stays `nil`, while card entry and the Luhn check continue to work. Always check that these keys exist in `meta_data` before you access them.
</Warning>

When the customer enters the 8th digit, the SDK calls the Cashfree BIN and TDR APIs and includes their results in the same `cardMetaData` callback. No additional setup is required. The lookups authenticate with the `payment_session_id` of the session that you passed at initialisation.

The component auto-detects the following card networks and displays the corresponding icon: Visa, Mastercard, American Express (Amex), RuPay, Diners Club, Discover, and JCB.

The following example illustrates how the `meta_data` dictionary evolves as the customer enters their card number. The `tdr_info` contents are omitted.

```json theme={"dark"}
// After the 1st digit
{"card_length": 1, "luhn_check": false}

// After the 8th digit, card_bin_info and tdr_info become available
{
  "card_length": 8,
  "luhn_check": false,
  "card_bin_info": {
    "scheme": "visa",
    "type": "filtered",
    "sub_type": "filtered",
    "brand": "filtered",
    "bank_name": "axis bank"
  },
  "tdr_info": { }
}

// After all 16 digits, luhn_check passes
{
  "card_length": 16,
  "luhn_check": true,
  "card_bin_info": {
    "scheme": "visa",
    "type": "filtered",
    "sub_type": "filtered",
    "brand": "filtered",
    "bank_name": "axis bank"
  },
  "tdr_info": { }
}
```

#### Set up the payment callback

The SDK exposes the `CFResponseDelegate` protocol to receive callbacks when the payment flow ends. This protocol consists of two methods:

```swift theme={"dark"}
func onError(_ error: CFErrorResponse, order_id: String)
func verifyPayment(order_id: String)
```

<Tip>Register the callback on `CFPaymentGatewayService` before you call `doPayment`. Call `paymentService.setCallback(self)` in your view controller before you initiate payment.</Tip>

The following example shows how to implement the callback:

```swift theme={"dark"}
extension CheckoutViewController: CFResponseDelegate {

    func onError(_ error: CFErrorResponse, order_id: String) {
        print("Error: \(error.message ?? "")")
        // Show the error to the customer
    }

    func verifyPayment(order_id: String) {
        print("Flow complete. Verify order: \(order_id)")
        // Verify the order status from your backend before proceeding.
    }
}
```

<Warning>
  Backend verification is mandatory. The SDK signals only that the payment UI flow ended, not that the payment succeeded. Always check the order status from your backend before you show a success message.
</Warning>

#### Build the payment object and initiate payment

When the customer fills in their card details and taps the pay button, build the `CFCard` and `CFCardPayment` objects and call `doPayment()` on `CFPaymentGatewayService`.

<Warning>
  You must call `.setCardComponent(cfCardComponent)` on the `CFCard` builder. Because the SDK manages the card number internally via `CFCardComponent`, your application does not have access to the raw card number. `.setCardComponent()` and `.setCardNumber()` are mutually exclusive. Never call both. The misspelt `.setCardComponet()` method is deprecated, so use `.setCardComponent()` instead.
</Warning>

The `CFCard.CFCardBuilder()` supports the following methods:

| Method | Required | Description |
| - | - | - |
| `.setChannel(_ channel: String)` | Yes (default: `"link"`) | Payment flow type. `"link"` redirects to the bank page; `"post"` uses the Headless OTP flow. |
| `.setCardComponent(_ component: CFCardComponent)` | Yes | Component reference. Replaces `.setCardNumber()`. |
| `.setCardHolderName(_ name: String)` | No | Cardholder name. |
| `.setCardExpiryMonth(_ month: String)` | Yes | Expiry month in `"MM"` format. |
| `.setCardExpiryYear(_ year: String)` | Yes | Expiry year in `"YY"` format. |
| `.setCVV(_ cvv: String)` | Yes | Card CVV. |

The `CFCardPayment.CFCardPaymentBuilder()` supports the following methods:

| Method | Required | Description |
| - | - | - |
| `.setCard(_ card: CFCard)` | Yes | The `CFCard` object built from the card details. |
| `.setSession(_ session: CFSession)` | Yes | The session from [Create a session](#create-a-session). |
| `.savePaymentMethod(_ flag: Bool)` | No | Saves the payment method for future use. |
| `.saveInstrument(_ flag: Bool)` | No | Saves the card instrument for future use. |

The following example builds the payment object and initiates payment:

```swift theme={"dark"}
@IBAction func payButtonTapped(_ sender: Any) {
    do {
        let card = try CFCard.CFCardBuilder()
            .setChannel("link")                // "link" (default) or "post" for Headless OTP
            .setCardComponent(cfCardComponent) // Pass the component, not the card number
            .setCardExpiryMonth(expiryMonthField.text ?? "")
            .setCardExpiryYear(expiryYearField.text ?? "")
            .setCardHolderName(cardHolderNameField.text ?? "")
            .setCVV(cvvField.text ?? "")
            .build()

        let payment = try CFCardPayment.CFCardPaymentBuilder()
            .setCard(card)
            .setSession(session)
            .build()

        let paymentService = CFPaymentGatewayService.getInstance()
        paymentService.setCallback(self)
        try paymentService.doPayment(payment, viewController: self)

    } catch {
        print(error.localizedDescription)
    }
}
```

<Note>
  There is no public API to read the card number out of `CFCardComponent`. The SDK uses it securely when you call `doPayment`.
</Note>

#### Sample code

The following example shows a complete custom card component payment flow, including session creation, card component initialisation, metadata handling, and payment initiation.

<AccordionGroup>
  <Accordion title="Custom card component payment sample">
    ```swift theme={"dark"}
    import UIKit
    import CashfreePGCoreSDK
    import CashfreePG

    class CheckoutViewController: UIViewController, CFCardListener, CFResponseDelegate {

        @IBOutlet weak var cfCardComponent: CFCardComponent!
        @IBOutlet weak var cardHolderNameField: UITextField!
        @IBOutlet weak var expiryMonthField: UITextField!
        @IBOutlet weak var expiryYearField: UITextField!
        @IBOutlet weak var cvvField: UITextField!
        @IBOutlet weak var payButton: UIButton!

        private var session: CFSession!

        override func viewDidLoad() {
            super.viewDidLoad()

            payButton.isEnabled = false

            cfCardComponent.layer.borderColor = UIColor.systemGray4.cgColor
            cfCardComponent.layer.borderWidth = 1.0
            cfCardComponent.layer.cornerRadius = 8.0

            do {
                session = try CFSession.CFSessionBuilder()
                    .setOrderID("<ORDER_ID>")
                    .setPaymentSessionId("<PAYMENT_SESSION_ID>")
                    .setEnvironment(.SANDBOX) // or .PRODUCTION
                    .build()

                cfCardComponent.initializeCardComponent(
                    session: session,
                    card_listener: self,
                    hint_text: "",
                    font: UIFont.systemFont(ofSize: 16),
                    textColor: UIColor.label,
                    enable_pasting: false
                )

                CFPaymentGatewayService.getInstance().setCallback(self)

            } catch {
                print(error.localizedDescription)
            }
        }

        // MARK: - CFCardListener

        func cardMetaData(card_listener_response: CFCardListenerResponse) {
            let meta = card_listener_response.meta_data ?? [:]
            let isLuhnValid = meta["luhn_check"] as? Bool ?? false

            DispatchQueue.main.async {
                self.payButton.isEnabled = isLuhnValid
            }
        }

        // MARK: - Payment

        @IBAction func payButtonTapped(_ sender: Any) {
            do {
                let card = try CFCard.CFCardBuilder()
                    .setChannel("link")
                    .setCardComponent(cfCardComponent)
                    .setCardExpiryMonth(expiryMonthField.text ?? "")
                    .setCardExpiryYear(expiryYearField.text ?? "")
                    .setCardHolderName(cardHolderNameField.text ?? "")
                    .setCVV(cvvField.text ?? "")
                    .build()

                let payment = try CFCardPayment.CFCardPaymentBuilder()
                    .setCard(card)
                    .setSession(session)
                    .build()

                payment.setFullScreen(true) // Optional

                try CFPaymentGatewayService.getInstance().doPayment(payment, viewController: self)

            } catch {
                print(error.localizedDescription)
            }
        }

        // MARK: - CFResponseDelegate

        func onError(_ error: CFErrorResponse, order_id: String) {
            print("Error: \(error.message ?? "")")
        }

        func verifyPayment(order_id: String) {
            print("Flow complete. Verify order: \(order_id)")
        }
    }
    ```
  </Accordion>
</AccordionGroup>

#### Sample GitHub code

For a working end-to-end implementation, refer to the sample integration on GitHub.

<AccordionGroup>
  <Accordion title="iOS custom card component sample">
    [iOS SDK on GitHub](https://github.com/cashfree/core-ios-sdk)
  </Accordion>
</AccordionGroup>

## Step 3: Confirm the payment <Badge color="green">Server-side</Badge>

After the SDK delivers a callback via `verifyPayment`, confirm the payment status from your backend before taking any action. The SDK callback signals only that the payment flow has ended. It does not guarantee a successful payment.

To verify an order you can call our `/pg/orders` endpoint from your backend. You can also use our SDK to achieve the same.

<CodeGroup>
  ```bash curl theme={"dark"}
  curl --request GET \
       --url https://sandbox.cashfree.com/pg/orders/{order_id} \
       --header 'accept: application/json' \
       --header 'x-api-version: 2025-01-01' \
       --header 'x-client-id: "YOUR APP ID GOES HERE"' \
       --header 'x-client-secret: "YOUR SECRET KEY GOES HERE"'
  ```

  ```javascript nodejs theme={"dark"}
  cashfree
  .PGFetchOrder("<order_id>")
  .then((response) => {
  	console.log("Order fetched successfully:", response.data);
  })
  .catch((error) => {
  	console.error("Error:", error.response.data.message);
  });
  ```

  ```python python theme={"dark"}
  from cashfree_pg.models.create_order_request import CreateOrderRequest
  from cashfree_pg.api_client import Cashfree
  from cashfree_pg.models.customer_details import CustomerDetails
  from cashfree_pg.models.order_meta import OrderMeta

  Cashfree.XClientId = "<x-client-id>"
  Cashfree.XClientSecret = "<x-client-secret>"
  Cashfree.XEnvironment = Cashfree.SANDBOX
  x_api_version = "2023-08-01"

  try:
      api_response = Cashfree().PGFetchOrder(x_api_version, "order_3242X4jQ5f0S9KYxZO9mtDL1Kx2Y7u", None)
      print(api_response.data)
  except Exception as e:
      print(e)

  ```

  ```java java theme={"dark"}
  import com.cashfree.*;
  //other code

  try {
      Cashfree.XClientId = "<x-client-id>";
      Cashfree.XClientSecret = "<x-client-secret>";
      Cashfree.XEnvironment = Cashfree.SANDBOX;

      Cashfree cashfree = new Cashfree();
      String xApiVersion = "2023-08-01";

      ApiResponse<OrderEntity> responseFetchOrder = cashfree.PGFetchOrder(xApiVersion, "<order_id>", null, null, null);
      System.out.println(response.getData().getOrderId());
  } catch (ApiException e) {
      throw new RuntimeException(e);
  }
  ```

  ```go go theme={"dark"}
  version := "2023-08-01"
  response, httpResponse, err := cashfree.PGFetchOrder(&version, "<order_id>", nil, nil, nil)
  if err != nil {
  	fmt.Println(err.Error())
  } else {
  	fmt.Println(httpResponse.StatusCode)
  	fmt.Println(response)
  }
  ```

  ```csharp .net theme={"dark"}
  using cashfree_pg.Client;
  using cashfree_pg.Model;

  Cashfree.XClientId = "<x-client-id>";
  Cashfree.XClientSecret = "<x-client-secret>";
  Cashfree.XEnvironment = Cashfree.SANDBOX;
  var cashfree = new Cashfree();
  var xApiVersion = "2023-08-01";

  try {
      var result = cashfree.PGFetchOrder(xApiVersion, "<order_id>>", null, null);
      Console.WriteLine(result);
      Console.WriteLine(result.StatusCode);
      Console.WriteLine((result.Content as OrderEntity));
  } catch (ApiException e) {
      Console.WriteLine("Exception when calling PGFetchOrder: " + e.Message);
      Console.WriteLine("Status Code: " + e.ErrorCode);
      Console.WriteLine(e.StackTrace);
  }
  ```

  ```php php theme={"dark"}
  $x_api_version = "2023-08-01";
  try {
      $response = $cashfree->PGFetchOrder($x_api_version, "<order_id>");
      print_r($response);
  } catch (Exception $e) {
      echo 'Exception when calling PGFetchOrder: ', $e->getMessage(), PHP_EOL;
  }
  ```
</CodeGroup>

<Note>
  Always verify the order status from your backend before you deliver goods or services to the customer. You can use the [Get Order API](/docs/api-reference/payments/latest/orders/get-order) for this. An order is successful when the `order_status` is `PAID`.
</Note>

## Error codes

If a required field or object is missing when you initiate payment, the SDK returns an error through the `catch` block or the `onError` callback. `CashfreeError` is an enum that inherits from the Foundation `Error` class.

<Accordion title="Show error codes">
  The SDK validation errors are grouped by category as follows:

  ### Session errors

  | Error code | Message |
  | - | - |
  | `SESSION_OBJECT_MISSING` | The "session" is missing in the request. |
  | `ORDER_ID_MISSING` | The "order\_id" is missing in the request. |
  | `ENVIRONMENT_MISSING` | The "environment" is missing in the request. |
  | `PAYMENT_OBJECT_MISSING` | The "payment" is missing in the request. |

  ### Card errors

  | Error code | Message |
  | - | - |
  | `CARD_OBJECT_MISSING` | The CFCard object is missing in the request. |
  | `CHANNEL_MISSING` | The "channel" is missing in the request. |
  | `CARD_EXPIRY_MONTH_MISSING` | The "card\_expiry\_mm" is missing in the request. |
  | `CARD_EXPIRY_YEAR_MISSING` | The "card\_expiry\_yy" is missing in the request. |
  | `CARD_CVV_MISSING` | The "card\_cvv" is missing in the request. |
</Accordion>

## Other options

The following optional configurations let you customise the payment screen behaviour. Apply them to the `CFCardPayment` object before you call `doPayment()`.

<AccordionGroup>
  <Accordion title="(Optional) Enable full-screen payment">
    To show the authentication web view in full screen, call `setFullScreen(true)` on the payment object:

    ```swift theme={"dark"}
    payment.setFullScreen(true)
    ```
  </Accordion>

  <Accordion title="(Optional) Show or hide the cancel button">
    To show or hide the cancel button on the authentication screen, call `setCancelButtonVisibility` on the payment object:

    ```swift theme={"dark"}
    payment.setCancelButtonVisibility(false)
    ```
  </Accordion>

  <Accordion title="(Optional) Customise the theme">
    To apply a custom theme to the SDK screens, pass a `CFTheme` object to `setTheme` on the payment object:

    ```swift theme={"dark"}
    payment.setTheme(theme)
    ```
  </Accordion>
</AccordionGroup>

<div class="hidden" data-table-of-contents="bottom">
  <p class="mt-4 font-medium flex items-center gap-2 related-docs-heading">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="w-4 h-4">
      <path d="M3 4h7a2 2 0 0 1 2 2v13a2 2 0 0 0-2-2H3z" />

      <path d="M21 4h-7a2 2 0 0 0-2 2v13a2 2 0 0 1 2-2h7z" />
    </svg>

    <span>Related topics</span>
  </p>

  <ul>
    <li><a href="/docs/api-reference/payments/latest/orders/create-order">Create Order API</a></li>
    <li><a href="/docs/api-reference/payments/latest/orders/get-order">Get Order API</a></li>
    <li><a href="/docs/payments/online/mobile/ios">iOS Integration</a></li>
    <li><a href="https://github.com/cashfree/core-ios-sdk" target="_blank">iOS SDK on GitHub</a></li>
  </ul>
</div>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.