can not send tax amount in stripe from next js app - next.js

In my project, I am using WordPress woo-commerce as backend and next js as frontend. I am trying to implement stripe payment. I can send line items in stripe sessionData and they show perfectly on the stripe page, how ever when I am trying to send the tax amounts with line items I am getting errors.
import { createCheckoutSession } from "next-stripe/client"; // #see https://github.com/ynnoj/next-stripe
import { loadStripe } from "#stripe/stripe-js";
.....
.....
const createCheckoutSessionAndRedirect = async (orderData) => {
const sessionData = {
success_url:
window.location.origin +
`/thank-you?session_id={CHECKOUT_SESSION_ID}&order_id=${orderData.orderId}`,
cancel_url: window.location.href,
customer_email: orderData.customer_email,
line_items: getStripeLineItems(orderData.products),
metadata: getMetaData(
orderData.billing,
orderData.shipping,
orderData.orderId
),
payment_method_types: ["card"],
mode: "payment",
total_details:{
amount_discount: 0,
amount_shipping: Math.round(10 * 100),
amount_tax: Math.round(10 * 100),
},
};
console.log("Session from another1:", sessionData);
const session = await createCheckoutSession(sessionData);
console.log("Session from another2:", sessionData);
console.log("from another2:", orderData);
try {
console.log("session data", session);
const stripe = await loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
);
if (stripe) {
stripe.redirectToCheckout({ sessionId: session.id });
}
} catch (error) {
console.log(error);
}
};

After many attempts, I could solve the problem.
This is the controller.
const stripe = require("stripe")(process.env.STRIPE_PRIVATE_KEY);
const stripePaymentService = require('../service/stripePaymentService');
module.exports.checkoutSessionCreate = async (req, res) => {
const responseFromService = await stripePaymentService.checkoutSessionCreate(req.body);
console.log("this is session data", responseFromService);
try{
const session = await stripe.checkout.sessions.create(responseFromService);
res.json({ url: session.url });
console.log("success session:", session.url);
}
catch(e){
res.status(500).json({ error: e.message })
console.log("success session:", e.message);
}
}
This is the service...
const stripe = require("stripe")(process.env.STRIPE_PRIVATE_KEY);
const lodash = require('lodash');
module.exports.checkoutSessionCreate = async (serviceData) => {
// console.log(serviceData.products);
const sessionData = {
success_url:`${process.env.CLIENT_URL}/thank-you?session_id={CHECKOUT_SESSION_ID}&order_id=${serviceData.orderId}`,
cancel_url: `${process.env.CLIENT_URL}`,
customer_email: serviceData.customer_email,
line_items: await getStripeLineItems(serviceData.products),
metadata: getMetaData(
serviceData.billing,
serviceData.shipping,
serviceData.orderId
),
payment_method_types: ["card"],
mode: "payment",
};
return sessionData;
}
const getMetaData = (billing, shipping, orderId) => {
return {
billing: JSON.stringify(billing),
shipping: JSON.stringify(shipping),
orderId,
};
};
let getStripeLineItems =async (products) => {
if (lodash.isEmpty(products) && !isArray(products)) {
return [];
}
const productData = await Promise.all(
products.map(async (product) => {
const taxArr = await getTaxID(product.tax_data);
return {
quantity: product?.quantity ?? 0,
name: product?.name ?? "",
images: [product?.images ?? ""],
amount: Math.round(product?.amount * 100),
currency: product?.currency ?? "",
tax_rates: taxArr,
};
})
);
return productData;
};
let getTaxID = async (taxData) => {
let idArr = await Promise.all(
taxData.map(async (item)=>{
const taxRate = await stripe.taxRates.create({
display_name: item.display_name,
inclusive: item.inclusive,
percentage: item.percentage,
});
return taxRate?.id;
})
);
return idArr;
}
I hope this will help somebody....

You cannot set total_details directly as you are trying to do. That is a calculated property of the object (ref), not part of the create endpoint (ref).
To add tax to a Checkout session, you should either use automatic taxes with Stripe Tax, or provide explicit tax rates.

Related

RTK Query - update all injectedEndpoints cache with one WebSocket connection (Best Practice)

