stripe / stripe-react-native

React Native library for Stripe.
https://stripe.dev/stripe-react-native
MIT License
1.28k stars 264 forks source link

Unsure how to create a token #1059

Closed gabriellend closed 2 years ago

gabriellend commented 2 years ago

Hello!

I'm migrating over from tipsi-stripe so I think I'm mostly integrated with Stripe already but I cannot for the life of me figure out how to create a token in the way I was doing it before. I would appreciate anyone pointing me in the right direction. I scoured the docs, examples, and issues for the last two days but got very lost/confused and think I might've ended up on the wrong site? I tried running the example project to investigate further too but couldn't get it to work.

In the app I'm working on, we allow users to add cards to use later (it's a food delivery app so they shouldn't have to add their card every time, only once). Before, I gathered the card info like this:

const params = {
        number: cardNumber,
        expMonth: Number(month),
        expYear: Number(year),
        cvc: security,
      };

and created a token (type object) like this:

const token = await stripe.createTokenWithCard(params);

I then sent the token to the backend where it talked to stripe which stored the card. The stripe-react-native function that seems to be analogous is createToken, which takes in an object where type is the only required field, and returns a token object and an error. Then what happens? How does createToken create a token that refers to a specific card with only the type (hypothetically) and not the number, cvc, etc? Is there another function that would let me take in my params and return an object with an id, or is this a much more complex problem?

I'm not using expo if that is important. I truly appreciate anyone's help. I'm still learning so some things aren't intuitive to me yet.

charliecruzan-stripe commented 2 years ago

createToken actually takes a couple different input options. But for cards, it relies on the CardField or CardForm components and pulls card data directly from there. This way, you aren't handling card data yourself and thus saves you a lot of time and effort around PCI compliance :) so I highly recommend going that route

Basically- render CardField and when the user fills that out, then you can create the token with createToken. See - https://github.com/stripe/stripe-react-native/blob/master/example/src/screens/CreateTokenScreen.tsx

gabriellend commented 2 years ago

@charliecruzan-stripe Thanks so much for the help! Does that mean I have to change my UI? I already have a component that takes in the card info, do I need to replace it with a CardField/CardForm component? And how does createToken know about CardField/CardForm? Is that just baked in?

charliecruzan-stripe commented 2 years ago

Totally baked in, you don't need to worry about it :)

And yes- you have to change your UI to incorporate CardField. Sorry for that, but it's definitely going to end up saving you lots of time in the long run since it makes your PCI approval process much easier: https://stripe.com/guides/pci-compliance. (Basically your requirements are far fewer)

gabriellend commented 2 years ago

@charliecruzan-stripe I truly thank you for your time!

idn-usmanmustafa commented 2 years ago

@charliecruzan-stripe I'm not getting the token. I don't know what i'm doing wrong here. below is my code. I'm trying to integrate stripe in my react-native app.

let userDetails;

  function CardSection() {
    return (
      <>
        <CardField
          postalCodeEnabled={true}
          placeholders={{
            number: '4242 4242 4242 4242',
          }}
          cardStyle={{
            backgroundColor: '#FFFFFF',
            textColor: '#000000',
          }}
          style={{
            width: '100%',
            height: 50,
            marginVertical: 30,
          }}
          onCardChange={cardDetails => {
            userDetails = cardDetails;
            // console.log('mycardDetails=', cardDetails);
          }}
          onFocus={focusedField => {
            // console.log('focusField', focusedField);
          }}
        />
        <Button
          title={'Checkout'}
          onPress={() => {
            stripePaymentFunction();
          }}
        />
      </>
    );
  }

below function will run on press button

function stripePaymentFunction() {
    createToken({
      type: 'Card',
    })
      .then(token => {
        console.log('token= ', token);
      })
      .catch(error => {
        console.log('error= ', error);
      });
  }

return (
    <StripeProvider
      publishableKey={'publishable_key'}>
      <CardSection />
    </StripeProvider>
  );

I'm getting below error when i press checkout button:

{"error": {"code": "Failed", "declineCode": null, "localizedMessage": "Card details not complete", "message": "Card details not complete", "stripeErrorCode": null, "type": null}}

