如何使用 Stripe 向 React Native 添加付款

在本教程中,我们将在适用于 iOS 和 Android 的 React Native 移动应用程序中实现一种支付方式。在本教程中,我将指导您完成每个步骤,在本教程结束时,您将能够使用您的移动应用程序进行任何实时支付。我们将使用React Native进行移动应用程序开发,NodeJs用于服务器,mongodb用于数据库,Stripe作为支付处理系统。为了托管我们的 API,我们将使用提供免费基本托管的 heroku,它足以满足我们的要求。

我将向您解释所有内容,让我们深入了解本教程。

我们将从创建 react-native 项目开始,并创建将在我们的应用程序中使用的 UI。由于我们的主要目的是学习支付实现,所以我们将专注于它而不是浪费我们的时间来构建现代设计的 UI。让我们通过以下命令创建 React Native 项目。

  • npx react-native init yourProjectName
  • cd yourProjectName
  • code

通过输入代码。该文件夹将在您的编辑器中打开。现在通过以下命令在您的设备上运行项目

  • npx react-native run-android

  • npx react-native run-ios

如果您是 react-native 的新手,那么您可以按照此链接进行环境设置和创建新项目 https://reactnative.dev/docs/environment-setup

现在创建一个文件夹名称 src 并使用以下名称创建子文件夹。

在此打开屏幕文件夹之后,并在其中创建子文件夹,如下所示

如果您按照本教程获取代码和文件有任何问题,请不要担心,我将在本教程末尾分享 github 链接。😊😊

下面是我在这个项目中使用的包列表,你可以通过 npm 或 yarn 安装这些包

  • @react-navigation/native
  • @react-navigation/native-stack
  • @stripe/stripe-react-native
  • formik
  • lottie-ios
  • lottie-react-native
  • react-native-dotenv
  • react-native-keyboard-aware-scrollview
  • react-native-modal
  • react-native-responsive-fontsize
  • react-native-responsive-screen
  • react-native-safe-area-context
  • react-native-screens
  • react-native-toast-message
  • 是的 安装这些包后不要忘记通过 npx react-native run-android 再次运行项目。😎😎。现在让我们进入您正在等待的编码部分😏😏

在 utils 文件夹中创建一个文件 Theme.js 并将以下代码粘贴到其中。

import {
  widthPercentageToDP as wp,
  heightPercentageToDP as hp,
} from 'react-native-responsive-screen'
import { RFValue } from 'react-native-responsive-fontsize'

export const Theme = {
  color: {
    primry: '#92056e',
    secondary: '#8abe01',
    tertiary: '#0087f0',
    helper: '#ffb600',
    error: '#f71114',
    text: '#555',
    white: '#fff',
    black: '#000',
    grey: '#808080',
  },
  font: {
    light: 'Barlow-Light',
    itlaic: 'Barlow-LightItalic',
    regular: 'Barlow-Regular',
    bold: 'Barlow-Bold',
    medium: 'Barlow-Medium',
  },
  hp,
  wp,
  rf: RFValue,
}

并保存此文件。

在 components 文件夹中创建以下文件

将以下代码粘贴到 FNButton 文件中

import React from 'react'
import { TouchableOpacity, Text } from 'react-native'

const FNButton = ({ style, label, labelStyle, onPress }) => {
  return (
    <TouchableOpacity
      style={style}
      activeOpacity={0.8}
      onPress={onPress}
    >
      <Text style={labelStyle}>{label}</Text>
    </TouchableOpacity>
  )
}

export default FNButton

将以下代码添加到 FNInput 文件

import React from 'react'
import { TextInput } from 'react-native'

const FNInput = ({
  value,
  onChangeText,
  style,
  keyboardType,
  placeholder,
  placeholderTextColor,
  secureTextEntry,
}) => {
  return (
    <TextInput
      placeholder={placeholder}
      placeholderTextColor={placeholderTextColor}
      value={value}
      onChangeText={onChangeText}
      style={style}
      keyboardType={keyboardType}
      underlineColorAndroid="transparent"
      secureTextEntry={secureTextEntry}
    />
  )
}

export default FNInput

如果您只是对代码部分感到无聊,您可以转到下面的支付实现部分,并可以在 github 存储库中找到代码。

在 FNLoader 文件中添加以下代码

import React from 'react'
import { View, Text, StyleSheet } from 'react-native'

import Modal from 'react-native-modal'
import LottieView from 'lottie-react-native'

import { Theme } from '../utils/Theme'

const FNLoader = ({ visible, label }) => {
  return (
    <Modal
      isVisible={visible}
      animationIn="bounceInRight"
      animationInTiming={1300}
    >
      <View style={styles.main}>
        <View style={styles.lottie}>
          <LottieView
            source={require('../assets/jsons/loading.json')}
            style={{ width: '100%', height: '100%' }}
            autoPlay
            loop
          />
        </View>
        <Text style={styles.label}>{label}</Text>
      </View>
    </Modal>
  )
}

const styles = StyleSheet.create({
  main: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.24)',
    alignItems: 'center',
    justifyContent: 'center',
  },
  lottie: {
    width: Theme.wp('30%'),
    height: Theme.wp('30%'),
    alignItems: 'center',
    justifyContent: 'center',
  },
  label: {
    color: Theme.color.secondary,
    fontSize: Theme.rf(20),
    fontFamily: Theme.font.light,
  },
})

