I'm trying to integrate stripe into my application and its proving to be really challenging.
My application is React front-end and google Firebase for database and hosting.
After a couple of days of pouring over the documentation and trail and error, I eventually found this article https://blog.risingstack.com/stripe-payments-integration-tutorial-javascript/
It is quite comprehensive and understandable, so I followed that until a point. My react app is structured so that there is an index.js file which is the root to the dom and then an app.js file which stores the navigation to the rest of the application.
I'm trying to add a shop page or a checkout page that will be able to take the customers credit card details and process the payment. So I decided to use stripe.
Initially, I tried to use google firebase cloud functions, which ended up not working at all - sidenote, I believe they are dealing with a bug? - Then I decided to use express and axios to handle the api requests.
That seems to work except for the response from stripe is "the server responded with a status of 405 (Method Not Allowed)".
I think that has to do with the fact that I'm running this locally so the request is being sent as a http request but I'm too inexperienced to know for sure and I would love some expert direction before I deploy this to staging. While I'm at it, how do I change the Cors origin to be the real site rather than localhost?
Code below, thanks in advance. Rory
// File Location //
Olas/Server/index.js
// File Location //
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const app = express();
const port = 3001;
const stripe = require('stripe')('****SECRET TEST KEY****');
app.use(cors({
origin: [/https:\/\/localhost:\d+$/],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
app.post('/api/shop/order', async(req, res) => {
const order = req.body.order;
const source = req.body.source;
try {
const stripeOrder = await stripe.orders.create(order);
console.log('Order completed: ${stripeOrder.id}');
await stripe.orders.pay(stripeOrder.id, {source})
} catch (err) {
// Handle stripe errors here: No such coupon, sku, ect
console.log(`Order error: ${err}`);
return res.sendStatus(404);
}
return res.sendStatus(200);
});
app.get('/api/', (req, res) => res.send({ version: '1.0' }));
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
// File Location //
src/Pages/Shop.js
// File Location //
import React, {Component} from "react";
import { CardElement } from 'react-stripe-elements';
import PropTypes from 'prop-types';
import axios from 'axios';
import qs from 'query-string-object';
import { injectStripe } from 'react-stripe-elements'
const prices = {
banana: 150,
cucumber: 100
};
const products = {
banana: "prod_HQJX0o5TAu8pJi",
cucumber: "prod_HQJYaYAlB1nSbJ",
};
const stripeAuthHeader = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Bearer *** TEST KEY`};
class Shop extends React.Component {
constructor(props) {
super(props);
this.state = {
fetching: false,
cart: {
banana: 0,
cucumber: 0
},
coupon: '',
email: '',
name: '',
address : {
line1: '',
city: '',
state: '',
country: '',
postal_code: ''
},
};
this.handleCartChange = this.handleCartChange.bind(this);
this.handleCartReset = this.handleCartReset.bind(this);
this.handleAddressChange = this.handleAddressChange.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleCartChange(evt) {
evt.preventDefault()
const cart = this.state.cart
cart[evt.target.name]+= parseInt(evt.target.value)
this.setState({cart})
}
handleCartReset(evt) {
evt.preventDefault()
this.setState({cart:{banana: 0, cucumber: 0}})
}
handleAddressChange(evt) {
evt.preventDefault()
const address = this.state.address
address[evt.target.name] = evt.target.value
this.setState({address})
}
handleChange(evt) {
evt.preventDefault()
this.setState({[evt.target.name]: evt.target.value})
}
handleSubmit(evt) {
evt.preventDefault();
this.setState({fetching:true});
const cart = this.state.cart;
this.props.stripe.createToken().then(({token}) => {
//Create Order
const order ={
currency: 'usd',
items: Object.keys(cart).filter((name) => cart[name] >0 ? true: false).map(
name => {
return {
type: 'good',
parent: products[name],
quantity: cart[name]
}
}),
email: this.state.email,
shipping: {
name: this.state.name,
address: this.state.address
}
}
// Add coupon if given
if (this.state.coupon) {
order.coupon = this.state.coupon
}
console.log(token);
//This is a simple direct charge from an earlier example//
// const price = cart.banana * prices.banana + cart.cucumber * prices.cucumber
// axios.post('https.//api.stripe.com/v1/charges',
// qs.stringify({
// source: token.id,
// amount: price,
// currency: 'usd'
// }),
// {headers:stripeAuthHeader})
// .then((resp) => {
// this.setState({fetching:false});
// alert('Thank you for your purchase! Your card has been charged with: ${(resp.data.amount / 100).toLocaleString(\'en-US\', {style: \'currency\', currency: \'usd\'})}`')
// })
// .catch(error => {
// this.setState({fetching: false});
// console.log(error)
// })
// .catch(error=> {
// this.setState({fetching:false});
// console.log(error)
// })
axios.post('http:localhost:3001/api/shop/order', {order, source: token.id})
.then(() => {
this.setState({fetching: false})
alert(`Thank you for your purchase!`)
})
.catch(error => {
this.setState({fetching: false})
console.log(error);
})
})
.catch(error => {
this.setState({fetching: false})
console.log(error)
})
}
render() {
const state = this.state;
const fetching = this.state.fetching;
const cart = this.state.cart;
const address = this.state.address;
const submittable = (cart.banana !== 0 || cart.cucumber !== 0) && state.email && state.name && address.line1 && address.city && address.state && address.country && address.postal_code
return (
<form onSubmit={this.handleSubmit} style={{width: '550px', margin: '20px', padding: '10px', border: '2px solid lightseagreen', borderRadius: '10px'}}>
<div>
Banana {(prices.banana / 100).toLocaleString('en-US', {style: 'currency', currency: 'usd'})}:
<div>
<button name={"banana"} value={1} onClick={this.handleCartChange}>+</button>
<button name={"banana"} value={-1} onClick={this.handleCartChange} disabled={cart.banana <=0}>-</button>
{cart.banana}
</div>
</div>
<div>
Cucumber {(prices.cucumber / 100).toLocaleString('en-US', {style:'currency', currency:'usd'})}:
<div>
<button name={"cucumber"} value={1} onClick={this.handleCartChange}>+</button>
<button name={"cucumber"} value={-1} onClick={this.handleCartChange} disabled={cart.cucumber <=0}>-</button>
{cart.cucumber}
</div>
</div>
<button onClick={this.handleCartReset}>Reset Cart</button>
<div style={{width: '450px', margin: '10px', padding: '5px', border: '2px solid green', borderRadius: '10px'}}>
<CardElement style={{base: {fontSize: '18px'}}}/>
</div>
<div>Name: <input type="text" name="name" onChange={this.handleChange}/></div>
<div>Email: <input type="text" name="email" onChange={this.handleChange}/></div>
<div>Address Line: <input type="text" name="line1" onChange={this.handleAddressChange}/></div>
<div>City: <input type="text" name="city" onChange={this.handleAddressChange}/></div>
<div>State: <input type="text" name="state" onChange={this.handleAddressChange}/></div>
<div>Country: <input type="text" name="country" onChange={this.handleAddressChange}/></div>
<div>Postal Code: <input type="text" name="postal_code" onChange={this.handleAddressChange}/></div>
<div>Coupon Code: <input type="text" name="coupon" onChange={this.handleChange}/></div>
{!fetching ?
<button type={'submit'} disabled={cart.banana=== 0 && cart.cucumber===0}>Purchase</button>
: 'Purchasing...'
}
Price: {((cart.banana * prices.banana + cart.cucumber * prices.cucumber) / 100).toLocaleString('en-US', {style: 'currency', currency: 'usd'})}
</form>
);
}
}
Shop.propTypes = {
stripe: PropTypes.shape({
createToken: PropTypes.func.isRequired}).isRequired
};
export default injectStripe(Shop)