charliecruzan-stripe commented 2 years ago

Card details not complete

This means CardField isn't completely filled out. You should disable your Checkout button until the onCardChange callback fires with cardDetails.complete === true

shashigit107 commented 1 year ago

Hello!

I'm migrating over from tipsi-stripe so I think I'm mostly integrated with Stripe already but I cannot for the life of me figure out how to create a token in the way I was doing it before. I would appreciate anyone pointing me in the right direction. I scoured the docs, examples, and issues for the last two days but got very lost/confused and think I might've ended up on the wrong site? I tried running the example project to investigate further too but couldn't get it to work.

In the app I'm working on, we allow users to add cards to use later (it's a food delivery app so they shouldn't have to add their card every time, only once). Before, I gathered the card info like this:

const params = {
        number: cardNumber,
        expMonth: Number(month),
        expYear: Number(year),
        cvc: security,
      };

and created a token (type object) like this:

const token = await stripe.createTokenWithCard(params);

I then sent the token to the backend where it talked to stripe which stored the card. The stripe-react-native function that seems to be analogous is createToken, which takes in an object where type is the only required field, and returns a token object and an error. Then what happens? How does createToken create a token that refers to a specific card with only the type (hypothetically) and not the number, cvc, etc? Is there another function that would let me take in my params and return an object with an id, or is this a much more complex problem?

I'm not using expo if that is important. I truly appreciate anyone's help. I'm still learning so some things aren't intuitive to me yet.

currently i am facing same issue you faced. can you please help , how you solved this.

gabriellend commented 1 year ago

@shashigit107 I added the CardForm component like this:

<CardForm
    autofocus
    onFormComplete={(cardDetails) => {
      if (cardDetails.complete) {
        this.setState({ formValid: true });
      }
    }}
    placeholders={{ postalCode: 'Zip'/* Android only */ }}
    cardStyle={styles.cardStyle}
    style={styles.newCard}
/>

And then a button below that:

<Button
    onPress={this.getStripeToken}
    text="ADD CARD"
    success={success}
    loading={loading}
    disabled={!formValid}
    style={styles.addCardButton}
/>

I call createToken in getStripeToken like this:

getStripeToken = async () => {
      const {
        loading,
      } = this.state;
      const { STRIPE_KEY } = CONFIG || {};

      if (loading) return;

      this.setState({ loading: true });

      try {
        initStripe({
          publishableKey: STRIPE_KEY,
        });

        const token = await createToken({ type: 'Card' });
        if (typeof (token) === 'object' && typeof (token.token.id) === 'string' && token.token.id.length) {
          await this.postCard(token.token.id);
          return;
        }

        this.setState({ loading: false });

        Alert.alert('Error', 'Unable to add card.', [{ text: 'OK', onPress: () => { } }]);
        return;
      } catch (e) {
        Alert.alert(
          'Error',
          'Card is invalid.',
          [{ text: 'OK', onPress: () => { this.setState({ loading: false }); } }],
        );
      }
    }

CardForm and createToken are aware of each other and talk to each other under the hood so that's all you have to do! Hope that helps!

hafizusman2 commented 9 months ago

@gabriellend @charliecruzan-stripe

not working for me, tried both approached

I tried multiple options as mentioned No issue found in my code I tried by using StripeProvider also tried by using initStripe method I am consistently facing the following error: {"error": {"code": "Failed", "declineCode": null, "localizedMessage": "Card details not complete", "message": "Card details not complete", "stripeErrorCode": null, "type": null}} blocked @charliecruzan-stripe @gabriellend

here is my code

import {Box, KeyboardAvoidingView, Stack, Text, useTheme, View} from 'native-base';
import {CardField, initStripe, StripeProvider, useStripe} from '@stripe/stripe-react-native';
import {Config} from 'react-native-config';
import React, {useContext, useEffect, useState} from 'react';
import LoadingScreen from 'components/LoadingScreen';
import {AuthContext} from 'context/authContext';
import GoToNext from 'screens/GoalCreating/components/GoToNext';
import {TOAST_TYPE, ToastContext} from 'context/toastContext';
import {CREATED_ROUTES} from 'screens/GoalCreating/constants';
import {useNavigation} from '@react-navigation/native';
import {
  AmericanExpressIcon,
  DinnerClubIcon,
  DiscoverCardIcon,
  MastercardIcon,
  UnionPayIcon,
  VisaCardIcon
} from 'assets/icons/cards';
import {Platform} from 'react-native';
const {STRIPE_PUBLISHABLE_KEY} = Config;

