GithubHelp home page GithubHelp logo

Comments (22)

viktorbakari avatar viktorbakari commented on May 27, 2024 9

Very disappointed with Stripe's implementation of their React library. I have to refactor a lot of code to make this work with my pre-existing app. Ideally I would have liked to import one line of JSX and be done with it. Why is there so much boilerplate code for no apparent reason?

from react-stripe-elements.

cweiss-stripe avatar cweiss-stripe commented on May 27, 2024 1

I added an explanation for this issue to the readme:
https://github.com/stripe/react-stripe-elements#troubleshooting

Please let me know if the suggested solutions do not solve your problems.

from react-stripe-elements.

siakaramalegos avatar siakaramalegos commented on May 27, 2024 1

The possible solutions in the Troubleshooting section did not work for me. Instead, I instantiated my stripe provider inside my connected component and used all my stripe stuff outside of redux (so my onSubmit is defined in my React component though I dispatch things from there to update my store after getting my token).

from react-stripe-elements.

siakaramalegos avatar siakaramalegos commented on May 27, 2024 1

@chagoy it has been a while, and the repo is private, but I think this is the basic gist - act like you're not using Redux:

Checkout Container

class CheckoutContainer extends Component {
  render() {
    const fonts = [{ cssSrc: "https://fonts.googleapis.com/css?family=Podkova:400" }]

    return (
      <StripeProvider apiKey={stripePubkey}>
        <Elements fonts={fonts}>
          <Checkout {...this.props} />
        </Elements>
      </StripeProvider>
    )
  }
}

const mapStateToProps = (state) => {
  // state to props things not related to the stripe element itself
}

const mapDispatchToProps = (dispatch) => {
  // dispatch to props things not related to the stripe element itself
}

export default connect(mapStateToProps, mapDispatchToProps)(CheckoutContainer)

And then checkout itself:

import {injectStripe} from 'react-stripe-elements';

class Checkout extends Component {
  onSubmit = (e) => {
    e.preventDefault()
    // Delete all previous errors and invalid states
    this.props.clearErrors()

    const { stripe, hold } = this.props
    const { gross, hold_id } = hold
    const form = e.target
    const data = validate.collectFormValues(form)
    const { first_name, last_name } = data
    const requestData = {
      first_name,
      last_name,
      hold_id,
      expecting_to_pay: gross,
    }

    // Disable payment button
    this.props.requestBooking()

    // Get stripe token if payment needed
    if (gross > 0) {
      stripe.createToken({ name: data.card_name })
      .then(({ token, error }) => {
        if (error) {
          this.props.receiveErrors({card: error.message})
          return
        }
        requestData.stripe_token = token.id
        this.props.createBooking(requestData, form)
      })
    } else {
      this.props.createBooking(requestData, form)
    }
  }

  render() {
    const {
      errors,
      hold,
      isLoading,
      requestingBooking,
    } = this.props;

  return (
    <div>
      <h2>Payment</h2>
      <form id="payment-form" onSubmit={this.onSubmit}>
        <Input
          name="first_name"
          placeholder="Your first name"
          required
          errors={errors.first_name} />
        <Input
          name="last_name"
          placeholder="Your last name"
          required
          errors={errors.last_name} />
        <CreditCardGroup
          total={hold.gross}
          errors={errors} />
        <PayButton
          isLoading={isLoading}
          total={hold.gross}
          requestingBooking={requestingBooking} />
      </form>
    </div>
  )
  }
}

export default injectStripe(BookingCheckout)

from react-stripe-elements.

chagoy avatar chagoy commented on May 27, 2024 1

from react-stripe-elements.

michelle avatar michelle commented on May 27, 2024

Hi there! How are you setting up your component tree? Is there a wrapping component before your Stripe-injected component?

from react-stripe-elements.

hurano avatar hurano commented on May 27, 2024

Thank you for your help.
fetchCards pulls users credit cards list and create the select box users can select one of cards from existing cards in database.
There are radio button users can select "existing" or "add new".
If users click "add new", I render <CardElement style={{base: {fontSize: '18px'}}} />
I got this error when I click the submit button then when I click the button again, it succeeds.

deposit.js

    return (
      <StripeProvider apiKey="xxxxxxxxxxxx">
        <Elements>
          <DepositElements />
        </Elements>
      </StripeProvider>
    );

deposit_element.js

  import { fetchCards, submitPurchase } from '../actions/account';

