#nightfly-react-checkout

1 messages · Page 1 of 1 (latest)

ruby heron
modern panther
#
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

ruby heron
#

ok thanks, will take a look shortly

#

ah, cors

modern panther
#

Thanks ❤️ been struggling with this for a couple of weeks😅

ruby heron
#

This is while running via local host?

#

Have you enabled CORS on your express router?

modern panther
#

app.use(cors())

#

got this on

#

in server.js file

#

if you want I can give you access to the github?

ruby heron
modern panther
#

Still gives network error

#

its a different error now

#

CORS Missing Allow Origin

NS_ERROR_DOM_BAD_URI

ruby heron
#

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

modern panther
#

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);
});
ruby heron
#

yep, then in the client you'd redirect to the url (or use stripe.redirectToCheckout({sessionId})

modern panther
#

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`,

ruby heron
#

what do you mean? what goes wrong?

modern panther
#

well you say that the problem lays at the URL? so I thought that was the issue?

ruby heron
#

no, the session URL, the same one you were redirecting to from the server

#

do the same thing, but redirecting from the client side

modern panther
#

return res.redirect(303, session.url); you mean this url in client side?

ruby heron
#

yes, exactly

#

so you can send back session.url then in the client do a redirect via location

modern panther
#

hmm I will try, can we keep this thread open?

ruby heron
#

yep, i'm here for a bit 🙂

modern panther
#

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

ruby heron
#

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

modern panther
#

If I change it back it works again

ruby heron
#

ok so for the time being i'd say just have it in both, solve that issue later 😄

modern panther
#

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?

ruby heron
#

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?

modern panther
#

it gets the url;

#

sorry I dont understand what you mean with this "instead of directing respond with the session object / url"

ruby heron
#

which server hander are you using currently?

modern panther
#

Reactjs?

#

Redux

ruby heron
#

i mean your client app is calling /:id/create-checkout-session?

modern panther
#

:id is the order id

#

and the /create-checkout-session is in the orders api

ruby heron
#

ok, and does your backend respond to your client app with data successfully?

modern panther
#

yes? for everything that comes from the server side its working.

ruby heron
#

ok and then you're doing the redirect in the client app?

modern panther
#
  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?

ruby heron
#

No, where you retrieve the new session from your /order API , you need to redirect there

modern panther
ruby heron
#

you tell me 😄 but it looks like it yes

#

so there you would use the contents of data to redirect

modern panther
#

but I am getting that here already right?

  const successPaymentHandler = () => {
    dispatch(stripePayment(order._id));

  };
ruby heron
#

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

modern panther
#

now I get CORS Missing Allow Origin

ruby heron
#

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()?

modern panther
#

sorry not working.

ruby heron
#

well hang on, those seems seem to conflict

#

whats happening with that json parse error?

#

whats the network response from your server?

modern panther
ruby heron
#

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

modern panther
#

Those are the only network responses I got?

modern panther
ruby heron
#

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.

smoky zodiac
#

👋 Give me a minute to get settle in and I'll take a look @modern panther

modern panther
#

Great! I am atm having dinner will take a look again after dinner

modern panther
#

I am back, have you found anything interesting? or do you need all the code?

smoky zodiac
#

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!

modern panther
#

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.

smoky zodiac
#

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

modern panther
#

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.

smoky zodiac
#

Yup, that helps! Let me take a look

#

I don't see you setting window.location.replace anywhere in this code?

modern panther
#

I had it in my action.js file

#

in the try catch

ruby heron
#

picking this back up

#

let me just start from scratch and see if its something new

modern panther
#

Found smth?

ruby heron
#

got pulled into some meeting but back on running the checkout example in a few minutes 👍

ruby heron
#

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?

ruby heron
#

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

modern panther
#

Will try now.

ruby heron
#

hello again! i'm around here for a couple of hours so happy to try to repro things -- my example is running 🙂

modern panther
#

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?

ruby heron
#
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})
});
modern panther
#

Working on it! found the res.json url on google and that stops giving an error!

ruby heron
#

nice 🙂 hopefully the client redirect works then, too

modern panther
#

good news it shows the checkout page! new errors but moving forward

ruby heron
#

huzzah!

#

what are the errors?

modern panther
#

well those are my end, they are related to securing that its paid.

#

now i can type ?success=true and its paid 😛

ruby heron
#

can you elaborate on that?

modern panther
#

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}

ruby heron
#

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

modern panther
#

do you know how I can get the payment result out of the session id?

ruby heron
#

as in checking if it was paid?

modern panther
#

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.

ruby heron
#

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?

modern panther
#

yes

#

thats fixed!

ruby heron
#

ok awesome 🙂

#

now you're trying to handle fulfillment, something customized upon redirect to your success url after checkout payment?

modern panther
#

now I want to check if its paid or not ^^

ruby heron
#

are you being redirected to the expected URL with a session id?

modern panther
#

yes

ruby heron
#

ok and having a problem parsing the session out of the URL?

#

what does your JS look like to get this?

modern panther
#

Not really, its redirecting the session to check if the session is paid

ruby heron
#

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

modern panther
#

Yes

modern panther
#

A value

#

It gives {} empty array

worthy gust
#

@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?

modern panther
#

Will do going to eat first👍

modern panther
#

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

worthy gust
#

@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

modern panther
worthy gust
#

Perfect, that's great progress

#

So now: does it not receive the id? Or is the code trying to find the id incorrect?

modern panther
#

I checked in Stripe

#

and the id is the same as the one in the dashboard.

worthy gust
#

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

modern panther
#

u mean the server side does not see the correct session id?

#

so the transition goes wrong from client to server

worthy gust
#

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

modern panther
#

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.

worthy gust
#

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

modern panther
#

session.payment_status

#

this should send the payment status?

#

it gives a blank response

worthy gust
#

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?

modern panther
#

sorry,

worthy gust
#

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

modern panther
#

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.

worthy gust
#

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

modern panther
#

Problem solved, thanks for your time everything works like it should now.

worthy gust
#

Amazing!