How do you implement payments in React Native 0.70? - reactjs

How do you implement payments in React Native 0.70. I worked with earlier React Native versions using react-native-credit-card-input and react-native-credit-card-input-plus that are now breaking.

Now it very easy to implement the payment methods in react-native because stripe provide official doc.
They provide a built-in UI for checkout and Card Tokenisation,
Here you can Follow Official Doc
1) Setup
install stripe official react-native sdk
yarn add #stripe/stripe-react-native
To initialise Stripe in your React Native app, either wrap your payment screen with the StripeProvider component, or use the initStripe initialisation method.
<StripeProvider publishableKey={PUBLISHABLE_KEY}>
<Navigation />
</StripeProvider>
How to get PUBLISHABLE_KEY
Now in your component
Either use the Stripe UI or create your own custom UI for getting card details. In this answer, I'm using rn-credit-card for getting a card, which gives me customization options 🙂.
2) Get Card details, create Card Token and save for future use
import CreditCardForm, { FormModel } from "rn-credit-card";
const handleConfirm = (model: FormModel) => {
axios
.post(
"https://api.stripe.com/v1/tokens",
{
"card[name]": model.holderName,
"card[number]": model.cardNumber,
"card[exp_month]": model.expiration.split("/")[0],
"card[exp_year]": model.expiration.split("/")[1],
"card[cvc]": model.cvv,
},
{
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Bearer ${STRIPE_KEY}`,
},
}
)
.then((res) => {
if (res?.data?.id) {
//res?.data?.id It will return the payment method ID sent to your backend
// You can also save it for future use by saving it in the database.
console.log(res?.data?.id)
}
})
.catch((err) => {
Alert.alert("Stripe Error", err.message);
});
};
For setting defaultValues
const formMethods = useForm<FormModel>({
mode: "onBlur",
defaultValues: {
holderName: "",
cardNumber: "",
expiration: "",
cvv: "",
},
});
const { handleSubmit, formState } = formMethods;
Form to get card details
<CreditCardForm
LottieView={LottieView}
horizontalStart={false}
overrides={{
labelText: {
marginTop: 16,
},
}}
/>
{formState.isValid && (
<Button
style={styles.button}
title={'CONFIRM PAYMENT'}
onPress={handleSubmit(handleConfirm)}
/>
)}
Now When you pay or checkout simple do the following step
3) Checkout or Payment Time
Create a PaymentIntent by passing the paymentMethods Id with other params like reservationId etc
The backend will return you clientSecret and also the calculated bill
Send the clientSecret to stripe
import { useStripe } from "#stripe/stripe-react-native";
const { confirmPayment } = useStripe();
const handlePay = async () => {
setStripeLoading(true);
try {
//Step 1
const response = await createPaymentIntent({
variables: {
paymentMethodId: paymentMethodId, // That you stored on the backend
reserveId: id, // In our case reserveId is required
amountToDeduct: 23,
},
});
if (response) {
//Step 2 by getting clientSecret
const { clientSecret } = response?.createPaymentIntent;
//sending clientSecret to deduct amount
const { error, paymentIntent } = await confirmPayment(clientSecret);
if (error) {
setStripeLoading(false);
Alert.alert(`Error code: ${error.code}`, error.message);
}
if (paymentIntent) {
setStripeLoading(false);
// Show Success Alert
}
}
} catch (error) {
setStripeLoading(false);
} finally {
setStripeLoading(false);
}
};
Tada you done 🥰

Related

Sending Expo Push Notifications to both Android and iOS devices from Reactjs Dashboard

I have an expo app which is installed on both Android and iOS devices.
These apps use Firebase as a backend. I have also created a dashboard in Reactjs which is also using same Firebase as a backend.
I want to send notification to a specific user whenever i perform some event on Reactjs Dashboard.
e.g: I change the order_status from "pending" to "delivered" then a firebase event changes the order_status for that user in firebase collection so i want the user to know his order has been dispatched.
How can i achieve this for both Android and iOS ?
How can i achieve this for both Android and iOS ?
SOLUTION:
App side code to setup receiving notifications:
const [expoPushToken, setExpoPushToken] = useState('');
const [notification, setNotification] = useState(false);
const notificationListener = useRef();
const responseListener = useRef();
async function sendPushNotification(expoPushToken) {
const message = {
to: expoPushToken,
sound: 'default',
title: 'Original Title',
body: 'And here is the body!',
data: { someData: 'goes here' },
};
console.log(expoPushToken);
await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
}
async function registerForPushNotificationsAsync() {
let token;
if (Device.isDevice) {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
alert('Failed to get push token for push notification!');
return;
}
token = (await Notifications.getExpoPushTokenAsync()).data;
console.log(token);
} else {
alert('Must use physical device for Push Notifications');
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
return token;
}
useEffect(() => {
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
// This listener is fired whenever a notification is received while the app is foregrounded
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
setNotification(notification);
});
// This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed)
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
console.log(response);
});
return () => {
Notifications.removeNotificationSubscription(notificationListener.current);
Notifications.removeNotificationSubscription(responseListener.current);
};
}, []);
// write below code outside export App function
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
You might need to install expo-notifications using:
npx expo install expo-notifications
From Above code you can get push Token which at first you can manually use to test notifications, then eventually you can store all user's device push tokens in some firebase DB or custom DB against their uid.
Then later use these tokens to send them individual notifications.
Server Side Code:
install this library first npm i node-fetch
push Token looks like this : ExponentPushToken[KA2CcEFolWMq_9TmIddctr]
import fetch from "node-fetch";
async function sendPushNotification(expoPushToken) {
const android = "pushToken";
const ios = "pushToken";
const message = {
to: ios,
sound: 'default',
title: 'Original Title',
body: 'And here is the body!',
data: { someData: 'goes here' },
};
await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
}
call this function in the end sendPushNotification();
Here is the code for expo nodejs sdk, check for other languages/frameworks
This is more like pseudo code
import Expo from 'expo-server-sdk';
// let sentNotificationId = //id for entry in db
// generating custom objects to store them in DB
const pushNotificationsItems = [{
// info like userId
// sentNotificationId, entry id etc
// data mostly needed by the app
to: notification.token,
notificationBody: {
// expo push notification related info
to: notification.token, // expo token
title: `Some title`,
body: message,
data: {
//custom data to be used in app
}
}
}
]
// make db entries with data you need
// checking valid expo messages
let tickets = [];
let notificationMessages = [];
for (const notification of pushNotificationsItems) {
if (!Expo.isExpoPushToken(notification.to)) {
continue
}
notificationMessages.push(notification.notificationBody)
}
// send actual notification using expo client
const expo = new Expo({});
const chunks = expo.chunkPushNotifications(notificationMessages);
//console.log('message chunks', chunks)
const resolvedPromises = await Promise.allSettled(map(chunks, async chunk => {
const ticketChunks = await expo.sendPushNotificationsAsync(chunk);
tickets.push(...ticketChunks)
}));
// saving the response if needed
if (tickets.length) {
const mappedTickets = map(tickets, t => {
return {
data: t,
notificationSentId: sentNotificationId,
organisationId,
createdAt: getCurrentUTCISOString() || '',
}
})
await this.prisma.notificationResponse.createMany({
data: [...mappedTickets]
})
}
Hope it helps you in some way

How to perform action or render component before/after every http request in React?

I want to know if there is a way to create a kind of middleware in React?
What i want is to have an alert component show if there is a failing result for an http request.
Right now, i am making http request on login,registration,etc and i am importing my alert component in every page and setting the Alert component props like type, message, visibility everywhere i need the component, but i think maybe there is a better way of doing this.
Here is my code:
...imports
export const RegisterPage = () => {
const [alertConfig, setAlertConfig] = useState({
type: "",
message: "",
show: false,
});
...code
const onSubmitHandler = async (e) => {
e.preventDefault();
if (!isFormValid()) return;
const formData = new FormData();
formData.append("password", formValues.password);
if (formValues.provider.startsWith("8")) {
formData.append("contact", formValues.provider);
} else {
formData.append("email", formValues.provider);
}
setIsLoading(true);
try {
const response = await fetch(
`${process.env.REACT_APP_API_URL}/auth/register`,
{
method: "POST",
body: formData,
}
);
const data = await response.json();
if (data.status === "success") {
const { token, user } = data.data;
dispatch(setCurrentUser(user, token));
navigate("/choose-actor");
} else {
setAlertConfig({
type: "warning",
message: data.message,
show: true,
});
}
} catch (error) {
console.log(error);
setAlertConfig({
type: "danger",
message: "Ocorreu algum erro",
show: true,
});
} finally {
setIsLoading(false);
}
};
return
...html
{alertConfig.show && <Alert {...alertConfig} />}
...more html
As you can see, i am changing the configuration for the alert inside inside the function that executes the http request, and i have to do the save for every page that performs this action.
I looking for a design patter where i dont have to repeat myself.
Hope my question is clear.

How to send a token from react to my server?

I am currently working on adding stripe payments to my Laravel app - I have the system all working with blate templates but I am now attempting to implement it into a react project I have.
I am searching for the best way to send a token I receive in a response to my server this is what I currently have:
const CheckoutForm = ({intent}) => {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event) => {
event.preventDefault();
const { data, setData, transform , post } = useForm({
//Token here maybe?
})
if (!stripe || !elements) {
return;
}
const result = await stripe.confirmCardSetup(intent.client_secret, {
payment_method: {
card: elements.getElement(CardElement),
},
});
if (result.error) {
console.log(result.error.message);
} else {
// maybe add: result.setupIntent.payment_method to data?
// post(route('subscriptions'));
}
};
return (
<form onSubmit={handleSubmit}>
<CardElement/>
<Button disabled={!stripe}>
Pay Now
</Button>
</form>
)
};
I am very new to React so struggling to even google my answer - I am assuming I need an inertia post route as this is how I did it in the blade template application. However, this doesn't work now due to react states not updating in that way?
Basically, I have no idea the best way to get result.setupIntent.payment_method to my server after card setup.
Thanks for your time!
Found a solution using Inertia.visit
...
Inertia.visit(
route('subscriptions.start'),
{
method: 'post',
data: {
token: result.setupIntent.payment_method,
}
}
);
...

How to fix Google expo auth session sign in Error "Something went wrong when trying to finish signing in"

I'm trying to implement google sign in in my expo using expo-auth-session,
When I click on my gmail to sign in, I'm redirected to this screen saying "Something went wrong when trying to finish signing in. Please close this screen to go back to the app".
//Google auth code:
import * as Google from 'expo-auth-session/providers/google';
const [request, response, promptAsync] = Google.useAuthRequest({
expoClientId: config.google.expoClientId,
redirectUri: config.google.redirectUri,
});
React.useEffect(() => {
//Handle google login
console.log(response)
if (response?.type === 'success') {
const { authentication } = response;
}
}, [response]);
//Button that calls the google sign in
<Button iconName={'google'} iconPressed={() => promptAsync({useProxy: true})} />
If someone is trying this now.
You can Follow This https://www.youtube.com/watch?v=hmZm_jPvWWM
In the code given in this video
replace promptAsync({useProxy: false, showInRecents: true}) => promptAsync()
I ended up using expo-google-app-auth, for some reason that I'm yet to figure out, you have to use host.expo.exponent as your package name and bundle identifier in the google developer console for this library to work.
Code:
import { Alert } from 'react-native';
import * as Google from 'expo-google-app-auth'
const GoogleLogin = async () => {
//Get those keys from the google developer console
const { iosClientId, androidClientId } = config.google
const { type, user } = await Google.logInAsync({
iosClientId,
androidClientId,
});
if (type === 'success') {
/* `accessToken` is now valid and can be used to get data from the Google API with HTTP requests */
return { email: user.email, id: user.id }
} else {
Alert.alert("Google login error.")
}
}
export default GoogleLogin;
I think you can try like this
import * as Google from 'expo-auth-session/providers/google';
import * as WebBrowser from 'expo-web-browser';
WebBrowser.maybeCompleteAuthSession();
....
const [request, response, promptAsync] = Google.useAuthRequest({
androidClientId: config.androidClientId,
iosClientId: config.iosClientId,
expoClientId: config.expoClientId,
scopes: config.scopes,
});
useEffect(() => {
if (response?.type === 'success') {
const { authentication } = response;
getGoogleUser((authentication as any).accessToken)
}
}, [response]);
const getGoogleUser = async (accessToken: string) => {
try{
const response = await fetch('https://www.googleapis.com/userinfo/v2/me', {
headers: { Authorization: `Bearer ${accessToken}`}
});
const user = response.json()
if (user?.email) {
const { email, name } = user; // you will get more data in the user object
.......
}
}
catch(error){
console.log('GoogleUserReq error: ', error);
}
}
return (
<View>
<Button
onPress={() => promptAsync() }
/>
</View>
);

React Microsoft Outlook Calendar data Without Login Session using Hello.js

I am creating a React calendar that take data from "Microsoft Outlook Calendar" using the client-side JavaScript SDK "hello.js" and Microsoft Graph (for the set up I also followed this guide: https://learn.microsoft.com/en-us/graph/auth-register-app-v2).
Using hello.login my app shows the calendar without any problem...but unfortunately I have to show it without a login session.
This is my code:
class CalendarView extends Component {
constructor(props) {
super(props);
hello.init({
microsoft: {
id: APP_ID,
oauth: {
version: 2,
auth: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
},
scope_delim: ' ',
form: false,
scope: SCOPES,
},
});
const { startDate, endDate } = this.props;
this.state = {
// events: [],
startDataTime: startDate.toISOString(),
endDataTime: endDate.toISOString(),
token: hello('microsoft').getAuthResponse().access_token,
};
}
In this other component I mange the Microsoft Graph Query:
class EventsList extends Component {
constructor() {
super();
this.state = {
events: [],
};
}
componentWillReceiveProps(nextProps) {
const { startDate, endDate, token } = nextProps;
// to know what is the Bearer toke
// -> https://stackoverflow.com/questions/25838183/what-is-the-oauth-2-0-bearer-token-exactly
axios.get(
`https://graph.microsoft.com/v1.0/me/calendarview?startdatetime=${startDate}&enddatetime=${endDate}&orderby=start/dateTime`,
{ headers: { Authorization: `Bearer ${token}` } },
).then(response => this.setState({ events: response.data.value }))
.catch((error) => {
console.log(error.response);
});
}
render() {
const { events } = this.state;
if (events !== null) return events.map(event => <EventList key={event.id} event={event} />);
return null;
}
}
The strange thing is that if I make a console.log(token) the app show me the token but, at the same time, I receive an "GET...401 (Unauthorized)" error
console log token and error message
That are my app propriety:
app propriety part 1
app propriety part 2
Maybe the problem is the Hello.js call?
I am testing my app with Jest and I have this error, can it be linked to my problem?
console.error node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/virtual-console.js:29
Error: Uncaught [TypeError: hello is not a function]
How Can I solve?
I found the solution!
I had to make 2 axios call:
one to obtain the token (with a POST)
one for use the token in my microsoft graph query (with a GET)
I had to register my app here https://portal.azure.com/#home so to obtain a Client ID and Secret.
After I needed to send a POST message to Azure Active Directory Authentication endpoint with following body parameters:
grant_type: The flow we want to use, client_credentials in my case.
client_id: The Client ID (Application ID) of the application I
created in the registration step;
client_secret: The Client Secret I created in the registration
step;
resource: The name of the resource I would like to get access,
https://graph.microsoft.com in this case.
So I created one component with the following axios POST request:
componentDidMount() {
axios.post(`https://cors-anywhere.herokuapp.com/https://login.microsoftonline.com/${AZURE_ACTIVE_DIRECTORY_TENANT_NAME}/oauth2/token`,
`grant_type=${GRANT_TYPE}&client_id=${APP_ID}&client_secret=${SECRET}&resource=${RESOURCE}`).then(res => this.setAccessToken(res.data.access_token))
.catch((error) => {
console.error(error.response);
});
}
setAccessToken(token) {
if (typeof token === 'string') this.setState({ accessToken: token });
}
NOTE
the resource value needed to be a bit changed to work:
https%3A%2F%2Fgraph.microsoft.com%2F
I had to put the string 'https://cors-anywhere.herokuapp.com' before micorsoftonline URL because otherwise the application generated
"a blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
(I don't know why, I am still working on it because putting this string before is not an optimal solution).
In EventList component I didn't need hellojs anymore, so I just use the token I generated to access. I had to change just a bit the microsoft graph query:
componentDidMount() {
const { accessToken } = this.props;
const { startDate, endDate } = this.state;
this.getEvents(startDate, endDate, accessToken);
}
getEvents(startDate, endDate, accessToken) {
const startDateString = startDate.toISOString();
const endDateString = endDate.toISOString();
axios.get(
`https://graph.microsoft.com/v1.0/users/${USER_PUBLIC_ID}/calendarview?startdatetime=${startDateString}&enddatetime=${endDateString}&orderby=start/dateTime`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
).then(response => this.setEvents(response.data.value))
.catch((error) => {
console.error(error.response);
});
}
setEvents(events) {
const validEvent = event => typeof event.subject === 'string';
this.setState({ events: events.filter(validEvent) });
}
I hope that my solution can be usefull also to other users

Resources