Skip to content

Commit

Permalink
feat: add disabled prop to CardField and CardForm (stripe#1403)
Browse files Browse the repository at this point in the history
* feat: add disabled prop to CardField and CardForm

* [skip actions] changelog
  • Loading branch information
charliecruzan-stripe authored May 30, 2023
1 parent 364341a commit 6b834ce
Show file tree
Hide file tree
Showing 15 changed files with 53 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

## Features

- Added a `disabled` prop to `CardField` and `CardForm` which applies a disabled state such that user input is not accepted. [#1403](https://github.com/stripe/stripe-react-native/pull/1403)

## Fixes

- Fixed an instance on Android where `collectBankAccountToken` or `collectFinancialConnectionsAccounts` could result in a fatal error. [#1401](https://github.com/stripe/stripe-react-native/pull/1401)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) {
}
}

fun setDisabled(isDisabled: Boolean) {
mCardWidget.isEnabled = !isDisabled
}

/**
* We can reliable assume that setPostalCodeEnabled is called before
* setCountryCode because of the order of the props in CardField.tsx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ class CardFieldViewManager : SimpleViewManager<CardFieldView>() {
view.setPlaceHolders(placeholders)
}

@ReactProp(name = "disabled")
fun setDisabled(view: CardFieldView, isDisabled: Boolean) {
view.setDisabled(isDisabled)
}

override fun createViewInstance(reactContext: ThemedReactContext): CardFieldView {
val stripeSdkModule: StripeSdkModule? = reactContext.getNativeModule(StripeSdkModule::class.java)
val view = CardFieldView(reactContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) {
setCountry(defaults.getString("countryCode"))
}

fun setDisabled(isDisabled: Boolean) {
cardForm.isEnabled = !isDisabled
}

private fun setCountry(countryString: String?) {
if (countryString != null) {
cardFormViewBinding.countryLayout.setSelectedCountryCode(CountryCode(countryString))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ class CardFormViewManager : SimpleViewManager<CardFormView>() {
view.setDefaultValues(defaults)
}

@ReactProp(name = "disabled")
fun setDisabled(view: CardFormView, isDisabled: Boolean) {
view.setDisabled(isDisabled)
}

override fun createViewInstance(reactContext: ThemedReactContext): CardFormView {
val stripeSdkModule: StripeSdkModule? = reactContext.getNativeModule(StripeSdkModule::class.java)
val view = CardFormView(reactContext)
Expand Down
4 changes: 4 additions & 0 deletions example/src/screens/MultilineWebhookPaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default function MultilineWebhookPaymentScreen() {
const [email, setEmail] = useState('');
const [saveCard, setSaveCard] = useState(false);
const [isComplete, setComplete] = useState(false);
const [inputDisabled, setInputDisabled] = useState(false);

const { confirmPayment, loading } = useConfirmPayment();

Expand All @@ -42,6 +43,7 @@ export default function MultilineWebhookPaymentScreen() {

const handlePayPress = async () => {
setComplete(false);
setInputDisabled(true);
// 1. fetch Intent Client Secret from backend
const clientSecret = await fetchPaymentIntentClientSecret();

Expand Down Expand Up @@ -80,6 +82,7 @@ export default function MultilineWebhookPaymentScreen() {
console.log('Success from promise', paymentIntent);
}
setComplete(true);
setInputDisabled(false);
};

return (
Expand All @@ -92,6 +95,7 @@ export default function MultilineWebhookPaymentScreen() {
style={styles.input}
/>
<CardForm
disabled={inputDisabled}
placeholders={{
number: '4242 4242 4242 4242',
postalCode: '12345',
Expand Down
4 changes: 3 additions & 1 deletion example/src/screens/WebhookPaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default function WebhookPaymentScreen() {
};

const handlePayPress = async () => {
setCanPay(false);
// 1. fetch Intent Client Secret from backend
const clientSecret = await fetchPaymentIntentClientSecret();

Expand All @@ -51,7 +52,7 @@ export default function WebhookPaymentScreen() {
postalCode: '77063',
},
}; // mocked data for tests
setCanPay(false);

// 3. Confirm payment with card details
// The rest will be done automatically using webhooks
const { error, paymentIntent } = await confirmPayment(
Expand Down Expand Up @@ -90,6 +91,7 @@ export default function WebhookPaymentScreen() {
style={styles.input}
/>
<CardField
disabled={!canPay}
postalCodeEnabled={false}
autofocus
placeholders={{
Expand Down
1 change: 1 addition & 0 deletions ios/CardFieldManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ @interface RCT_EXTERN_MODULE(CardFieldManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(cardStyle, NSDictionary)
RCT_EXPORT_VIEW_PROPERTY(placeholders, NSDictionary)
RCT_EXPORT_VIEW_PROPERTY(autofocus, BOOL)
RCT_EXPORT_VIEW_PROPERTY(disabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(dangerouslyGetFullCardDetails, BOOL)
RCT_EXTERN_METHOD(focus:(nonnull NSNumber*) reactTag)
RCT_EXTERN_METHOD(blur:(nonnull NSNumber*) reactTag)
Expand Down
6 changes: 6 additions & 0 deletions ios/CardFieldView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ class CardFieldView: UIView, STPPaymentCardTextFieldDelegate {
public var cardParams: STPPaymentMethodParams? = nil
public var cardPostalCode: String? = nil

@objc var disabled: Bool = false {
didSet {
cardField.isUserInteractionEnabled = !disabled
}
}

@objc var postalCodeEnabled: Bool = true {
didSet {
cardField.postalCodeEntryEnabled = postalCodeEnabled
Expand Down
4 changes: 2 additions & 2 deletions ios/CardFormManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ @interface RCT_EXTERN_MODULE(CardFormManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(onFormComplete, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(dangerouslyGetFullCardDetails, BOOL)
RCT_EXPORT_VIEW_PROPERTY(autofocus, BOOL)
RCT_EXPORT_VIEW_PROPERTY(isUserInteractionEnabledValue, BOOL)
RCT_EXPORT_VIEW_PROPERTY(cardStyle, NSDictionary)
RCT_EXPORT_VIEW_PROPERTY(disabled, BOOL)
RCT_EXTERN_METHOD(focus:(nonnull NSNumber*) reactTag)
RCT_EXTERN_METHOD(blur:(nonnull NSNumber*) reactTag)
RCT_EXPORT_VIEW_PROPERTY(cardStyle, NSDictionary)
@end
18 changes: 9 additions & 9 deletions ios/CardFormView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CardFormView: UIView, STPCardFormViewDelegate {
@objc var dangerouslyGetFullCardDetails: Bool = false
@objc var onFormComplete: RCTDirectEventBlock?
@objc var autofocus: Bool = false
@objc var isUserInteractionEnabledValue: Bool = true
@objc var disabled: Bool = false

override func didSetProps(_ changedProps: [String]!) {
if let cardForm = self.cardForm {
Expand All @@ -20,7 +20,7 @@ class CardFormView: UIView, STPCardFormViewDelegate {
let style = self.cardStyle["type"] as? String == "borderless" ? STPCardFormViewStyle.borderless : STPCardFormViewStyle.standard
let _cardForm = STPCardFormView(style: style)
_cardForm.delegate = self
// _cardForm.isUserInteractionEnabled = isUserInteractionEnabledValue
_cardForm.isUserInteractionEnabled = !disabled

if autofocus == true {
let _ = _cardForm.becomeFirstResponder()
Expand All @@ -33,10 +33,10 @@ class CardFormView: UIView, STPCardFormViewDelegate {

@objc var cardStyle: NSDictionary = NSDictionary() {
didSet {
setStyles()
setStyles()
}
}

func cardFormView(_ form: STPCardFormView, didChangeToStateComplete complete: Bool) {
if onFormComplete != nil {
let brand = STPCardValidator.brand(forNumber: cardForm?.cardParams?.card?.number ?? "")
Expand All @@ -49,7 +49,7 @@ class CardFormView: UIView, STPCardFormViewDelegate {
"postalCode": cardForm?.cardParams?.billingDetails?.address?.postalCode ?? "",
"country": cardForm?.cardParams?.billingDetails?.address?.country
]

if (dangerouslyGetFullCardDetails) {
cardData["number"] = cardForm?.cardParams?.card?.number ?? ""
cardData["cvc"] = cardForm?.cardParams?.card?.cvc ?? ""
Expand All @@ -76,13 +76,13 @@ class CardFormView: UIView, STPCardFormViewDelegate {
cardForm?.backgroundColor = UIColor(hexString: backgroundColor)
}
/**
The following reveals a bug in STPCardFormView where there's a extra space in the layer,
and thus must remain commented out for now.
The following reveals a bug in STPCardFormView where there's a extra space in the layer,
and thus must remain commented out for now.
if let borderWidth = cardStyle["borderWidth"] as? Int {
cardForm?.layer.borderWidth = CGFloat(borderWidth)
cardForm?.layer.borderWidth = CGFloat(borderWidth)
} else {
cardForm?.layer.borderWidth = CGFloat(0)
cardForm?.layer.borderWidth = CGFloat(0)
}
*/
Expand Down
2 changes: 2 additions & 0 deletions src/components/CardField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export interface Props extends AccessibilityProps {
postalCodeEnabled?: boolean;
/** Controls the postal code entry shown (if the postalCodeEnabled prop is set to true). Defaults to the device's default locale. */
countryCode?: string;
/** Applies a disabled state such that user input is not accepted. Defaults to false. */
disabled?: boolean;
cardStyle?: CardFieldInput.Styles;
placeholders?: CardFieldInput.Placeholders;
autofocus?: boolean;
Expand Down
6 changes: 2 additions & 4 deletions src/components/CardForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ export interface Props extends AccessibilityProps {
style?: StyleProp<ViewStyle>;
autofocus?: boolean;
testID?: string;

/** Applies a disabled state such that user input is not accepted. Defaults to false. */
disabled?: boolean;
/** All styles except backgroundColor, cursorColor, borderColor, and borderRadius are Android only */
cardStyle?: CardFormView.Styles;
// isUserInteractionEnabled?: boolean;

// TODO: will make it public when iOS SDK allows for this
// postalCodeEnabled?: boolean;
Expand Down Expand Up @@ -78,7 +78,6 @@ export const CardForm = forwardRef<CardFormView.Methods, Props>(
{
onFormComplete,
cardStyle,
// isUserInteractionEnabled = true,
// postalCodeEnabled = true,
// onFocus,
// onBlur,
Expand Down Expand Up @@ -181,7 +180,6 @@ export const CardForm = forwardRef<CardFormView.Methods, Props>(
// disabledBackgroundColor: cardStyle?.disabledBackgroundColor,
// type: cardStyle?.type,
}}
// isUserInteractionEnabledValue={isUserInteractionEnabled}
placeholders={{
number: placeholders?.number,
expiration: placeholders?.expiration,
Expand Down
1 change: 1 addition & 0 deletions src/types/components/CardFieldInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export interface NativeProps {
): void;
cardStyle?: Styles;
placeholders?: Placeholders;
disabled?: boolean;
}

export interface Methods {
Expand Down
2 changes: 1 addition & 1 deletion src/types/components/CardFormView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export type DefaultValues = {
export interface NativeProps {
style?: StyleProp<ViewStyle>;
autofocus?: boolean;
// isUserInteractionEnabledValue?: boolean;
cardStyle?: Styles;
/** Android only */
placeholders?: Placeholders;
Expand All @@ -66,6 +65,7 @@ export interface NativeProps {
event: NativeSyntheticEvent<{ focusedField: FieldName | null }>
): void;
onFormComplete(event: NativeSyntheticEvent<Details>): void;
disabled?: boolean;
}

export interface Methods {
Expand Down

0 comments on commit 6b834ce

Please sign in to comment.