# Cards

Card payments are handled by the Banxa React Native SDK, which presents Primer's card input drawer — a bottom sheet that overlays your app. The user enters their card details, and Primer handles PCI-compliant card capture and 3D Secure authentication. Your app never touches raw card data.

This guide covers payment execution and 3DS SDK installation only. Identity creation, KYC, eligibility, and React Native SDK installation are covered in the [Integration Guide](/products/native-api/docs/guides/foundations) — start there if you haven't set those up yet. This guide picks up from `paymentReady: true`.

## Execute payment

After your backend confirms `paymentReady: true` from eligibility, call `createOrderAndShowPrimerCheckout` with `paymentMethod: 'card'`:


```typescript
import {
  isPrimerCheckoutWebViewResult,
} from '@banxa-official/react-native-sdk';

const result = await banxa.buy.createOrderAndShowPrimerCheckout(orderRequest, {
  paymentMethod: 'card',
  callbacks: {
    onCheckoutComplete: () => {
      // Payment confirmed — transaction is in progress
    },
    onError: (error) => {
      // Payment failed — see error handling below
    },
    onDismiss: () => {
      // User dismissed the card drawer without completing
    },
  },
});

if (isPrimerCheckoutWebViewResult(result)) {
  // SDK fell back to hosted WebView
  render(<CheckoutWebView {...result.webViewProps} />);
}
```

The `orderRequest` must include `externalCustomerId` — pass the same `identityReference` you used for eligibility.

## Install the 3DS SDK

Card payments require Primer's 3DS SDK in addition to the Banxa React Native SDK. Install this before invoking `createOrderAndShowPrimerCheckout` — the card payment sheet will not initialise without it.