I am new to RTK query and need only one WebSocket connection for my entire application as you can see below I implemented it like an example in GitHub.
I need to somehow send my payload to this WebSocket by subscribing to it.
and then whenever the message comes in I update the other injected Endpoints' cache.
import { ApiSlice } from 'api';
import { instrumentsAdapter } from './marketSlice';
const socket = new WebSocket(process.env.REACT_APP_SOCKET_BASE_URL);
const socketConnected = new Promise((resolve, reject) => {
// Connection opened
try {
socket.addEventListener('open', (event) => {
resolve(event);
});
} catch (err) {
console.log('err', err);
reject(err);
}
});
export const socketApi = ApiSlice.injectEndpoints({
endpoints: (builder) => ({
socketChannel: builder.mutation({
async queryFn(arg) {
await socketConnected;
const { type, topic } = arg;
const sendPayload = { type, path: topic };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
},
async onCacheEntryAdded(arg, { cacheDataLoaded, cacheEntryRemoved }) {
console.log('arg', arg);
await cacheDataLoaded;
// Listen for messages
socket.onmessage = (res) => {
const message = JSON.parse(res.data);
try {
// ApiSlice.util.updateQueryData('getInstrumentByRefId', arg, (draft) => {
// console.log('arg', arg);
// draft = { ...message.value, baseVolume: 3 };
// });
} catch (err) {
console.log('err', err);
}
};
await cacheEntryRemoved;
socket.close();
}
})
})
});
export const { useSocketChannelMutation } = socketApi;
after so much reading docs and researching I finally find this solution working but I do not know if this is a best practice or not.
Here is my not-empty ApiSlice.
/* eslint-disable import/prefer-default-export */
// Or from '#reduxjs/toolkit/query' if not using the auto-generated hooks
import { createApi } from '#reduxjs/toolkit/query/react';
import axiosBaseQuery from './axiosBaseQuery';
export const socket = new WebSocket(process.env.REACT_APP_SOCKET_BASE_URL);
const socketConnected = new Promise((resolve, reject) => {
try {
socket.addEventListener('open', (event) => {
resolve(event);
});
} catch (err) {
reject(err);
}
});
// initialize an empty api service that we'll inject endpoints into later as needed
export const ApiSlice = createApi({
reducerPath: 'api',
baseQuery: axiosBaseQuery(),
endpoints: (builder) => ({
subscribeSocket: builder.mutation({
async queryFn(arg) {
await socketConnected;
const sendPayload = { type: 'SUBSCRIBE', path: arg };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
}
}),
unsubscribeSocket: builder.mutation({
async queryFn(arg) {
await socketConnected;
const sendPayload = { type: 'UNSUBSCRIBE', path: arg };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
}
}),
channel: builder.mutation({
async queryFn(onMessage) {
await socketConnected;
socket.addEventListener('message', onMessage);
return { data: { messages: [] } };
}
})
})
});
export const { useUnsubscribeSocketMutation, useSubscribeSocketMutation, useChannelMutation } =
ApiSlice;
and this is my enhanced Api slice
import { createEntityAdapter } from '#reduxjs/toolkit';
import { ApiSlice } from 'api';
export const instrumentsAdapter = createEntityAdapter({
selectId: (item) => item?.state?.symbol
});
export const marketApi = ApiSlice.injectEndpoints({
overrideExisting: false,
endpoints: (builder) => ({
getMarketMap: builder.query({
query: (type) => ({
url: `/market/map?type=${type}`,
method: 'get'
})
}),
getInstruments: builder.query({
query: (type) => ({
url: `/market/instruments?type=${type}`,
method: 'get'
})
}),
getInstrumentByRefId: builder.query({
query: (refId) => ({
url: `/market/instruments/${refId}/summary`,
method: 'get'
}),
transformResponse: (res) => {
return instrumentsAdapter.addOne(instrumentsAdapter.getInitialState(), res);
},
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved, dispatch }
) {
await cacheDataLoaded;
const payload = `instruments.${arg}.summary`;
// subs to socket
dispatch(ApiSlice.endpoints.subscribeSocket.initiate(payload));
// Listen for messages
const onMessage = (res) => {
const message = JSON.parse(res.data);
try {
updateCachedData((draft) => {
instrumentsAdapter.setOne(draft, message.value);
});
} catch (err) {
// eslint-disable-next-line no-console
console.log('err', err);
}
};
dispatch(ApiSlice.endpoints.channel.initiate(onMessage));
await cacheEntryRemoved;
// unsubs to socket
dispatch(ApiSlice.endpoints.unsubscribeSocket.initiate(payload));
}
}),
getCandles: builder.query({
query: ({ refId, bucket, end, limit = 1 }) => ({
url: `/market/instruments/${refId}/candles?bucket=${bucket}&end=${end}&limit=${limit}`,
method: 'get'
})
})
})
});
export const {
useGetMarketMapQuery,
useGetInstrumentByRefIdQuery,
useGetInstrumentsQuery,
useGetCandlesQuery
} = marketApi;
and I try to dispatch my socket endpoints from ApiSlice inside of onCacheEntryAdded.
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved, dispatch }
) {
await cacheDataLoaded;
const payload = `instruments.${arg}.summary`;
// subs to socket
dispatch(ApiSlice.endpoints.subscribeSocket.initiate(payload));
// Listen for messages
const onMessage = (res) => {
const message = JSON.parse(res.data);
try {
updateCachedData((draft) => {
instrumentsAdapter.setOne(draft, message.value);
});
} catch (err) {
// eslint-disable-next-line no-console
console.log('err', err);
}
};
dispatch(ApiSlice.endpoints.channel.initiate(onMessage));
await cacheEntryRemoved;
// unsubs to socket
dispatch(ApiSlice.endpoints.unsubscribeSocket.initiate(payload));
}
}),
```

How to mutation store state in build query redux toolkit

Created an initialState and will be updated the totalPage and currentPage after got the users list.
I found out onQueryStarted from docs, it able to update the store state in this method but only look like only for builder.mutation.
what's the correct way to get the user list and update the store page value in redux toolkit?
Listing two part of the code below:
apiSlice
component to use the hook
// 1. apiSlice
const usersAdapter = createEntityAdapter({})
export const initialState = usersAdapter.getInitialState({
totalPage: 0,
currentPage: 0,
})
export const usersApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getUsers: builder.query({ // <--- the docs are using builder.mutation, but i needed to pass params
query: (args) => {
const { page, limit } = args;
return {
url: `/api/users`,
method: "GET",
params: { page, limit },
}
},
validateStatus: (response, result) => {
return response.status === 200 && !result.isError
},
transformResponse: (responseData) => { // <<-- return { totalPages: 10, currentPage: 1, users: [{}] }
const loadedUsers = responseData?.users.map((user) => user)
return usersAdapter.setAll(initialState, loadedUsers)
},
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled
const {totalPages, currentPage} = data; <----- totalPages & currentPage values are still 0 as initialState
dispatch(setPages({ currentPage, totalPages }))
} catch (error) {
console.error("User Error: ", error)
}
},
providesTags: (result, error, arg) => {
if (result?.ids) {
return [
{ type: "User", id: "LIST" },
...result.ids.map((id) => ({ type: "User", id })),
]
} else return [{ type: "User", id: "LIST" }]
},
})
})
});
export const {
useGetUsersQuery,
} = usersApiSlice
component to use the hook
Try to use the hook in user landing page
const UsersList = () => {
const { data: users, isLoading, isSuccess, isError } = useGetUsersQuery({page: 1, limit: 10 })
return (
<div>return the users data</div>
)
}
update the store value after get the data return

How to resolve this OAuth authorization error( Error 400: invalid request )?

Alright. I'll cut to the chase and won't waste your time.
There's an expo bare-workflow app to which, the authentication with expo-sessions and firebase has to be added. Google is the provider that's needed to be used. But the app has been slapping me with this error for quite a sometime now.
Here's the code:
const AuthContext = createContext({});
const config = {
clientId:
"380XXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com",
redirectUri:
"com.newauth.newauth://newauth-XXXXXXXXXX.firebaseapp.com/__/auth/handler",
// redirectUri: "https://newauth-XXXXXXXXXX.firebaseapp.com/auth/handler",
};
export const AuthProvider = ({ children }: any) => {
const [user, setUser] = useState(null);
const [message, setMessage] = useState<String>();
const [error, setError] = useState<String>();
const [accessToken, setAccessToken] = useState<String>();
const [request, response, promptAsync] = Google.useIdTokenAuthRequest(config);
useEffect(() => {
setMessage(JSON.stringify(response));
if (response?.type === "success") {
setAccessToken(response?.authentication?.accessToken);
}
console.log(
"response is ==> ",
response,
"and is of type ==> ",
response?.type
);
console.log("message: ", message);
console.log("request: ", request);
console.log("response: ", response);
console.log("promptAsync: ", promptAsync);
}, [response]);
async function signInWithGoogle() {
console.log("#signInWithGoogle");
WebBrowser.maybeCompleteAuthSession();
try {
console.log("At try block");
promptAsync({ useProxy: false, showInRecents: true });
// Google.useAuthRequest(config, discovery);
console.log("request: ", JSON.stringify(request));
console.log("response: ", JSON.stringify(response));
console.log("response type: ", response?.type);
} catch (error) {
console.log("Error: ", error);
}
}
return (
<AuthContext.Provider
value={{
user: null,
signInWithGoogle,
}}
>
{children}
</AuthContext.Provider>
);
};
export default function useAuth() {
return useContext(AuthContext);
}
This ends up slamming me with this error
Do you know a way out of this?
Thanks and have a fantastic weekend!

Dispatch function seems to run more than one time after I test on stand alone app expo-deeplink

It sounds so weird to me and I have no idea what's wrong here because everything is fine in a development environment. So the way app works are simple, user sign in, choose it's therapist then pay for it and after successful payment, booking is confirmed, but the problem is booking is being booked exactly 3 times in firebase real-time database no matter what and I don't know why... (in the development area all is fine and it's gonna book just once as the user requested)
here's my code of booking:
const bookingHandler = () => {
Linking.openURL('http://www.medicalbookingapp.cloudsite.ir/sendPay.php');
}
const handler = (e) => handleOpenUrl(e.url);
useEffect(() => {
Linking.addEventListener('url', handler)
return () => {
Linking.removeEventListener('url', handler);
}
});
const handleOpenUrl = useCallback((url) => {
const route = url.replace(/.*?:\/\/\w*:\w*\/\W/g, '') // exp://.... --> ''
const id = route.split('=')[1]
if (id == 1) {
handleDispatch();
toggleModal();
} else if (id == 0) {
console.log('purchase failed...');
toggleModal();
}
});
const handleDispatch = useCallback(() => {
dispatch(
BookingActions.addBooking(
therapistId,
therapistFirstName,
therapistLastName,
selected.title,
moment(selectedDate).format("YYYY-MMM-DD"),
selected.slots,
)
);
dispatch(
doctorActions.updateTherapists(therapistId, selected.slots, selectedDate, selected.title, selectedPlanIndex, selectedTimeIndex)
);
setBookingConfirm(true)
})
booking action:
export const addBooking = (therapistId, therapistFirstName, therapistLastName, sessionTime, sessionDate, slotTaken) => {
return async (dispatch, getState) => {
let userId = firebase.auth().currentUser.uid
const confirmDate = moment(new Date()).format("ddd DD MMMM YYYY")
const response = await fetch(
`https://mymedicalbooking.firebaseio.com/bookings/${userId}.json`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
userId,
therapistId,
confirmDate,
therapistFirstName,
therapistLastName,
sessionTime,
sessionDate,
slotTaken
})
}
);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const resData = await response.json();
dispatch({
type: ADD_BOOKING,
bookingData: {
userId: userId,
therapistId: therapistId,
therapistFirstName: therapistFirstName,
therapistLastName: therapistLastName,
sessionTime: sessionTime,
sessionDate: sessionDate
}
});
};
};
Booking reducer:
const initialState = {
bookings: [],
userBookings: []
};
export default (state = initialState, action) => {
switch (action.type) {
case ADD_BOOKING:
const newBooking = new Booking(
action.bookingData.id,
action.bookingData.therapistId,
action.bookingData.therapistFirstName,
action.bookingData.therapistLastName,
action.bookingData.bookingdate
);
return {
...state,
bookings: state.bookings.concat(newBooking)
};
case FETCH_BOOKING:
const userBookings = action.userBookings;
return {
...state,
userBookings: userBookings
};
}
return state;
};
also, I use expo, SDK 38, Firebase as a database.
I really need to solve this, please if you have any idea don't hesitate to leave a comment or answer all of them kindly appreciated.
UPDATE:
I commented out all deep linking functionality and test the result, it's all fine. so I think the problem is with the eventListener or how I implemented my deep linking code but I still don't figure out what's wrong with the code that does fine in expo and has a bug in stand-alone.
UPDATE 2
I tried to add dependency array as suggested but still I have same problem..
there is an issue in expo-linking which on the standalone detached android app: event url fires multiple times ISSUE
I just wrapped my handling function in lodash's debounce with 1000ms wait
install lodash like this
yarn add lodash
import _ from 'lodash';
const handleOpenUrl = _.debounce((event) => {
// here is other logic
},1000);
here is your code
just add an empty dependency array into useEffect and use useCallback like this
useEffect(() => {
Linking.addEventListener('url', handleOpenUrl)
return () => {
Linking.removeEventListener('url', handleOpenUrl);
}
},[]); //like this []
const handleOpenUrl = _.debounce((url) => {
const route = url.replace(/.*?:\/\/\w*:\w*\/\W/g, '') // exp://.... --> ''
const id = route.split('=')[1]
if (id == 1) {
handleDispatch();
toggleModal();
} else if (id == 0) {
console.log('purchase failed...');
toggleModal();
}
},1000); //like this []
const handleDispatch = useCallback(() => {
dispatch(
BookingActions.addBooking(
therapistId,
therapistFirstName,
therapistLastName,
selected.title,
moment(selectedDate).format("YYYY-MMM-DD"),
selected.slots,
)
);
dispatch(
doctorActions.updateTherapists(therapistId, selected.slots, selectedDate, selected.title, selectedPlanIndex, selectedTimeIndex)
);
setBookingConfirm(true)
},[selected])