export default FNLoader

恭喜🥳🥳。您已经为应用程序创建了所有必需的组件,现在让我们转向创建屏幕部分。在 src/screens/auth 文件夹中添加以下文件

将以下文件复制并粘贴到 formHandling 文件中

import { Formik } from 'formik'
import * as yup from 'yup'

export const registerForm = yup.object({
  FIRSTNAME: yup
    .string()
    .min(3, 'minimun 3 chracters required')
    .max(35, 'maximum 35 characters allowed')
    .required('first name is required'),
  LASTNAME: yup
    .string()
    .min(3, 'minimun 3 chracters required')
    .max(35, 'maximum 35 characters allowed')
    .required('last name is required'),
  EMAIL: yup
    .string()
    .email('Please enter a valid email')
    .min(5, 'minimun 5 chracters required')
    .max(255, 'maximum 255 characters allowed')
    .required('email is required'),
  PHONE: yup
    .string()
    .min(11, 'minimun 3 chracters required')
    .max(13, 'maximum 50 characters allowed')
    .required('phone number is required'),
  PASSWORD: yup
    .string()
    .min(8, 'minimun 8 chracters required')
    .max(255, 'maximum 255 characters allowed')
    .required('password is required'),
})

export const loginForm = yup.object({
  EMAIL: yup
    .string()
    .email('Please enter a valid email')
    .min(5, 'minimun 5 chracters required')
    .max(255, 'maximum 255 characters allowed')
    .required('email is required'),
  PASSWORD: yup
    .string()
    .min(8, 'minimun 8 chracters required')
    .max(255, 'maximum 255 characters allowed')
    .required('password is required'),
})

将此代码添加到 style.js 文件

import { StyleSheet } from 'react-native'

import { Theme } from '../../utils/Theme'

const styles = StyleSheet.create({
  logoView: {
    width: Theme.wp('40%'),
    height: Theme.hp('15%'),
    marginTop: Theme.hp('5%'),
    marginBottom: Theme.hp('1%'),
    justifyContent: 'center',
    alignItems: 'center',
    alignSelf: 'center',
  },
  loginText: {
    fontSize: Theme.rf(24),
    fontFamily: Theme.font.bold,
    color: Theme.color.primry,
    textAlign: 'center',
  },
  desc: {
    fontSize: Theme.rf(14),
    fontFamily: Theme.font.regular,
    color: Theme.color.text,
    textAlign: 'center',
    marginVertical: Theme.hp('1%'),
  },
  body: {
    marginTop: Theme.hp('5%'),
    paddingHorizontal: Theme.wp('7%'),
  },
  input: {
    width: Theme.wp('86%'),
    height: Theme.hp('6%'),
    borderWidth: 0.7,
    borderColor: Theme.color.helper,
    borderRadius: 18,
    fontSize: Theme.rf(16),
    fontFamily: Theme.font.regular,
    color: Theme.color.text,
    padding: 0,
    paddingLeft: Theme.wp('3%'),
  },
  passView: {
    width: Theme.wp('86%'),
    height: Theme.hp('6%'),
    borderWidth: 0.7,
    borderColor: Theme.color.helper,
    borderRadius: 18,
    flexDirection: 'row',
    alignItems: 'center',
    marginTop: Theme.hp('1%'),
  },
  inputPass: {
    width: Theme.wp('74%'),
    height: Theme.hp('6%'),
    borderRadius: 18,
    fontSize: Theme.rf(16),
    fontFamily: Theme.font.regular,
    color: Theme.color.text,
    padding: 0,
    paddingLeft: Theme.wp('3%'),
  },
  iconView: {
    width: Theme.wp('12%'),
    height: Theme.hp('6%'),
    borderRadius: 18,
    justifyContent: 'center',
    alignItems: 'center',
  },
  forgotPass: {
    fontSize: Theme.rf(14),
    fontFamily: Theme.font.itlaic,
    color: Theme.color.error,
    textAlign: 'right',
  },
  button: {
    width: Theme.wp('86%'),
    height: Theme.hp('6%'),
    borderRadius: 18,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: Theme.color.primry,
    marginTop: Theme.hp('5%'),
  },
  label: {
    fontSize: Theme.rf(16),
    fontFamily: Theme.font.bold,
    color: Theme.color.white,
    marginLeft: Theme.wp('2%'),
  },
  noAccount: {
    fontSize: Theme.rf(14),
    fontFamily: Theme.font.regular,
    color: Theme.color.text,
    marginVertical: Theme.hp('3.5%'),
    textAlign: 'center',
  },
  create: {
    fontSize: Theme.rf(16),
    fontFamily: Theme.font.regular,
    color: Theme.color.tertiary,
    textDecorationLine: 'underline',
  },
  errorsText: {
    fontSize: Theme.rf(10),
    fontFamily: Theme.font.light,
    color: Theme.color.error,
  },
})

export default styles

在 Register.js 文件中粘贴以下代码。现在我将把 handleRegister 留空,我们将在创建 API 之后添加所需的代码。因此,现在添加此代码并耐心等待,因为我将告诉您所有内容。

import React, { useState, useRef } from 'react'
import { View, Text, Modal, TouchableOpacity, Alert, Image } from 'react-native'

