我犯了这个错误
IntegrationError:我们无法从指定元素检索数据。请确保您尝试使用的元素仍然已安装。
条纹交易显示付款但不完整,当我将鼠标悬停在工具提示上时,它显示
The customer has not entered their payment method.
参见图片。但我使用的是他们的测试卡号码,所以我不确定他们的意思
这里是结帐组件
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { loadStripe } from '@stripe/stripe-js';
import { Elements } from '@stripe/react-stripe-js';
import PageTitleHero from '../components/PageTitleHero';
import { useCart } from '../context/cartContext';
import { useAuth } from '../context/authContext';
import PropTypes from 'prop-types';
import Loading from '../components/Loading';
import UserInformation from '../components/checkout/UserInformation';
import StripePaymentForm from '../components/checkout/StripePaymentForm';
const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLIC_KEY);
const Checkout = () => {
const { cart, setCart } = useCart();
const { currentUser } = useAuth();
const [useShippingAsBilling, setUseShippingAsBilling] = useState(true);
const [formData, setFormData] = useState({});
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
useEffect(() => {
const savedCart = localStorage.getItem('cart');
if (savedCart) {
setCart(JSON.parse(savedCart));
}
if (currentUser) {
setFormData({
firstName: currentUser.firstName || '',
lastName: currentUser.lastName || '',
email: currentUser.email || '',
address: currentUser.address || '',
city: currentUser.city || '',
state: currentUser.state || '',
zip: currentUser.zip || '',
'shipping-address': currentUser.shippingAddress || '',
'shipping-city': currentUser.shippingCity || '',
'shipping-state': currentUser.shippingState || '',
'shipping-postal-code': currentUser.shippingPostalCode || '',
'billing-address': currentUser.billingAddress || currentUser.shippingAddress || '',
'billing-city': currentUser.billingCity || currentUser.shippingCity || '',
'billing-state': currentUser.billingState || currentUser.shippingState || '',
'billing-postal-code': currentUser.billingPostalCode || currentUser.shippingPostalCode || '',
});
}
}, [setCart, currentUser]);
const handleUseShippingAsBilling = (event) => {
setUseShippingAsBilling(event.target.checked);
setFormData((prevData) => ({
...prevData,
'billing-address': event.target.checked ? prevData['shipping-address'] : '',
'billing-city': event.target.checked ? prevData['shipping-city'] : '',
'billing-state': event.target.checked ? prevData['shipping-state'] : '',
'billing-postal-code': event.target.checked ? prevData['shipping-postal-code'] : '',
}));
};
const handleFormChange = (event) => {
const { name, value } = event.target;
setFormData((prevData) => ({ ...prevData, [name]: value }));
};
const total = cart.reduce((sum, product) => sum + product.price * product.quantity, 0) + 30;
if (loading) {
return <Loading />;
}
if (cart.length === 0) {
return (
<div className="flex items-center justify-center min-h-screen">
<p className="text-lg font-semibold">Your cart is empty. Please add items to your cart before proceeding to checkout.</p>
</div>
);
}
return (
<>
<PageTitleHero text="Checkout" />
<div className="relative mx-auto grid max-w-7xl grid-cols-1 gap-x-16 lg:grid-cols-2 lg:px-8 lg:pt-16">
<OrderSummary total={total} cart={cart} />
<PaymentAndShipping
useShippingAsBilling={useShippingAsBilling}
handleUseShippingAsBilling={handleUseShippingAsBilling}
handleFormChange={handleFormChange}
formData={formData}
total={total}
setLoading={setLoading}
navigate={navigate}
cart={cart}
/>
</div>
</>
);
};
const OrderSummary = ({ total, cart }) => (
<section aria-labelledby="summary-heading" className="py-12 md:px-10 lg:col-start-2 lg:row-start-1 lg:mx-auto lg:w-full lg:max-w-lg lg:bg-transparent lg:px-0 lg:pb-24 lg:pt-0">
<div className="rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-black shadow-sm p-12">
<h2 id="summary-heading" className="sr-only">Order summary</h2>
<dl>
<dt className="text-sm font-bold text-indigo-500">Amount due</dt>
<dd className="mt-1 text-3xl font-bold tracking-tight text-black dark:text-white">${total.toFixed(2)}</dd>
</dl>
<ul role="list" className="divide-y divide-gray-200 dark:divide-gray-600 text-sm font-medium">
{cart.map((product, index) => (
<li key={index} className="flex items-start space-x-4 py-6">
<img alt={product.name} src={product.imageSrc} className="h-20 w-20 flex-none rounded-md object-cover object-center bg-slate-200 dark:bg-slate-800" />
<div className="flex-auto space-y-1">
<h3 className="text-slate-800 dark:text-slate-300">{product.name}</h3>
<p className="text-slate-500 dark:text-slate-400">{product.quantity} x ${product.price}</p>
</div>
<p className="flex-none text-base font-medium text-slate-800 dark:text-slate-300">${product.price * product.quantity}</p>
</li>
))}
</ul>
<dl className="space-y-6 border-t border-gray-300 dark:border-gray-600 pt-6 text-sm font-medium">
<div className="flex items-center justify-between">
<dt>Subtotal</dt>
<dd>${cart.reduce((sum, product) => sum + product.price * product.quantity, 0).toFixed(2)}</dd>
</div>
<div className="flex items-center justify-between">
<dt>Shipping (Flat Rate)</dt>
<dd>$30.00</dd>
</div>
<div className="flex items-center justify-between border-t border-gray-300 dark:border-gray-600 pt-6 font-bold text-black dark:text-white">
<dt className="text-base">Total</dt>
<dd className="text-base">${total.toFixed(2)}</dd>
</div>
</dl>
</div>
</section>
);
const PaymentAndShipping = ({
useShippingAsBilling,
handleUseShippingAsBilling,
handleFormChange,
formData,
total,
setLoading,
navigate,
}) => (
<section aria-labelledby="payment-and-shipping-heading" className="py-16 lg:col-start-1 lg:row-start-1 lg:mx-auto lg:w-full lg:max-w-lg lg:pb-24 lg:pt-0">
<h2 id="payment-and-shipping-heading" className="sr-only">Payment and shipping details</h2>
<div>
<div className="mx-auto max-w-2xl px-4 lg:max-w-none lg:px-0">
<UserInformation
useShippingAsBilling={useShippingAsBilling}
setUseShippingAsBilling={handleUseShippingAsBilling}
handleFormChange={handleFormChange}
formData={formData}
/>
<Elements stripe={stripePromise}>
<StripePaymentForm
total={parseFloat(total)}
formData={formData}
setLoading={setLoading}
navigate={navigate}
/>
</Elements>
</div>
</div>
</section>
);
OrderSummary.propTypes = {
total: PropTypes.number.isRequired,
cart: PropTypes.array.isRequired,
};
PaymentAndShipping.propTypes = {
useShippingAsBilling: PropTypes.bool.isRequired,
handleUseShippingAsBilling: PropTypes.func.isRequired,
handleFormChange: PropTypes.func.isRequired,
formData: PropTypes.object.isRequired,
total: PropTypes.number.isRequired,
setLoading: PropTypes.func.isRequired,
navigate: PropTypes.func.isRequired,
cart: PropTypes.array.isRequired,
};
export default Checkout;
还有 StripePaymentForm.jsx
import { useStripe, useElements, CardElement } from '@stripe/react-stripe-js';
import axios from 'axios';
import PropTypes from 'prop-types';
import PrimaryButton from '../PrimaryButton';
const StripePaymentForm = ({ total, formData, setLoading, navigate }) => {
const stripe = useStripe();
const elements = useElements();
const secret_key = import.meta.env.VITE_STRIPE_SECRET_KEY;
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
if (!stripe || !elements) {
setLoading(false);
return;
}
const cardElement = elements.getElement(CardElement);
try {
// Create a PaymentIntent on the client side
const { data: paymentIntent } = await axios.post(
'https://api.stripe.com/v1/payment_intents',
new URLSearchParams({
amount: Math.round(total * 100), // Ensure the amount is correctly rounded and converted to cents
currency: 'usd',
'payment_method_types[]': 'card',
}),
{
headers: {
'Authorization': `Bearer ${secret_key}`,
'Content-Type': 'application/x-www-form-urlencoded',
}
}
);
const clientSecret = paymentIntent.client_secret;
const paymentResult = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: `${formData.firstName} ${formData.lastName}`,
email: formData.email,
address: {
line1: formData.address,
city: formData.city,
state: formData.state,
postal_code: formData.zip,
},
},
},
});
if (paymentResult.error) {
console.error('Payment failed:', paymentResult.error);
setLoading(false);
return;
}
if (paymentResult.paymentIntent.status === 'succeeded') {
navigate('/confirmation');
}
} catch (error) {
console.error('Error processing payment:', error);
} finally {
setLoading(false);
}
};
return (
<div className='grid grid-cols-1 gap-4 items-center mt-4'>
<div className='col-span-1'>
<h3 className='text-xl font-bold text-indigo-500'>Payment Information</h3>
<CardElement className="mt-1 p-2 border border-gray-300 bg-white dark:bg-black rounded" />
</div>
<div className='col-span-1'>
<PrimaryButton
text={`Pay $${total.toFixed(2)}`}
onClick={handleSubmit}
disabled={!stripe}
className={`w-full ${!stripe ? 'opacity-50 cursor-not-allowed' : ''}`}
/>
</div>
</div>
);
};
StripePaymentForm.propTypes = {
total: PropTypes.number.isRequired,
formData: PropTypes.object.isRequired,
setLoading: PropTypes.func.isRequired,
navigate: PropTypes.func.isRequired,
};
export default StripePaymentForm;
非常感谢任何帮助。
Dashboard 上的 PaymentIntent 为“Incomplete”,表示尚未确认。您的代码尚未到达提交功能中的
await stripe.confirmCardPayment
。
您可以在
handleSubmit
函数中添加更多调试功能。但我也看到您正在从js创建PaymentIntent,您应该永远不要这样做,因为这会将您的密钥泄露给前端。密钥应该隐藏在后端,您应该只调用 AJAX 到后端来检索密钥。
更重要的是,这是 CardElement(您可以在此处下载工作示例),但它是旧版本,实际上您应该改用 PaymentElement。