OAuth2 authorization - how do I get around cors? - node.js

I am trying to obtain user tokens from the mavenlink API, docs here, I am currently sending a get request from the client side app to the backend (node.js server) which then sends a get request to the mavenlink API with the required parameters in the url.
The desired outcome is when the user clicks the button on client side a new window opens with the mavenlink OAuth login page, then once user has logged in and authorised the application from their account in mavenlink it redirects to the designated redirect_uri and has a code in the url which then needs to be sent off in post request.
However issue is I get to the point where window is opened but whenever I try to login it gives error "cannot post / login", rather than redirecting to a url.
I am using a get request to the server, which then triggers a get request from the server to the API to get arounds the cors error/issue. But when I tested the url I am using in the get request from the server, by just pasting it into my browser, it worked fine and I could authorize the app.
So I am guessing it needs to work by coming from the client side? but how is that possible? or does it need to go from server side and I am doing it wrong?
Code below.
Client side API call:
import { Button, Container, Grid, Paper } from '#mui/material';
import React, {useEffect, useState} from 'react';
import { getAuth } from "firebase/auth";
import axios from 'axios';
import {db} from '../firebase';
import { doc, getDoc, } from 'firebase/firestore'
import '../styles/modules/mavenlinkPage.scss';
import Fab from '#mui/material/Fab';
import AddIcon from '#mui/icons-material/Add';
import {SuccessSnackbar, ErrorSnackbar} from '../components/PopupSnackbar';
export const MavenlinkPage = () => {
const auth = getAuth();
const user = auth.currentUser;
const [apiData, setApiData] = useState([]);
const [tokenResponse, setTokenResponse] = useState([]);
const [oauthToken, setOauthToken] = useState("");
const [secretToken, setSecretToken] = useState("");
const [clientId, setClientId] = useState("");
const [accessCode, setAccessCode] = useState("");
const [mavenlinkConnected, setMavenlinkConnected] = useState(false);
const [errorAlert, setErrorAlert] = useState(false);
const [successAlert, setSuccessAlert] = useState(false);
//Destructuring the objects that contain relevant keys for firestore db that we got in useEffect below
const {token, secret_token} = secretToken;
const {id, client_id} = clientId;
const handleAlertClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setSuccessAlert(false) && setErrorAlert(false);
};
//Function to retrieve the oauth token for mavenlink stored in firebase database
const getToken = async () => {
const docRefOauth = doc(db, 'mavenlink', 'oauth');
const docRefToken = doc(db, 'mavenlink', 'secret_token');
const docRefClientId = doc(db, 'mavenlink', 'application_id');
const docOauth = await getDoc(docRefOauth);
const docToken = await getDoc(docRefToken);
const docClientId = await getDoc(docRefClientId);
if (docOauth.exists() && docToken.exists() && docClientId.exists()) {
setOauthToken(docOauth.data())
setSecretToken(docToken.data())
setClientId(docClientId.data())
} else {
console.log("error: no document")
}
}
const getAuthorization = () => {
console.log(id);
console.log(token);
axios({
method: 'get',
url: 'http://localhost:5000/oauth/authorize',
data: {}
})
.then((response) => {
window.open('http://localhost:5000/oauth/authorize', 'Mavenlink')
})
}
useEffect(() => {
getToken();
}, [])
return(
<>
<Container>
<div className="mavenlink-page">
<Grid container spacing={2}>
<Grid item xs={12}>
<h1>Mavenlink</h1>
</Grid>
<Grid item xs={12}>
<Paper className="connection-status" elevation={1}>
<h4 className="title">Connection Status:</h4>
{!mavenlinkConnected ? <h4 className="response-error">{user.email} is not connected</h4> : <h4 className="response-success">{user.email} is connected</h4>}
</Paper>
</Grid>
<Grid item xs={6}>
<Paper elevation={1}>
<h4>Sync account to mavenlink API?</h4>
<Fab onClick={getData} color="primary" aria-label="add">
<AddIcon />
</Fab>
</Paper>
</Grid>
<Grid item xs={6}>
<Paper elevation={1}>
<h4>*Test User Token</h4>
<Fab onClick={getAuthorization} color="warning" aria-label="add">
<AddIcon />
</Fab>
</Paper>
</Grid>
{/* <Button onClick={getData}>Test API</Button> */}
</Grid>
</div>
{successAlert === true ? <SuccessSnackbar open={successAlert} handleClose={handleAlertClose}/> : <></> }
{errorAlert === true ? <ErrorSnackbar open={errorAlert} handleClose={handleAlertClose}/> : <></> }
</Container>
</>
);
};
server side:
const { default: axios } = require('axios');
const router = require('express').Router();
require('dotenv').config();
const mavenlink_app_id = process.env.MAVENLINK_APP_ID;
const secret_token_mavenlink = process.env.SECRET_TOKEN_MAVENLINK;
router.get('/oauth/authorize', (req, res) => {
axios({url:'https://app.mavenlink.com/oauth/authorize?client_id='+mavenlink_app_id+'&response_type=code&redirect_uri=http://localhost:5000/oauth/callback',})
.then((response) => {
console.log(response.data)
res.send(response.data)
})
.catch(error => console.log(error));
})
router.get('/oauth/callback', (req, res) => {
try {
let returnTo = req.protocol +'://'+req.hostname;
const port = req.connection.localPort
if (port !== undefined && port !== 5000) {
returnTo = `${returnTo}:3000`;
}
let access_token = req.query.code;
console.log(access_token)
res.status(201)
.cookie('mavenlink_token', access_token, {
expires: new Date(Date.now() + 120000), // cookie will be removed after 2 minutes
})
.redirect(301, `${returnTo}/`)
} catch(error) {
res.status(500).send('Server Error')
}
})
module.exports = router;