import { Formik } from 'formik'
import Toast from 'react-native-toast-message'
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scrollview'

import styles from './Style'
import { Theme } from '../../utils/Theme'
import { registerForm } from './formHandling'
import FNInput from '../../components/FNInput'
import FNLoader from '../../components/FNLoader'
import FNButton from '../../components/FNButton'

const Register = ({ navigation }) => {
  const [firstName] = useState('')
  const [lastName] = useState('')
  const [email] = useState('')
  const [password] = useState('')
  const [show, setShow] = useState(true)
  const [loading, setLoading] = useState(false)
  const [phone, setPhone] = useState('')
  const [logo] = useState(require('../../assets/images/logo.png'))

  const showToastError = message => {
    Toast.show({
      type: 'error',
      text1: 'Error',
      text2: message,
    })
  }

  const showToastSuccess = message => {
    Toast.show({
      type: 'success',
      text1: 'Success',
      text2: message,
    })
  }

  const handleRegister = async v => {}

  return (
    <KeyboardAwareScrollView showsVerticalScrollIndicator={false}>
      <View style={styles.logoView}>
        <Image
          source={logo}
          style={{ width: '100%', height: '100%' }}
          resizeMode="center"
        />
      </View>

      <Text style={styles.loginText}>Sign Up</Text>
      <Text style={styles.desc}>Buy now, Pay now</Text>

      <View style={styles.body}>
        <Formik
          initialValues={{
            FIRSTNAME: firstName,
            LASTNAME: lastName,
            EMAIL: email,
            PHONE: phone,
            PASSWORD: password,
          }}
          validationSchema={registerForm}
          onSubmit={(v, a) => {
            handleRegister(v)
          }}
        >
          {props => (
            <>
              <FNInput
                placeholder="first name"
                placeholderTextColor={Theme.color.text}
                style={styles.input}
                value={props.values.FIRSTNAME}
                onChangeText={props.handleChange('FIRSTNAME')}
              />
              <Text
                style={{ ...styles.errorsText, marginBottom: Theme.hp('1%') }}
              >
                {props.touched.FIRSTNAME && props.errors.FIRSTNAME}
              </Text>
              <FNInput
                placeholder="last name"
                placeholderTextColor={Theme.color.text}
                style={styles.input}
                value={props.values.LASTNAME}
                onChangeText={props.handleChange('LASTNAME')}
              />
              <Text
                style={{ ...styles.errorsText, marginBottom: Theme.hp('1%') }}
              >
                {props.touched.LASTNAME && props.errors.LASTNAME}
              </Text>
              <FNInput
                placeholder="Email"
                placeholderTextColor={Theme.color.text}
                style={styles.input}
                keyboardType="email-address"
                value={props.values.EMAIL}
                onChangeText={props.handleChange('EMAIL')}
              />
              <Text style={styles.errorsText}>
                {props.touched.EMAIL && props.errors.EMAIL}
              </Text>

              <FNInput
                placeholder="Phone"
                placeholderTextColor={Theme.color.text}
                style={styles.input}
                keyboardType="numeric"
                value={props.values.PHONE}
                onChangeText={props.handleChange('PHONE')}
              />
              <Text style={styles.errorsText}>
                {props.touched.PHONE && props.errors.PHONE}
              </Text>

              <FNInput
                placeholder="Password"
                placeholderTextColor={Theme.color.text}
                style={styles.input}
                secureTextEntry={show}
                value={props.values.PASSWORD}
                onChangeText={props.handleChange('PASSWORD')}
              />
              <Text style={styles.errorsText}>
                {props.touched.PASSWORD && props.errors.PASSWORD}
              </Text>

              <FNButton
                style={styles.button}
                label="Sign Up"
                icon="sign-in-alt"
                size={20}
                color={Theme.color.white}
                labelStyle={styles.label}
                onPress={props.handleSubmit}
                // onPress={() => handleRegister()}
              />
              <Text style={styles.noAccount}>
                don't have an account?
                <Text
                  style={styles.create}
                  onPress={() => navigation.goBack()}
                >
                  {' '}
                  Create Now
                </Text>
              </Text>
            </>
          )}
        </Formik>
      </View>
      <FNLoader
        visible={loading}
        label="Please wait..."
      />
    </KeyboardAwareScrollView>
  )
}

export default Register

当我们做了一个注册用户的 UI 时,为什么我们不应该添加用于登录的 UI 😉 😉。粘贴以下内容,您就完成了。🥳🥳🥳

import React, { useState } from 'react'
import { View, Text, Alert, Image } from 'react-native'

import { Formik } from 'formik'
import Toast from 'react-native-toast-message'
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scrollview'

import styles from './Style'
import { Theme } from '../../utils/Theme'
import { loginForm } from './formHandling'
import FNInput from '../../components/FNInput'
import FNLoader from '../../components/FNLoader'
import FNButton from '../../components/FNButton'

