发布于 2026-01-06 1 阅读
0

使用 Next.js 实现 Stripe 结账 - 完整指南

使用 Next.js 实现 Stripe 结账 - 完整指南

你好世界

我的名字是阿希克·查帕盖恩。

  • 全栈 Web 开发人员
  • 内容创作者
  • 大学生
  • 尼泊尔人

本文将带您了解在Next.js中使用Stripe Checkout的整个过程。

指数

介绍

让我们简要了解一下StripeStripe Checkout

Stripe Stripe是一个面向企业的在线支付处理和信用卡处理平台。

Stripe Checkout Stripe Checkout是一个预先构建的、托管的支付页面,针对转化率进行了优化。

演示

在线预览: https://stripe-checkout-next-js-demo.vercel.app/

Github: https://github.com/projectashik/stripe-checkout-next-js-demo

让我们一起建造

安装

正如标题所述,我们将使用Next.js来构建这个程序。

要使Next.js正常工作,我们需要安装Node.jsnpm拥有它。

首先,如果您还没有安装Node.js ,请先安装它。
您可以按照以下指南安装Node.js。

可选:
如果您想使用yarn,请使用以下命令安装npm



npm install --global yarn


Enter fullscreen mode Exit fullscreen mode

现在,创建一个新的 Next.js 应用。



npx create-next-app stripe-checkout

yarn create next-app stripe-checkout



Enter fullscreen mode Exit fullscreen mode

我使用了Tailwind CSS来设置组件样式。所以,我们也来安装Tailwind CSS吧。



yarn add --dev tailwindcss@latest postcss@latest autoprefixer@latest

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest


Enter fullscreen mode Exit fullscreen mode

然后运行以下命令创建 Tailwind CSS 配置文件和 PostCSS 配置文件。



npx tailwindcss init -p


Enter fullscreen mode Exit fullscreen mode

现在用你喜欢的文本编辑器或 IDE(集成开发环境)打开项目。

对我来说,是Visual Studio Code



# Run the following command to open the project in VS Code.
code .


Enter fullscreen mode Exit fullscreen mode

现在,打开tailwind.config.js并更新purge选项并添加mode: 'jit'



// tailwind.config.js

module.exports = {
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  mode: 'jit',
  ...
};


Enter fullscreen mode Exit fullscreen mode

然后,打开styles/globals.css并删除所有内容,并添加以下内容。



@tailwind base;
@tailwind utilities;
@tailwind components;



Enter fullscreen mode Exit fullscreen mode

*简便方法:*
如果您想使用简便方法,
只需运行以下命令即可。



npx create-next-app -e with-tailwindcss stripe-checkout
yarn create next-app -e with-tailwindcss stripe-checkout


Enter fullscreen mode Exit fullscreen mode

现在,启动开发服务器并开始编写代码。



yarn dev
npm run dev


Enter fullscreen mode Exit fullscreen mode

打扫

从 . 中移除mainfooter标签pages/index.js

如果你想编辑标签中的` titleand` ,那么你可以这样做。description<Head>



// pages/index.js
import Head from 'next/head';
import Image from 'next/image';
import styles from '../styles/Home.module.css';

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Stripe Checkout with Next.js</title>
        <meta name='description' content='Complete Step By Step Tutorial for integrating Stripe Checkout with Next.js' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

.container并移除除in之外的所有样式styles/Home.module.css



/* styles/Home.module.css */
.container {
  min-height: 100vh;
  padding: 0 0.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100vh;
}


Enter fullscreen mode Exit fullscreen mode

制作用户界面

首先,我们将创建一个索引页面,其中包含一个用户可以购买的商品。

让我们给对象添加物品的详细信息。



// pages/index.js

const [item, setItem] = useState({
    name: 'Apple AirPods',
    description: 'Latest Apple AirPods.',
    image:
      'https://images.unsplash.com/photo-1572569511254-d8f925fe2cbb?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1400&q=80',
    quantity: 0,
    price: 999,
  });


Enter fullscreen mode Exit fullscreen mode

别忘了导入useState钩子。



import { useState } from 'react';


Enter fullscreen mode Exit fullscreen mode

由于我们使用 Unsplash 作为图片来源,因此我们需要images.unsplash.comimages配置文件的相应部分进行配置next.config.js



module.exports = {
  reactStrictMode: true,
  images: {
    domains: ['images.unsplash.com'],
  },
};


Enter fullscreen mode Exit fullscreen mode

现在,让我们在用户界面中显示以下详细信息,并添加按钮来增加和减少用户想要购买的数量。

在,pages/index.js



