简体   繁体   English

尝试使用 Stripe + React + Express 创建结帐时无法 POST / 错误 session

[英]Cannot POST / error when trying to create a checkout session with Stripe + React + Express

I am using Stripe's pre-built checkout method to order and pay for products on my Reactjs app.我正在使用 Stripe 的预建结账方法在我的 Reactjs 应用程序上订购和支付产品。 I recently developed a basic shopping cart and now I am trying to create a "Go To Checkout" input form that would allow the user to send the products in the cart to my express server POST route where express will redirect the user to the stripe checkout page.我最近开发了一个基本的购物车,现在我正在尝试创建一个“转到结帐”输入表单,允许用户将购物车中的产品发送到我的快递服务器 POST 路线,快递将用户重定向到条带结帐页。 The issue is that the moment when I press on the form input to make the HTTP post request, I get a "Cannot POST /cart" response with no error messages.问题是,当我按下表单输入以发出 HTTP 发布请求时,我收到“Cannot POST /cart”响应,没有任何错误消息。

The interesting is that by using Postman I am able to reach the POST route.有趣的是,通过使用 Postman 我能够到达 POST 路由。 Also, I have other routes set up that are used to GET data from other APIs and they are working fine, but for some reason, this POST route is not working no matter what I am doing.此外,我还设置了其他路由,用于从其他 API 获取数据,它们工作正常,但出于某种原因,无论我在做什么,这个 POST 路由都不起作用。

Any suggestions would be welcome.欢迎大家提出意见。

Below are the relevant files and the code found in them.以下是相关文件和其中的代码。

cart-page.js - this is the code that is responsible for the cart and has the code for the form that is supposed to make the HTTP request when pressed ('const goToCheckout') cart-page.js - 这是负责购物车的代码,并且具有在按下时应该发出 HTTP 请求的表单代码('const goToCheckout')

import React from "react";

require('dotenv').config();

const nodeEnv = process.env.REACT_APP_NODE_ENV === 'development';

//* Allows Stripe to authentificate our API requests with our key
const stripePublishableKey = nodeEnv ? process.env.REACT_APP_stripe_dev_publishable_key : process.env.REACT_APP_stripe_prod_pubishable_key;
const stripe = require('stripe')(stripePublishableKey);

const CartPage = (props) => {

    const { cart, onAdd, onRemove } = props;

    const productTotal = cart.reduce((a, c) => a + c.unit_amount * c.qty, 0) // default value 0
    const taxTotal = <p>Tax is included in the price.</p>
    const shippingTotal = <p>You can choose your shipping options at checkout.</p>
    const totalCost = productTotal;

    const checkoutData = cart.map(item => (
        { 
            price: item.id, 
            quantity: item.qty,
        }
    ));

    const goToCheckout = async () => {

        // Call your backend to create the Checkout Session
        await fetch('/create-checkout-session', {
            method: "POST",
            headers: {
                    "Content-Type": "application/json"
                },
            body: JSON.stringify({
                items: [
                    checkoutData
                ]
            }),
        })
        .then(function(response) {
            return response.json();
        })
        .then(function(session) {
            return stripe.redirectToCheckout({ sessionId: session.id });
        })
        .then(function(result) {
            // If `redirectToCheckout` fails due to a browser or network
            // error, you should display the localized error message to your
            // customer using `error.message`.
            if (result.error) {
                alert(result.error.message);
            }
        })
        .catch((error) => {
            console.error(error);
        });
    };

    const currencyFormatter = new Intl.NumberFormat('en-gb', {
        style:"currency", 
        currency:"GBP"
    }) 

    return (
        <main>

            <h1>Your Cart</h1>

            {cart.length === 0 && <p>Your Cart is Empty...</p>}

            {cart.map((item) => (
                <section className='cart-item' key={item.product.id}>
                    <h4>{item.product.name}</h4>
                        
                    <section className='cart-item-buttons'>
                        <button onClick={() => onAdd(item)}>+</button>
                        <button onClick={() => onRemove(item)}>-</button>
                    </section>
                        
                    <p>{item.qty} * {currencyFormatter.format(item.unit_amount / 100)}</p>
                </section>
            ))}

            {cart.length !== 0 && (
                <section>
                    <p>Total Product Price: {currencyFormatter.format(productTotal / 100)}</p>
                    <p>Toal Tax: {taxTotal}</p>
                    <p>Shipping Costs: {shippingTotal}</p>
                    <p><strong>Total Costs: {currencyFormatter.format(totalCost / 100)}</strong></p>
                </section>
            )}

            {cart.length > 0 && (
                <section>
                    <p>ADD CHECKOUT BUTTON</p>

                    <form method='POST' action={goToCheckout}>
                        <input type='submit' value='Go To Checkout' />
                    </form>

                </section>
            )}

        </main>
    );
};