const Login = ({ navigation }) => {
  const [email] = useState('')
  const [password] = useState('')
  const [show, setShow] = useState(true)
  const [loading, setLoading] = useState(false)
  const [logo] = useState(require('../../assets/images/logo.png'))

  const showToastError = message => {
    Toast.show({
      type: 'error',
      text1: 'Error',
      text2: message,
    })
  }

  const showToastSuccess = message => {
    Toast.show({
      type: 'success',
      text1: 'Success',
      text2: message,
    })
  }

  const doLogin = async v => {

  }

  return (
    <KeyboardAwareScrollView>
      <View style={styles.logoView}>
        <Image
          source={logo}
          style={{ width: '100%', height: '100%', marginLeft: Theme.wp('5%') }}
          resizeMode="cover"
        />
      </View>

      <Text style={styles.loginText}>Login</Text>
      <Text style={styles.desc}>Buy now, Pay now</Text>

      <View style={styles.body}>
        <Formik
          initialValues={{
            EMAIL: email,
            PASSWORD: password,
          }}
          validationSchema={loginForm}
          onSubmit={(v, a) => {
            doLogin(v)
          }}
        >
          {props => (
            <>
              <FNInput
                placeholder="Email"
                placeholderTextColor={Theme.color.text}
                style={styles.input}
                keyboardType="email-address"
                value={props.values.EMAIL}
                onChangeText={props.handleChange('EMAIL')}
              />
              <Text style={styles.errorsText}>
                {props.touched.EMAIL && props.errors.EMAIL}
              </Text>
              <View style={styles.passView}>
                <FNInput
                  placeholder="Password"
                  placeholderTextColor={Theme.color.text}
                  style={styles.inputPass}
                  secureTextEntry={show}
                  value={props.values.PASSWORD}
                  onChangeText={props.handleChange('PASSWORD')}
                />
                <View style={styles.iconView}></View>
              </View>
              <Text style={styles.errorsText}>
                {props.touched.PASSWORD && props.errors.PASSWORD}
              </Text>
              <Text style={styles.forgotPass}>Forgot Password?</Text>
              <FNButton
                style={styles.button}
                label="Login"
                icon="sign-in-alt"
                size={20}
                color={Theme.color.white}
                labelStyle={styles.label}
                onPress={props.handleSubmit}
              />
            </>
          )}
        </Formik>
        <Text style={styles.noAccount}>
          don't have an account?
          <Text
            style={styles.create}
            onPress={() => navigation.navigate('Register')}
          >
            {' '}
            Create Now
          </Text>
        </Text>
      </View>
      <FNLoader
        visible={loading}
        label="Logging In..."
      />
    </KeyboardAwareScrollView>
  )
}

export default Login

哦,不,我们的启动画面在哪里????😕😕😕😕。让我从你那里找到。请稍等!!!首先在splash文件夹中创建两个文件,如下

是的,就在这里。不要看这条线。代码在这一行下面!!

Splash.js

import React, { useEffect } from 'react'
import { View } from 'react-native'

import LottieView from 'lottie-react-native'

import styles from './Style'
import { Theme } from '../../utils/Theme'

const Splash = ({ navigation }) => {
  useEffect(() => {
    setTimeout(() => {
      navigation.navigate('Login')
    }, 100)
  })

  return (
    <View style={styles.main}>
      <LottieView
        source={require('../../assets/jsons/splash.json')}
        autoPlay
        style={{ flex: 1, height: Theme.hp('100%') }}
      />
    </View>
  )
}

export default Splash

在 Style.js 中

import { StyleSheet } from 'react-native'

import { Theme } from '../../utils/Theme'

const styles = StyleSheet.create({
  main: {
    flex: 1,
    // backgroundColor: Theme.color.white,
  },
  heading: {
    color: Theme.color.tertiary,
    fontSize: Theme.rf(16),
    fontFamily: Theme.font.bold,
  },
})

export default styles

我们应该制作一个注册成功屏幕吗?是的,那你为什么不应该像这样在这个文件夹中创建两个文件呢?

以下是这些文件的代码👇👇

import React from 'react'
import { View, Text } from 'react-native'

import LottieView from 'lottie-react-native'

import styles from './Style'

const RegisterSuccess = ({ navigation, route }) => {
  const { name } = route.params

  return (
    <View style={styles.main}>
      <Text style={styles.thanksText}>Welcome {name}.</Text>
      <View style={styles.lottieView}>
        <LottieView
          source={require('../../assets/jsons/success.json')}
          autoPlay
          loop={false}
          style={{ width: '100%', height: '100%' }}
        />
      </View>
      <Text style={styles.thanksText}>
        You have successfully created your account.
      </Text>
      <Text
        style={styles.login}
        onPress={() => navigation.replace('Login')}
      >
        Login
      </Text>
    </View>
  )
}

export default RegisterSuccess

和 Style.js

import { StyleSheet } from 'react-native'

import { Theme } from '../../utils/Theme'

const styles = StyleSheet.create({
  main: {
    flex: 1,
    backgroundColor: Theme.color.white,
    justifyContent: 'center',
    alignItems: 'center',
  },
  lottieView: {
    width: Theme.wp('50%'),
    height: Theme.hp('25%'),
  },
  thanksText: {
    color: Theme.color.text,
    fontSize: Theme.rf(16),
    fontFamily: Theme.font.regular,
    textAlign: 'center',
    paddingHorizontal: Theme.wp('5%'),
    lineHeight: Theme.hp('2.9%'),
  },
  login: {
    color: Theme.color.tertiary,
    fontSize: Theme.rf(18),
    fontFamily: Theme.font.medium,
    textDecorationLine: 'underline',
    marginTop: Theme.hp('2%'),
  },
})

