这里要准备好几个东西:
一个支付成功过后的回调
还有一个下单的接口
一旦进入这个下单界面,就要去调下单的接口的,用 post,
这个 接口你自己写,可以写在后端中,也可以放到 nextjs 的 api 中。
首先说的是这个下单接口
可以这样写:
javascript
import { NextRequest, NextResponse } from "next/server";
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
export async function POST(request: NextRequest) {
try {
const { amount } = await request.json();
const paymentIntent = await stripe.paymentIntents.create({
amount: amount,
currency: "usd",
automatic_payment_methods: { enabled: true },
});
return NextResponse.json({ clientSecret: paymentIntent.client_secret });
} catch (error) {
console.error("Internal Error:", error);
// Handle other errors (e.g., network issues, parsing errors)
return NextResponse.json(
{ error: `Internal Server Error: ${error}` },
{ status: 500 }
);
}
}
这个东西一般是放后端,因为有个 secrets key,原则 nextjs 的 api 也算是后端。
要传入的参数呢,只有一个是金额,一个是 secret key ,
返回的信息是给前端用的,一个 client secret key.
可以理解为一个通用凭证。
前端怎么利用这个 key 。
javascript
const { error } = await stripe.confirmPayment({
elements,
clientSecret,
confirmParams: {
return_url: `http://www.localhost:3000/payment-success?amount=${amount}`,
},
});
这个 elements 是 stripe 自带的,要利用到里面的一些组件,比如你开了 wechat 就要自动显示。
而不是自己写页面。
clientSecret 这个 client key 就是从后端返回的。
大约就是这样简单,最后这个 return url 中的。
我不太清楚,这样的话,还需要 webhook 吗,还需要去验证。
整个表单的代码我放一下:
javascript
"use client";
import React, { useEffect, useState } from "react";
import {
useStripe,
useElements,
PaymentElement,
} from "@stripe/react-stripe-js";
import convertToSubcurrency from "@/lib/convertToSubcurrency";
const CheckoutPage = ({ amount }: { amount: number }) => {
const stripe = useStripe();
const elements = useElements();
const [errorMessage, setErrorMessage] = useState<string>();
const [clientSecret, setClientSecret] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
fetch("/api/create-payment-intent", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ amount: convertToSubcurrency(amount) }),
})
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, [amount]);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setLoading(true);
if (!stripe || !elements) {
return;
}
const { error: submitError } = await elements.submit();
if (submitError) {
setErrorMessage(submitError.message);
setLoading(false);
return;
}
const { error } = await stripe.confirmPayment({
elements,
clientSecret,
confirmParams: {
return_url: `http://www.localhost:3000/payment-success?amount=${amount}`,
},
});
if (error) {
// This point is only reached if there's an immediate error when
// confirming the payment. Show the error to your customer (for example, payment details incomplete)
setErrorMessage(error.message);
} else {
// The payment UI automatically closes with a success animation.
// Your customer is redirected to your `return_url`.
}
setLoading(false);
};
if (!clientSecret || !stripe || !elements) {
return (
<div className="flex items-center justify-center">
<div
className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-e-transparent align-[-0.125em] text-surface motion-reduce:animate-[spin_1.5s_linear_infinite] dark:text-white"
role="status"
>
<span className="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]">
Loading...
</span>
</div>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="bg-white p-2 rounded-md">
{clientSecret && <PaymentElement />}
{errorMessage && <div>{errorMessage}</div>}
<button
disabled={!stripe || loading}
className="text-white w-full p-5 bg-black mt-2 rounded-md font-bold disabled:opacity-50 disabled:animate-pulse"
>
{!loading ? `Pay $${amount}` : "Processing..."}
</button>
</form>
);
};
export default CheckoutPage;