class DepositElements extends Component {
  componentWillMount(){
    this.props.fetchCards();
  }
  onSubmit = (values) => {
    createStripeToken(values){
      this.props.stripe.createToken({type: 'card', name: values.card_name}).then(({token}) => {
         this.props.submitPurchase(Object.assign(values, token), this.callBack);
    }
  }

  render(){
  <form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
  ......
      <label><Field name="card_type" component="input" type="radio" value="existing" /> Use existing</label>
        <label><Field name="card_type" component="input" type="radio" value="new" /> Add New</label>

  ......
        <Field className="select-field" name="card_id" component={renderSelectField} label="Credit Card">
          {this.props.cards.data.cards.map((card) =>
              <MenuItem key={card.id} value={card.id} primaryText={`${card.card_name}  (${card.card_brand} ending in ${card.card_last4})`} />
             )}
        </Field>
  ......

          <Field
            name="card_name"
            component="input"
            type="text"
            placeholder="Name on card"
          />
          <CardElement style={{base: {fontSize: '18px'}}} />
    <button className="button button--primary"  >Contine</button>
  </form>
  ......
  }
}

  function mapStateToProps(state){
      return {
          cards: state.cards, // response from fetchCards() 
          form: state.form,
       };
  }

  export default reduxForm({
    form: 'DepositForm',
    errors: []
  })(
    connect(mapStateToProps, { fetchCards, submitPurchase })(injectStripe(DepositElements))
  );

from react-stripe-elements.

hurano avatar hurano commented on May 27, 2024

I also get this error on the server side.
You cannot use a Stripe token more than once: tok_1An1keHVN1dQlS3mGxnaHdzw.

from react-stripe-elements.

fred-stripe avatar fred-stripe commented on May 27, 2024

@hurano You cannot use a Stripe token more than once means that your backend code is using the token more than once, it's unrelated to this Redux/React issue. If you have more questions about that please contact Stripe Support and we can help you further: https://support.stripe.com/email/login

I'm not sure what's going on with the Redux issue but I am curious to see what turns up!

from react-stripe-elements.

hurano avatar hurano commented on May 27, 2024

It seems like there are some issue with connect not Redux form. I think its because of too much rendering. If I remove connect, there is no error on frontend and backend. Thanks.

from react-stripe-elements.

richban avatar richban commented on May 27, 2024

@hurano how do you actually map the elements states to props?

from react-stripe-elements.

fred-stripe avatar fred-stripe commented on May 27, 2024

I was able to dig in further, and I finally have a page that reproduces the error! So—progress. What seems to be happening is that both the Redux HOC and the Stripe HOC want to configure things in the React context for the component, however, whichever component is the outermost "wins". For example, if you do this:

injectStripe(connect()(YourComponent))

The Stripe context (which keeps track of the elements) will be available in the wrapped version of YourComponent.

If instead you do this:

connect()(injectStripe(YourComponent))

The Redux context (which is used to pass the Redux store into connect()d components) will be used instead. If Redux's context is the "outer" HOC, then the Stripe-created HOC will lose track of the <Elements> components registered with it.

There doesn't seem to be any merging of the wrapped component's contextTypes, and React contexts only share the data that you explicitly ask for.[1]

I'm not sure what the solution is here, but now we know what's happening and how to reproduce. I'll continue research on this end, as I'm not sure if it's a Stripe bug, a Redux bug, or a bug with how React contexts + HOC work (there are some issues on the React repo that suggest this may be the case).

[1] https://facebook.github.io/react/docs/context.html#how-to-use-context
See the very last sentence here: "If contextTypes is not defined, then context will be an empty object."

from react-stripe-elements.

ljbade avatar ljbade commented on May 27, 2024

Interesting, this could indeed be the issue I was having with redux-form as I found changing the order did casue Stripe to work, but then the form couldn't access the Redux state.

from react-stripe-elements.

saurishkar avatar saurishkar commented on May 27, 2024

Hi @cweiss-stripe, @fred-stripe
I am still facing an issue with the Error: You did not specify the type of Source or Token to create.We could not infer which Element you want to use for this operation.

I have a CardAdd component that is a simple form with just one element:

import React, { Component } from 'react';
import { injectStripe } from 'react-stripe-elements';

class CardAdd extends Component {
  constructor(props) {
    super(props);
    this.state = {input: ''};
    this.handleFormSubmit = this.handleFormSubmit.bind(this);
    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleFormSubmit(event, values) {
  	event.preventDefault();
  	const promise = this.props.stripe.createToken({type: 'card', name: 'sample'});
  	promise.then((response) => {
  		console.log('response', response);
  	}).catch((err) => {
  		console.log('error', err);
  	});
  }

  handleInputChange(event) {
  	this.setState({
  		input: event.target.value
  	});
  }

  render() {
  	// const { handleSubmit } = this.props;
    return (
      <div>
        <form className="add-card-form" onSubmit={this.handleFormSubmit}>
          <a className="closeBtn" onClick={() => this.props.closeModal()}><span className="icon icon-close">&times;</span></a>
          <div className="sbs-row">
            <div className="sbs-col-12">
              <input type="number" name="credit_card[number]" label="Card Number" placeholder="Your Card Number" onChange={this.handleInputChange} />
            </div>
          </div>
          
          <button type="submit">Add</button>
        </form>
      </div>
    );
  }
}

export default injectStripe(CardAdd);

I have a CardList component that basically mounts the CardAdd component as a collapsible component.

import React, { Component } from 'react';
import { Elements, StripeProvider } from 'react-stripe-elements';

import AddCard from './add';
import SECRETS from '../../../constants/app-secrets';

class CardList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      showAddModal: false
    };
    this.openAddModal = this.openAddModal.bind(this);
    this.closeAddModal = this.closeAddModal.bind(this);
  }

