You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

电商网站Checkout按钮与Stripe通信失败问题求助

电商网站Stripe结账跳转问题咨询

我正在搭建一个电商网站,遇到提交(结账按钮)无法跳转到Stripe结账页面的问题。我是按照自定义支付流程进行开发的,想咨询是否可能遗漏了某些步骤?

以下是我的代码,包含server.js、checkout.js和index.html:

server.js

const express = require("express");
const app = express();
// This is your test secret API key.
const stripe = require("stripe")('sk_test_XXXXXXXX');

app.use(express.static("public"));
app.use(express.json());

const calculateOrderAmount = (items) => {
    [1, { priceInCents: 8500, name: "Black Sweatsuit"}],
    [2, { priceInCents: 8500, name: "Grey Sweatsuit"}],
    [3, { priceInCents: 8500, name: "Red Sweatsuit"}],
    [4, { priceInCents: 8500, name: "Blue Sweatsuit"}]

  return 1400;
};

app.post("/create-payment-intent", async (req, res) => {
  const { items } = req.body;
  // Create a PaymentIntent with the order amount and currency
  const paymentIntent = await stripe.paymentIntents.create({
    amount: calculateOrderAmount(items),
    payment_method_types: ["card"],
    mode: "payment", 
    currency: "usd",
    automatic_payment_methods: {
      enabled: true,
    },
  });

  res.send({
    clientSecret: paymentIntent.client_secret,
  });
});

const endpointSecret = "whsec_93f2310c53f1bdbb9496049a38ea5ceadbce03fcf4223b2be51c5b6717f2cc60";

app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {
  const sig = request.headers['stripe-signature'];

  let event;

  try {
    event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
  } catch (err) {
    response.status(400).send(`Webhook Error: ${err.message}`);
    return;
  }

  // Handle the event
  switch (event.type) {
    case 'payment_intent.succeeded':
      const paymentIntent = event.data.object;
      // Then define and call a function to handle the event payment_intent.succeeded
      break;
    // ... handle other event types
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  // Return a 200 response to acknowledge receipt of the event
  response.send();
});


app.listen(4242, () => console.log('Running on port 4242'));

checkout.js

// This is your test publishable API key.
const stripe = Stripe("pk_test_XXXXX");

// The items the customer wants to buy
const items =  [{ id: "Black Sweatsuit"}]
               [{ id: "Grey Sweatsuit"}]
               [{ id: "Red Sweatsuit"}]
               [{ id: "Blue Sweatsuit"}];

let elements;

initialize();
checkStatus();

document
  .querySelector("#payment-form")
  .addEventListener("submit", handleSubmit);

// Fetches a payment intent and captures the client secret
async function initialize() {
  const response = await fetch("/create-payment-intent", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ items }),
  });
  const { clientSecret } = await response.json();

  const appearance = {
    theme: 'stripe',
  };
  elements = stripe.elements({ appearance, clientSecret });

  const paymentElement = elements.create("payment");
  paymentElement.mount("#payment-element");
}

async function handleSubmit(e) {
  e.preventDefault();
  setLoading(true);

  const { error } = await stripe.confirmPayment({
    elements,
    confirmParams: {
      // Make sure to change this to your payment completion page
      return_url: "http://localhost:4242/checkout.html",
    },
  });

  // This point will only be reached if there is an immediate error when
  // confirming the payment. Otherwise, your customer will be redirected to
  // your `return_url`. For some payment methods like iDEAL, your customer will
  // be redirected to an intermediate site first to authorize the payment, then
  // redirected to the `return_url`.
  if (error.type === "card_error" || error.type === "validation_error") {
    showMessage(error.message);
  } else {
    showMessage("An unexpected error occurred.");
  }

  setLoading(false);
}

