Closed yaromyrshmid closed 1 year ago
@yaromyrshmid would you please share your code so I can help you.
@yaromyrshmid would you please share your code so I can help you.
Thank you for the response. I've seen this topic: , so I'm not sure you'll be able to help ;) But here is our implementation in case it will help you with your problem.
useStripeTerminal:
import { useCallback, useEffect, useState } from 'react';
import { useStripeTerminal } from '@stripe/stripe-terminal-react-native';
import { APP_ENV } from 'react-native-dotenv';
import { useAppSelector } from 'store/hooks';
import { selectUser } from 'store/slices/user/userSlice';
import { PaymentAPI } from 'common/services/API/PaymentAPI/PaymentAPI';
import { ManualRequestData } from 'types/Request';
import { toastError, toastSuccess } from 'utils/toast';
export const useTapToPay = () => {
const user = useAppSelector(selectUser);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const {
discoverReaders,
connectedReader,
discoveredReaders,
connectLocalMobileReader,
cancelDiscovering,
collectPaymentMethod,
processPayment,
retrievePaymentIntent,
isInitialized,
} = useStripeTerminal({
onFinishDiscoveringReaders: (error) => {
if (error?.message) {
setError(error.message);
setLoading(false);
}
},
onDidChangeConnectionStatus: () => {
setLoading(false);
},
});
const discoverLocalMobile = useCallback(async () => {
if (isInitialized && !connectedReader) {
setError(null);
setLoading(true);
await cancelDiscovering();
await discoverReaders({
discoveryMethod: 'localMobile',
simulated: APP_ENV === 'staging',
});
}
}, [discoverReaders, isInitialized, connectedReader]);
useEffect(() => {
discoverLocalMobile();
}, [discoverReaders]);
const connectReader = async () => {
await connectLocalMobileReader({
reader: discoveredReaders[0],
locationId: user?.company?.stripeLocationId || '',
onBehalfOf: user?.company?.stripeAccountId || '',
});
};
useEffect(() => {
if (discoveredReaders[0]) {
connectReader();
}
}, [discoveredReaders]);
const handleTapToPay = async (requestData: ManualRequestData) => {
try {
if (!user?.company?.stripeAccountId)
throw new Error('No stripe account id');
const requestDto = {
stockNumber: requestData.description,
customerName: requestData.name || 'test',
...(requestData.type === 'email'
? {
customerEmail: requestData.email,
}
: {
customerPhone: requestData.phone,
}),
};
const { clientSecret } = await PaymentAPI.collectReaderPayment({
amount: requestData.amount,
...requestDto,
});
const paymentIntentResponse = await retrievePaymentIntent(clientSecret);
if (paymentIntentResponse.error || !paymentIntentResponse.paymentIntent)
throw new Error(
paymentIntentResponse.error?.message ||
'Failed to retrieve payment intent',
);
// Exception thrown executing this:
const paymentMethodResponse = await collectPaymentMethod({
paymentIntentId: paymentIntentResponse.paymentIntent.id,
});
if (paymentMethodResponse.error || !paymentMethodResponse.paymentIntent)
throw new Error(
paymentMethodResponse.error?.message ||
'Failed to collect payment method',
);
const processPaymentResponse = await processPayment(
paymentMethodResponse.paymentIntent.id,
);
if (processPaymentResponse.error || !processPaymentResponse.paymentIntent)
throw new Error(
processPaymentResponse.error?.message || 'Failed to process payment',
);
await PaymentAPI.captureReaderPayment({
paymentIntentId: processPaymentResponse.paymentIntent.id,
...requestDto,
});
toastSuccess('Your payment has succeeded!');
return true;
} catch (error: any) {
toastError(error?.message || 'Failed to process payment');
}
};
return {
handleTapToPay,
loading,
error,
};
};
Here's the initialization hook:
import { useEffect } from 'react';
import {
requestNeededAndroidPermissions,
useStripeTerminal,
} from '@stripe/stripe-terminal-react-native';
import { Platform } from 'react-native';
import { selectAuthState } from 'store/slices/auth/authSlice';
import { useAppSelector } from 'store/hooks';
import { toastError } from 'utils/toast';
export const useInitializeStripeTerminal = () => {
const { initialize } = useStripeTerminal();
const { isAuth } = useAppSelector(selectAuthState);
const initializeAndroid = async () => {
const granted = await requestNeededAndroidPermissions({
accessFineLocation: {
title: 'Location Permission',
message: 'Stripe Terminal needs access to your location',
buttonPositive: 'Accept',
},
});
if (granted) {
initialize();
} else {
toastError(
'Location and BT services are required in order to connect to a reader.',
);
}
};
useEffect(() => {
if (isAuth) {
if (Platform.OS === 'android') {
initializeAndroid();
} else {
initialize();
}
}
}, [isAuth]);
};
And here is snippet of App component:
const App = () => {
return (
<StripeTerminalProvider
logLevel="verbose"
tokenProvider={PaymentReadersAPI.getReaderConnectionToken}>
...other providers
<RootNavigator />
</StripeTerminalProvider>
Any update on this? I am also running into this issue.
For me, it only happens when simulated = true
. If simulated = false
, I am able to use a stripe test account and the terminal card perfectly fine.
For discover readers which kind of readers are we can use? Can we use credit card or debit card information using discoverReader of the local mobile reader? Or we need stripe specify readers only.
You're able to use certain NFC enabled cards, so it can use both debit and credit cards. Here is a list of simulated/terminal cards you can use. Here is the discoverReaders javascript API docs. Discover readers is for all of the Stripe payment devices as well as discovering the local reader.
However, I don't see how this applies to the current issue.
hey @yaromyrshmid this is a known issue with simulated localMobile
readers on iOS and will be fixed in the next release. I'll provide an update here when that's out
@yaromyrshmid we just released a new version last Friday and bumped the native iOS SDK version. That should fix this issue!
Describe the bug When using After using collectPaymentMethod from useStripeTerminalHook app crashes with Exception 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread'
To Reproduce Steps to reproduce the behavior:
discoverReaders({ discoveryMethod: 'localMobile', simulated: true, })
Expected behavior A new screen should be displayed indicating that card should be pressed against the device (as on Android).
Stripe Terminal React Native SDK version ^0.0.1-beta.12
Smartphone:
Device: physical iPhone 12mini
OS: iOS 16.4.1
Device: Xcode simulator iPhone 13
OS: iOS 16.4
Additional context React Native: 0.72.0-rc.5
On Android everything works as expected.
Thank you for investigating.