I am developing a React-Native application for iOS where the user can transfer money from person to another by scanning a QR code (this holds the payment destination) via Stripe. After I scan the QR Code I get this error ‘{“error”: {“error”: “TypeError: Network request failed”, “status”: “FETCH_ERROR”}}’ in the terminal. What is supposed to happen is the payment sheet appears with the value to be paid which is calculated on the previous screen. Stripe is the service I’m using to handle the payments. I am able to make a post request through the terminal and it shows up on my dashboard on the Stripe website and I receive a response.
I have all of the code for scanning the QR code and displaying the payment sheet in one file.
QRCodeScan.js
import { View,StyleSheet, Text, Button, Alert } from 'react-native'
import React, { useState, useEffect } from 'react'
import { BarCodeScanner } from 'expo-barcode-scanner'
import { useNavigation } from '@react-navigation/native'
import { useCreatePaymentIntentMutation } from '../../store/apiSlice'
import { useRoute } from '@react-navigation/native'
import {useStripe} from '@stripe/stripe-react-native'
const QRCodeScan = () => {
//This screen is used to scan QR codes and then display the Stripe payment functionality
const[hasPermission, setHasPermission] = useState('')
const[scanned, setScanned] = useState(false)
const[iban, setIban] = useState('Scan a QR Code')
const [createPaymentIntent] = useCreatePaymentIntentMutation()
const{initPaymentSheet, presentPaymentSheet} = useStripe()
const navi = useNavigation()
const route = useRoute()
const Tip = route.params.calculatedTip
//Asks the device for permission to use the camera and sets it to granted if allowed
const askForCameraPermission = () =>{
(async () =>{
const {status} = await BarCodeScanner.requestPermissionsAsync()
setHasPermission(status === 'granted')
})()
}
//The display box for the permission request
useEffect(() =>{
askForCameraPermission()
}, [])
//Scans and stores the information scanned from the barcode and then loads the Stripe payment overlay
const handleBarCodeScanned = ({type, data}) =>{
setScanned(true)
setIban(data)
console.log("Type: " + type)
console.log("Data: " + data)
onMakePayment()
}
//Sets the total from the calculated Tip
const onMakePayment = async () => {
const response = await createPaymentIntent({amount: route.params.Tip })
console.log(response)
if(response.error){
Alert.alert('Something went wrong,', response.error)
return;
}
const { paymentIntent } = response.data;
//Sets the payment details and sets the employees IBAN as the destination of the payment
const paymentMethod = {
type: 'sepa_debit',
sepa_debit: {
iban: iban, // set the IBAN to the scanned value
},
billing_details: {
name: '***', // set the name for the billing details
},
};
const { error } = await stripe.createPaymentMethod(paymentMethod);
if (error) {
Alert.alert('Something went wrong,', error.message);
return;
}
const initResponse = await initPaymentSheet({
merchantDisplayName: 'Tipper',
paymentIntentClientSecret: paymentIntent.client_secret,
customFlow: true,
paymentMethodId: paymentMethod.id
})
if(initResponse.error){
console.log(initResponse.error)
Alert.alert('Something went wrong,', response.error)
return;
}
await presentPaymentSheet()
};
//If hasnt been granted or denied, device requests user to either grant or deny permission
if(hasPermission === null) {
return(
<View style={styles.container}>
<Text>Requesting camera permission</Text>
</View>
)
}
//If permission isnt granted, camera isnt used and displays no access to camera
if(hasPermission === false){
return(
<View style={styles.container}>
<Text style={{margin: 10}}>No access to camera</Text>
<Button title={'Allow camera'} onPress={() => askForCameraPermission()}></Button>
</View>
)
}
return(
<View style={styles.container}>
<View >
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={{height: 400, width: 400}}>
</BarCodeScanner>
</View>
<Text style={styles.maintext}>{iban}</Text>
{scanned && <Button title={'Scan again'} onPress={() => setScanned(false)} color='tomato'></Button>}
</View>
)
}
const styles = StyleSheet.create({
root:{
alignItems: 'center',
padding: 25
},
title:{
fontSize: 20,
fontWeight: 'bold',
margin: 10
},
container: {
backgroundColor: 'white',
width: '100%',
borderColor: '#E8E8E8',
borderWidth: 1,
borderRadius: 5,
paddingHorizontal: 10,
paddingVertical: 10,
marginVertical: 7
},
value:{
fontSize: 20,
fontWeight: 'bold',
margin: 10
},
barcodebox:{
alignItems: 'center',
justifyContent: 'center',
height: 300,
width: 300,
overflow: 'hidden',
borderRadius: 30,
backgroundColor: 'tomato'
},
maintext:{
fontSize: 16,
margin: 20
}
})
export default QRCodeScan
server.js
const express = require('express')
const paymentRoutes = require('./PaymentLogic/PaymentRoute')
const bodyParser = require('body-parser')
const app = express()
const PORT = 3000
app.use(bodyParser.json())
app.use('/payments', paymentRoutes)
app.listen(PORT, () =>{
console.log('API listening on port ', PORT)
})
PaymentRoute.js
this file connects my app to Stripe with the secret key
const express = require('express')
const router = express.Router()
const stripe = require('stripe')(Took key out for security for this post )
//This is used to post the transaction details to Stripe account for Tipper
router.post('/intents', async(req, res) =>{
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: req.body.amount,
currency: 'eur',
automatic_payment_methods:{
enabled: true
}
})
res.json({paymentIntent: paymentIntent.client_secret})
} catch (error) {
res.status(400).json({
error: error.message
})
}
})
module.exports = router
apiSlice.js
//This file is used to create the payments that would be sent to Stripe
import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react'
const baseUrl = 'http://localhost:3000/';
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl }),
endpoints: (builder) => ({
// Payments
createPaymentIntent: builder.mutation({
query: (data) => ({
url: 'payments/intents',
method: 'POST',
body: data,
}),
}),
}),
});
export const {
useCreatePaymentIntentMutation
} = apiSlice;
index.js for apiSlice.js
import { configureStore } from '@reduxjs/toolkit';
import { apiSlice } from './apiSlice';
export const store = configureStore({
reducer: {
api: apiSlice.reducer,
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware),
})
And this is just a screenshot of the error after the QR code is scanned.