export default styles

我知道教程越来越长,但我相信你会在本教程结束时感到高兴,所以这是我们最终的支付屏幕 UI。在主文件夹中创建这些文件

Home.js

import React from 'react'
import { Text, View, Alert } from 'react-native'

import Toast from 'react-native-toast-message'
import { useStripe } from '@stripe/stripe-react-native'

import styles from './Style'
import { Theme } from '../../utils/Theme'
import FNButton from '../../components/FNButton'

const Home = ({ navigation, route }) => {
  const { id, firstName, lastName, email, phone } = route.params.response
  const [amount, setAmount] = React.useState(0)

  const { initPaymentSheet, presentPaymentSheet } = useStripe()

  const fetchPaymentSheetParams = async () => {
   }

  const initializePaymentSheet = async () => {

  }

  const openPaymentSheet = async () => {

  }

  return (
    <View style={styles.main}>
      <View style={styles.header}>
        <Text style={styles.heading}>Welcome Fiyaz</Text>

        <Text style={styles.desc}>
          This is helping project for developers for implementing payments using
          Stripe in React Native
        </Text>

        <Text style={styles.desc1}>
          You can use test card for this transcation
        </Text>

        <View style={styles.line}>
          <Text style={styles.text1}>Card:</Text>
          <Text style={styles.text2}>4242 42424 4242 4242</Text>
        </View>

        <View style={styles.line}>
          <Text style={styles.text1}>Exp Date:</Text>
          <Text style={styles.text2}>Any valid date after today.</Text>
        </View>
        <View style={styles.line}>
          <Text style={styles.text1}>CVC:</Text>
          <Text style={styles.text2}>any 3 digits.</Text>
        </View>

        <Text
          style={{
            ...styles.desc1,
            marginVertical: 0,
            marginTop: Theme.hp('3%'),
          }}
        >
          For US you can use any 5 digits zip code.
        </Text>
      </View>

      <View style={styles.body}>
        <View style={styles.line1}>
          <FNButton
            style={styles.pay}
            label="100"
            labelStyle={styles.label}
            onPress={() => setAmount(100)}
          />
          <FNButton
            style={{ ...styles.pay, backgroundColor: Theme.color.secondary }}
            label="150"
            labelStyle={styles.label}
            onPress={() => setAmount(150)}
          />
        </View>

        <View style={styles.line1}>
          <FNButton
            style={{ ...styles.pay, backgroundColor: Theme.color.tertiary }}
            label="200"
            labelStyle={styles.label}
            onPress={() => setAmount(200)}
          />
          <FNButton
            style={{ ...styles.pay, backgroundColor: Theme.color.helper }}
            label="250"
            labelStyle={styles.label}
            onPress={() => setAmount(250)}
          />
        </View>

        <FNButton
          style={styles.payBtn}
          label="Pay"
          labelStyle={styles.label}
          onPress={() => initializePaymentSheet()}
        />
      </View>
    </View>
  )
}

export default Home

Style.js

import { StyleSheet } from 'react-native'

import { Theme } from '../../utils/Theme'

const styles = StyleSheet.create({
  main: {
    flex: 1,
  },
  header: {
    flex: 5,
    paddingHorizontal: Theme.wp('10%'),
    backgroundColor: 'rgba(108, 150, 35, 0.8)',
    margin: 5,
    borderRadius: 28,
  },
  body: {
    flex: 5,
    paddingHorizontal: Theme.wp('10%'),
  },
  heading: {
    color: Theme.color.primry,
    fontSize: Theme.rf(17),
    fontWeight: '700',
    marginVertical: Theme.hp(' 3%'),
  },
  desc: {
    color: Theme.color.text,
    fontSize: Theme.rf(14),
    textAlign: 'center',
    fontWeight: '500',
    lineHeight: Theme.hp('2.6%'),
  },
  desc1: {
    color: Theme.color.white,
    fontSize: Theme.rf(14),
    textAlign: 'center',
    fontWeight: '400',
    lineHeight: Theme.hp('2.6%'),
    marginVertical: Theme.hp(' 3%'),
  },
  line: {
    flexDirection: 'row',
    alignItems: 'center',
    marginTop: Theme.hp(' 1%'),
    // justifyContent: 'space-between',
  },
  text1: {
    width: Theme.wp('26%'),
    color: Theme.color.primry,
    fontSize: Theme.rf(16),
    fontWeight: '700',
  },
  text2: {
    color: Theme.color.text,
    fontSize: Theme.rf(16),
    fontWeight: '400',
  },
  line1: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginTop: Theme.hp('5%'),
  },
  pay: {
    width: Theme.wp('30%'),
    height: Theme.hp('6%'),
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: Theme.color.primry,
    borderRadius: 12,
  },
  label: {
    color: Theme.color.white,
    fontSize: Theme.rf(16),
    fontWeight: '400',
  },
  payBtn: {
    width: Theme.wp('60%'),
    height: Theme.hp('6%'),
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: Theme.color.primry,
    borderRadius: 12,
    marginTop: Theme.hp('8%'),
    alignSelf: 'center',
  },
})

export default styles