export default CartPage;

createCheskoutSession.js - this file contains all the code that is responsible for the '/create-checkout-session' route. createCheskoutSession.js - 此文件包含负责“/create-checkout-session”路由的所有代码。 It is supposed to accept the request and using the Stripe API creates the checkout page that will be populated with the cart items.它应该接受请求并使用 Stripe API 创建结帐页面,该页面将填充购物车项目。 From what I understand, my POST request does not reach this point.据我了解,我的 POST 请求没有达到这一点。 I think...我认为...

require('dotenv').config();

const nodeEnv = process.env.REACT_APP_NODE_ENV === 'development';

const YOUR_DOMAIN = nodeEnv ? process.env.REACT_APP_dev_domain : process.env.REACT_APP_prod_domain;

//* Allows Stripe to authentificate our API requests with our key
const stripeSecretKey = nodeEnv ? process.env.REACT_APP_stripe_dev_secret_key : process.env.REACT_APP_stripe_prod_secret_key;
const stripe = require('stripe')(stripeSecretKey);

//* To override the API version, provide the apiVersion option:
//*  Before upgrading your API version in the Dashboard, review both the API changelog and the library changelog.
/*
const stripe = require('stripe')(stripeSecretKey, {
  apiVersion: '2020-08-27',
});
*/

//* After creating a Checkout Session, redirect your customer to the URL returned in the response.
//* Add an endpoint on your server that creates a Checkout Session. A Checkout Session controls what your customer sees 
//* in the Stripe-hosted payment page such as line items, the order amount and currency, and acceptable payment methods.

const createCheckoutSession = async (req, res) => {
    
    const session = await stripe.checkout.sessions.create({

    //* Prefill customer data
    //* Use customer_email to prefill the customer’s email address in the email input field. You can also pass a
    //*  Customer ID to customer field to prefill the email address field with the email stored on the Customer.
    //* customer_email: 'customer@example.com',
        
    //* Pick a submit button // Configure the copy displayed on the Checkout submit button by setting the submit_type. There are four different submit types.
        submit_type: 'donate',
        
        /*
        Collect billing and shipping details
        Use billing_address_collection and shipping_address_collection to collect your customer’s address. 
        shipping_address_collection requires a list of allowed_countries. Checkout displays the list of allowed
        countries in a dropdown on the page.
        */
            
        billing_address_collection: 'auto',
        shipping_address_collection: {
            allowed_countries: ['US', 'CA', 'LV'],
        },

        /*
        Define a product to sell
        Always keep sensitive information about your product inventory, like price and availability, on your server 
        to prevent customer manipulation from the client. Define product information when you create the Checkout
        Session using predefined price IDs or on the fly with price_data.
        */
        /*
            line_items: [
                {
                    price: 'price_1JsxdVBSHV1ZLiWD7n4PcKf9',
                    quantity: 1,
                },
            ],
        */
        //* Provide the exact Price ID (e.g. pr_1234) of the product you want to sell
/*
            line_items: [
                cartItems.map(item => {
                    return {
                        price: item.price,
                        quantity: item.quantity,
                    }
                })
            ],
*/
            line_items: req.body.items,
            
            /*
            req.body.items.map(item => {
                return {
                    price: item.id,
                    quantity: item.qty,
                },
            },
            */

        //* When you pass multiple payment methods, Checkout dynamically displays them to prioritize what’s most 
        //* relevant to the customer. Apple Pay and Google Pay are included automatically when you include card in 
        //* payment_method_types.
        //* Apple Pay and Google Pay are enabled by default and automatically appear in Checkout when a customer 
        //* uses a supported device and has saved at least one card in their digital wallet. 
            payment_method_types: [
                'card',
            ],

        //* Choose the mode
        //* Checkout has three modes: payment, subscription, or setup. Use payment mode for one-time purchases.
        //*  Learn more about subscription and setup modes in the docs.
            mode: 'payment',

        //* Supply success and cancel URLs
        //* Specify URLs for success and cancel pages—make sure they are publicly accessible so Stripe can redirect 
        //* customers to them. You can also handle both the success and canceled states with the same URL.
            success_url: `${YOUR_DOMAIN}/stripe/stripe-success.html`, //! Change
            cancel_url: `${YOUR_DOMAIN}/stripe/stripe-cancel.html`, //! Change
        //* Activate Stripe Tax to monitor your tax obligations, automatically collect tax, and access the reports you need to file returns.
        //* automatic_tax: {enabled: true},
    });

    //* Redirect to Checkout
    //* After creating the session, redirect your customer to the Checkout page’s URL returned in the response.
  
    res.redirect(303, session.url);

};

