Reputation: 88
Hey i am working on payment gateway on my website maded with react so i am using stripe js (for test) i have wraped my payment component in Elements in app.js
const promise = loadStripe("my stipe publishable key")
<Route path='/payment'>
<Header/>
<Elements stripe={promise}>
<Payment/>
</Elements>
</Route>
and my code in payment component is
import React, { useEffect, useState } from 'react';
import CheckoutProduct from './CheckoutProduct';
import "./Payment.css";
import { useStateValue } from "./StateProvider";
import { Link, useHistory } from "react-router-dom";
import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js";
import CurrencyFormat from "react-currency-format";
import { getBasketTotal } from './Reducer';
import axios from "./axios"
function Payment() {
const [{ basket, user }, dispatch] = useStateValue();
const history = useHistory();
const stripe = useStripe();
const elements = useElements();
const [succeeded, setSucceeded] = useState(false);
const [processing, setProcessing] = useState("");
const [error, setError] = useState(null);
const [disabled, setDisabled] = useState(true);
const [clientSecret, setClientSecret] = useState(true);
useEffect(() => {
// generate the special stripe secret which allows us to charge a customer
const getClientSecret = async () => {
const response = await axios({
method: 'post',
url: `/payment/create?total=${getBasketTotal(basket) * 100}`
});
setClientSecret(response.data.clientSecret)
}
getClientSecret();
}, [basket])
console.log('THE SECRET IS >>>', clientSecret)
const handleSubmit = async (event) => {
event.preventDefault();
setProcessing(true);
const payload = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement)
}
}).then(({ paymentIntent }) => {
//paymentIntent = Payment confirmation
setSucceeded(true);
setError(null)
setProcessing(false)
history.replace('/order')
})
};
const handleChange = event => {
// This will listen for changes in the CardElement
// and then display any errors as the customer types their card details
setDisabled(event.empty);
setError(event.error ? event.error.message : "");
}
return (
<div className="payment">
<div className="payment__container">
<h1>
Checkout (<Link to="/checkout">{basket?.length} items</Link>)
</h1>
{/* Payment section - delivery address*/}
<div className="payment__section">
<div className="payment__title">
<h1>Delivery address</h1>
</div>
<div className="payment__address">
<p>{user?.email}</p>
<p>123 React Lane</p>
<p>Los Angeles, CA</p>
</div>
</div>
{/* Payment section - Review Items */}
<div className="payment__section">
<div className="payment__title">
<h1>Review items and delivery</h1>
</div>
<div className="payment__items">
{basket.map(item => (
<CheckoutProduct
id={item.id}
title={item.title}
image={item.image}
price={item.price}
rating={item.rating}
/>
))}
</div>
</div>
{/* Payment section - Payment method */}
<div className="payment__section">
<div className="payment__title">
<h3>Payment Method</h3>
</div>
<div className="payment__details">
<form onSubmit={handleSubmit}>
<CardElement onChange={handleChange} />
<div className="payment__priceContainer">
<CurrencyFormat
renderText={(value) => (
<>
<h3>Order Total: {value}</h3>
</>
)}
decimalScale={2}
value={getBasketTotal(basket)}
displayType={"text"}
thousandSeparator={true}
prefix={"$"}
/>
<button disabled={processing || disabled ||succeeded}>
<span>{processing ? <p>Processing</p> :"Buy Now" }</span>
</button>
</div>
{error && <div>{error}</div>}
</form>
</div>
</div>
</div>
</div>
)
}
export default Payment
and i am using firebase hosting as backend so my running command firebase init a folder is created named function ("because i selected function option after running firebase init")and for axios i have written this code in my axios.js file
import axios from "axios";
const instance = axios.create({
baseURL: "http://localhost:5001/*****/******/api"
});
export default instance;
and baseURL used here i get by writing some code in index.js in function folder created by running firebase init the code i wrote in index.js is
const functions = require('firebase-functions');
const express = require("express");
const cors = require("cors");
const stripe = require("stripe")('my stripe secret key here')
//API
//App config
const app = express();
//Middlewares
app.use(cors({origin: true}));
app.use(express.json());
//API routes
app.get('/', (request, response) => response.status(200).send('hello world'))
app.post('/payment/create', async(request, response) => {
const total = request.query.total;
console.log('Payment Request Recieved BOOM!!! for this amount', total)
const paymentIntent = await stripe.paymentIntents.create({
amount: total,
currency: "usd",
});
//OK Created
response.status(201).send({
clientSecret: paymentIntent.client_secret,
})
})
//Listen command
exports.api = functions.https.onRequest(app)
from here i got my api that is use as base url in terminal functions[api]: http function initialized (http://localhost:5001/abcdabcdabcd/abcabcd/api).
now all thing is setup i got on my website running on local host then when i go to /payment then is console it is comming
THE SECRET IS >>> true
THE SECRET IS >>> true
THE SECRET IS >>> true
THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg
THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg
and whenever i enter 424242.... (used to test in stripe js) so whenever i type first 42 then my THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg is repeating 3 times i have no problem with this but when i click on buy now button yes it is redirecting me to /orders page which i have not maded so it redirecting me on home page but when i check my account on stripe js (test account)no test money is added there and in my console it is comming:-
THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg
THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg
THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg
THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg
THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg
**shared-a0382af03b7a06522c9bc0f5c75b552d.js:1 POST https://api.stripe.com/v1/payment_intents/pi_fgdhfdgsdfgfg/confirm 400***
(anonymous) @ shared-a0382af03b7a06522c9bc0f5c75b552d.js:1
f @ shared-a0382af03b7a06522c9bc0f5c75b552d.js:1
Q @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
$ @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
rt @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
(anonymous) @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
Promise.then (async)
confirmPaymentIntent @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
(anonymous) @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
(anonymous) @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
Gt._respondUsingPromise @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
Gt.handleAction @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
value @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
(anonymous) @ controller-207a0a1b39a190f1e22c08ab25c9f3ee.js:1
THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg
THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg
THE SECRET IS >>> pi_fgdhfdgsdfgfg_secret_fgsdgfdgdfgdg
please tell me how to solve this
Upvotes: 1
Views: 1607
Reputation: 5847
A few things to consider here:
By putting your call to /payment/create
in your useEffect
hook, you are creating a new PaymentIntent every time your component updates. This is quite inefficient and will leave you with many unused PaymentIntents, cluttering up your Stripe account. Instead you should only create the PaymentIntent when your user intends to purchase something, like when they click the "buy" button.
You are passing in the total amount to be charged from the client. This means that it is trivial for a malicious user to add many things to their basket and then edit that request to ensure that they are charged a much lower amount than you expect. All logic pertaining to calculating amount totals should be done on the server, not the client.
Your server logs don't show any failure in actual payments. Since you are confirming on the client, it's possible that you are getting an error there but redirecting before you see the error. You should listen for the error object instead of immediately redirecting:
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement)
}
}).then((result) => {
if (result.error) {
// payment failed, do something with the error
console.log(result.error.message);
} else {
setSucceeded(true);
setError(null)
setProcessing(false)
history.replace('/order')
});
You can also inspect your Stripe logs by looking at your dashboard: https://dashboard.stripe.com/test/logs
Upvotes: 2