Why do you need your server to proxy request to mavenlink? This seems to be the issue as the mavenlink login form seems to be posting back to your server. Why can't your server just redirect the browser to mavenlink url instead?
If you open a window with a URL http://localhost:5000/oauth/authorize, your server will redirect to mavenlink and the login form will submit to mavenlink and the redirect will come back to your server at http://localhost:3000/mavenlinkpage with the auth code.

Related

NextJs mqtt app client does not update context in event handlers

I am developing mqtt web client for an IOT project using NextJs and mqtt package. In order to allow the client object to be shared among all components, I implemented a context API in which I defined some states as seen in the code below. The issue I am having here is, anytime I make update to msg state using setMsg function in the 'message' event handler, the msg does not get updated.
If I also try to publish a message by clicking a button, the message is not published. The only I was able to publish is by calling the client.publish intermittently inside setInterval
I am using the shiftr.io mqtt broker and I see an error stating that connection failed even though the shiftr.io dashboard indicated that connection is established by showing the client with its ID.
Thank you in advance.
** index.js file:**
import Car from '../components/Car';
import CarList from '../components/CarList';
import Board from '../components/Board';
import Notification from '../components/Notification';
import { useState, useEffect } from 'react';
import { useGlobalContext } from '../lib/context';
import mqtt from 'mqtt'
import Head from 'next/head'
export default function Home() {
// console.log(JSON.parse(client).connected)
const client = mqtt.connect('mqtt://tatwo:K1FADvdffhfff#tatwo.cloud.shiftr.io', {
clientId: 'client1'
})
const [freeSpace, setFreeState] = useState(1)
const {setSpaceStatus, setMqttClient, mqttClient, setMsg, msg} = useGlobalContext()
const [spaceMessageString, setSpaceMessageString] = useState(['0','0','0'])
const publishStatus = (msg, clt)=>{
client.publish('/reservation', msg)
}
if(client){
setMqttClient(client)
}
client.on('connect', function(){
console.log('connected')
client.subscribe('space')
})
client.on('message', function(topic, message){
console.log('receieved: ', message.toString().split(','))
// setSpaceMessageString(message.toString().split(','))
setMsg(message.toString())
// setSpaceStatus(message.toString().split(','))
})
useEffect(() => {
return ()=>{
if(mqttClient){
mqttClient.end()
}
}
}, [spaceMessageString])
return (
<div className='flex flex-col items-center justify-center'>
<h1 className='text-white text-3xl md:text-5xl font-extrabold text-center'>Parking without stress</h1>
<p className='text-amber-500 text-lg my-5'>Use smart parking system to check for parking space before you drive. </p>
<Board />
<p>{msg}: {mqttClient?.connected == true ? 'Onlined': 'offline'}</p>
{
freeSpace === 0 ?
// <Notification /> : <CarList spaceMessageString={spaceMessageString} />
<Notification /> : (
<div className='grid grid-cols-1 md:grid-cols-3 gap-4 my-5 w-full'>
{
spaceMessageString.map((space, index)=>
<Car
name={`Space ${index + 1}`}
message={spaceMessageString[index]}
key={index}
identity={index + 1}
publishStatus={publishStatus}
/>
)
}
</div>
)
}
</div>
)
}
** context.js: **
import React from 'react'
import { createContext, useContext, useState } from 'react'
const AppContext = createContext({});
const AppProvider = ({children}) => {
const [user, setUser] = useState(null)
const [reservations, setReservations] = useState([])
const [spaceStatus, setSpaceStatus] = useState([1, 0, 0])
const [connected, setConnected] = useState(false)
const [mqttClient, setMqttClient] = useState(null)
const [msg, setMsg] = useState('no message')
const client = null;
return (
<AppContext.Provider value={
{
user,
setUser,
spaceStatus,
setSpaceStatus,
reservations,
setReservations,
connected,
setConnected,
client,
setMqttClient,
mqttClient,
setMsg,
msg
}
}>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => useContext(AppContext);
export default AppProvider
** _app.js: **
import Layout from '../components/Layout'
import '../styles/globals.css'
import AppProvider from '../lib/context';
export default function App({ Component, pageProps }) {
return (
<AppProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</AppProvider>
)
}

Fetch request unable to get backend data due to Uncaught AxiosError - next.js and express.js

I'm trying to fetch some backend data on my Express.js backend which looks like this:
const express = require('express')
const app = express()
app.get("/api", (req, res) => {
res.json({"data": ["data1", "data2", "data3"]})
})
app.listen(5000, () => { console.log("Server started on port 5000, hi") })
Every time the specific page loads I want it to fetch the {"data": ["data1", "data2", "data3"]} from the backend, I added a button that makes the same request for testing as well. Whenever I click the button and whenever the page loads I get this error:
I don't really understand why I'm getting this error, here is my next.js code:
import React, { Component, useEffect, useState } from 'react';
import axios from 'axios';
export default function Product() {
const [backendData, setBackendData] = useState([{}])
useEffect(() => {
axios.get('/api').then(
response => response.json()
).then(
data => {
setBackendData(data)
}
)
console.log("ran")
}, [])
const test = () => {
axios.get('/api').then(
response => response.json()
).then(
data => {
setBackendData(data)
}
)
console.log("test clicked")
}
return (
<div style={styles.container}>
<div style={styles.speechTitle}>Talk to us, tell us about your day...</div>
<div style={styles.speechBox}>
Test
</div>
<button onClick={console.log("start")}>
Start
</button>
<button onClick={console.log("stop")}>Stop</button>
<button onClick={console.log("reset")}>Reset</button>
{(typeof backendData.data === 'undefined') ? (
<p>Loading...</p>
) : (
backendData.data.map((data, i) => (
<p key={i}>{data}</p>
))
)}
<button onClick={() => test()}>asdasd</button>
</div>
);
}
I'm running this component called Product you see above in this file called product.js which is in my pages folder:
import React from 'react';
import { ThemeProvider } from 'theme-ui';
import { StickyProvider } from 'contexts/app/app.provider';
import theme from 'theme';
import SEO from 'components/seo';
import Layout from 'components/layout';
import Product from 'components/product-input'
export default function ProductPage() {
return (
<ThemeProvider theme={theme}>
<StickyProvider>
<Layout>
<SEO title="Talkhappi" />
<Product/>
</Layout>
</StickyProvider>
</ThemeProvider>
);
}
I am also getting a network error when I open up the network tab in developer tools:
I'm unsure how to fix this problem and retrieve the data I want to retrieve from my backend running at port 5000.
You seem to have to call your apis at port 5000 instead of 3000 you did.
const baseURL = 'http://localhost:5000';
const test = () => {
axios.get(baseURL + '/api').then(
response => response.json()
).then(
data => {
setBackendData(data)
}
)
console.log("test clicked")
}

How display payment succeeded without return_url param in Stripe

I have one problem in integration Stripe into my React application. I use code from official Stripe documentation. It works expected. My question is how to check is payment succeeded without using return_url ? Am I required to use return url ? I found in Stripe documentation redirect: "if_required" option, but that doesnt make anything. I just get error problem in my console if I put this object in confirmPayment method. I would like have scenario is payment successfull that client navigate to some Confirmation page and to get message payment successfully.
App.jsx
import { loadStripe } from "#stripe/stripe-js";
import { Elements } from "#stripe/react-stripe-js";
import CheckoutForm from "./CheckoutForm";
import "./App.css";
// Make sure to call loadStripe outside of a component’s render to avoid
// recreating the Stripe object on every render.
// This is your test publishable API key.
const stripePromise = loadStripe("pk_test_51LmE9VAoYs2flpvClDqeh0f1vhaDUkBM0bRGaJgThjtaMd3PiPUGQOHjn9f7XW1HGgSQBvTq3xoLy9PovlWLPUnR0031srjgyb");
export default function App() {
const [clientSecret, setClientSecret] = useState("");
useEffect(() => {
// Create PaymentIntent as soon as the page loads
fetch("/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }),
})
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, []);
const appearance = {
theme: 'stripe',
};
const options = {
clientSecret,
appearance,
};
return (
<div className="App">
{clientSecret && (
<Elements options={options} stripe={stripePromise}>
<CheckoutForm />
</Elements>
)}
</div>
);
}
CheckoutForm.jsx
import {
PaymentElement,
useStripe,
useElements
} from "#stripe/react-stripe-js";
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [message, setMessage] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!stripe) {
return;
}
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
switch (paymentIntent.status) {
case "succeeded":
setMessage("Payment succeeded!");
break;
case "processing":
setMessage("Your payment is processing.");
break;
case "requires_payment_method":
setMessage("Your payment was not successful, please try again.");
break;
default:
setMessage("Something went wrong.");
break;
}
});
}, [stripe]);
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
setIsLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:3000",
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
setMessage(error.message);
} else {
setMessage("An unexpected error occurred.");
}
setIsLoading(false);
};
return (
<form id="payment-form" onSubmit={handleSubmit}>
<PaymentElement id="payment-element" />
<button disabled={isLoading || !stripe || !elements} id="submit">
<span id="button-text">
{isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
</span>
</button>
{/* Show any error or success messages */}
{message && <div id="payment-message">{message}</div>}
</form>
);
}
When using redirect: 'if_required', then the return_url attribute becomes not required.
If no redirection is required then you need to wait for the confirmation from the method stripe.confirmPayment and check if there is an error in the response.
To do so, you can adapt your CheckoutForm.jsx file and adapt your function handleSubmit like below:
setIsLoading(true);
const response = await stripe.confirmPayment({
elements,
confirmParams: {
},
redirect: 'if_required'
});
if (response.error) {
showMessage(response.error.message);
} else {
showMessage(`Payment Succeeded: ${response.paymentIntent.id}`);
}
setIsLoading(false);
Also, if you want to get notified from your backend when a successful payment has occurred, you can set a webhook[1] and listen to this Stripe event payment_intent.succeeded[2]
[1] https://stripe.com/docs/webhooks
[2] https://stripe.com/docs/api/events/types#event_types-payment_intent.succeeded

