Skip to content

Commit ae9c590

Browse files
authored
feat: add native PCI-compliant credit card input (iOS + Android) (#51)
### Description Adds a `NativeCreditCard` component that replaces the WebView-based card input with platform-native text fields (`UITextField` on iOS, `EditText` on Android). Card data stays in native Swift/Kotlin memory and never enters the JavaScript heap. Tokenization uses NaCl-encrypted communication (Curve25519-XSalsa20-Poly1305 via vendored TweetNaCl) with Bolt's tokenizer service, matching the protocol used by the existing WebView iframe. The native component coexists with the existing WebView implementation — merchants opt in by changing a single import. Includes all PCI security controls: screen capture prevention (SR-7), accessibility hardening (SR-8), memory zeroing on all exit paths (SR-2), debug/simulator detection (SR-12), jailbreak/root detection (SR-13), runtime integrity checks (SR-15), ProGuard/R8 rules (SR-14), dark mode support, native `setStyles()` with full prop wiring, and validation error parity with the WebView. Transport security is provided by NaCl `crypto_box` end-to-end encryption — a MITM cannot decrypt the payload without the server's private key. Also bumps React Native from 0.84 to 0.85. ### Testing - **Unit tests**: 27 new tests across 3 test files (`NativeCreditCard.test.tsx`, `NativeCreditCardComponent.test.tsx`, `useNativeCreditCardController.test.ts`) covering namespace exports, component rendering, prop forwarding, all 4 event types, tokenize success/error/unmounted/malformed JSON/null fields/null native module, and `setStyles`. Updated `index.test.tsx` with export validation. All 147 tests pass. - **iOS manual**: `yarn example ios` → "Native" tab renders card fields, card brand icon appears on BIN detection (Visa/MC/Amex/Discover), PAN masks on blur, validation errors highlight red, tokenization succeeds with Bolt sandbox, screen capture overlay appears during recording, `setStyles()` applies changes, dark mode colors update on trait change. - **Android manual**: `yarn example android` → same flows verified with compound drawable icons, postal code watcher enables button, `FLAG_SECURE` applied on attach and cleared on detach, TalkBack announces "Card number ending in XXXX" without reading full PAN, dark mode colors respond to night mode. - **Coexistence**: "WebView" tab continues to work alongside "Native" tab with no interference. - **TypeScript**: `npx tsc --noEmit` passes with 0 errors. - **Lint**: `npx eslint` passes with 0 errors. - **npm pack**: should be tested with a fresh Expo project before release per established process. ### Security Review > [!IMPORTANT] > A security review is required for every PR in this repository to comply with PCI requirements. - [x] I have considered and reviewed security implications of this PR and included the summary below. #### Security Impact Summary This PR introduces native handling of cardholder data (PAN, CVV, expiry) in Swift and Kotlin. Security controls implemented: - **SR-1 CHD isolation**: Card data exists only in native `UInt8[]` / `CharArray` buffers, never crosses the JS bridge as strings. - **SR-2 Memory zeroing**: All card buffers explicitly zeroed after tokenization on every exit path (success, failure, timeout, view unmount). Local copies in the TurboModule completion handler also zeroed. Client secret key zeroed in-place after decryption (no Swift CoW copy). Postal field text cleared. - **SR-5 CVV masked**: `isSecureTextEntry` / `TYPE_NUMBER_VARIATION_PASSWORD` at all times. - **SR-6 PAN masked**: Only last 4 visible after field loses focus. - **SR-7 Screen capture**: iOS monitors `UIScreen.capturedDidChangeNotification` and overlays card fields when recording. Android applies `FLAG_SECURE` on attach and clears on detach. - **SR-8 Accessibility**: iOS PAN returns "ending in XXXX" for VoiceOver, CVV sets `isAccessibilityElement = false`. Android PAN uses custom `AccessibilityDelegate` showing masked value. - **SR-9 No disk persistence**: `autocorrectionType = .no`, `IMPORTANT_FOR_AUTOFILL_NO`, `TYPE_TEXT_FLAG_NO_SUGGESTIONS`, `dispatchFreezeSelfOnly` prevents `Bundle` serialization. - **SR-12 Debug detection**: Detects debug builds and simulators/emulators. Logs warning in production for jailbroken/rooted devices only — does not block during development. - **SR-13 Jailbreak/root detection**: iOS checks Cydia/apt paths and sandbox escape. Android checks su binaries, test-keys, and root management apps. Reports via `BoltDeviceIntegrity.integrityReport()`. - **SR-14 ProGuard**: `proguard-rules.pro` with `-keepclassmembers` for crypto and zeroing methods, `-keepnames` for bridge registration. Consumer rules applied via `defaultConfig`. - **SR-15 Runtime integrity**: iOS verifies code signing validity. Android verifies APK signature presence. - **Encrypted tokenization**: PAN/CVV encrypted via NaCl `crypto_box` (Curve25519-XSalsa20-Poly1305) before transmission. Plaintext bytes zeroed immediately after encryption. No `String` intermediaries for CHD (FR-3.4). Transport protection provided by end-to-end encryption — MITM cannot decrypt without server's private key. - **Crypto safety**: `SecRandomCopyBytes` return checked — aborts on PRNG failure. Empty token responses rejected. - **Dark mode**: Dynamic colors on both platforms prevent UI artifacts that could expose field state. - **PCI SSS certification**: In progress. Native component labeled as beta. WebView remains the production SAQ A path until certification completes.
1 parent 9b13d0e commit ae9c590

File tree

72 files changed

+6610
-887
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+6610
-887
lines changed

BoltReactNativeSdk.podspec

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ Pod::Spec.new do |s|
1313
s.platforms = { :ios => min_ios_version_supported }
1414
s.source = { :git => "https://github.com/BoltApp/bolt-react-native-sdk.git", :tag => "#{s.version}" }
1515

16-
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17-
s.private_header_files = "ios/**/*.h"
16+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp,c}"
17+
s.public_header_files = "ios/tweetnacl.h", "ios/tweetnacl-bridge.h"
18+
s.private_header_files = "ios/BoltReactNativeSdk.h"
19+
s.resource_bundles = { "BoltCardBrandAssets" => ["ios/CardBrandAssets/*.png"] }
1820

