> For the complete documentation index, see [llms.txt](https://docs.ecommerce.seb.ee/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ecommerce.seb.ee/integration/mobile-sdk/ios-sdk/using-the-sdk/native-card-form.md).

# Native card form

Collect card details in your own UI, with on-device encryption and 3-D Secure handled by the SDK.

Use this guide if you want card details collected in your own interface, styled by your own design system, instead of on the hosted checkout page.

You build the form. The SDK takes the values, encrypts them on the device, creates the card session, drives EMV® 3-D Secure if the issuer asks for it, and delivers one result. Everything in [Backend integration](/integration/mobile-sdk/ios-sdk/guides/backend-integration.md) still applies: the payment is created by your backend exactly as it is for hosted checkout.

***

## Configure

The card flow talks to three hosts, and `Configuration` keeps them as three explicit URLs.

```swift
let configuration = Configuration(
    apiBaseURL: URL(string: "https://your-backend.example.com"),
    checkoutAPIBaseURL: URL(string: "https://sandbox-api.example.com"),
    checkoutBaseURL: URL(string: "https://sandbox-checkout.example.com"),
    environment: .sandbox
)

let paymentKit = PaymentKit(configuration: configuration)
```

| Property             | What it serves                                                                                                                   |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `apiBaseURL`         | Your backend, for hosted checkout's payment status lookup. Not used by the card form.                                            |
| `checkoutAPIBaseURL` | The client-facing checkout API: card session create and read, 3-D Secure authentication, and challenge result. Required.         |
| `checkoutBaseURL`    | The checkout page host, which serves the card encryption key and the page the issuer posts a challenge result back to. Required. |

None of the three is derived from another. Set each one explicitly for the environment you are targeting.

***

## Collect the card details

`CardInput` is a headless model with no views of its own. Fill it from whatever interface you built:

```swift
let card = CardInput()
card.cardholderName = nameField.text ?? ""
card.cardNumber = numberField.text ?? ""
card.expiryMonth = expiryField.month
card.expiryYear = expiryField.year
card.securityCode = securityCodeField.text ?? ""
```

`expiryMonth` is two digits (`"01"` through `"12"`) and `expiryYear` is two digits (`"30"`).

### Optional text fields

The SDK ships three `UITextField` subclasses that handle input behaviour and formatting only. They set no colours, fonts, borders or insets, so they inherit your app's appearance by doing nothing:

```swift
let numberField = CardNumberTextField()
let expiryField = CardExpiryTextField()
let securityCodeField = CardSecurityCodeTextField()

// Keep the security-code length in step with the detected card brand.
securityCodeField.brand = numberField.brand
```

| Field                       | Behaviour                                                                                                                                                                          |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CardNumberTextField`       | Groups digits as the shopper types, caps the length, and sets `textContentType = .creditCardNumber` so iOS autofill and Scan Card work. Exposes `digits` and the detected `brand`. |
| `CardExpiryTextField`       | Formats `MM/YY` and exposes `month` and `year` separately.                                                                                                                         |
| `CardSecurityCodeTextField` | Masks input and caps the digit count. Set its `brand` so the cap follows the card.                                                                                                 |

Using these is optional. `CardInput` does not care where the strings came from.

### Validate

```swift
if let error = card.validationError(for: .cardNumber) {
    numberField.showError(error.localizedDescription)
}
```

Or check everything at once before submitting:

```swift
guard card.isValid else { return }
```

`validationError(for:)` covers `.cardholderName`, `.cardNumber`, `.expiry` and `.securityCode`, and checks presence, Luhn, length, and that the expiry is in the future.

It does not check the card's brand against the methods your shop accepts. Brand detection in the SDK exists for display and input formatting. Whether a shop accepts a brand is decided server-side and comes back as a clear error, which keeps working when a shop's configuration changes without your app being rebuilt. See [Card brand not accepted](/integration/mobile-sdk/ios-sdk/guides/error-handling.md#card-brand-not-accepted).

***

## Start the payment

```swift
let request = CardPaymentRequest(
    paymentId: payment.paymentId,
    verificationKey: payment.verificationKey
)

paymentKit.startCardPayment(request, card: card, from: self) { result in
    switch result.outcome {
    case .succeeded:  self.showReceipt(reference: result.paymentId)
    case .failed:     self.showFailure(message: result.error?.localizedDescription)
    case .cancelled:  break
    case .unresolved: self.confirmWithBackend(paymentId: result.paymentId)
    }
}
```

`CardPaymentRequest` takes no checkout URL and no return URL. The shopper never leaves your app, so there is nothing to return from.

The guarantees match `startPayment`: exactly one callback, on the main thread, after any interface the SDK presented has been dismissed.

Most card payments present **no interface at all**. The SDK only puts something on screen when the issuer requires a 3-D Secure challenge, and that is presented modally over the view controller you pass as `from:`.

{% hint style="warning" %}
**Never log, persist, or send the raw card fields yourself.** The card number and security code exist only in `CardInput` and in the plaintext the SDK builds immediately before encrypting it. The SDK writes neither to disk and logs neither. Anything your own code does with those strings is outside that guarantee.
{% endhint %}

***

## What the SDK does

1. Validates the card input. Invalid details fail through your callback before any network call, so nothing reaches the network.
2. Fetches the card encryption key from `checkoutBaseURL` and encrypts the card data as a JWE (RSA-OAEP-256 key encryption, AES-256-GCM content encryption, compact serialization).
3. Creates the card session against `checkoutAPIBaseURL`, sending the encrypted card data.
4. Follows the session: if the issuer requires 3-D Secure, the SDK submits the authentication request and, when a challenge is needed, presents it and captures the result.
5. Delivers one result from the final session status: completed is `.succeeded`, failed is `.failed`. No separate status lookup is made, so `paymentStatus` and `orderNumber` are `nil` for card payments.

`result.sessionId` carries the card session identifier, which is useful for correlating with your backend. It is `nil` for hosted checkout.

***

## Offering both flows

One payment can be paid either way. An app that offers hosted checkout and its own card form makes the same single backend call and lets the shopper choose, then calls `startPayment` or `startCardPayment` with the same `paymentId` and `verificationKey`.

***

## Next steps

* [Handling the result](/integration/mobile-sdk/ios-sdk/using-the-sdk/handling-the-result.md)
* [Security](/integration/mobile-sdk/ios-sdk/guides/security.md)
* [API reference](/integration/mobile-sdk/ios-sdk/reference/api.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.ecommerce.seb.ee/integration/mobile-sdk/ios-sdk/using-the-sdk/native-card-form.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