Implementing a collaborative text editor using nodejs/react/socket but encountering problems because of slow heroku servers

I've tried making a collaborative editor using socket.io with reactjs frontend and node backend. Here's the piece of logic which I think is causing problems....
When a client starts typing on the editor, I've used onInput event to emit a socket response say "typing" which carries the complete text on the editor inside data object at the moment client presses a key. Now server catches this typing event and in response to that, emits another socket response called "typed" which contains the same data but the server sends this response to all the clients connected to the server.... Now all clients receive this event inside componentDidMount and then update the state variable "codeValue" which updates the editor content for all the clients.
There are two problems, first one that on one single typing event, server is emitting numerous typed events ( it happens only in heroku server and not on local host ) and the other problem is that heroku servers are slow and before the server sends response to update the state of clients, clients had already entered more text on the editor which simply vanishes when the state is updated.....
FRONTEND CODE:
import React from "react";
import { Dropdown } from "semantic-ui-react";
import languages from "../utils/languages";
//Styles
import "../styles/app.css";
//Editor
import * as ace from "ace-builds";
// import SocketIOClient from "socket.io-client";
import "ace-builds/src-noconflict/mode-c_cpp";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/ext-language_tools";
import AceEditor from "react-ace";
let check = true;
let ld;
// const endpoint = "http://localhost:4676";
// const socket = SocketIOClient(endpoint, { transports: ["websocket"] });
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
codeValue: languages[0].template,
currentLang: languages[0].key,
};
this.codeEditor = React.createRef();
this.fireTyping = this.fireTyping.bind(this);
this.onDDChange = this.onDDChange.bind(this);
this.runCode = this.runCode.bind(this);
this.handleOutput = this.handleOutput.bind(this);
}
componentDidMount() {
this.props.socket.on("typed", (data) => {
console.log(35, data.text)
this.setState({
codeValue: data.text,
});
check = true;
console.log(check)
});
this.props.socket.on('ans',(data) => {
console.log(data.output)
//handleOutput(data.output)
})
}
fireTyping = () => {
ld = this.codeEditor.current.editor.getValue()
//console.log(ld)
if(check) {
console.log(48, this.codeEditor.current.editor.getValue(), check);
this.props.socket.emit("typing", {
text: ld,
});
check = false;
}
console.log(check)
};
onDDChange = (e, data) => {
const selectedVal = languages.filter((v) => v.key == data.value)
this.setState({currentLang : data.value, codeValue: selectedVal[0].template})
}
runCode = () => {
this.props.socket.emit('run', {
code: this.codeEditor.current.editor.getValue(),
lang: this.state.currentLang,
input: ''
})
}
handleOutput = () => {
}
render() {
return (
<div>
<Dropdown
placeholder="Languages"
onChange = {this.onDDChange}
selection
value = {this.state.currentLang}
options={languages}
/>
<AceEditor
style={{
margin: "3rem auto",
width: "80vw",
height: "70vh",
}}
fontSize={18}
ref={this.codeEditor}
mode="c_cpp"
theme="github"
value={this.state.codeValue}
onInput={this.fireTyping}
showPrintMargin={false}
name="UNIQUE_ID_OF_DIV"
editorProps={{ $blockScrolling: true }}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
}}
/>
<div className="container">
<button
onClick={this.runCode}
style={{
marginLeft: "40rem",
}}
className="large ui teal button"
>
Run
</button>
</div>
</div>
);
}
}
export default App;
BACKEND CODE:
const express = require("express");
const request = require("request");
const app = express();
const http = require("http");
const server = http.createServer(app);
const path = require('path')
const socket = require("socket.io");
const io = socket(server);
const port = process.env.PORT || 4676
app.use(express.static(path.join(__dirname, 'client/build')))
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/client/build/index.html'))
})
io.on("connection", (socket) => {
let previousCode, currentCode;
console.log(socket.id);
socket.on("typing", (data) => {
currentCode = data.text
console.log('typing')
console.log(previousCode === currentCode)
if(previousCode !== currentCode){
console.log(1)
io.emit("typed", data);
previousCode = currentCode;
currentCode = ''
}
});
});
server.listen(port, () => {
console.log("server started at http://localhost:4676");
});
I've spent hours trying to fix this but I couldn't.... Any help would be appreciated ☺️
Let me know if you need code reference, I'll share the repository