console.log('STRIPE_PUBLISHABLE_KEY', STRIPE_PUBLISHABLE_KEY);

const PaymentToken = () => {
  const {createToken} = useStripe();
  const {me} = useContext(AuthContext);
  const {colors} = useTheme();
  const {addToast} = useContext(ToastContext);
  const {navigate} = useNavigation();

  const [loading, setLoading] = useState(false);
  const [loadingPay, setLoadingPay] = useState(false);
  const [paymentMethodData, setPaymentMethodData] = useState();
  const [cardData, setCardData] = useState();

  const initializeStripe = async () => {
    setLoading(true);
    try {
      await initStripe({
        publishableKey: 'pk_test_U9omQHnm44eocSCqocC4JUOp00tnnig3IV'
      });
      console.log('stripe initialized');
    } catch (e) {
      console.log('error in init stripe', e);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    initializeStripe();
  }, []);

  const handlePayPress = async () => {
    setLoadingPay(true);
    try {
      if (!cardData.complete) {
        console.log('card details not complete in creating stripe token', cardData);
        addToast({type: TOAST_TYPE.ERROR, text: 'Please fill the card details'});
        return;
      }

      await initializeStripe();

      console.log('cardData', cardData);
      const resToken = await createToken({
        type: 'Card'
      });
      if (resToken.error) {
        console.log('error in creating stripe token', resToken);
        addToast({type: TOAST_TYPE.ERROR, text: resToken.error.message});
        return;
      }

      addToast({type: TOAST_TYPE.SUCCESS, text: 'Card was added successfully'});
    } catch (e) {
      console.log('error in creating stripe token', e);
    } finally {
      setLoadingPay(false);
    }
  };

  const fetchCardDetails = cardDetail => {
    if (cardDetail?.complete) {
      return setCardData(cardDetail);
    }
    // setCardData(null);
  };

  const behavior = Platform.OS === 'ios' ? 'padding' : 'height';

  if (loading) return <LoadingScreen />;
  return (
    <View flex={1} px={4}>
      <KeyboardAvoidingView
        keyboardVerticalOffset={120}
        behavior={behavior}
        style={{
          flex: 1
        }}
      >
        <View flex={1}>
          <Text fontWeight={'600'}>Add payment method</Text>
          <Stack direction={'row'} justifyContent={'space-between'} mt={5}>
            <MastercardIcon />
            <VisaCardIcon />
            <UnionPayIcon />
            <AmericanExpressIcon />
            <DinnerClubIcon />
            <DiscoverCardIcon />
          </Stack>
          <StripeProvider publishableKey={STRIPE_PUBLISHABLE_KEY}>
            <CardField
              postalCodeEnabled={false}
              placeholders={{
                number: '4242 4242 4242 4242'
              }}
              cardStyle={{
                backgroundColor: '#FFFFFF',
                textColor: '#000000',
                placeholderColor: colors.secondary[200]
              }}
              style={{
                width: '100%',
                height: 50,
                marginVertical: 30
              }}
              onCardChange={cardDetails => {
                fetchCardDetails(cardDetails);
              }}
            />
          </StripeProvider>
          <View style={{flexGrow: 1}} />

          <Box mb={6}>
            <GoToNext handleSubmit={handlePayPress} isLoading={loadingPay} title={'save'} disabled={!cardData} />
          </Box>
        </View>
      </KeyboardAvoidingView>
    </View>
  );
};

export default PaymentToken;

https://github.com/stripe/stripe-react-native/issues/1594 https://github.com/stripe/stripe-react-native/issues/1594

lukeirvin commented 7 months ago

Is there an example of this for Swift/SwiftUI? I'm trying to create a token but I can only get the last four from STPPaymentMethod and it seems I need the full card number to create a token. cc @charliecruzan-stripe