  openAddModal() {
  	this.setState({
  		showAddModal: true
  	});
  }

  closeAddModal() {
  	this.setState({
  		showAddModal: false
  	});
  }

  render() {
    return (
      <div className="card-list">
        <button className="add-card-btn" onClick={this.openAddModal}>Add Card</button>
        { this.state.showAddModal && <Elements><AddCard closeModal={this.closeAddModal} /></Elements>}
        <div className="sbs-row">
        	<div className="sbs-col-12">
        		Here you can see the list of all cards.
        	</div>
        </div>
      </div>
    );
  }
}

export default CardList;

My App Component where i attach the provider:

class App extends Component {
  render() {
    return (
      <StripeProvider apiKey={SECRETS.stripeApiKey}>
        <Provider store={createStoreWithMiddleware(reducers)}>
          <BrowserRouter>
            <div>
              {configRoutes()}
            </div>
          </BrowserRouter>
        </Provider>
      </StripeProvider>
    );
  }
}

On filling the values and submitting it (using the a sample value gives me the error mentioned above).
Initially, this was implemented with redux-form, i just tried to test it on a simple form ( thought redux form might be doing something to it) but it's still gives the same error.

Version : 1.4.1,
Browser: Chrome
OS: Mac OS Sierra

from react-stripe-elements.

atty-stripe avatar atty-stripe commented on May 27, 2024

@saurishkar it looks like you aren't using the <CardElement /> from react-stripe-elements in your CardAdd component. The <CardElement /> or the <CardNumberElement /> are the only supported way of accepting card numbers. You cannot use your own input.

That said, the error message should have been clearer here and tell you that you should be using an Element. We'll work on fixing that!


@siakaramalegos sorry to hear that! Do you have a reproduction or sample code to show why the troubleshooting section didn't work? It would also be helpful to know the versions of React, Redux, react-redux, and react-stripe-elements you're using.


@viktorbakari sorry that using the library has proven frustrating. Can you share what you hope your ideal API might look like ("one line of JSX" as you mention)?

The boilerplate might seem like too much, but in many ways is necessary.

  • <StripeProvider /> provides the Stripe object to your codebase. It needs to be separated from the actual form components since you might want to use the Stripe object in many places (and you will, when calling this.props.stripe.createToken, for example.)
  • <Elements /> serves the purpose of grouping together multiple input components, for the purposes of autofill as well as common configuration (you probably only want to specify custom fonts in one place.)
  • Individual components, such as <CardElement /> or <IbanElement /> each serve the purpose of accepting their own kind of input.

We're always open to feedback on simplifying the interface, so would love to hear about what is frustrating or confusing, and how we can improve things.

from react-stripe-elements.

siakaramalegos avatar siakaramalegos commented on May 27, 2024

@atty-stripe See my comment for the solution that worked for me.

from react-stripe-elements.

chagoy avatar chagoy commented on May 27, 2024

@siakaramalegos do you have an example of your code anywhere? I've been having the same trouble and have my redux form with my stripe injected form inside of it. I can't figure out how to connect the two.

from react-stripe-elements.

jorbascrumps avatar jorbascrumps commented on May 27, 2024

Has anyone actually managed to set this up with redux-form yet? I have struggled with this for the better part of a week.

An official example using redux-form would be very appreciated.

from react-stripe-elements.

scalebig avatar scalebig commented on May 27, 2024

^^^ The 'onSubmitSuccess' callback approach worked well. If you need to call redux actions from the callback, they are in the props argument passed into the callback.

