An application created using React Native and Expo with the aim of creating a sample app with Tap to Pay functionality using stripe-terminal-react-native.
- Expo SDK 49+
- React Native 0.72+
- Stripe Account (Development) to use API tokens
- Expo Account to generate the build via EAS
-
To use the mock, you need to download this repository and build it via EAS using the command:
❯ eas build --profile development
After that, EAS will take care of creating the build and installing it on your emulator (Android/iOS).
-
With the app installed on the simulator, you need to start the project with the command:
❯ npm run start
and also the local server created with json-server
❯ npm run server
With these two commands running, our project is up and ready for testing.
Stripe uses public keys to make the front-back connection, and it is necessary to change two of them for things to work:
- Public key to connect to the readers:
/src/db/db.json
- LocationId to identify the physical company:
/src/stripe-page.tsx
Both keys can be created or updated using the Stripe CLI. Below is the code used:
- As a standard for the mock here, I decided to wrap all requests inside trycatch and also add alerts for successful cases. For error cases, I will only return a console.log.
useEffect(() => {
discoverReaders({
discoveryMethod: 'localMobile',
simulated: true,
})
}, [discoverReaders])
This useEffect has a function from stripe-terminal itself that is responsible for locating available readers. After locating them, we move on to the second step.
async function connectReader(selectedReader: any) {
setLoadingConnectingReader(true)
try {
const { reader, error } = await connectLocalMobileReader({
reader: selectedReader,
locationId: locationIdStripeMock,
})
if (error) {
console.log('connectLocalMobileReader error:', error)
return
}
Alert.alert('Reader connected successfully')
console.log('Reader connected successfully', reader)
} catch (error) {
console.log(error)
} finally {
setLoadingConnectingReader(false)
}
}
Function used to connect to the reader found earlier. In it, we can destructure the reader and the error, which can be handled if it occurs. It is also at this step that we use LocationId to identify which establishment that reader belongs to.
async function paymentIntent() {
setLoadingCreatePayment(true)
try {
const { error, paymentIntent } = await createPaymentIntent({
amount: 100,
currency: 'usd',
paymentMethodTypes: ['card_present'],
offlineBehavior: 'prefer_online',
})
if (error) {
console.log('Error creating payment intent', error)
return
}
setPayment(paymentIntent)
Alert.alert('Payment intent created successfully')
} catch (error) {
console.log(error)
} finally {
setLoadingCreatePayment(false)
}
}
In this function, we create the payment intent responsible for capturing the amount, currency type, payment methods (card, virtual card, etc.), and so on. Just like in the previous function, we can destructure the request and obtain the payment intent data or the error, if it occurs.
*** The amount always equals its full value, without dots. Example: U$ 1.00 = 100; U$ 10.00 = 1000;
async function collectPayment() {
setLoadingCollectPayment(true)
try {
const { error, paymentIntent } = await collectPaymentMethod({
paymentIntent: payment,
})
if (error) {
console.log('Error collecting payment', error)
return
}
Alert.alert('Payment successfully collected', '', [
{
text: 'Ok',
onPress: async () => {
await confirmPayment(paymentIntent)
},
},
])
} catch (error) {
console.log(error)
} finally {
setLoadingCollectPayment(false)
}
}
In this function, we collect the payment intent created earlier. It is here that we open the Tap to Pay functionality for the user to touch the payment method and make the payment.
async function confirmPayment(payment: any) {
setLoadingConfirmPayment(true)
try {
const { error, paymentIntent } = await confirmPaymentIntent(payment)
if (error) {
console.log('Error confirm payment', error)
return
}
Alert.alert('Payment successfully confirmed!', 'Congratulations')
console.log('Payment confirmed', paymentIntent)
} catch (error) {
console.log(error)
} finally {
setLoadingConfirmPayment(false)
}
}
Here, we confirm all previous steps and finalize the sales process.
Now you can observe how the app behaves in real time!
Here are the documentations that helped me create the mock and my production app:
This project is licensed. Please see the license file for more details.
Arlan Gustavo Biati