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

在本教程中,我們將在適用于 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末烂完,一起剝皮案震驚了整個(gè)濱河市试疙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抠蚣,老刑警劉巖祝旷,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嘶窄,居然都是意外死亡怀跛,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)柄冲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)敌完,“玉大人,你說(shuō)我怎么就攤上這事羊初”醺龋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵长赞,是天一觀的道長(zhǎng)晦攒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)得哆,這世上最難降的妖魔是什么脯颜? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮贩据,結(jié)果婚禮上栋操,老公的妹妹穿的比我還像新娘。我一直安慰自己饱亮,他們只是感情好矾芙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著近上,像睡著了一般剔宪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天葱绒,我揣著相機(jī)與錄音感帅,去河邊找鬼。 笑死地淀,一個(gè)胖子當(dāng)著我的面吹牛失球,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帮毁,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼她倘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了作箍?” 一聲冷哼從身側(cè)響起硬梁,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胞得,沒(méi)想到半個(gè)月后荧止,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阶剑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年跃巡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牧愁。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡素邪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猪半,到底是詐尸還是另有隱情兔朦,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布磨确,位于F島的核電站沽甥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏乏奥。R本人自食惡果不足惜摆舟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望邓了。 院中可真熱鬧恨诱,春花似錦、人聲如沸骗炉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)痕鳍。三九已至硫豆,卻和暖如春龙巨,著一層夾襖步出監(jiān)牢的瞬間笼呆,已是汗流浹背熊响。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诗赌,地道東北人汗茄。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像铭若,于是被迫代替她去往敵國(guó)和親洪碳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345