module.exports = createCheckoutSession;

server.js - this is most of the code that is responsible for managing my express server. server.js - 这是负责管理我的快速服务器的大部分代码。 There is another file that uses Router to define the express endpoints/routes.还有另一个文件使用 Router 来定义快速端点/路由。

const express = require('express');
const helmet = require('helmet'); 
const cors = require('cors'); 
const path = require('path'); // Allows to access files through the server in our filesystem

/**
**  ------------- GENERAL SETUP -------------
*/

// Provides access to variables from the .env file by using process.env.REACT_APP_variable_name
    require('dotenv').config();

    const nodeEnv = process.env.REACT_APP_NODE_ENV === 'development';
    const devPort = process.env.REACT_APP_server_dev_port;
    const prodPort = process.env.REACT_APP_server_prod_port;

//* Creates the Express server instance as "app" 
    const app = express();

//* MIDDLEWARE
// Called BETWEEN processing the Request and sending the Response in your application method.
    app.use(cors()); // To allow cross origin conections (Allows our React app to make HTTP requests to Express application)
    app.use(helmet()); // Sets many http headers to make them more secure
    app.use(express.static(path.join(__dirname, 'public'))); // To load static files or client files from here http://localhost:3000/images/kitten.jpg
    // Instead of using body-parser middleware, use the new Express implementation of the same thing
        app.use(express.json()); // To recognize the incoming Request Object (req.body) as a JSON Object
        app.use(express.urlencoded({ extended: false })); // To recognize the incoming Request Object as strings or arrays

/**
** -------------- SERVER ----------------
*/
       
// Determines the PORT and enables LISTENing for requests on the PORT (http://localhost:8000)

    const PORT = nodeEnv ? devPort : prodPort;
       
    app.listen(PORT, () => {
      console.debug(`Server is listening at http://localhost:${PORT}`);
    });
  
/**
** ------- ROUTES / ENDPOINTS ---------
*/

// Go to /test to make sure the basic API functioning is working properly
    app.get('/test', (req, res) => {
        res.status(200).send('The Basic API endpoints are working.')
    });

// Imports all of the routes from ./routes/index.js
    app.use(require('./routes/allRoutes'));

Update 1#更新 1#

This is what my browser's request console is showing这是我浏览器的请求控制台显示的内容

Request URL: http://localhost:3000/cart
Request Method: POST
Status Code: 404 Not Found
Remote Address: 127.0.0.1:3000
Referrer Policy: strict-origin-when-cross-origin
access-control-allow-origin: *
connection: close
content-length: 144
content-security-policy: default-src 'none'
content-type: text/html; charset=utf-8
date: Fri, 26 Nov 2021 05:25:35 GMT
expect-ct: max-age=0
referrer-policy: no-referrer
strict-transport-security: max-age=15552000; includeSubDomains
Vary: Accept-Encoding
x-content-type-options: nosniff
x-dns-prefetch-control: off
x-download-options: noopen
x-frame-options: SAMEORIGIN
x-permitted-cross-domain-policies: none
X-Powered-By: Express
x-xss-protection: 0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: lv-LV,lv;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: max-age=0
Connection: keep-alive
Content-Length: 0
Content-Type: application/x-www-form-urlencoded
DNT: 1
Host: localhost:3000
Origin: http://localhost:3000
Referer: http://localhost:3000/cart
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1

