在本教程中,我們將在適用于 iOS 和 Android 的 React Native 移動(dòng)應(yīng)用程序中實(shí)現(xiàn)一種支付方式狮含。在本教程中洋满,我將指導(dǎo)您完成每個(gè)步驟,在本教程結(jié)束時(shí)锣咒,您將能夠使用您的移動(dòng)應(yīng)用程序進(jìn)行任何實(shí)時(shí)支付侵状。我們將使用React Native進(jìn)行移動(dòng)應(yīng)用程序開(kāi)發(fā),NodeJs用于服務(wù)器毅整,mongodb用于數(shù)據(jù)庫(kù)趣兄,Stripe作為支付處理系統(tǒng)。為了托管我們的 API悼嫉,我們將使用提供免費(fèi)基本托管的 heroku艇潭,它足以滿足我們的要求。
我將向您解釋所有內(nèi)容戏蔑,讓我們深入了解本教程蹋凝。
我們將從創(chuàng)建 react-native 項(xiàng)目開(kāi)始,并創(chuàng)建將在我們的應(yīng)用程序中使用的 UI总棵。由于我們的主要目的是學(xué)習(xí)支付實(shí)現(xiàn)鳍寂,所以我們將專(zhuān)注于它而不是浪費(fèi)我們的時(shí)間來(lái)構(gòu)建現(xiàn)代設(shè)計(jì)的 UI。讓我們通過(guò)以下命令創(chuàng)建 React Native 項(xiàng)目情龄。
- npx react-native init yourProjectName
- cd yourProjectName
- code
通過(guò)輸入代碼迄汛。該文件夾將在您的編輯器中打開(kāi)『慈溃現(xiàn)在通過(guò)以下命令在您的設(shè)備上運(yùn)行項(xiàng)目
npx react-native run-android
npx react-native run-ios
如果您是 react-native 的新手,那么您可以按照此鏈接進(jìn)行環(huán)境設(shè)置和創(chuàng)建新項(xiàng)目 https://reactnative.dev/docs/environment-setup
現(xiàn)在創(chuàng)建一個(gè)文件夾名稱(chēng) src 并使用以下名稱(chēng)創(chuàng)建子文件夾鞍爱。
在此打開(kāi)屏幕文件夾之后鹃觉,并在其中創(chuàng)建子文件夾,如下所示
如果您按照本教程獲取代碼和文件有任何問(wèn)題硬霍,請(qǐng)不要擔(dān)心帜慢,我將在本教程末尾分享 github 鏈接。????
下面是我在這個(gè)項(xiàng)目中使用的包列表唯卖,你可以通過(guò) 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
- 是的 安裝這些包后不要忘記通過(guò) npx react-native run-android 再次運(yùn)行項(xiàng)目粱玲。????。現(xiàn)在讓我們進(jìn)入您正在等待的編碼部分????
在 utils 文件夾中創(chuàng)建一個(gè)文件 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 文件夾中創(chuàng)建以下文件
將以下代碼粘貼到 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
如果您只是對(duì)代碼部分感到無(wú)聊,您可以轉(zhuǎn)到下面的支付實(shí)現(xiàn)部分橄碾,并可以在 github 存儲(chǔ)庫(kù)中找到代碼卵沉。
在 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
恭喜????。您已經(jīng)為應(yīng)用程序創(chuàng)建了所有必需的組件法牲,現(xiàn)在讓我們轉(zhuǎn)向創(chuàng)建屏幕部分史汗。在 src/screens/auth 文件夾中添加以下文件
將以下文件復(fù)制并粘貼到 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 文件中粘貼以下代碼。現(xiàn)在我將把 handleRegister 留空拒垃,我們將在創(chuàng)建 API 之后添加所需的代碼停撞。因此,現(xiàn)在添加此代碼并耐心等待悼瓮,因?yàn)槲覍⒏嬖V您所有內(nèi)容戈毒。
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
當(dāng)我們做了一個(gè)注冊(cè)用戶的 UI 時(shí),為什么我們不應(yīng)該添加用于登錄的 UI ?? ??横堡。粘貼以下內(nèi)容埋市,您就完成了。??????
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
哦命贴,不道宅,我們的啟動(dòng)畫(huà)面在哪里?胸蛛?污茵??????????胚泌。讓我從你那里找到省咨。請(qǐng)稍等!g枋摇零蓉!首先在splash文件夾中創(chuàng)建兩個(gè)文件笤受,如下
是的,就在這里敌蜂。不要看這條線箩兽。代碼在這一行下面!章喉!
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
我們應(yīng)該制作一個(gè)注冊(cè)成功屏幕嗎汗贫?是的,那你為什么不應(yīng)該像這樣在這個(gè)文件夾中創(chuàng)建兩個(gè)文件呢秸脱?
以下是這些文件的代碼????
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
我知道教程越來(lái)越長(zhǎng)落包,但我相信你會(huì)在本教程結(jié)束時(shí)感到高興,所以這是我們最終的支付屏幕 UI摊唇。在主文件夾中創(chuàng)建這些文件
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
最后咐蝇,我們?cè)O(shè)計(jì)了所有的 UI,現(xiàn)在將創(chuàng)建屏幕的導(dǎo)航部分巷查。因此有序,在導(dǎo)航文件夾中創(chuàng)建一個(gè)名為 AppStack.js 的文件,并在該文件中添加以下代碼以完成我們支付移動(dòng)應(yīng)用程序的導(dǎo)航部分岛请。
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>
)
}
不旭寿,因?yàn)槲覀円呀?jīng)完成了屏幕和導(dǎo)航部分,這意味著我們幾乎完成了我們的 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
我們已經(jīng)完成了我們的UI部分僚匆,如果你累了微渠,休息一下搭幻,喝杯咖啡????
Stripe
現(xiàn)在我將指導(dǎo)您如何創(chuàng)建條帶帳戶并獲取我們處理付款所需的密鑰咧擂。Stripe 是一套 API,為各種規(guī)模的互聯(lián)網(wǎng)企業(yè)提供在線支付處理和商務(wù)解決方案檀蹋。接受付款并更快地?cái)U(kuò)展松申。您可以在https://stripe.com/了解有關(guān)條紋的更多信息
如果您沒(méi)有條帶帳戶,請(qǐng)點(diǎn)擊此鏈接創(chuàng)建您的條帶帳戶https://dashboard.stripe.com/register俯逾。您可以跳過(guò)商業(yè)注冊(cè)流程贸桶,因?yàn)楝F(xiàn)在我們必須在測(cè)試模式下付款,但對(duì)于實(shí)時(shí)模式桌肴,您必須完成所有注冊(cè)流程皇筛。
成功注冊(cè)后,從儀表板右上角打開(kāi)測(cè)試模式坠七。https://dashboard.stripe.com/test/dashboard然后點(diǎn)擊開(kāi)發(fā)者水醋。
您可以找到 Publishable key 和 Secret key旗笔。我們將在客戶端(移動(dòng)應(yīng)用程序 UI)中需要 Publishable 密鑰,在我們的側(cè)代碼中需要 Secret 密鑰拄踪。
數(shù)據(jù)庫(kù)
我們將使用 mongodb 作為我們的數(shù)據(jù)庫(kù)蝇恶。因此,如果您沒(méi)有 mongodb 帳戶惶桐,請(qǐng)按照https://account.mongodb.com/account/register創(chuàng)建您的新帳戶撮弧。注冊(cè)成功后,您將看到如下儀表板姚糊。
單擊構(gòu)建數(shù)據(jù)庫(kù)贿衍,您將看到一個(gè)新屏幕,如下所示救恨。您可以創(chuàng)建共享數(shù)據(jù)庫(kù)舌厨,因?yàn)樗敲赓M(fèi)的,足以滿足我們的需求忿薇。
在下一個(gè)屏幕上裙椭,單擊屏幕底部的創(chuàng)建集群按鈕,您將看到一個(gè)新屏幕署浩。在此處添加您的用戶名和密碼并將它們保存在本地文件中揉燃,因?yàn)楫?dāng)我們將后端連接到數(shù)據(jù)庫(kù)時(shí)將需要這兩個(gè)。不用擔(dān)心筋栋,您也可以隨時(shí)在集群中找到它們炊汤。
添加此屏幕的底部,通過(guò)單擊添加我的當(dāng)前 IP 地址來(lái)添加您的機(jī)器 IP 地址弊攘。這將使您能夠使用這臺(tái)筆記本電腦訪問(wèn)數(shù)據(jù)庫(kù)抢腐。如果您要添加 0.0.0.0/0,那么您可以從任何筆記本電腦訪問(wèn)數(shù)據(jù)庫(kù)襟交。在此之后迈倍,我們完成了我們的數(shù)據(jù)庫(kù)設(shè)置??????。
服務(wù)器端
我們將使用 NodeJS 和 express 進(jìn)行我們的側(cè)邊開(kāi)發(fā)捣域。NodeJS 是現(xiàn)代開(kāi)發(fā)世界中連接 mongodb 的重要語(yǔ)言啼染。在不深入了解 NodeJS 和表達(dá)的細(xì)節(jié)的情況下,我們將轉(zhuǎn)向我們最重要的部分焕梅,即編碼????迹鹅。
創(chuàng)建一個(gè)文件夾并在其中打開(kāi)命令提示符。通過(guò)鍵入 npx init -y 初始化項(xiàng)目贞言,然后按 Enter斜棚。使用 npm 安裝一些必要的包。
- bcrypt
- body-parser
- config
- cors
- express
- helmet
- joi
- mongodb
- mongoose
- stripe
在根文件夾中創(chuàng)建一個(gè)文件 index.js ,然后在項(xiàng)目文件夾的根目錄中創(chuàng)建一個(gè)文件夾config弟蚀、models和routes脂新。
配置
在文件夾內(nèi)添加兩個(gè)名為 custom-environment-variables.json 和 default.json 的文件,如下所示粗梭。
并在兩個(gè)文件中粘貼相同的內(nèi)容
{
"PrivateKey": "mongodb+srv://username:password@cluster0.zy6jo.mongodb.net/easyPay?retryWrites=true&w=majority",
"StripeKey": "add your stripe secret key here"
}
請(qǐng)記住争便,您需要在此處添加您的條帶密鑰。https://dashboard.stripe.com/apikeys断医。在 PrivateKey 中滞乙,將用戶名和密碼替換為我們?cè)谙旅嫫聊粍?chuàng)建數(shù)據(jù)庫(kù)時(shí)添加的 mongodb 用戶名和密碼。
模型
在此文件夾中創(chuàng)建一個(gè)名為 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
路線
這個(gè)文件夾將包含我們的路由文件,我們將在這個(gè)文件夾中創(chuàng)建 API醉锅。請(qǐng)?jiān)诖宋募A中創(chuàng)建四個(gè)文件兔簇,如下所示。
在 auth.js 中添加以下代碼硬耍,此路由將負(fù)責(zé)登錄我們的應(yīng)用程序用戶垄琐。這是此文件的代碼。
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 的代碼经柴,用于注冊(cè)我們的用戶
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
現(xiàn)在我們將為教程中最重要的部分創(chuàng)建 API狸窘,即創(chuàng)建 paymentIntent。根據(jù) Stripe 的官方文檔坯认,PaymentIntent 會(huì)指導(dǎo)您完成向客戶收取付款的過(guò)程翻擒。PaymentIntent 在其整個(gè)生命周期內(nèi)轉(zhuǎn)換多個(gè)狀態(tài),因?yàn)樗c Stripe.js 交互以執(zhí)行身份驗(yàn)證流程并最終創(chuàng)建最多一次成功的收費(fèi)牛哺。你可以在這里了解更多關(guān)于它的信息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
現(xiàn)在我們將創(chuàng)建 webhook 以在成功交易后更新我們的數(shù)據(jù)庫(kù)陋气。使用 webhook 更新數(shù)據(jù)庫(kù)很重要,而不是通過(guò)前端或客戶端更新數(shù)據(jù)庫(kù)引润,因?yàn)樗赡軙?huì)泄漏性能巩趁。讓我分享一下條紋官方文檔的原因。
當(dāng)支付完成時(shí)椰拒,Stripe 會(huì)發(fā)送一個(gè) payment_intent.succeeded 事件晶渠。監(jiān)聽(tīng)這些事件而不是等待來(lái)自客戶端的回調(diào)凰荚。在客戶端燃观,客戶可以在回調(diào)執(zhí)行之前關(guān)閉瀏覽器窗口或退出應(yīng)用程序,惡意客戶端可以操縱響應(yīng)便瑟。設(shè)置集成以偵聽(tīng)異步事件使您能夠通過(guò)單個(gè)集成接受不同類(lèi)型的付款方式
在 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 是一個(gè)云平臺(tái)即服務(wù)到涂,支持多種編程語(yǔ)言脊框。我們需要在名為 Procfile 的根目錄中創(chuàng)建文件并將以下代碼粘貼到其中
web: node index.js
然后去創(chuàng)建你的新賬戶https://signup.heroku.com/颁督。成功登錄后創(chuàng)建新應(yīng)用程序。
在此之后按照 heroku git 說(shuō)明部署您的應(yīng)用程序浇雹。
環(huán)境變量
現(xiàn)在我們將在我們的 react native 項(xiàng)目的根目錄下創(chuàng)建 .env 和 .babelrc 文件
將此代碼粘貼到 .env 文件中沉御,這是您的 heroku 基本 url 和條帶可發(fā)布密鑰
SERVER_URL=https://your_url/
STRIPE_KEY=pk_test_51KRAUJHyv7k**************************
將以下代碼粘貼到 .babelrc
{
"plugins": [["module:react-native-dotenv"]]
}
更新 Register.js
現(xiàn)在在 reigster.js 中導(dǎo)入這一行
import { SERVER_URL } from ' @env ' 然后更新函數(shù) 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
現(xiàn)在在 Login.js 中更新函數(shù)如下
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 現(xiàn)在更新這些函數(shù)如下
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.',
})
}
現(xiàn)在我們已經(jīng)完成了教程,我們現(xiàn)在可以成功付款了昭灵。在本教程中吠裆,我們學(xué)習(xí)了如何使用條帶在 React Native 應(yīng)用程序中進(jìn)行實(shí)時(shí)支付。
Github
點(diǎn)擊這里查找代碼https://github.com/Fiyaz6772/payments
文章來(lái)源:https://fiyaz2110.hashnode.dev/how-to-add-payment-into-react-native-using-stripe