**Last updated**: 30 October 2025 | [**Change log**](/access/products/checkout/ios/changelog/)

# Card Validation

Validate your customer's card details before processing them.

Warning
The validation does not check if your customer's card details are correct. The validation only checks the format of the entered details.

## Get started

* you have added the `AccessCheckoutSDK` to your project as a Cocoapods dependency
* you have added an `import AccessCheckoutSDK` at the top of your swift file


The four basic components for this integration are:

* Your UITextField's for PAN, expiry date and CVC
* An `AccessCheckoutCardValidationDelegate` is designed to receive events pertaining to validation
* An instance of `CardValidationConfig` contains all the information required for the initialization of the validation flow, including references to the view components to enable validation for
* An `AccessCheckoutValidationInitialiser` is responsible for initializing the validation flow


Full Sample Integration
You can see an example of the CVC validation integration [here](https://github.com/Worldpay/access-checkout-ios/tree/v2.4.1/AccessCheckoutDemo).

## Create and reference the UI components

To display your checkout form, you must create your layout first using your storyboard.

Here's an example of how you would reference your UI components using unique identifiers.


```swift
import AccessCheckoutSDK

class ViewController: UIViewController {

    @IBOutlet weak var panTextField: UITextField!
    @IBOutlet weak var expiryDateTextField: UITextField!
    @IBOutlet weak var cvcTextField: UITextField!
    ...
```

## Implement the `AccessCheckoutCardValidationDelegate` protocol

This ensures you are notified of validation events and card brand changes on customer input.

Here's an example.


```swift
extension ViewController: AccessCheckoutCardValidationDelegate {

    // This event handler is notified when the card brand detected by the SDK has changed
    func cardBrandChanged(cardBrand: CardBrand?) {
        // This piece of code updates in the UI a card image displayed in a UIImageView next to the PAN
        // A cardBrand contains a name and an array of images objects.
        // Each image object has a URL and a type (either image/png or image/svg+xml) and represents the same brand 
        if let imageUrl = cardBrand?.images.filter({ $0.type == "image/png" }).first?.url, let url = URL(string: imageUrl) {
            DispatchQueue.global(qos: .userInteractive).async {
                if let data = try? Data(contentsOf: url) {
                    DispatchQueue.main.async {
                        self.imageView.image = UIImage(data: data)
                    }
                }
            }
        } else {
            // This occurs when the SDK has not been able to identify the card brand from the PAN entered by the customer
            self.imageView.image = ...
        }
    }

    // This event handler is notified when the PAN becomes valid or invalid
    func panValidChanged(isValid: Bool) {
        // You might want to change the text color
        panTextField.textColor = isValid ? nil : UIColor.red
        
        if !valid {
            // You might want to disable a submit button which would normally be clicked on when all fields are valid
            submitButton.isEnabled = false
        }
    }

    // This event handler is notified when the CVC becomes valid or invalid
    func cvcValidChanged(isValid: Bool) {
        // You might want to change the text color
        cvcTextField.textColor = isValid ? nil : UIColor.red
        
        if !valid {
            // You might want to disable a submit button which would normally be clicked on when all fields are valid
            submitButton.isEnabled = false
        }
    }

    // This event handler is notified when the expiry date becomes valid or invalid
    func expiryDateValidChanged(isValid: Bool) {
        // You might want to change the text color
        expiryDateTextField.textColor = isValid ? nil : UIColor.red
        
        if !valid {
            // You might want to disable a submit button which would normally be clicked on when all fields are valid
            submitButton.isEnabled = false
        }
    }

    // This event handler is notified when the PAN, expiry date and CVC are all valid
    func validationSuccess() {
        // You might want to enable a submit button when all fields are valid
        submitButton.isEnabled = true
    }
}
```

## Instantiate a `CardValidationConfig` and initialize the validation

We highly recommend to do this in the `viewDidLoad()` handler of your UIViewController. This is because the `AccessCheckoutValidationInitialiser` asynchronously requests a file containing validation configuration.


```swift
override func viewDidLoad() {
    ...
    let validationConfig = try! CardValidationConfig.builder()
        .pan(panTextField)
        .expiryDate(expiryDateTextField)
        .cvc(cvcTextField()
        .accessBaseUrl(<ACCESS_BASE_URL>)
        .validationDelegate(self)
        .build()
    
    AccessCheckoutValidationInitialiser().initialise(validationConfig)
}
```

| Placeholder | Descriptions |
|  --- | --- |
| `<ACCESS_BASE_URL>` | For testing use: `https://try.access.worldpay-bsh.securedataplatform.com/`For live use: `https://access.worldpay-bsh.securedataplatform.com/` |


#### Card validation rules

The validation logic in the SDK, is based off a set of default rules for any card type. Specific brand rules are fetched from a `CardTypes` JSON file, which holds the validation rules and card brand logos for each brand. The icons are available in both `SVG` and `PNG`.

#### Validation rules

The table below shows the rules that our SDK uses to validate your customer's card details.

| Card Name | BIN Range | PAN Length | CVC Length |
|  --- | --- | --- | --- |
| Amex | 34, 37 | 15 | 4 |
| Diners | 300-305, 3095, 36, 38, 39 | 14, 16, 19 | 3 |
| Discover | 6011, 644 - 649, 65 | 16, 19 | 3 |
| JCB | 2131, 1800, 3088 - 3094, 3096 - 3102, 3112 - 3120, 3158 - 3159, 3337 - 3349, 3528 - 3589 | 16, 17, 18, 19 | 3 |
| Maestro | 493698, 500000 - 506698, 506779 - 508999, 56 - 59, 63, 67, 6 | 12, 13, 14, 15, 16, 17, 18, 19 | 3 |
| MasterCard | 51 - 55, 2221 - 2229, 223 - 229, 23 - 26, 270 - 271, 2720: optimized using 22 - 27 | 16 | 3 |
| Visa | 4 | 13, 16, 18, 19 | 3 |


## Card brand restriction

The SDK enables you to optionally provide a list of card brands that you support. This means that if you do not support a certain card brand, the SDK notifies your code of an invalid PAN if an unsupported brand is recognized.

By default, the SDK allows cards from any brand. If you do not wish to restrict the card brands that you accept then you do not need to pass any configuration.

#### Example Configuration

To restrict the card brands that you accept, simply pass in an array of the brands that you **do** wish to accept when initializing the SDK.

The following validation configuration restricts the SDK to accept only American Express, Visa or Mastercard BIN ranges.


```swift
let validationConfig = try! CardValidationConfig.builder()
    ...
    .acceptedCardBrands(["visa", "mastercard", "AMEX"])
    ...
    .build()
```

#### Currently supported card brands

The SDK is able to recognize the following card brands:

| Brand | Code |
|  --- | --- |
| American Express | `"amex"` |
| Diners | `"diners"` |
| Discover | `"discover"` |
| JCB | `"jcb"` |
| Maestro | `"maestro"` |
| Mastercard | `"mastercard"` |
| Visa | `"visa"` |


Note
If the SDK does not recognize a PAN as one of the above brands, it will be permitted as long as it meets the usual criteria for a valid PAN.

## PAN formatting

The SDK allows PAN formatting as the customer types. This feature is disabled by default.

The PAN is formatted in the following way:

| Card Type | Formatting |
|  --- | --- |
| Visa, Mastercard, JCB, Discover, Diners, Maestro | XXXX XXXX XXXX XXXX |
| Amex | XXXX XXXXXX XXXXX |


#### Enabling PAN formatting

To enable the PAN formatting behavior, simply call the `enablePanFormatting` method on the builder.


```swift
let validationConfig = try! CardValidationConfig.builder()
    ...
    .enablePanFormatting()
    ...
    .build()
```

### Create session(s)

Once validation is in place you can request `sessions` to implement your payment flow.

**Next steps**

[Create a session to pay with a card](/access/products/checkout/ios/v2/card-only) or 
[Create sessions to pay with a card and cvc](/access/products/checkout/ios/v2/card-and-cvc)