How can I make a https request to Stripe API with Axios?

Viewed 2392

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)
1 Answers

You can't make server-side API calls to Stripe from the client side, and you should never (ever [ever ever]) put your Secret Key in client-side code.

You'll need to use some kind of server-side code to do that part, or use client-only Checkout.

Related