...
<main>
  <div className='shadow-lg border rounded p-2 '>
    <Image src={item.image} width={300} height={150} alt={item.name} />
    <h2 className='text-2xl'>$ {item.price}</h2>
    <h3 className='text-xl'>{item.name}</h3>
    <p className='text-gray-500'>{item.description}</p>
    <p className='text-sm text-gray-600 mt-1'>Quantity:</p>
    <div className='border rounded'>
      <button
        className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
      >
        -
      </button>
      <input
        type='number'
        className='p-2'
        defaultValue={item.quantity}
      />
      <button
        className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
      >
        +
      </button>
    </div>
    <p>Total: ${item.quantity * item.price}</p>
    <button
      disabled={item.quantity === 0}
      className='bg-blue-500 hover:bg-blue-600 text-white block w-full py-2 rounded mt-2 disabled:cursor-not-allowed disabled:bg-blue-100'
    >
      Buy
    </button>
  </div>
</main>
...


Enter fullscreen mode Exit fullscreen mode

现在你的页面应该看起来像这样。

用户界面演示

现在,我们来制作增加和减少数量的按钮。

现在,我们将创建按钮点击时增加和减少物品数量的函数onQuantityPlusonQuantityMinus



const changeQuantity = (value: number) => {
  // Don't allow the quantity less than 0, if the quantity is greater than value entered by user then the user entered quantity is used, else 0
  setItem({ ...item, quantity: Math.max(0, value) });
};

const onQuantityPlus = () => {
  changeQuantity(item.quantity + 1);
};

const onQuantityMinus = () => {
  changeQuantity(item.quantity - 1);
};


Enter fullscreen mode Exit fullscreen mode

现在,将此函数添加到相应按钮的 onClick 事件中。



...
<button
  onClick={onQuantityMinus}
  className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
>
  -
</button>
...
<button
  onClick={onQuantityPlus}
  className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
>
  +
</button>
...


Enter fullscreen mode Exit fullscreen mode

现在,让我们onChange在输入字段上添加事件来更新商品数量的值,也别忘了将其更改defaultValuevalue



...
<input
  type='number'
  className='p-2'
  onChange={onInputChange}
  value={item.quantity}
/>
...


Enter fullscreen mode Exit fullscreen mode

现在,让我们创建onInputChange函数。



...
//
const onInputChange = (e) => {
    changeQuantity(parseInt(e.target.value));
};
...


Enter fullscreen mode Exit fullscreen mode

工作项加减法演示

创建后端

首先,我们先来安装stripe库。



yarn add stripe
npm install stripe


Enter fullscreen mode Exit fullscreen mode

现在,让我们添加所需内容environment variables。在根目录中
创建一个新文件,.env.local并添加以下数据。



NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=YOUR_STRIPE_PUBLISHABLE_KEY
STRIPE_SECRET_KEY=YOUR_STRIPE_SECRET_KEY


Enter fullscreen mode Exit fullscreen mode

您可以从以下位置获取这些凭据Dashboard -> Developers -> API Keys

获取 Stripe 凭证

现在,我们需要构建一个 API 来获取将用户重定向到结账页面所需的会话 ID。

在 .目录下创建一个新文件api/create-stripe-session.js,并添加以下内容。



const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

export default async (req, res) => {
  const { item } = req.body;
};


Enter fullscreen mode Exit fullscreen mode

在这里,我们将通过前端调用的 POST 方法获取商品详情。

创建 Stripe 所需的商品形状。

Stripe 需要接收特定类型的对象,这就是该对象。如果您愿意,请使用您的本地货币,而不是“usd”。



const transformedItem = {
    price_data: {
      currency: 'usd',
      product_data: {
        images: [item.image],
        name: item.name,
      },
      unit_amount: item.price * 100,
    },
    description: item.description,
    quantity: item.quantity,
  };


Enter fullscreen mode Exit fullscreen mode

*在后端创建 Stripe 会话:*

您需要创建一个 Stripe 会话对象,并在其中定义一些数据。



const session = await stripe.checkout.sessions.create({
  payment_method_types: ['card'],
  line_items: [transformedItem],
  mode: 'payment',
  success_url: redirectURL + '?status=success',
  cancel_url: redirectURL + '?status=cancel',
  metadata: {
    images: item.image,
  },
});

res.json({ id: session.id });


Enter fullscreen mode Exit fullscreen mode
  • payment_method_type在这里,我们添加了支付产品价格的支付方式。点击此处了解更多支付方式。

  • success_urlsuccess_url中,您可以定义用户在付款成功后将要访问的页面。

  • cancel_urlcancel_url中,您可以定义用户点击返回按钮后将跳转到的页面。它可以是取消页面,也可以是结账页面。

  • metadata:在元数据中,我们将添加产品图片,如果您愿意,也可以添加其他选项。

如需了解其他选项,请点击此处。

现在,最终create-stripe-session.js文件应该看起来像这样。