// Fetches the payment intent status after payment submission
async function checkStatus() {
  const clientSecret = new URLSearchParams(window.location.search).get(
    "payment_intent_client_secret"
  );

  if (!clientSecret) {
    return;
  }

  const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);

  switch (paymentIntent.status) {
    case "succeeded":
      showMessage("Payment succeeded!");
      break;
    case "processing":
      showMessage("Your payment is processing.");
      break;
    case "requires_payment_method":
      showMessage("Your payment was not successful, please try again.");
      break;
    default:
      showMessage("Something went wrong.");
      break;
  }
}

// ------- UI helpers -------

function showMessage(messageText) {
  const messageContainer = document.querySelector("#payment-message");

  messageContainer.classList.remove("hidden");
  messageContainer.textContent = messageText;

  setTimeout(function () {
    messageContainer.classList.add("hidden");
    messageText.textContent = "";
  }, 4000);
}

// Show a spinner on payment submission
function setLoading(isLoading) {
  if (isLoading) {
    // Disable the button and show a spinner
    document.querySelector("#submit").disabled = true;
    document.querySelector("#spinner").classList.remove("hidden");
    document.querySelector("#button-text").classList.add("hidden");
  } else {
    document.querySelector("#submit").disabled = false;
    document.querySelector("#spinner").classList.add("hidden");
    document.querySelector("#button-text").classList.remove("hidden");
  }
}

index.html

</div> 
                        <div class="subtotal">Subtotal: $0.00</div>
                        <form id="payment-form">
                            <input type="text" id="email" placeholder="Enter email address" />
                            <div id="payment-element">
                              <!--Stripe.js injects the Payment Element-->
                            </div>
                            <button id="submit">
                              <div class="spinner hidden" id="spinner"></div>
                              <span id="button-text">Checkout</span>
                            </button>
                            <div id="payment-message" class="hidden"></div>
                          </form>
                    </div>

问题排查与修复建议

1. 前端checkout.js语法错误(核心问题)

你在定义items时的写法完全错误,当前代码会抛出语法异常,导致后续的initialize()函数无法正常执行,Stripe Elements无法初始化,最终点击按钮无反应:

// 错误写法
const items =  [{ id: "Black Sweatsuit"}]
               [{ id: "Grey Sweatsuit"}]
               [{ id: "Red Sweatsuit"}]
               [{ id: "Blue Sweatsuit"}];

正确的数组定义需要用逗号分隔每个对象:

// 正确写法
const items = [
  { id: "Black Sweatsuit" },
  { id: "Grey Sweatsuit" },
  { id: "Red Sweatsuit" },
  { id: "Blue Sweatsuit" }
];

这个语法错误会直接阻断代码执行,浏览器控制台可以看到报错信息。

2. 确认Stripe.js脚本已引入

你的index.html片段中没有显示引入Stripe的官方脚本,必须在页面中添加这行代码(建议放在<head>标签内),否则Stripe对象无法被识别:

<script src="https://js.stripe.com/v3/"></script>

同时要确保checkout.js也正确引入到页面中,比如在</body>前添加:

<script src="/checkout.js"></script>

3. 后端calculateOrderAmount函数未正确处理商品金额

当前函数只是罗列了商品数据,但完全没有使用传入的items参数计算金额,直接返回固定值1400(14美元)。虽然这不会直接导致跳转失败,但会导致订单金额与实际商品不符,建议修复为根据传入的items计算总金额:

const calculateOrderAmount = (items) => {
  // 先定义商品价格映射
  const itemPrices = {
    "Black Sweatsuit": 8500,
    "Grey Sweatsuit": 8500,
    "Red Sweatsuit": 8500,
    "Blue Sweatsuit": 8500
  };
  // 计算选中商品的总金额
  return items.reduce((total, item) => total + (itemPrices[item.id] || 0), 0);
};

4. 检查浏览器控制台报错

点击结账按钮前,打开浏览器开发者工具(F12)的控制台标签,查看是否有其他报错信息,比如网络请求失败(/create-payment-intent接口返回错误)、元素未找到等,这些都可能导致流程中断。

按照上述步骤修复后,重新启动服务测试,应该能正常触发Stripe的支付流程。


内容的提问来源于stack exchange,提问作者user16147870

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 08:05:26