#nightfly-react-checkout
1 messages · Page 1 of 1 (latest)
orderRouter.post(
"/:id/create-checkout-session",
expressAsyncHandler(async (req, res) => {
const order = await Order.findById(req.params.id);
if (order) {
var s = order.totalPrice.toFixed(2) + "";
s = s.replace(".", "");
s = parseInt(s);
const stripe = Stripe(process.env.STRIPE_CLIENT_ID);
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: 'payment',
line_items: [
{
name: "NightCoach",
description: "Coaching Session",
amount: s,
quantity: 1,
currency: "EUR",
},
],
// ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
success_url: `http://localhost:3000/order/${order._id}?success`,
cancel_url: `http://localhost:3000/order/${order._id}?canceled`,
});
return res.redirect(303, session.url);
}
})
);
this is the code I am using now
gives a network error
CORS-header ‘Access-Control-Allow-Origin’ error
Thanks ❤️ been struggling with this for a couple of weeks😅
This is while running via local host?
Have you enabled CORS on your express router?
eg, with something like this: http://expressjs.com/en/resources/middleware/cors.html
app.use(cors())
got this on
in server.js file
if you want I can give you access to the github?
hmm can you try these custom headers?
https://stackoverflow.com/a/42907792
Still gives network error
its a different error now
CORS Missing Allow Origin
NS_ERROR_DOM_BAD_URI
]
AH interesting, this may be a deeper limitation: https://stackoverflow.com/questions/34949492/cors-request-with-preflight-and-redirect-disallowed-workarounds
I'd suggest you respond to the client with the session URL and redirect client-side -- it's going to be the simplest solution here
you mean something like this?
app.get('/checkout-session', async (req, res) => {
const { sessionId } = req.query;
const session = await stripe.checkout.sessions.retrieve(sessionId);
res.send(session);
});
yep, then in the client you'd redirect to the url (or use stripe.redirectToCheckout({sessionId})
so baiscally
it goes wrong in this part
success_url: `http://localhost:3000/order/${order._id}?success`, cancel_url: `http://localhost:3000/order/${order._id}?canceled`,
what do you mean? what goes wrong?
well you say that the problem lays at the URL? so I thought that was the issue?
no, the session URL, the same one you were redirecting to from the server
do the same thing, but redirecting from the client side
return res.redirect(303, session.url); you mean this url in client side?
yes, exactly
so you can send back session.url then in the client do a redirect via location
hmm I will try, can we keep this thread open?
yep, i'm here for a bit 🙂
hmm I am struggling a bit now getting proxy errors?
Proxy error: Could not proxy request /api/orders/612e0d843a9540842cb2a6a9 from localhost:3000 to http://127.0.0.1:5003.
See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (ECONNREFUSED).
Proxy error: Could not proxy request /checkout-session?sessionId= from localhost:3000 to http://127.0.0.1:5003.
See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (ECONNREFUSED).
Proxy error: Could not proxy request /api/orders/612e0d843a9540842cb2a6a9 from localhost:3000 to http://127.0.0.1:5003.
See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (ECONNREFUSED).
I got now this
orderRouter.get('/checkout-session', async (req, res) => {
const { sessionId } = req.query;
const session = await stripe.checkout.sessions.retrieve(sessionId);
res.send(session);
});
orderRouter.post(
"/:id/create-checkout-session",
expressAsyncHandler(async (req, res) => {
const domainURL = process.env.DOMAIN;
const order = await Order.findById(req.params.id);
if (order) {
var s = order.totalPrice.toFixed(2) + "";
s = s.replace(".", "");
s = parseInt(s);
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: 'payment',
line_items: [
{
name: "NightCoach",
description: "Coaching Session",
amount: s,
quantity: 1,
currency: "EUR",
},
],
// ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
success_url: `${domainURL}/order/${order._id}?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${domainURL}/order/${order._id}?canceled`,
});
return res.redirect(303, session.url);
}
})
);
the change was that I took out the const stripe = new Stripe(); and putted it on top to make it global for all the posts/puts/gets/sets
that shouldn't have made a difference related to the proxy error you're seeing - that sounds more like something related to create react app / webpack config and how the app is running
If I change it back it works again
ok so for the time being i'd say just have it in both, solve that issue later 😄
Sorry I am just confused...
export const stripePayment = (orderId) => async (dispatch, getState) => {
dispatch({ type: ORDER_STRIPE_REQUEST, payload: { orderId } });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.post(
`/api/orders/${orderId}/create-checkout-session`, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: ORDER_STRIPE_SUCCESS, payload: data });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: ORDER_STRIPE_FAIL, payload: message });
}
};
So this is how I post the stripe payment method, but basically this needs to get a /create-checkout-session url?
alright, so in your handler for the post, instead of directing respond with the session object / url
and does your client application get back the response with that payload in the browser?
it gets the url;
sorry I dont understand what you mean with this "instead of directing respond with the session object / url"
which server hander are you using currently?
i mean your client app is calling /:id/create-checkout-session?
:id is the order id
http://localhost:3000/order/612e0d843a9540842cb2a6a9 /create-checkout-session
and the /create-checkout-session is in the orders api
ok, and does your backend respond to your client app with data successfully?
yes? for everything that comes from the server side its working.
ok and then you're doing the redirect in the client app?
eg using window.location.replace(session.url) ?
https://developer.mozilla.org/en-US/docs/Web/API/Location/replace
The replace() method of the Location
interface replaces the current resource with the one at the provided URL. The difference
from the assign() method is that after using
replace() the current page will not be saved in session
History, meaning the user won't be able to use the back button
to navigate to it.
const [session, setSession] = useState({});
const location = useLocation();
const sessionId = location.search.replace('?session_id=', '');
useEffect(() => {
async function fetchSession() {
setSession(
await fetch('/checkout-session?sessionId=' + sessionId).then((res) =>
res.json()
)
);
}
fetchSession();
}, [sessionId]);
you mean this?
No, where you retrieve the new session from your /order API , you need to redirect there
here is where I retrieve the session correct?
you tell me 😄 but it looks like it yes
so there you would use the contents of data to redirect
but I am getting that here already right?
const successPaymentHandler = () => {
dispatch(stripePayment(order._id));
};
i'm really not sure what you're doing, this is your app, but i expected to see something like:
const { data } = await Axios.post(
`/api/orders/${orderId}/create-checkout-session`, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
window.location.replace(data.session.url);
any other state management and such is an app concern unrelated to the stripe integration
now I get CORS Missing Allow Origin
hmm
i think the options headers are carrying forward with the redirect
can you try using window.location.href = data.session.url instead of replace()?
well hang on, those seems seem to conflict
whats happening with that json parse error?
whats the network response from your server?
Still this
whats "this"?
the image you linked to is a request to checkout.stripe.com -- i'd like to see what your server is respond to your client with
Those are the only network responses I got?
Thats from the location.windows.href
i need to step away, but @smoky zodiac can take another look with you, and i can help again when i return to get this figured out. i think we're missing something small.
👋 Give me a minute to get settle in and I'll take a look @modern panther
Great! I am atm having dinner will take a look again after dinner
I am back, have you found anything interesting? or do you need all the code?
I didn't get very far, but now that you're here I have a question - can you give me more details about that json parse error you're getting?
and yes, the current state of your code would be helpful!
could you give me an email address where I can add you to the github? since it has senstive information (secret key)
the Json.Parse
has nothing to do with the code
it seems to be a code I added to check something
useEffect(() => {
async function fetchSession() {
setSession(
await fetch('/checkout-session?sessionId=' + sessionId).then((res) =>
res.json()
)
);
}
fetchSession();
}, [sessionId]);
this gave the error
since it doesnt have any json.
so it couldnt parse.
Unfortuantely I can't give out an email address to be added to a github, but you could paste it somewhere else and strip out the secret key
okay I will give you the router/server/action/reducer/constant/screen
import express from "express";
import mongoose from "mongoose";
import dotenv from "dotenv";
import coachRouter from "./routers/coachRouter.js";
import userRouter from "./routers/userRouter.js";
import bookingRouter from "./routers/bookingRouter.js";
import orderRouter from "./routers/orderRouter.js";
import cors from 'cors';
dotenv.config();
const app = express();
app.use(cors())
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested With, Content-Type, Accept');
next();
});
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
mongoose.connect(process.env.MONGODB_URL || "mongodb://localhost/nightcoachy", {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
});
app.use("/api/users", userRouter);
app.use("/api/coaches", coachRouter);
app.use("/api/bookings", bookingRouter);
app.use("/api/orders", orderRouter);
app.get("/api/config/stripe", (req, res) => {
res.send(process.env.STRIPE_CLIENT_ID || 'sb');
});
app.get("/", (req, res) => {
res.send("Server is ready");
});
app.use((err, req, res, next) => {
res.status(500).send({ message: err.message });
});
const port = process.env.PORT || 5003;
app.listen(port, () => {
console.log(`Serve at http://localhost:${port}`);
});
Server.js
orderRouter.get('/checkout-session', async (req, res) => {
const stripe = Stripe(process.env.STRIPE_CLIENT_ID);
const { sessionId } = req.query;
const session = await stripe.checkout.sessions.retrieve(sessionId);
res.send(session);
});
orderRouter.post(
"/:id/create-checkout-session",
expressAsyncHandler(async (req, res) => {
const domainURL = process.env.DOMAIN;
const order = await Order.findById(req.params.id);
if (order) {
var s = order.totalPrice.toFixed(2) + "";
s = s.replace(".", "");
s = parseInt(s);
const stripe = Stripe(process.env.STRIPE_CLIENT_ID);
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: 'payment',
line_items: [
{
name: "NightCoach",
description: "Coaching Session",
amount: s,
quantity: 1,
currency: "EUR",
},
],
// ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
success_url: `http://localhost:3000/order/${order._id}?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `http://localhost:3000/order/${order._id}?canceled`,
});
return res.redirect(303, session.url);
}
})
);
export default orderRouter;
``` router
export const stripePayment = (orderId) => async (dispatch, getState) => {
dispatch({ type: ORDER_STRIPE_REQUEST, payload: { orderId } });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.post(
`/api/orders/${orderId}/create-checkout-session`, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: ORDER_STRIPE_SUCCESS, payload: data });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: ORDER_STRIPE_FAIL, payload: message });
}
};
``` Action (frontend)
export const ORDER_CREATE_REQUEST = 'ORDER_CREATE_REQUEST';
export const ORDER_CREATE_SUCCESS = 'ORDER_CREATE_SUCCESS';
export const ORDER_CREATE_FAIL = 'ORDER_CREATE_FAIL';
export const ORDER_CREATE_RESET = 'ORDER_CREATE_RESET';
export const ORDER_DETAILS_REQUEST = 'ORDER_DETAILS_REQUEST';
export const ORDER_DETAILS_SUCCESS = 'ORDER_DETAILS_SUCCESS';
export const ORDER_DETAILS_FAIL = 'ORDER_DETAILS_FAIL';
export const ORDER_PAY_REQUEST = 'ORDER_PAY_REQUEST';
export const ORDER_PAY_SUCCESS = 'ORDER_PAY_SUCCESS';
export const ORDER_PAY_FAIL = 'ORDER_PAY_FAIL';
export const ORDER_PAY_RESET = 'ORDER_PAY_RESET';
export const ORDER_STRIPE_REQUEST = 'ORDER_STRIPE_REQUEST';
export const ORDER_STRIPE_SUCCESS = 'ORDER_STRIPE_SUCCESS';
export const ORDER_STRIPE_FAIL = 'ORDER_STRIPE_FAIL';
export const ORDER_MINE_LIST_REQUEST = 'ORDER_MINE_LIST_REQUEST';
export const ORDER_MINE_LIST_SUCCESS = 'ORDER_MINE_LIST_SUCCESS';
export const ORDER_MINE_LIST_FAIL = 'ORDER_MINE_LIST_FAIL';
``` order constants
export const orderStripeReducer = (state = {}, action) => {
switch (action.type) {
case ORDER_STRIPE_REQUEST:
return { loading: true };
case ORDER_STRIPE_SUCCESS:
return { loading: false, success: true };
case ORDER_STRIPE_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
``` reducer
the screen
hopefully this helps.
Yup, that helps! Let me take a look
I don't see you setting window.location.replace anywhere in this code?
@smoky zodiac
I had it in my action.js file
in the try catch
Found smth?
got pulled into some meeting but back on running the checkout example in a few minutes 👍
So I confirmed the base case as demonstrated here for react+node works as written:
https://stripe.com/docs/checkout/integration-builder
note though that it uses a form submission to point to the supplied endpoint
but the localhost redirect does work without any cors errors, so there's something integration-specific going on
reviewing what you'd shared, i noticed there was duplication of the cors config with the package version and the custom headers
can you try commenting out the cors() version to see if it matters?
I also confirmed it works switching to a fetch() to a get endpoint, then using location.href = url, so i'm not sure which part of the stack is failing for you
Will try now.
hello again! i'm around here for a couple of hours so happy to try to repro things -- my example is running 🙂
So I have tried switching off cors and it does not matter
with fetch() do you mean fetching the create-session-checkout? or fetching the result?
const handleClick = async () => {
const result = await fetch('/create-checkout-session');
console.log(result);
const data = await result.json();
console.log(data);
window.location.href = data.url;
}
this is what i do when clicking a "checkout" button
on the server its just this:
app.get('/create-checkout-session', async (req, res) => {
const session = await stripe.checkout.sessions.create({
line_items: [
{
// TODO: replace this with the `price` of the product you want to sell
price: 'price_1JSMgnLt4dXK03v5HDK12AWF',
quantity: 1,
},
],
payment_method_types: [
'card',
],
mode: 'payment',
success_url: `${YOUR_DOMAIN}?success=true`,
cancel_url: `${YOUR_DOMAIN}?canceled=true`,
});
// res.redirect(303, session.url)
res.json({url: session.url})
});
Working on it! found the res.json url on google and that stops giving an error!
nice 🙂 hopefully the client redirect works then, too
good news it shows the checkout page! new errors but moving forward
well those are my end, they are related to securing that its paid.
now i can type ?success=true and its paid 😛
can you elaborate on that?
after you make a payment you need to be able to check if its paid or not
now if you change the header you can make it "paid" without paying
I believe I need to be using this: ?session_id={CHECKOUT_SESSION_ID}
right, so yes you customer could "trick" you if you fulfill on that return url, so you should make sure to check the session status by retrieving it from the api before fulfilling
do you know how I can get the payment result out of the session id?
as in checking if it was paid?
You can check if payment_status=paid
https://stripe.com/docs/api/checkout/sessions/object#checkout_session_object-payment_status
hmm I got issues with it
orderRouter.get('/checkout-session', async (req, res) => {
const stripe = Stripe(process.env.STRIPE_CLIENT_ID);
const { sessionId } = req.query;
const session = await stripe.checkout.sessions.retrieve(sessionId);
const isPaid = await stripe.paymentResult.session.retrieve(sessionId);
res.send(session);
});
I have this to get the session for now
in the github I saw that this was used
const [session, setSession] = useState({});
const location = useLocation();
const sessionId = location.search.replace('?session_id=', '');
useEffect(() => {
async function fetchSession() {
setSession(
await fetch('/checkout-session?sessionId=' + sessionId).then((res) =>
res.json()
)
);
}
fetchSession();
}, [sessionId]);
so I did
<pre>{JSON.stringify(session, null, 2)}</pre>
this was used to read out of it
but the result is {}
while it does get the sessionId.
well wait thats for after the session, on complete
that's what you're doing now?
to be clear: are you past the problems with redirected to checkout?
ok awesome 🙂
now you're trying to handle fulfillment, something customized upon redirect to your success url after checkout payment?
now I want to check if its paid or not ^^
are you being redirected to the expected URL with a session id?
yes
ok and having a problem parsing the session out of the URL?
what does your JS look like to get this?
Not really, its redirecting the session to check if the session is paid
what do you mean redirecting?
when your customer ends up back on your success page, you'll want to grab the session id out of the url, then send that you your server to retrieve from the api and pass back the relevant parts for your client to display
Yes
This is not returning
A value
It gives {} empty array
@modern panther Add logs, at every line
try to debug this as the developer, what is each line getting, is the sessionId present? Can you retrieve it from Stripe? What are you returning?
Will do going to eat first👍
So I recieve an object
but it gives me an error related to a parse
maybe it has to do with the fetch()? Normally I use axios for get from the server
@modern panther separate client-side and server-side. Step 1: what happens server-side what do you retrieve from Stripe's API, do you have the correct Session id, etc. Once you have proven the server-side code retrieves the session successfully and returns it, then you can debug the client-side code
the problem I found is that the server not recieves the sessionId and gives an error 404 not found.
Perfect, that's great progress
So now: does it not receive the id? Or is the code trying to find the id incorrect?
sure sure but that's not the issue
your code, that tries to get the Session Id (the cs_test_123) and send it to your server is likely bugged and that's what you need to debug
u mean the server side does not see the correct session id?
so the transition goes wrong from client to server
Possibly. All I mean is that you can debug this with logs: where do you get the session id the first time, log it, how do you send it to the server, log this, what the server gets, etc.
const sessionId = location.search.replace('?session_id=', ''); this for example seems fishy to me and I'd log that
found where the issue lays
const session = await stripe.checkout.sessions.retrieve(sessionId);
somehow this is undefined
ah I made it work
but now a new error 😛
Error: Objects are not valid as a React child (found: object with keys {id, object, after_expiration, allow_promotion_codes, amount_subtotal, amount_total, automatic_tax, billing_address_collection, cancel_url, client_reference_id, consent, consent_collection, currency, customer, customer_details, customer_email, expires_at, livemode, locale, metadata, mode, payment_intent, payment_method_options, payment_method_types, payment_status, recovered_from, setup_intent, shipping, shipping_address_collection, submit_type, subscription, success_url, total_details, url}). If you meant to render a collection of children, use an array instead.
Hard to say, seems like a React error with the way you deserialize the Session. There's no reason to send the entire Session object client-side
thats true but its in the example ^^
session.payment_status
this should send the payment status?
it gives a blank response
sorry you keep doing it. I need you to be a bit clearer and more specific to get help
what is giving a blank response? Stripe's API? Your server-side code? the client-side code that deserializes it?
sorry,
It's crucial that you debug things slowly, add logs and understand your own code. We love to help but we can't answer 200 questions as you come across them
the redux method I created to respond to the client side
I want to know how to send only the payment result to the client side.
So have you logged session.payment_status server-side? Is it the value you want? If so, what's blocking you? Returning a string to the client isn't Stripe-specific, it's something you likely do dozens of times in your app today
Problem solved, thanks for your time everything works like it should now.
Amazing!