ReactJS Stripe Payments not returning successful message after clicking pay with node and express

I am currently using ReactJS, node, and express with the Stripe Payment API. After clicking the pay button and entering the dummy credit card credentials, the page doesnt process the payment. I have entered the correct publishing key and api key that I got from my dashboard.I believe it likely has somnething to do with what I need to add in the server.js file(aka node backend).I have read through the docs for any clues I can get. Also have searched here on Stack Overflow. None of the questions had the same thing I was looking for. Please see below for pictures and code. Thanks
This is before pressing the button. Please Note the console on the right side.
This is after pressing the button. The loading spinner just displays forever. Also note the console on right side
// Donate.js
import React from "react";
import "./Donate.css";
import { loadStripe } from "#stripe/stripe-js";
import { Elements } from "#stripe/react-stripe-js";
import CheckoutForm from "./CheckoutForm";
// Make sure to call `loadStripe` outside of a component’s render to avoid
// recreating the `Stripe` object on every render.
const stripe = loadStripe(
"pk*****************************"
);
stripe.then((data) => {
console.log(data);
});
const Donate = () => {
return (
<div className="donate">
<h1 className="donate__sectionHeader">Donate Now</h1>
<Elements stripe={stripe}>
<CheckoutForm />
</Elements>
</div>
);
};
export default Donate;
//CheckoutForm
import React, { useState, useEffect } from "react";
import { CardElement, useStripe, useElements } from "#stripe/react-stripe-js";
import "./CheckoutForm.css";
export default function CheckoutForm() {
const [succeeded, setSucceeded] = useState(false);
const [error, setError] = useState(null);
const [processing, setProcessing] = useState("");
const [disabled, setDisabled] = useState(true);
const [clientSecret, setClientSecret] = useState("");
const stripe = useStripe();
const elements = useElements();
useEffect(() => {
// Create PaymentIntent as soon as the page loads
window
.fetch("/donate", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }),
})
.then((res) => {
return res.json();
})
.then((data) => {
setClientSecret(data.clientSecret);
});
}, []);
const cardStyle = {
style: {
base: {
color: "#32325d",
fontFamily: "Arial, sans-serif",
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#32325d",
},
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a",
},
},
};
const handleChange = async (event) => {
// Listen for changes in the CardElement
// and display any errors as the customer types their card details
setDisabled(event.empty);
setError(event.error ? event.error.message : "");
};
const handleSubmit = async (ev) => {
ev.preventDefault();
setProcessing(true);
const payload = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
},
});
if (payload.error) {
setError(`Payment failed ${payload.error.message}`);
setProcessing(false);
} else {
setError(null);
setProcessing(false);
setSucceeded(true);
}
console.log(clientSecret);
};
return (
<form id="payment-form" onSubmit={handleSubmit}>
<CardElement
id="card-element"
options={{ hidePostalCode: true, cardStyle }}
onChange={handleChange}
/>
<button disabled={processing || disabled || succeeded} id="submit">
<span id="button-text">
{processing ? <div className="spinner" id="spinner"></div> : "Pay"}
</span>
</button>
{/* Show any error that happens when processing the payment */}
{error && (
<div className="card-error" role="alert">
{error}
</div>
)}
{/* Show a success message upon completion */}
<p className={succeeded ? "result-message" : "result-message hidden"}>
Payment succeeded, see the result in your
<a href={`https://dashboard.stripe.com/test/payments`}>
{" "}
Stripe dashboard.
</a>{" "}
Refresh the page to pay again.
</p>
</form>
);
}
//server.js
const express = require("express");
const app = express();
const { resolve } = require("path");
// This is your real test secret API key.
const stripe = require("stripe")(
"sk_test_**********************************"
);
app.use(express.static("."));
app.use(express.json());
const calculateOrderAmount = (items) => {
// Replace this constant with a calculation of the order's amount
// Calculate the order total on the server to prevent
// people from directly manipulating the amount on the client
return 1400;
};
app.post("/create-payment-intent", async (req, res) => {
const { items } = req.body;
// Create a PaymentIntent with the order amount and currency
const paymentIntent = await stripe.paymentIntents.create({
amount: 1099,
currency: "usd",
// Verify your integration in this guide by including this parameter
metadata: { integration_check: "accept_a_payment" },
});
res.send({
clientSecret: paymentIntent.client_secret,
});
});
app.listen(4242, () => console.log("Node server listening on port 4242!"));
You need to review the server call/network response with the client_secret. The console error indicates you've provided an invalid secret to confirmCardPayment, apparently an empty string.
You specified: .
It would appear that your app is not setting the state via setClientSecret as intended, and you end up with the initial empty string value from useState("");.
Check your client_secret value before the confirmCardPayment call, and step backwards to find where the value is being dropped.

Resources