So basically, the redux form onSubmit handler is called first. If your validation passes, you will return the the submitted data in the return of the handler. Next, the 'onSubmitSuccess' callback is called. The submitted data is then sent as the first argument - the function signature is onSubmitSuccess(result, dispatch, props). Inside of of your callback you call the stripe createToken().

strip extra Billing Info fields to pass into createToken():

{ 
  name: '',
  address_city: ''
  address_country: ''
  address_line1: ''
  address_line2: ''
  address_state: ''
  address_zip: ''
}

Callback example:

const submitSuccess = (result, dispatch, props) => {
// result has any of your custom fields from your form
// I am passing in the extra stripe fields
  props.stripe.createToken({ ...result})
    .then(({ token }) => {
      props.setBillingInfo({ billingInfo: token })
    })
}

let BillingInfoFormRedux = reduxForm({
  form: 'billing-info-form',
  onSubmitSuccess: submitSuccess
})(BillingInfoForm)

This is what my form container looks like:

// Stripped down for example purpose
  onSubmit = (formData) => {
    return formData
  }

  render () {
    return (
      <StripeProvider stripe={this.state.stripe}>
        <div id='payment-billing-container'>
          <Elements>
            <BillingInfoForm onSubmit={this.onSubmit} setBillingInfo={this.props.setBillingInfo} />
          </Elements>
        </div>
      </StripeProvider>
    )
  }

So what chagoy said, just a few different words 😄

Hey I got it to work. Let’s see if this makes sense. Here is my component structure // in componentDidMount you’ll set the stripe key as it’s done on the docs. Also, write an onSubmit function that will be called on form submission, however form will submit on the InjectedCheckoutForm, not from here. // pass it a prop called stripe which is equal to your stripe key.

In this component is where you create a token and make your post to your stripe endpoint. -> also, make sure at the bottom you are doing export default injectStripe(InjectedCheckoutForm) // goes inside of here as well. At the bottom you gotta define MainFormComponent = reduxForm({form: ‘name’, onSubmitSuccess: doSubmitSuccess}) and then export default connect(mapStateToProps)(MainFormComponent) I hope this all makes sense. If not just message me and I’d be willing to walk through it with you.

On Dec 6, 2018, at 9:32 AM, Chris Wright @.***> wrote: Has anyone actually managed to set this up with redux-form yet? I have struggled with this for the better part of a week. An official example using redux-form would be very appreciated. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#55 (comment)>, or mute the thread https://github.com/notifications/unsubscribe-auth/AQGzuArRKEshRorAf5BqA5qOT1wTR6m9ks5u2VSngaJpZM4OsFx7.

from react-stripe-elements.

aguscha333 avatar aguscha333 commented on May 27, 2024

@scalebig

Were are you injecting the stripe? I tried following your example but I can't get the values of redux form into the submitSuccess function

from react-stripe-elements.

chagoy avatar chagoy commented on May 27, 2024

from react-stripe-elements.

scalebig avatar scalebig commented on May 27, 2024

@scalebig

Were are you injecting the stripe? I tried following your example but I can't get the values of redux form into the submitSuccess function

@aguscha333 sorry for delay - trying to get our Stripe integration out the door before next semester classes start!

I will provide more of my code here...

This first file is the parent component to the billing form component -
I register stripe here - I had some weirdness registering... so feel free to provide better alternatives.