const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

async function CreateStripeSession(req, res) {
  const { item } = req.body;

  const redirectURL =
    process.env.NODE_ENV === 'development'
      ? 'http://localhost:3000'
      : 'https://stripe-checkout-next-js-demo.vercel.app';

  const transformedItem = {
    price_data: {
      currency: 'usd',
      product_data: {
        images: [item.image],
        name: item.name,
      },
      unit_amount: item.price * 100,
    },
    description: item.description,
    quantity: item.quantity,
  };

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    line_items: [transformedItem],
    mode: 'payment',
    success_url: redirectURL + '?status=success',
    cancel_url: redirectURL + '?status=cancel',
    metadata: {
      images: item.image,
    },
  });

  res.json({ id: session.id });
}

export default CreateStripeSession;


Enter fullscreen mode Exit fullscreen mode

现在,我们的后端已经准备就绪,现在我们需要向 API 发送 POST 请求来获取会话。

正在重定向到 Stripe 结账页面

为了重定向到 Stripe 结账页面,我们需要安装以下库。



yarn add @stripe/stripe-js axios
npm install @stripe/stripe-js axios


Enter fullscreen mode Exit fullscreen mode

首先,我们先创建一个stripePromise变量。



const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
const stripePromise = loadStripe(publishableKey);


Enter fullscreen mode Exit fullscreen mode

现在,我们将创建createCheckoutSession一个函数来获取结账的 Stripe 会话。



const createCheckOutSession = async () => {
  const stripe = await stripePromise;
  const checkoutSession = await axios.post('/api/create-stripe-session', {
    item: item,
  });
  const result = await stripe.redirectToCheckout({
    sessionId: checkoutSession.data.id,
  });
  if (result.error) {
    alert(result.error.message);
  }
};


Enter fullscreen mode Exit fullscreen mode

别忘了导入loadStripeaxios



import { loadStripe } from '@stripe/stripe-js';
import axios from 'axios';


Enter fullscreen mode Exit fullscreen mode

现在,我们需要在用户点击Buy按钮时调用这个函数。
onClick={createCheckoutSession}



<button
  disabled={item.quantity === 0}
  onClick={createCheckOutSession}
  className='bg-blue-500 hover:bg-blue-600 text-white block w-full py-2 rounded mt-2 disabled:cursor-not-allowed disabled:bg-blue-100'
>
  Buy
</button>


Enter fullscreen mode Exit fullscreen mode

现在,我们来测试一下结账功能。

由于我们已经更新next.config.js并添加了该.env.local文件,因此请重启开发服务器。



yarn dev
npm run dev


Enter fullscreen mode Exit fullscreen mode

现在,结果应该如下所示。

信用卡支付演示

显示成功和取消消息

如果你们还记得的话,我们之前在and中使用过?status=查询参数success_urlcancel_url



// pages/api/create-stripe-session.js
...
const session = await stripe.checkout.sessions.create({
    ...
    success_url: redirectURL + '?status=success',
    cancel_url: redirectURL + '?status=cancel',
    ...
  });

...


Enter fullscreen mode Exit fullscreen mode

因此,通过这个查询参数,我们就能知道付款是成功还是取消,并显示相应的消息。

首先,我们来获取状态。



// pages/index.js

import { useRouter } from 'next/router';

...
const router = useRouter();
const { status } = router.query;


Enter fullscreen mode Exit fullscreen mode

现在,在主标签下方添加以下内容,以显示消息。



// pages/index.js

{status && status === 'success' && (
  <div className='bg-green-100 text-green-700 p-2 rounded border mb-2 border-green-700'>
    Payment Successful
  </div>
)}
{status && status === 'cancel' && (
  <div className='bg-red-100 text-red-700 p-2 rounded border mb-2 border-red-700'>
    Payment Unsuccessful
  </div>
)}


Enter fullscreen mode Exit fullscreen mode

现在,结果应该是这样的。

警报消息演示

我们的应用需要一些时间来分配 Stripe 会话。因此,在此期间,我们将显示其他内容,Processing...而不是Buy像演示中那样显示在按钮内部。

为此,
创建一个名为 loading 的新状态,并赋予其默认值false



const [loading, setLoading] = useState(false);


Enter fullscreen mode Exit fullscreen mode

然后,在创建 Stripe 会话时,将loading值更新为true。再次在创建 Stripe 会话时将loading值更新为 。false



const createCheckOutSession = async () => {
  setLoading(true);
  ...
  setLoading(false);
};


Enter fullscreen mode Exit fullscreen mode

现在,更新Buy按钮。



<button
  disabled={item.quantity === 0 || loading}
  onClick={createCheckOutSession}
  className='bg-blue-500 hover:bg-blue-600 text-white block w-full py-2 rounded mt-2 disabled:cursor-not-allowed disabled:bg-blue-100'