1921
install_modules_dependencies(s)
2022
end

README.md

Lines changed: 115 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Bolt React Native SDK for payments. Provides Credit Card tokenization, 3D Secure
88

99
## Architecture
1010

11-
- **Credit Card & 3DS** — WebView-based, loading secure pages from `connect.bolt.com`. Card data never touches your app (PCI compliant).
11+
- **Credit Card (WebView)** — WebView-based, loading secure pages from `connect.bolt.com`. Card data never touches your app (PCI SAQ A compliant).
12+
- **Credit Card (Native)** — Platform-native text fields (`UITextField` on iOS, `EditText` on Android). Card data stays in native memory, never enters the JS heap. Requires Bolt PCI SSS certification for SAQ A — see [Native Credit Card (Beta)](#7-native-credit-card-beta) below.
1213
- **Apple Pay & Google Pay** — Native Fabric view components for buttons (`PKPaymentButton` on iOS, `PayButton` on Android) with TurboModules for the payment sheet.
1314

1415
## Installation
@@ -269,6 +270,101 @@ cc.setStyles({
269270
});
270271
```
271272

273+
### 7. Native Credit Card (Beta)
274+
275+
> **Note:** The native card input is in beta. Card data is kept in native memory and never enters the JS heap. PCI SSS certification is in progress — contact your Bolt representative for the latest compliance guidance.
276+
277+
The `NativeCreditCard` component uses platform-native text fields instead of a WebView. Benefits include faster rendering (no WebView cold-start), native autofill, iOS 17+ card camera scanner support, and built-in security controls (screen capture prevention, accessibility protection, memory zeroing).
278+
279+
```typescript
280+
import { NativeCreditCard } from '@boltpay/react-native/payments';
281+
282+
function CheckoutScreen() {
283+
const cc = NativeCreditCard.useController();
284+
285+
cc.on('valid', () => setCanSubmit(true));
286+
cc.on('error', (msg) => setFieldError(msg));
287+
288+
const handlePayment = async () => {
289+
const result = await cc.tokenize();
290+
if (result instanceof Error) {
291+
console.error(result.message);
292+
return;
293+
}
294+
// result: { token?, last4?, bin?, network?, expiration?, postal_code? }
295+
};
296+
297+
return (
298+
<>
299+
<NativeCreditCard.Component
300+
controller={cc}
301+
showPostalCode={true}
302+
/>
303+
<Button onPress={handlePayment} title="Pay" />
304+
</>
305+
);
306+
}
307+
```
308+
309+
The `NativeCreditCard` API mirrors `CreditCard` — same `useController()` / `Component` / `on()` / `tokenize()` pattern. The two implementations coexist; merchants opt in to native by importing `NativeCreditCard` instead of `CreditCard`.
310+
311+
#### Styling
312+
313+
Native fields use `NativeCardFieldStyles` instead of CSS custom properties:
314+
315+
```typescript
316+
cc.setStyles({
317+
textColor: '#333333',
318+
fontSize: 16,
319+
placeholderColor: '#9ca3af',
320+
borderColor: '#d1d5db',
321+
borderRadius: 10,
322+
backgroundColor: '#fafafa',
323+
});
324+
```
325+
326+
Or pass initial styles via controller options:
327+
328+
```typescript
329+
const cc = NativeCreditCard.useController({
330+
styles: { textColor: '#333', borderRadius: 8 },
331+
});
332+
```
333+
334+
#### Migrating from WebView to Native
335+
336+
The migration is a one-line import change. The controller API (`on()`, `tokenize()`) and `TokenResult` shape are identical.
337+
338+
```diff
339+
- import { CreditCard } from '@boltpay/react-native/payments';
340+
+ import { NativeCreditCard } from '@boltpay/react-native/payments';
341+
342+
- const cc = CreditCard.useController({
343+
- styles: { '--bolt-input-backgroundColor': '#fafafa' },
344+
- showBillingZIPField: true,
345+
- });
346+
+ const cc = NativeCreditCard.useController();
347+
348+
- <CreditCard.Component controller={cc} style={styles.cardInput} />
349+
+ <NativeCreditCard.Component controller={cc} showPostalCode={true} style={styles.cardInput} />
350+
```
351+
352+
**What stays the same:**
353+
354+
- `cc.on('valid' | 'error' | 'focus' | 'blur', callback)` — identical API
355+
- `cc.tokenize()` — returns `Promise<TokenResult | Error>`, same shape
356+
- `TokenResult``{ token?, last4?, bin?, network?, expiration?, postal_code? }`
357+
358+
**What changes:**
359+
360+
| | WebView (`CreditCard`) | Native (`NativeCreditCard`) |
361+
| ------------------ | ---------------------------------------------------- | ------------------------------------------------------- |
362+
| Import | `CreditCard` | `NativeCreditCard` |
363+
| Postal code prop | `showBillingZIPField` (on controller options) | `showPostalCode` (on Component) |
364+
| Styling | CSS custom properties (`--bolt-*`) via `setStyles()` | `NativeCardFieldStyles` (`textColor`, `fontSize`, etc.) |
365+
| WebView dependency | Requires `react-native-webview` | No WebView needed |
366+
| PCI compliance | SAQ A (production-ready) | SAQ A pending PCI SSS certification (beta) |
367+
272368
## API Reference
273369

274370
### Root (`@boltpay/react-native`)
@@ -282,13 +378,15 @@ cc.setStyles({
282378

283379
### Payments (`@boltpay/react-native/payments`)
284380

285-
| Export | Description |
286-
| ------------------------------------ | ------------------------------------------------------------------------- |
287-
| `CreditCard.Component` | WebView-based credit card input |
288-
| `CreditCard.useController(options?)` | Returns a controller with `tokenize()`, `on()`, and `setStyles()` |
289-
| `useThreeDSecure()` | Hook returning `{ Component, fetchReferenceID(), challengeWithConfig() }` |
290-
| `ApplePay` | Native `PKPaymentButton` (iOS only, renders nothing on Android) |
291-
| `GoogleWallet` | Native Google Pay `PayButton` (Android only, renders nothing on iOS) |
381+
| Export | Description |
382+
| ------------------------------------------ | ------------------------------------------------------------------------- |
383+
| `CreditCard.Component` | WebView-based credit card input (PCI SAQ A) |
384+
| `CreditCard.useController(options?)` | Returns a controller with `tokenize()`, `on()`, and `setStyles()` |
385+
| `NativeCreditCard.Component` | Native platform credit card input (Beta — PCI SSS pending) |
386+
| `NativeCreditCard.useController(options?)` | Returns a controller with `tokenize()`, `on()`, and `setStyles()` |
387+
| `useThreeDSecure()` | Hook returning `{ Component, fetchReferenceID(), challengeWithConfig() }` |
388+
| `ApplePay` | Native `PKPaymentButton` (iOS only, renders nothing on Android) |
389+
| `GoogleWallet` | Native Google Pay `PayButton` (Android only, renders nothing on iOS) |
292390

293391
### Credit Card Controller
294392

@@ -313,15 +411,15 @@ cc.setStyles({
313411

314412
### GoogleWallet Props
315413

316-
| Prop | Type | Default | Description |
317-
| -------------- | --------------------------- | --------- | ----------------------------------------------------------------------------------------------------- |
414+
| Prop | Type | Default | Description |
415+
| -------------- | --------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- |
318416
| `config` | `GooglePayConfig` | required | Presentation options: currency, amount, label, billing address format. Merchant config is auto-fetched from Bolt. |
319-
| `onComplete` | `(GooglePayResult) => void` | required | Called with token, bin, expiration, and billing address on success |
320-
| `onError` | `(Error) => void` || Called on payment failure or cancellation |
321-
| `buttonType` | `GooglePayButtonType` | `'plain'` | Maps to Google Pay `ButtonConstants.ButtonType`. Button text is rendered natively and auto-localized. |
322-
| `buttonTheme` | `GooglePayButtonTheme` | `'dark'` | Button color theme: `'dark'` or `'light'`. Maps to `ButtonConstants.ButtonTheme`. |
323-
| `borderRadius` | `number` || Corner radius in dp applied to the Google Pay button |
324-
| `style` | `ViewStyle` || Container style overrides (height, margin, etc.) |
417+
| `onComplete` | `(GooglePayResult) => void` | required | Called with token, bin, expiration, and billing address on success |
418+
| `onError` | `(Error) => void` || Called on payment failure or cancellation |
419+
| `buttonType` | `GooglePayButtonType` | `'plain'` | Maps to Google Pay `ButtonConstants.ButtonType`. Button text is rendered natively and auto-localized. |
420+
| `buttonTheme` | `GooglePayButtonTheme` | `'dark'` | Button color theme: `'dark'` or `'light'`. Maps to `ButtonConstants.ButtonTheme`. |
421+
| `borderRadius` | `number` || Corner radius in dp applied to the Google Pay button |
422+
| `style` | `ViewStyle` || Container style overrides (height, margin, etc.) |
325423

326424
### 3D Secure
327425

@@ -339,6 +437,7 @@ cc.setStyles({
339437
- `ThreeDSError` — Error subclass with numeric `code` (1001–1010)
340438
- `CreditCardId``{ id: string, expiration: string }` (from Bolt's Add Card API)
341439
- `CreditCardInfo``CreditCardId | TokenResult` (input for `fetchReferenceID`)
440+
- `NativeCardFieldStyles``{ textColor?, fontSize?, placeholderColor?, borderColor?, borderWidth?, borderRadius?, backgroundColor?, fontFamily? }` (for `NativeCreditCard`)
342441
- `EventType``'error' | 'valid' | 'blur' | 'focus'`
343442
- `ApplePayResult``{ token, bin?, expiration?, billingContact?, boltReference? }`
344443
- `ApplePayButtonType` — Apple-approved button label variants (`'plain'`, `'buy'`, `'checkout'`, `'book'`, `'subscribe'`, `'donate'`, `'order'`, `'setUp'`, `'inStore'`, `'reload'`, `'addMoney'`, `'topUp'`, `'rent'`, `'support'`, `'contribute'`, `'tip'`)

android/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ android {
4646
buildConfig true
4747
}
4848

49+
defaultConfig {
50+
consumerProguardFiles 'proguard-rules.pro'
51+
}
52+
4953
buildTypes {
5054
release {
5155
minifyEnabled false

android/proguard-rules.pro

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# SR-14: ProGuard/R8 rules for credit card data handling classes.
2+
#
3+
# These rules ensure critical crypto and zeroing methods are not
4+
# optimized away or inlined by R8. Class names are NOT kept (allowing
5+
# obfuscation) — only the specific methods needed for correctness.
6+
7+
# Keep NaCl crypto methods from being removed or inlined
8+
-keepclassmembers class com.boltreactnativesdk.creditcardfield.TweetNaCl {
9+
public static int crypto_box_keypair(byte[], byte[]);
10+
public static int crypto_box(byte[], byte[], long, byte[], byte[], byte[]);
11+
public static int crypto_box_open(byte[], byte[], long, byte[], byte[], byte[]);
12+
public static void randombytes(byte[]);
13+
}
14+
15+
# Ensure buffer zeroing calls are not optimized away
16+
-keepclassmembers class com.boltreactnativesdk.creditcardfield.BoltCardFieldView {
17+
public void zeroAllBuffers();
18+
}
19+
20+
# Keep React Native ViewManager and Module names for bridge registration
21+
-keepnames class com.boltreactnativesdk.creditcardfield.BoltCardFieldManager
22+
-keepnames class com.boltreactnativesdk.creditcardfield.BoltCardFieldModule

android/src/main/java/com/boltreactnativesdk/BoltReactNativeSdkPackage.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ import com.facebook.react.bridge.ReactApplicationContext
66
import com.facebook.react.module.model.ReactModuleInfo
77
import com.facebook.react.module.model.ReactModuleInfoProvider
88
import com.facebook.react.uimanager.ViewManager
9+
import com.boltreactnativesdk.creditcardfield.BoltCardFieldManager
10+
import com.boltreactnativesdk.creditcardfield.BoltCardFieldModule
911

1012
class BoltReactNativeSdkPackage : BaseReactPackage() {
1113
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
1214
return when (name) {
1315
BoltReactNativeSdkModule.NAME -> BoltReactNativeSdkModule(reactContext)
1416
GooglePayModule.NAME -> GooglePayModule(reactContext)
1517
NetworkingModule.NAME -> NetworkingModule(reactContext)
18+
BoltCardFieldModule.NAME -> BoltCardFieldModule(reactContext)
1619
else -> null
1720
}
1821
}
@@ -42,13 +45,21 @@ class BoltReactNativeSdkPackage : BaseReactPackage() {
4245
needsEagerInit = false,
4346
isCxxModule = false,
4447
isTurboModule = false
48+
),
49+
BoltCardFieldModule.NAME to ReactModuleInfo(
50+
name = BoltCardFieldModule.NAME,
51+
className = BoltCardFieldModule.NAME,
52+
canOverrideExistingModule = false,
53+
needsEagerInit = false,
54+
isCxxModule = false,
55+
isTurboModule = false
4556
)
4657
)
4758
}
4859

4960
override fun createViewManagers(
5061
reactContext: ReactApplicationContext
5162
): List<ViewManager<*, *>> {
52-
return listOf(GooglePayButtonViewManager())
63+
return listOf(GooglePayButtonViewManager(), BoltCardFieldManager())
5364
}
5465
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.boltreactnativesdk.creditcardfield
2+
3+
import com.facebook.react.bridge.Arguments
4+
import com.facebook.react.bridge.WritableMap
5+
import com.facebook.react.uimanager.events.Event
6+
7+
/**
8+
* DirectEventHandler events for BoltCreditCardField.
9+
* ADR-4: Use direct events (not bubbling) for component-scoped state signals.
10+
*
11+
* Event names use the "top" prefix convention required by Fabric.
12+
*/
13+
14+
class OnValidEvent(surfaceId: Int, viewId: Int) : Event<OnValidEvent>(surfaceId, viewId) {
15+
override fun getEventName(): String = "topCardValid"
16+
override fun getEventData(): WritableMap = Arguments.createMap()
17+
}
18+
19+
class OnErrorEvent(surfaceId: Int, viewId: Int, private val message: String) :
20+
Event<OnErrorEvent>(surfaceId, viewId) {
21+
override fun getEventName(): String = "topCardError"
22+
override fun getEventData(): WritableMap = Arguments.createMap().apply {
23+
putString("message", message)
24+
}
25+
}
26+
27+
class OnFocusEvent(surfaceId: Int, viewId: Int) : Event<OnFocusEvent>(surfaceId, viewId) {
28+
override fun getEventName(): String = "topCardFocus"
29+
override fun getEventData(): WritableMap = Arguments.createMap()
30+
}
31+
32+
class OnBlurEvent(surfaceId: Int, viewId: Int) : Event<OnBlurEvent>(surfaceId, viewId) {
33+
override fun getEventName(): String = "topCardBlur"
34+
override fun getEventData(): WritableMap = Arguments.createMap()
35+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.boltreactnativesdk.creditcardfield
2+
3+
import com.facebook.react.module.annotations.ReactModule
4+
import com.facebook.react.uimanager.SimpleViewManager
5+
import com.facebook.react.uimanager.ThemedReactContext
6+
import com.facebook.react.uimanager.ViewManagerDelegate
7+
import com.facebook.react.uimanager.annotations.ReactProp
8+
import com.facebook.react.viewmanagers.BoltCreditCardFieldManagerDelegate
9+
import com.facebook.react.viewmanagers.BoltCreditCardFieldManagerInterface
10+
11+
/**
12+
* Fabric ViewManager for the native credit card input.
13+
*
14+
* The @ReactModule name MUST exactly match the string passed to
15+
* codegenNativeComponent('BoltCreditCardField') in the TypeScript spec.
16+
*/
17+
@ReactModule(name = BoltCardFieldManager.NAME)
18+
class BoltCardFieldManager :
19+
SimpleViewManager<BoltCardFieldView>(),
20+
BoltCreditCardFieldManagerInterface<BoltCardFieldView> {
21+
22+
companion object {
23+
const val NAME = "BoltCreditCardField"
24+
}
25+
26+
private val delegate = BoltCreditCardFieldManagerDelegate(this)
27+
28+
override fun getDelegate(): ViewManagerDelegate<BoltCardFieldView> = delegate
29+
30+
override fun getName(): String = NAME
31+
32+
override fun createViewInstance(context: ThemedReactContext): BoltCardFieldView {
33+
return BoltCardFieldView(context)
34+
}
35+
36+
@ReactProp(name = "publishableKey")
37+
override fun setPublishableKey(view: BoltCardFieldView, key: String?) {
38+
// Stored for use by the companion TurboModule during tokenize
39+
}
40+
41+
@ReactProp(name = "showPostalCode", defaultBoolean = false)
42+
override fun setShowPostalCode(view: BoltCardFieldView, show: Boolean) {
43+
view.updateShowPostalCode(show)
44+
}
45+
46+
// Style props
47+
@ReactProp(name = "styleTextColor")
48+
override fun setStyleTextColor(view: BoltCardFieldView, color: String?) {
49+
view.applyStyleTextColor(color)
50+
}
51+
52+
@ReactProp(name = "styleFontSize", defaultFloat = 0f)
53+
override fun setStyleFontSize(view: BoltCardFieldView, size: Float) {
54+
view.applyStyleFontSize(size)
55+
}
56+
57+
@ReactProp(name = "stylePlaceholderColor")
58+
override fun setStylePlaceholderColor(view: BoltCardFieldView, color: String?) {
59+
view.applyStylePlaceholderColor(color)
60+
}
61+
62+
@ReactProp(name = "styleBorderColor")
63+
override fun setStyleBorderColor(view: BoltCardFieldView, color: String?) {
64+
view.applyStyleBorderColor(color)
65+
}
66+
67+
@ReactProp(name = "styleBorderWidth", defaultFloat = 0f)
68+
override fun setStyleBorderWidth(view: BoltCardFieldView, width: Float) {
69+
view.applyStyleBorderWidth(width)
70+
}
71+
72+
@ReactProp(name = "styleBorderRadius", defaultFloat = 0f)
73+
override fun setStyleBorderRadius(view: BoltCardFieldView, radius: Float) {
74+
view.applyStyleBorderRadius(radius)
75+
}
76+
77+
@ReactProp(name = "styleBackgroundColor")
78+
override fun setStyleBackgroundColor(view: BoltCardFieldView, color: String?) {
79+
view.applyStyleBackgroundColor(color)
80+
}
81+
82+
@ReactProp(name = "styleFontFamily")
83+
override fun setStyleFontFamily(view: BoltCardFieldView, family: String?) {
84+
view.applyStyleFontFamily(family)
85+
}
86+
87+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
88+
return mapOf(
89+
"topCardValid" to mapOf("registrationName" to "onCardValid"),
90+
"topCardError" to mapOf("registrationName" to "onCardError"),
91+
"topCardFocus" to mapOf("registrationName" to "onCardFocus"),
92+
"topCardBlur" to mapOf("registrationName" to "onCardBlur"),
93+
)
94+
}
95+
}

0 commit comments

Comments
 (0)