Update 2#更新 2#

I've had some progress.我有一些进步。 I was able to call the goToCheckout() function by changing...我可以通过更改来调用 goToCheckout() function ...

<form method='POST' action={goToCheckout}>
     <input type='submit' value='Go To Checkout' />
</form>

To...到...

<form type="button" onSubmit={goToCheckout}>
      <button>
          Go To Checkout
      </button>
</form>

The only issue now is that after I press the checkout button, the code in the goToCheckout function tires to execute, but I get redirected to the cart page, the only difference now is that if before the URL was "http://localhost:3000/cart" now it is "http://localhost:3000/cart?".现在唯一的问题是,在我按下结帐按钮后,goToCheckout function 中的代码无法执行,但我被重定向到购物车页面,现在唯一的区别是如果在 URL 之前是“http://localhost: 3000/cart”现在是“http://localhost:3000/cart?”。 I think this is because the button is in a form (but that is the only way I have been able to figure out how to call the goToCheckout() function).我认为这是因为该按钮位于表单中(但这是我能够弄清楚如何调用 goToCheckout() 函数的唯一方法)。 I tried to do add event.preventDefault() into the function, but that did not seem to do anything.我尝试将 event.preventDefault() 添加到 function 中,但这似乎没有任何作用。

Does anyone have an idea why the fetch code is not properly executing and redirecting the user to the stripe checkout page but instead just bringing me back to the same URL with a?有谁知道为什么获取代码没有正确执行并将用户重定向到条带结帐页面,而是只是让我回到同一个 URL? without any params attached to it.没有附加任何参数。

Update 3#更新 3#

When I access the same route from Postman, I am able to get the Stripe checkout URL to redirect the user to the Checkout page so that they can pay for the products in test mode (for now).当我从 Postman 访问相同的路线时,我能够获得 Stripe 结帐 URL 以将用户重定向到结帐页面,以便他们可以在测试模式下为产品付款(目前)。

Meaning that the route itself works as intended.这意味着路线本身按预期工作。

Now I only have to figure out how to stop the page to refresh when I use the form to call my fetch function, adding a "?"现在我只需要弄清楚当我使用表单调用我的fetch function时如何停止页面刷新,添加一个“?” sign at the end of the URL, and execute the fetch just like Postman does it.在 URL 的末尾签名,然后像 Postman 一样执行提取。

If anyone knows how to do that without using a form, which is what I am using now, that would be a great help.如果有人知道如何在不使用表格的情况下做到这一点,这就是我现在使用的表格,那将是一个很大的帮助。 I tried using a, but no matter how I added the goToCheckout();我尝试使用 a,但无论我如何添加 goToCheckout(); function to onClick/action, etc. the functions would not call. function 到 onClick/action 等函数不会调用。

This was a long time ago but I guess I might as well answer my own question.这是很久以前的事了,但我想我也可以回答我自己的问题。

  1. Had to change the internal logic of the goToCheckout function:必须更改 goToCheckout function 的内部逻辑:
    const goToCheckout = (e) => {
        e.preventDefault();
        fetch(`${customProxy}/create-checkout-session`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ items: checkoutData}),
        })
        .then(res => {
            if (res.ok) return res.json()
            return res.json().then(json => Promise.reject(json))
        })
        .then(({ url }) => {
            window.location = url
        })
        .catch((error) => {
            // console.error(error);
            alert("Create Stripe checkout:" + error);
        });
    };
  1. Changed the HTML for the goToCheckout button为 goToCheckout 按钮更改了 HTML
<button className='go-to-checkout-button' onClick={goToCheckout}>
       Go To Checkout
</button>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM