【发布时间】:2022-01-04 05:51:30
【问题描述】:
我正在使用 Stripe 的预构建结帐方法在我的 Reactjs 应用程序上订购和支付产品。我最近开发了一个基本的购物车,现在我正在尝试创建一个“Go To Checkout”输入表单,允许用户将购物车中的产品发送到我的快递服务器 POST 路由,快递会将用户重定向到条带结账页。 问题是,当我按下表单输入以发出 HTTP 发布请求时,我收到“无法发布 /cart”响应,但没有错误消息。
有趣的是,通过使用 Postman,我能够到达 POST 路线。此外,我还设置了其他路由,用于从其他 API 获取数据,它们工作正常,但由于某种原因,无论我在做什么,这条 POST 路由都无法正常工作。
欢迎提出任何建议。
以下是相关文件和其中的代码。
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 - 此文件包含负责“/create-checkout-session”路由的所有代码。它应该接受请求并使用 Stripe API 创建将填充购物车项目的结帐页面。据我了解,我的 POST 请求没有达到这一点。我认为...
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 - 这是负责管理我的快递服务器的大部分代码。还有另一个文件使用路由器来定义快速端点/路由。
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'));
更新 1#
这是我浏览器的请求控制台显示的内容
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
更新2#
我已经取得了一些进展。 我可以通过更改来调用 goToCheckout() 函数...
<form method='POST' action={goToCheckout}>
<input type='submit' value='Go To Checkout' />
</form>
到...
<form type="button" onSubmit={goToCheckout}>
<button>
Go To Checkout
</button>
</form>
现在唯一的问题是,在我按下结帐按钮后,goToCheckout 函数中的代码会疲于执行,但我被重定向到购物车页面,现在唯一的区别是如果之前的 URL 是“http:/ /localhost:3000/cart”现在是“http://localhost:3000/cart?”。我认为这是因为按钮是一个表单(但这是我能够弄清楚如何调用 goToCheckout() 函数的唯一方法)。我尝试将 event.preventDefault() 添加到函数中,但这似乎没有任何作用。
有没有人知道为什么 fetch 代码没有正确执行并将用户重定向到条带结帐页面,而只是将我带回到带有 ?没有附加任何参数。
更新3#
当我从 Postman 访问相同的路由时,我能够获取 Stripe 结帐 URL 以将用户重定向到结帐页面,以便他们可以在测试模式下支付产品费用(目前)。
意味着路线本身按预期工作。
现在我只需要弄清楚当我使用表单调用我的 fetch 函数时如何停止页面刷新,添加一个“?”在 URL 的末尾签名,然后像 Postman 一样执行 fetch。
如果有人知道如何在不使用表单的情况下做到这一点,这就是我现在使用的,那将是一个很大的帮助。我尝试使用 a ,但无论我如何添加 goToCheckout(); onClick/action 等函数不会调用。
【问题讨论】:
-
这看起来更像是一个关于 Express 的问题。您可以通过检查浏览器控制台上的请求来开始调试。它看起来怎样?你还有来自 Express 服务器的日志吗?
-
@orakaro 感谢您的回复。我在 Update#1 中添加了浏览器控制台中显示的内容。谈到 Express 日志。我不熟悉日志记录 Express,我认为我没有在我的服务器上实现任何日志记录解决方案。我会调查的。
标签: javascript reactjs express http stripe-payments