在本教程中,我们将在适用于 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