最后,我们设计了所有的 UI,现在将创建屏幕的导航部分。因此,在导航文件夹中创建一个名为 AppStack.js 的文件,并在该文件中添加以下代码以完成我们支付移动应用程序的导航部分。

import React from 'react'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import Login from '../screens/Auth/Login'
import Register from '../screens/Auth/Register'
import Splash from '../screens/Splash/Splash'
import RegisterSuccess from '../screens/RegisterSuccess/RegisterSuccess'
import Home from '../screens/Home/Home'

const Stack = createNativeStackNavigator()

export default function AppStack() {
  return (
    <Stack.Navigator
      initialRouteName="Splash"
      screenOptions={{
        headerShown: false,
      }}
    >
      <Stack.Screen
        name="Splash"
        component={Splash}
      />
      <Stack.Screen
        name="Login"
        component={Login}
      />
      <Stack.Screen
        name="Register"
        component={Register}
      />
      <Stack.Screen
        name="RegisterSuccess"
        component={RegisterSuccess}
      />
      <Stack.Screen
        name="Home"
        component={Home}
      />
    </Stack.Navigator>
  )
}

不,因为我们已经完成了屏幕和导航部分,这意味着我们几乎完成了我们的 UI 端,我们只需将根 App.js 文件的代码替换为以下代码,我们就完成了。

 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React from 'react'
import { NavigationContainer } from '@react-navigation/native'

import AppStack from './src/navigation/AppStack'

const App = () => {
  return (
      <NavigationContainer>
        <AppStack />
      </NavigationContainer>
  )
}

export default App

我们已经完成了我们的UI部分,如果你累了,休息一下,喝杯咖啡☕️☕️

Stripe

现在我将指导您如何创建条带帐户并获取我们处理付款所需的密钥。Stripe 是一套 API,为各种规模的互联网企业提供在线支付处理和商务解决方案。接受付款并更快地扩展。您可以在https://stripe.com/了解有关条纹的更多信息

如果您没有条带帐户,请点击此链接创建您的条带帐户https://dashboard.stripe.com/register。您可以跳过商业注册流程,因为现在我们必须在测试模式下付款,但对于实时模式,您必须完成所有注册流程。

成功注册后,从仪表板右上角打开测试模式。https://dashboard.stripe.com/test/dashboard然后点击开发者。

您可以找到 Publishable key 和 Secret key。我们将在客户端(移动应用程序 UI)中需要 Publishable 密钥,在我们的侧代码中需要 Secret 密钥。

数据库

我们将使用 mongodb 作为我们的数据库。因此,如果您没有 mongodb 帐户,请按照https://account.mongodb.com/account/register创建您的新帐户。注册成功后,您将看到如下仪表板。

单击构建数据库,您将看到一个新屏幕,如下所示。您可以创建共享数据库,因为它是免费的,足以满足我们的需求。

在下一个屏幕上,单击屏幕底部的创建集群按钮,您将看到一个新屏幕。在此处添加您的用户名和密码并将它们保存在本地文件中,因为当我们将后端连接到数据库时将需要这两个。不用担心,您也可以随时在集群中找到它们。

添加此屏幕的底部,通过单击添加我的当前 IP 地址来添加您的机器 IP 地址。这将使您能够使用这台笔记本电脑访问数据库。如果您要添加 0.0.0.0/0,那么您可以从任何笔记本电脑访问数据库。在此之后,我们完成了我们的数据库设置🥳🥳🥳。

服务器端

我们将使用 NodeJS 和 express 进行我们的侧边开发。NodeJS 是现代开发世界中连接 mongodb 的重要语言。在不深入了解 NodeJS 和表达的细节的情况下,我们将转向我们最重要的部分,即编码😋😋。

创建一个文件夹并在其中打开命令提示符。通过键入 npx init -y 初始化项目,然后按 Enter。使用 npm 安装一些必要的包。

  • bcrypt
  • body-parser
  • config
  • cors
  • express
  • helmet
  • joi
  • mongodb
  • mongoose
  • stripe

在根文件夹中创建一个文件 index.js ,然后在项目文件夹的根目录中创建一个文件夹config、models和routes。

配置

在文件夹内添加两个名为 custom-environment-variables.json 和 default.json 的文件,如下所示。

并在两个文件中粘贴相同的内容

{
  "PrivateKey": "mongodb+srv://username:password@cluster0.zy6jo.mongodb.net/easyPay?retryWrites=true&w=majority",
  "StripeKey": "add your stripe secret key here"
}

请记住,您需要在此处添加您的条带密钥。https://dashboard.stripe.com/apikeys。在 PrivateKey 中,将用户名和密码替换为我们在下面屏幕创建数据库时添加的 mongodb 用户名和密码。

模型

在此文件夹中创建一个名为 user.js 的文件,并在此文件中添加以下代码。

const Joi = require("joi")
const mongoose = require("mongoose")

const userSchema = new mongoose.Schema({
  firstName: {
    type: String,
    required: true,
    minlength: 3,
    maxlength: 35,
  },
  lastName: {
    type: String,
    required: true,
    minlength: 3,
    maxlength: 35,
  },
  email: {
    type: String,
    required: true,
    minlength: 5,
    maxlength: 255,
    unique: true,
  },
  phone: {
    type: String,
    required: true,
    minlength: 10,
    maxlength: 16,
  },
  password: {
    type: String,
    required: true,
    minlength: 8,
    maxlength: 255,
  },
  paid: {
    type: Boolean,
    required: true,
  },
})

const User = mongoose.model("User", userSchema)

function validateUser(user) {
  const schema = Joi.object({
    firstName: Joi.string().min(3).max(35).required(),
    lastName: Joi.string().min(3).max(35).required(),
    email: Joi.string()
      .min(5)
      .max(255)
      .required()
      .email({ minDomainSegments: 2, tlds: { allow: ["com", "net"] } }),
    phone: Joi.string().min(10).max(16).required(),
    password: Joi.string().min(8).max(255).required(),
  })

  return schema.validate(user)
}

exports.User = User
exports.validate = validateUser

路线

这个文件夹将包含我们的路由文件,我们将在这个文件夹中创建 API。请在此文件夹中创建四个文件,如下所示。

在 auth.js 中添加以下代码,此路由将负责登录我们的应用程序用户。这是此文件的代码。

const Joi = require("joi")
const bcrypt = require("bcrypt")
const express = require("express")

const { User } = require("../models/user")

const router = express.Router()

router.post("/", async (req, res) => {
  //validating request
  const { error } = validate(req.body)

  if (error) {
    return res.status(400).send(error.details[0].message)
  }

  let user = await User.findOne({ email: req.body.email })

  if (!user) {
    return res.status(404).send(`No user found for eamil ${req.body.email}`)
  }

  const validPassword = await bcrypt.compare(req.body.password, user.password)
  if (!validPassword) {
    return res.status(400).send("Please check your password again")
  }

  const data = {
    id: user._id,
    firstName: user.firstName,
    lastName: user.lastName,
    email: user.email,
    phone: user.phone,
    paid: user.paid,
  }

  res.status(200).send(data)
})

function validate(req) {
  const schema = Joi.object({
    email: Joi.string()
      .min(5)
      .max(255)
      .required()
      .email({ minDomainSegments: 2, tlds: { allow: ["com", "net"] } }),
    password: Joi.string().min(8).max(255).required(),
  })

  return schema.validate(req)
}

module.exports = router

这是 user.js 的代码,用于注册我们的用户

const bcrypt = require("bcrypt")
const express = require("express")

const { User, validate } = require("../models/user")

const router = express.Router()

router.post("/", async (req, res) => {
  //validating request
  const { error } = validate(req.body)

  if (error) {
    return res.status(400).send(error.details[0].message)
  }

  // Check if this user already exisits
  let user = await User.findOne({ email: req.body.email })
  if (user) {
    return res.status(409).send(`Someone is already using ${req.body.email}.`)
  }

  user = new User({
    firstName: req.body.firstName,
    lastName: req.body.lastName,
    email: req.body.email,
    phone: req.body.phone,
    password: req.body.password,
    paid: false,
  })

  const salt = await bcrypt.genSalt(10)
  user.password = await bcrypt.hash(user.password, salt)

  await user.save()

  const data = {
    id: user._id,
    firstName: user.firstName,
    lastName: user.lastName,
    email: user.email,
    phone: user.phone,
    paid: user.paid,
  }

  res.status(200).send(data)
})

module.exports = router

现在我们将为教程中最重要的部分创建 API,即创建 paymentIntent。根据 Stripe 的官方文档,PaymentIntent 会指导您完成向客户收取付款的过程。PaymentIntent 在其整个生命周期内转换多个状态,因为它与 Stripe.js 交互以执行身份验证流程并最终创建最多一次成功的收费。你可以在这里了解更多关于它的信息https://stripe.com/docs/api/payment_intents

将此代码粘贴到 PaymentIntent.js 文件中

const config = require("config")
const express = require("express")

const key = config.get("StripeKey")

const stripe = require("stripe")(key)

const router = express.Router()

router.post("/", async (req, res) => {
  try {
    const paymentIntent = await stripe.paymentIntents.create({
      amount: req.body.amount,
      currency: "usd",
      payment_method_types: ["card"],
      description: req.body.description,
      receipt_email: req.body.receipt_email,
      shipping: {
        address: {
          city: "",
          country: "",
          line1: "",
          line2: "",
          postal_code: "",
          state: "",
        },
        name: req.body.name,
        phone: req.body.phone,
      },
      metadata: {
        uid: req.body.uid,
      },
    })

    res.json({ paymentIntent: paymentIntent.client_secret })
  } catch (error) {
    res.json({ error })
  }
})

module.exports = router

现在我们将创建 webhook 以在成功交易后更新我们的数据库。使用 webhook 更新数据库很重要,而不是通过前端或客户端更新数据库,因为它可能会泄漏性能。让我分享一下条纹官方文档的原因。

当支付完成时,Stripe 会发送一个 payment_intent.succeeded 事件。监听这些事件而不是等待来自客户端的回调。在客户端,客户可以在回调执行之前关闭浏览器窗口或退出应用程序,恶意客户端可以操纵响应。设置集成以侦听异步事件使您能够通过单个集成接受不同类型的付款方式

在 webhook.js 文件中粘贴此代码

const config = require("config")
const express = require("express")

const key = config.get("StripeKey")
const stripe = require("stripe")(key)

const { User } = require("../models/user")

const router = express.Router()

