我正在尝试使用 TypeScript 将 Stripe Payment Element 集成到我的 Next.js 13 App Router 项目中。我的项目是用“app”目录组织的。我已经浏览了 Stripe 网站和 GitHub 存储库的各种选项,但遇到了困难。每次尝试只会显示“卡号”、“MM / YY”、“CVC”和“卡付款”等文本,而不是字段和按钮等实际 UI 元素。
我尝试遵循 Stripe 文档中提供的示例,特别是来自 https://stripe.com/docs/ payments/ payment-element 和 https://stripe.com/docs/ payments/ 的代码快速入门?客户端=下一步。最初,我尝试了 .js 格式的代码,但没有成功。然后我将其转换为 .tsx 和 .ts,但问题仍然存在。删除与 /api 和密钥相关的逻辑后,UI 元素最终显示,但没有样式或表单字段。
这是我当前的条纹形式。
我遇到了与组件中的 options 对象相关的错误,这导致我删除了 options={options} 属性。这可能是导致问题的原因之一。我也尝试过使用基于人工智能的解决方案,但没有成功。
以下是我最近尝试的相关代码片段:
page.tsx:
'use client';
import React, { useEffect, useState } from "react";
import { loadStripe } from "@stripe/stripe-js";
import { Elements } from "@stripe/react-stripe-js";
import CheckoutForm from "../components/CheckoutForm";
// Make sure to call loadStripe outside of a component’s render to avoid
// recreating the Stripe object on every render.
// This is your test publishable API key.
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
export default function App() {
const [clientSecret, setClientSecret] = useState<string>("");
useEffect(() => {
// Create PaymentIntent as soon as the page loads
fetch("/api/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }),
})
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, []);
const appearance = {
theme: 'stripe',
};
const options = {
clientSecret,
appearance,
};
return (
<div className="App">
{clientSecret && (
<Elements stripe={stripePromise}>
<CheckoutForm />
</Elements>
)}
</div>
);
}
CheckoutForm.tsx:
import React, { useEffect, useState } from "react";
import { StripePaymentElementOptions, StripeLinkAuthenticationElementChangeEvent } from "@stripe/stripe-js";
import {
PaymentElement,
LinkAuthenticationElement,
useStripe,
useElements
} from "@stripe/react-stripe-js";
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [email, setEmail] = useState<string>('');
const [message, setMessage] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
if (!stripe) {
return;
}
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
if (!paymentIntent) {
return;
}
switch (paymentIntent.status) {
case "succeeded":
setMessage("Payment succeeded!");
break;
case "processing":
setMessage("Your payment is processing.");
break;
case "requires_payment_method":
setMessage("Your payment was not successful, please try again.");
break;
default:
setMessage("Something went wrong.");
break;
}
});
}, [stripe]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!stripe || !elements) {
// Stripe.js hasn't yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
setIsLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:3000",
},
});
// 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 && (error.type === "card_error" || error.type === "validation_error")) {
setMessage(error.message || ''); // Use an empty string as a default value
} else {
setMessage("An unexpected error occurred.");
}
setIsLoading(false);
};
const paymentElementOptions: StripePaymentElementOptions = {
layout: "tabs",
};
return (
<form id="payment-form" onSubmit={handleSubmit}>
<LinkAuthenticationElement
id="link-authentication-element"
onChange={(e: StripeLinkAuthenticationElementChangeEvent) => setEmail(e.value!.toString())}
/>
<PaymentElement id="payment-element" options={paymentElementOptions} />
<button disabled={isLoading || !stripe || !elements} id="submit">
<span id="button-text">
{isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
</span>
</button>
{/* Show any error or success messages */}
{message && <div id="payment-message">{message}</div>}
</form>
);
}
/api/create- payment-intent/route.ts:
import { NextApiRequest, NextApiResponse } from "next";
import Stripe from "stripe";
// This is your test secret API key.
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2023-08-16", // Specify your preferred API version
});
const calculateOrderAmount = (items: any[]): number => {
// Replace this function with a calculation of the order's amount
// Calculate the order total on the server to prevent
// people from directly manipulating the amount on the client
return 1400;
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { items } = req.body;
// Create a PaymentIntent with the order amount and currency
const paymentIntent = await stripe.paymentIntents.create({
amount: calculateOrderAmount(items),
currency: "usd",
// In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default.
automatic_payment_methods: {
enabled: true,
},
});
res.send({
clientSecret: paymentIntent.client_secret,
});
} catch (error) {
console.error("Error creating PaymentIntent:", error);
res.status(500).json({ error: "An error occurred while processing your request." });
}
}
非常感谢您为解决此问题提供的任何帮助。 UI 元素似乎没有按预期呈现,并且我不确定如何正确配置组件内的选项对象。预先感谢您的帮助!
您可以使用 serverActions 将处理程序替换为函数
由于状态和 onChange 处理程序的原因,您可能需要在结帐表单中使用“使用客户端”指令。
您还可以使用'@stripe/stripe-js'包中的StripeElements,与stripe React包相比,它提供了更灵活的API
const stripeElements = useRef<StripeElements>(null)
const FormVisible = async () => {
const stripe = await getStripe();
const response = await fetch('/api/stripe/create-payment-intent', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(NotImportantBodyToQuestionContext),
});
//get client secret, it stored in payment intent
const {clientSecret} = await response.json();
const appearance = {
theme: 'stripe',
};
stripeElements.current = stripe!.elements({appearance, clientSecret});
const paymentElementOptions = {
layout: 'tabs',
};
// @ts-ignore
const paymentElement = stripeElements.current.create('payment', paymentElementOptions);
paymentElement.mount('#payment-element'); //or any other id
setloader(false)
}
希望对你有帮助,有问题欢迎提问