The version you install must match the version of `@primer-io/react-native` in your project — see the [interoperability matrix](#interoperability-matrix) below.

### iOS

Adding 3D Secure on iOS requires the `Primer3DS` pod. The configuration depends on how your project links CocoaPods. Run `bundle exec pod install` in your `ios` folder to check:

- `Framework build type is static library` → [Static library](#static-library)
- `Configuring Pod with dynamically linked Frameworks` → [Dynamic framework](#dynamic-framework)
- `Configuring Pod with statically linked Frameworks` → [Static framework](#static-framework)


#### Static library

The default for most React Native apps.

Add `pod 'Primer3DS'` to your target and configure the `post_install` block:


```ruby
target 'MyApp' do
  # ... other CocoaPods config ...
  pod 'Primer3DS'
  post_install do |installer|
    installer.pods_project.targets.each do |target|
      if target.name == "primer-io-react-native" || target.name == "PrimerSDK"
        target.build_configurations.each do |config|
          config.build_settings['FRAMEWORK_SEARCH_PATHS'] = "$(inherited) ${PODS_CONFIGURATION_BUILD_DIR}/Primer3DS"
          config.build_settings['LIBRARY_SEARCH_PATHS'] = "$(inherited) ${PODS_CONFIGURATION_BUILD_DIR}/Primer3DS"
          config.build_settings['SWIFT_INCLUDE_PATHS'] = "$(inherited) ${PODS_CONFIGURATION_BUILD_DIR}/Primer3DS"
          # Use this line for static libraries:
          config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Primer3DS/Primer3DS.framework/Modules/Primer3DS.modulemap"'
          config.build_settings['OTHER_LDFLAGS'] = '$(inherited) -weak_library -l"Primer3DS"'
        end
      end
    end
  end
end
```

Run `bundle exec pod install` to finish.

#### Dynamic framework

Add `pod 'Primer3DS'` to your target — no additional config needed:


```ruby
target 'MyApp' do
  # ... other RN iOS config ...
  pod 'Primer3DS'
end
```

Run `bundle exec pod install` to finish. The 3DS pod is detected automatically at runtime.

#### Static framework

Static frameworks require the `cocoapods-pod-linkage` plugin to allow the 3DS pod to be loaded dynamically at runtime.

Add to your `Gemfile`:


```ruby
gem 'cocoapods-pod-linkage'
```

Run `bundle install`, then add the plugin to your `Podfile`:


```ruby
plugin 'cocoapods-pod-linkage'
```

Configure your target to link `PrimerSDK` and `Primer3DS` dynamically:


```ruby
use_frameworks! :linkage => :static

target 'MyApp' do
  # ... other CocoaPods config ...
  pod 'PrimerSDK', :linkage => :dynamic
  pod 'Primer3DS', :linkage => :dynamic

  post_install do |installer|
    installer.pods_project.targets.each do |target|
      if target.name == "primer-io-react-native" || target.name == "PrimerSDK"
        target.build_configurations.each do |config|
          config.build_settings['FRAMEWORK_SEARCH_PATHS'] = "$(inherited) ${PODS_CONFIGURATION_BUILD_DIR}/Primer3DS"
          config.build_settings['LIBRARY_SEARCH_PATHS'] = "$(inherited) ${PODS_CONFIGURATION_BUILD_DIR}/Primer3DS"
          config.build_settings['SWIFT_INCLUDE_PATHS'] = "$(inherited) ${PODS_CONFIGURATION_BUILD_DIR}/Primer3DS"
          config.build_settings['OTHER_LDFLAGS'] = '$(inherited) -framework Primer3DS'
        end
      end
    end
  end
end
```

Run `bundle exec pod install` to finish.

### Android

Add the `io.primer:3ds-android` library to the `dependencies` block in your app's `build.gradle`. Replace `{version}` with the correct version from the [interoperability matrix](#interoperability-matrix):


```groovy
dependencies {
  // ... other dependencies ...
  implementation "io.primer:3ds-android:{version}"
}
```

### Interoperability matrix

The version of `io.primer:3ds-android` and `Primer3DS` you install must match the version of `@primer-io/react-native` in your project.

| `@primer-io/react-native` | `io.primer:3ds-android` | `Primer3DS` (iOS) |
|  --- | --- | --- |
| >= 2.42.0 | 1.8.0 | >= 2.7.0 |
| 2.41.0 – 2.42.0 | 1.7.0 | 2.6.0 – 2.7.0 |
| 2.35.0 – 2.40.0 | 1.6.2 | 2.4.2 – 2.5.x |
| 2.34.1 – 2.35.0 | 1.6.2 | 2.3.0 – 2.4.2 |
| 2.27.2 – 2.34.0 | 1.5.0 | 2.3.0 – 2.4.2 |
| 2.24.0 – 2.27.1 | 1.4.3 | 2.3.0 – 2.4.2 |
| 2.21.0 – 2.23.0 | 1.4.2 | 2.3.0 – 2.4.2 |
| 2.20.0 | 1.4.0 | 2.1.0 – 2.2.1 |
| 2.19.0 – 2.19.2 | 1.3.0 | 2.1.0 – 2.2.1 |
| 2.17.2 – 2.18.0 | 1.2.0 | 2.0.0 – 2.0.2 |
| 2.16.0 – 2.17.1 | 1.1.2 | 2.0.0 – 2.0.2 |
| 2.15.0 – 2.15.1 | 1.1.1 | 2.0.0 – 2.0.2 |


## OOB redirects (optional)

From 3DS protocol version 2.2.0, you can enable automatic redirect back to your app after an out-of-band (OOB) authentication challenge completes in another app. Without this, users must return to your app manually — which adds friction at the payment step.

To enable OOB redirects, pass `threeDsAppRequestorUrl` in your Primer settings. The iOS value must be an [iOS Universal Link](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content) and the Android value must be an [Android App Link](https://developer.android.com/training/app-links).


```typescript
import { Banxa } from '@banxa-official/react-native-sdk';

const banxa = new Banxa({
  apiKey: 'YOUR_API_KEY',
  partner: 'your-partner-id',
  environment: 'production',
  primerSettings: {
    paymentMethodOptions: {
      threeDsOptions: {
        iOS: {
          threeDsAppRequestorUrl: 'https://yourdomain.com/3ds',
        },
        android: {
          threeDsAppRequestorUrl: 'https://yourdomain.com/3ds',
        },
      },
    },
  },
});
```

See Apple's [Universal Links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content) and Google's [Android App Links](https://developer.android.com/training/app-links) documentation for setup steps.

## Testing

Use Banxa's sandbox environment during development:


```typescript
const banxa = new Banxa({
  environment: 'sandbox',
  // ...
});
```

If your app is not installed from a trusted source — a debug build, not installed from the store, or running on an emulator — 3DS initialisation may fail due to security checks. Disable the sanity check in development:


```typescript
const banxa = new Banxa({
  // ...
  primerSettings: {
    debugOptions: {
      is3DSSanityCheckEnabled: false,
    },
  },
});
```

Development only
Set `is3DSSanityCheckEnabled: false` only in development builds. Do not ship this to production.

For test cards and 3DS challenge scenarios, see [Primer's 3DS Testing Guide](https://primer.io/docs/payment-services/3d-secure/testing).

## Going live

Before releasing to production:

- Remove `is3DSSanityCheckEnabled: false` from your configuration
- Switch to production credentials in the SDK
- Confirm with your Banxa integration contact that your account is provisioned for production card payments