router.post(
  "/",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    let event = req.body

    // Handle the event
    switch (event.type) {
      case "payment_intent.succeeded":
        const paymentIntent = event.data.object
        updateUser(paymentIntent.metadata.uid)
        // Then define and call a function to handle the event payment_intent.succeeded
        break

      default:
        console.log(`Unhandled event type ${event.type}`)
    }
    res.send({ received: true })
  }
)

const updateUser = async (id) => {
  const update = { paid: true }
  await User.findByIdAndUpdate(id, update)
}

module.exports = router

托管

为了托管我们的 API,我们将使用 heroku。Heroku 是一个云平台即服务,支持多种编程语言。我们需要在名为 Procfile 的根目录中创建文件并将以下代码粘贴到其中

web: node index.js

然后去创建你的新账户https://signup.heroku.com/。成功登录后创建新应用程序。

在此之后按照 heroku git 说明部署您的应用程序。

环境变量

现在我们将在我们的 react native 项目的根目录下创建 .env 和 .babelrc 文件

将此代码粘贴到 .env 文件中,这是您的 heroku 基本 url 和条带可发布密钥

SERVER_URL=https://your_url/
STRIPE_KEY=pk_test_51KRAUJHyv7k**************************

将以下代码粘贴到 .babelrc

{
  "plugins": [["module:react-native-dotenv"]]
}

更新 Register.js

现在在 reigster.js 中导入这一行

import { SERVER_URL } from ' @env ' 然后更新函数 handleRegister 如下

  const handleRegister = async v => {
    setLoading(true)
    const url = `${SERVER_URL}api/signUp`
    try {
      const res = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          firstName: v.FIRSTNAME,
          lastName: v.LASTNAME,
          email: v.EMAIL,
          phone: v.PHONE,
          password: v.PASSWORD,
        }),
      })

      const resp = await res.text()
      console.log(resp)
      setLoading(false)

      if (res.status == 200) {
        showToastSuccess('Congratulation!')
        setTimeout(() => {
          navigation.replace('RegisterSuccess', {
            name: v.FIRSTNAME,
          })
        }, 1200)
      } else {
        showToastError(resp)
      }
    } catch (error) {
      Alert.alert('Error', error.message)
      console.log(error)
      setLoading(false)
    }
  }

更新 Login.js

现在在 Login.js 中更新函数如下

  const doLogin = async v => {
    setLoading(true)
    const url = `${SERVER_URL}api/login`
    try {
      const res = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          email: v.EMAIL,
          password: v.PASSWORD,
        }),
      })

      setLoading(false)

      if (res.status == 200) {
        const response = await res.json()
        showToastSuccess('Login successfull')
        navigation.replace('Home', {
          response,
        })
        // navigation.reset({
        //   index: 0,
        //   routes: [{ name: 'Home' }, { params: response }],
        // })
      } else {
        const resp = await res.text()
        showToastError(resp)
      }
    } catch (error) {
      console.log(error)
      setLoading(false)
      Alert.alert('Error', error.message)
    }
  }

更新 Home.js

在 Home.js 现在更新这些函数如下

  const fetchPaymentSheetParams = async () => {
    let url = `${SERVER_URL}api/paymentIntent`
    try {
      const res = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          amount: amount * 100,
          description:
            'This is test payment done by Fiyaz for his helping blog for developers',
          receipt_email: email,
          name: `${firstName} ${lastName}`,
          phone: phone,
          uid: id,
        }),
      })
      if (res.status === 200) {
        const response = await res.json()
        console.log(response)
        if (response.error) {
          Alert.alert(
            `${response.error.statusCode}`,
            response.error.raw.message,
          )
        } else {
          const { paymentIntent } = response
          return { paymentIntent }
        }
      }
    } catch (error) {
      Alert.alert('Error', error.message)
    }
  }

  const initializePaymentSheet = async () => {
    if (amount > 0) {
      const { paymentIntent } = await fetchPaymentSheetParams()
      const { error } = await initPaymentSheet({
        paymentIntentClientSecret: paymentIntent,
        merchantDisplayName: 'Fiyaz Hussain',
      })

      if (error) {
        const message = error.message
        Toast.show({
          type: 'error',
          text1: 'Error',
          text2: `${message}`,
        })
        return
      }

      openPaymentSheet()
    } else {
      Toast.show({
        type: 'info',
        text1: 'Add Amount',
        text2: 'Please select any payment to process',
      })
    }
  }

  const openPaymentSheet = async () => {
    const { error } = await presentPaymentSheet()
    if (error) {
      if (error.message === 'Canceled') {
        return null
      } else {
        Toast.show({
          type: 'error',
          text1: 'Error',
          text2: `${error.message}`,
        })
        return
      }
    }

    Toast.show({
      type: 'success',
      text1: 'Success',
      text2: 'Thank you! You have paid successfully.',
    })
  }

现在我们已经完成了教程,我们现在可以成功付款了。在本教程中,我们学习了如何使用条带在 React Native 应用程序中进行实时支付。

Github

点击这里查找代码https://github.com/Fiyaz6772/payments

文章来源:https://fiyaz2110.hashnode.dev/how-to-add-payment-into-react-native-using-stripe

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,230评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,261评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,089评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,542评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,542评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,544评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,922评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,578评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,816评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,576评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,658评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,359评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,920评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,859评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,381评论 2 342