>
  {loading ? 'Processing...' : 'Buy'}
</button>


Enter fullscreen mode Exit fullscreen mode

这里,我们在加载时禁用按钮,并Processing...在加载时显示。

现在,结果应该是这样的:

按钮处理演示

全部完成。

文件

最终,您的pages/index.js文件应该如下所示:



import Head from 'next/head';
import Image from 'next/image';
import styles from '../styles/Home.module.css';
import { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import axios from 'axios';
import { useRouter } from 'next/router';

export default function Home() {
  const router = useRouter();
  const { status } = router.query;

  const [loading, setLoading] = useState(false);

  const [item, setItem] = useState({
    name: 'Apple AirPods',
    description: 'Latest Apple AirPods.',
    image:
      'https://images.unsplash.com/photo-1572569511254-d8f925fe2cbb?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1400&q=80',
    quantity: 0,
    price: 999,
  });

  const changeQuantity = (value) => {
    // Don't allow the quantity less than 0, if the quantity is greater than value entered by user then the user entered quantity is used, else 0
    setItem({ ...item, quantity: Math.max(0, value) });
  };

  const onInputChange = (e) => {
    changeQuantity(parseInt(e.target.value));
  };

  const onQuantityPlus = () => {
    changeQuantity(item.quantity + 1);
  };

  const onQuantityMinus = () => {
    changeQuantity(item.quantity - 1);
  };

  const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
  const stripePromise = loadStripe(publishableKey);
  const createCheckOutSession = async () => {
    setLoading(true);
    const stripe = await stripePromise;
    const checkoutSession = await axios.post('/api/create-stripe-session', {
      item: item,
    });
    const result = await stripe.redirectToCheckout({
      sessionId: checkoutSession.data.id,
    });
    if (result.error) {
      alert(result.error.message);
    }
    setLoading(false);
  };
  return (
    <div className={styles.container}>
      <Head>
        <title>Stripe Checkout with Next.js</title>
        <meta
          name='description'
          content='Complete Step By Step Tutorial for integrating Stripe Checkout with Next.js'
        />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <main>
        {status && status === 'success' && (
          <div className='bg-green-100 text-green-700 p-2 rounded border mb-2 border-green-700'>
            Payment Successful
          </div>
        )}
        {status && status === 'cancel' && (
          <div className='bg-red-100 text-red-700 p-2 rounded border mb-2 border-red-700'>
            Payment Unsuccessful
          </div>
        )}
        <div className='shadow-lg border rounded p-2 '>
          <Image src={item.image} width={300} height={150} alt={item.name} />
          <h2 className='text-2xl'>$ {item.price}</h2>
          <h3 className='text-xl'>{item.name}</h3>
          <p className='text-gray-500'>{item.description}</p>
          <p className='text-sm text-gray-600 mt-1'>Quantity:</p>
          <div className='border rounded'>
            <button
              onClick={onQuantityMinus}
              className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
            >
              -
            </button>
            <input
              type='number'
              className='p-2'
              onChange={onInputChange}
              value={item.quantity}
            />
            <button
              onClick={onQuantityPlus}
              className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
            >
              +
            </button>
          </div>
          <p>Total: ${item.quantity * item.price}</p>
          <button
            disabled={item.quantity === 0 || loading}
            onClick={createCheckOutSession}
            className='bg-blue-500 hover:bg-blue-600 text-white block w-full py-2 rounded mt-2 disabled:cursor-not-allowed disabled:bg-blue-100'
          >
            {loading ? 'Processing...' : 'Buy'}
          </button>
        </div>
      </main>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

应该pages/api/create-stripe-sessoin.js看起来像这样。



const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

async function CreateStripeSession(req, res) {
const { item } = req.body;

const redirectURL =
process.env.NODE_ENV === 'development'
? 'http://localhost:3000'
: 'https://stripe-checkout-next-js-demo.vercel.app';

const transformedItem = {
price_data: {
currency: 'usd',
product_data: {
images: [item.image],
name: item.name,
},
unit_amount: item.price * 100,
},
description: item.description,
quantity: item.quantity,
};

const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [transformedItem],
mode: 'payment',
success_url: redirectURL + '?status=success',
cancel_url: redirectURL + '?status=cancel',
metadata: {
images: item.image,
},
});

res.json({ id: session.id });
}

export default CreateStripeSession;

Enter fullscreen mode Exit fullscreen mode




结论

好了,文章到此结束。希望您喜欢这篇文章。请考虑给我一些反馈意见。

重要链接

鸣谢

联系我:

文章来源:https://dev.to/byteslash/stripe-checkout-with-next-js-the-complete-guide-3i1