Firebase react native loading issue

I'm new at react native and I try to build an mobile app. I'm using Firebase for applications. When I try to login, it stucks on the loading page. And getting this warning:
Setting a timer for a long period of time, i.e. multiple minutes, is a performance and correctness issue on Android as it keeps the timer module awake, and timers can only be called when the app is in the foreground. See https://github.com/facebook/react-native/issues/12981 for more info.
(Saw setTimeout with duration 3600000ms)
What should I do?
Also authentication code is here:
import { AsyncStorage } from 'react-native';
export const SIGNUP = 'SIGNUP';
export const LOGIN = 'LOGIN';
export const AUTHENTICATE = 'AUTHENTICATE';
export const LOGOUT = 'LOGOUT';
let timer;
export const authenticate = (userId, token, expiryTime) => {
return dispatch => {
dispatch(setLogoutTimer(expiryTime));
dispatch({ type: AUTHENTICATE, userId: userId, token: token });
};
};
export const signup = (email, password) => {
return async dispatch => {
const response = await fetch(
'https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=...',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: password,
returnSecureToken: true
})
}
);
if (!response.ok) {
const errorResData = await response.json();
const errorId = errorResData.error.message;
let message = 'Bir terslik var!';
if (errorId === 'EMAIL_EXISTS') {
message = 'Bu e-posta zaten kayıtlı!';
}
throw new Error(message);
}
const resData = await response.json();
console.log(resData);
dispatch(
authenticate(
resData.localId,
resData.idToken,
parseInt(resData.expiresIn) * 1000
)
);
const expirationDate = new Date(
new Date().getTime() + parseInt(resData.expiresIn) * 1000
);
saveDataToStorage(resData.idToken, resData.localId, expirationDate);
};
};
export const login = (email, password) => {
return async dispatch => {
const response = await fetch(
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=...',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: password,
returnSecureToken: true
})
}
);
if (!response.ok) {
const errorResData = await response.json();
const errorId = errorResData.error.message;
let message = 'Bir terslik var!';
if (errorId === 'EMAIL_NOT_FOUND') {
message = 'Böyle bir e-posta yok!';
} else if (errorId === 'INVALID_PASSWORD') {
message = 'Bu şifre geçersiz!';
}
throw new Error(message);
}
const resData = await response.json();
console.log(resData);
dispatch(
authenticate(
resData.localId,
resData.idToken,
parseInt(resData.expiresIn) * 1000
)
);
const expirationDate = new Date(
new Date().getTime() + parseInt(resData.expiresIn) * 1000
);
saveDataToStorage(resData.idToken, resData.localId, expirationDate);
};
};
export const logout = () => {
clearLogoutTimer();
AsyncStorage.removeItem('userData');
return { type: LOGOUT };
};
const clearLogoutTimer = () => {
if (timer) {
clearTimeout(timer);
}
};
const setLogoutTimer = expirationTime => {
return dispatch => {
timer = setTimeout(() => {
dispatch(logout());
}, expirationTime);
};
};
const saveDataToStorage = (token, userId, expirationDate) => {
AsyncStorage.setItem(
'userData',
JSON.stringify({
token: token,
userId: userId,
expiryDate: expirationDate.toISOString()
})
);
};

Resources