See onSubmit = (formFieldValues) => { gets called when you submit the form. If you return a list of values from this callback then will be passed to the onSubmitSuccess callback next.

import BillingInfoForm from './billing-info-form'

import {
  StripeProvider,
  Elements
} from 'react-stripe-elements'

class BillingInfo extends React.Component {
  constructor () {
    super()
    this.state = {stripe: null}
  }

  componentDidMount () {
    const stripeJs = document.createElement('script')
    stripeJs.src = 'https://js.stripe.com/v3/'
    stripeJs.async = true

    stripeJs.onload = () => {
      setTimeout(() => {
        this.setState({
          stripe: window.Stripe('YOUR STRIPE PK HERE')
        })
      }, 500)
    }

    document.body && document.body.appendChild(stripeJs)
  }

  onSubmit = (formFieldValues) => {
  // Do field level validations here
 // return for success then to onSubmitSuccess
    return formFieldValues
  }

  render () {
    return (
      <StripeProvider stripe={this.state.stripe}>
        <div id='payment-billing-container'>
          <Elements>
            <BillingInfoForm
              onSubmit={this.onSubmit}
              setBillingInfo={this.props.setBillingInfo}
              skuInfoList={
                this.props.skuList
                  .filter((skuItem) => (skuItem.include))
                  .map((skuItem) => ({
                    price: skuItem.price,
                    taxCode: skuItem.product.taxCode
                  }))}
            />
          </Elements>
        </div>
      </StripeProvider>
    )
  }
}

export default BillingInfo

Here is the ReduxForm class (with onSubmitSuccess registered with ReduxForm HOC)

class BillingInfoForm extends React.Component {
  constructor () {
    super()
    this.state = {
      stripeNumberElement: null,
      stripeExpiryElement: null,
      stripeCVCElement: null,
      ccInfo: 'uknown'
    }
  }

  render () {
    const { handleSubmit } = this.props

    // removed code not needed for example
    
    let self = this
    return (
      <React.Fragment>
        <form onSubmit={handleSubmit}>
          <h2>Payment method</h2>

          <div className="row">
            <div className="field">
              <Field
                fullwidth
                label='Name on card'
                name='name'
                component={ ReduxTextFieldInline }
                test-id='billing-name-field'
              />
            </div>
          </div>

          <div className="row">
            <div className="field">
              <Field
                fullwidth
                label='Address'
                name='address_line1'
                component={ ReduxTextFieldInline }
                test-id='address-line1-field'
              />
            </div>
          </div>

          // ... other fields here - trying to make this readable

          <div className='stripe-mdc'>
            <div className="row">
              <div className="field">
                <div id='card-number'>
                  <CardNumberElement
                    className='input'
                    style={elementStyles}
                    classes={elementClasses}
                    onReady={(el) => {
                      self.setState({
                        stripeNumberElement: el
                      })
                    }}
                    onChange={(evt) => {
                      self.setState({
                        ccIcon: evt.brand ? evt.brand : 'unknown'
                      })
                    }}

                  />
                  <label htmlFor='card-number' data-tid="elements_examples.form.card_number_label"
                    onClick={
                      () => {
                        self.state.stripeNumberElement && self.state.stripeNumberElement.focus()
                      }
                    }
                  >Card number</label>
                  <div className="baseline"></div>
                </div>

              </div>
              <div className="cc-icon"><img src={'/app/images/cc-' + self.state.ccIcon + '.svg'}/></div>
            </div>

            <div className="row">
              <div className="field half-width">
                <div id='card-expiry'>
                  <CardExpiryElement
                    className='input'
                    style={elementStyles}
                    classes={elementClasses}
                    onReady={(el) => {
                      self.setState({
                        stripeExpiryElement: el
                      })
                    }}
                  />
                  <label htmlFor="example2-card-expiry" data-tid="elements_examples.form.card_expiry_label"
                    onClick={
                      () => {
                        self.state.stripeExpiryElement.focus()
                      }
                    }
                  >Expiration</label>
                  <div className="baseline"></div>
                </div>
              </div>
              <div className="field half-width" style={{marginLeft: '10px'}} >
                <div id="example2-card-cvc" className="input empty"></div>
                <CardCVCElement
                  className='input'
                  style={elementStyles}
                  classes={elementClasses}
                  onReady={(el) => {
                    self.setState({
                      stripeCVCElement: el
                    })
                  }}
                />
                <label htmlFor="example2-card-cvc" data-tid="elements_examples.form.card_cvc_label"
                  onClick={
                    () => {
                      self.state.stripeCVCElement.focus()
                    }
                  }
                >CVC</label>
                <div className="baseline"></div>
              </div>
            </div>
          </div>
          <div className='button-bar'>
            <div><Button outlined testing-id='cofirm-payment-button'>Cancel</Button></div>
            <div><Button unelevated testing-id='cofirm-payment-button'>Continue</Button></div>
          </div>
        </form>
      </React.Fragment>
    )
  }
}


const submitSuccess = (result, dispatch, props) => {
  props.stripe.createToken({ ...result, address_country: 'US' })
    .then(({ token }) => {
      console.log('token', token)
      props.setBillingInfo({
        token,
        skuInfoList: props.skuInfoList,
        addressInfo: {
          country: 'US',
          zip: token.card.address_zip,
          state: token.card.address_state,
          city: token.card.address_city,
          street: token.card.address_line1
        }
      })
    })
}

let BillingInfoFormRedux = reduxForm({
  form: 'billing-info-form',
  touchOnChange: true,
  destroyOnUnmount: false,
  onSubmitSuccess: submitSuccess
})(BillingInfoForm)

export default injectStripe(BillingInfoFormRedux)

from react-stripe-elements.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.