stripe-archive / react-stripe-elements

Moved to stripe/react-stripe-js.
https://github.com/stripe/react-stripe-js
MIT License
3.03k stars 319 forks source link

Force PostalCodeElement to be type="tel" input #339

Closed dancrew32 closed 5 years ago

dancrew32 commented 5 years ago

I understand that the<PostalCodeElement /> does not have numerical input due to non-US zip codes that contain alpha characters. Say that I'm only using <PostalCodeElement /> with US customers. How can I force this element to accept numerical input on mobile web? The tel numpad is preferable. Thanks for your help!

asolove-stripe commented 5 years ago

Hi @dancrew32: there is not currently a way to do this when using the separate PostalCodeElement. We do automatically select the right format/keyboard for the postal code field when it is part of the combined single CardElement but there are some pitfalls when using separate elements that make it a bit dangerous for us to expose this.

If you want to do per-country postal code entry optimization with separate fields, you can collect the postal code with your own input field and simply attach this information to the card using element.update (like in this code snippet).

dancrew32 commented 5 years ago

@asolove-stripe sorry to bug again but I saw: https://github.com/stripe/react-stripe-elements/blob/master/CHANGELOG.md#v300---2019-04-17

PostalCodeElement has been removed. We suggest that you build your own postal code input.

Looking at the code snippet:

<script>
const myPostalCodeField = document.querySelector('input[name="my-postal-code"]');
myPostalCodeField.addEventListener('change', ({target}) => {
  card.update({value: {postalCode: target.value}});
});
</script>

I'd appreciate help determining the best way to obtain the card variable referenced in this exercise when the form could previously have looked like the following with no card variable.

<>
  <CardNumberElement />
  <CardExpiryElement />
  <CardCVCElement />
  <PostalCodeElement />
</>

If I change to:

<>
  <CardNumberElement />
  <CardExpiryElement />
  <CardCVCElement />
  <input type="tel" name="my-postal-code" onChange={({target}) => {
    // TODO: determine method for obtaining "card"
    card.update({value: {postalCode: target.value}});
  }} />
</>

I also want to help document best practices for error handling on these kinds of "disconnected fields", e.g. "name" or "postalCode".

I'd really appreciate your help on this. Thanks for your time.

dweedon-stripe commented 5 years ago

Hi @dancrew32, sorry for the confusion! We should have documented this better. Going forward we suggest that you pass in the postal code, name, and other relevant customer data directly to your call to createToken, createSource, or createPaymentMethod.

PaymentMethods

Using the billing_details field with stripe.createPaymentMethod Stripe.js Reference | API Reference

stripe.createPaymentMethod('card', {
  billing_details: {
    name: this.state.cardHolderName,
    address: {postal_code: this.state.postalCode},
  },
});

Tokens

Using the name and address_zip fields with stripe.createToken Stripe.js Reference

stripe.createToken({
  type: 'card',
  name: this.state.cardHolderName,
  address_zip: this.state.postalCode,
})

Sources

Using the owner field with stripe.createSource Stripe.js Reference | API Reference

stripe.createSource({
  type: 'card',
  owner: {
    name: this.state.cardHolderName,
    address: {postal_code: this.state.postalCode},
  },
});
dancrew32 commented 5 years ago

Nice! Thank you for the examples.

We just needed to add explicit error handling when error.code is postal_code_invalid via (https://stripe.com/docs/error-codes)

type StripeResponseTokenType = {
  card: StripeResponseTokenCardType,
  client_ip: string,
  created: number,
  id: string,
  livemode: boolean,
  object: string,
  type: string,
  used: boolean,
};

type StripeResponseErrorType = {
  code: string,
  message: string,
  type: string,
};

type StripeResponseType = {
  token?: StripeResponseTokenType,
  error?: StripeResponseErrorType,
};

//... in onSubmit()

  this.setState({postalCodeError: false});

  stripeInstance.createToken({
    name: this.state.name,
    address_zip: this.state.postalCode,
  }).then((response: StripeResponseType) => {
    const {token, error} = response;
    if (token) {
      return this.charge(token.id);
    }
    if (error && error.code === 'postal_code_invalid') {
      return this.setState({postalCodeError: true});
    }
  }).catch(() => {
    // ...
  });

//... in render()

  <input type="tel" className={classNames({'error': this.state.postalCodeError})} name="postal-code" onChange={({target}) => {
    this.setState({postalCode: target.value});
  }} />
dweedon-stripe commented 5 years ago

Looks great! Keep in mind you can additionally do your own client-side validation if you know what your customer's postal code format should be.