type
Post
status
Published
date
Feb 8, 2026
slug
nextjs-shopping-platform-008
summary
tags
Next.js
category
编程学习
icon
password
😀

1. Section Intro

Now we need to implement our first payment solution, and that will be PayPal. 现在我们需要实现我们的第一个支付解决方案,那就是 PayPal。
First we need to setup an app in the PayPal developer site and create two sandbox accounts. One for personal and one for business. The busineess is to recieve money and and the personal is to buy and test. We're working with the sandbox, so of course there is not real money or banks involved. 首先,我们需要在 PayPal 开发者网站上设置一个应用并创建两个沙箱账户。一个是个人账户,一个是企业账户。企业账户用于收款,个人账户用于购买和测试。我们使用的是沙箱环境,所以当然不涉及真实的资金或银行。
We're going to setup a file with our functions that interact with the PayPal API. There will be 3 functions here. One to generate an access token, one to create an order and I mean an order on PayPal's side not in our database yet. And then one to capture payment. 我们将设置一个文件,包含与 PayPal API 交互的函数。这里将有 3 个函数。一个用于生成访问令牌,一个用于创建订单(指的是 PayPal 端的订单,还不在我们的数据库中),还有一个用于捕获支付。
This is where I want to do a little unit testing. We have no other way to run and test these functions, so we'll write a test for each one to make sure they work before we use them in our app. 在这里,我想做一些单元测试。我们没有其他方法来运行和测试这些函数,所以我们会为每个函数编写一个测试,在我们在应用中使用它们之前确保它们能正常工作。
Once we know they work, we'll start to create the server action to call these functions in our app. 一旦我们确认它们能正常工作,我们就会开始创建服务器操作,在我们的应用中调用这些函数。
Then we can finally add the PayPal buttons on the order page. We'll be using a package called React-PayPal-JS for that. 然后我们终于可以在订单页面上添加 PayPal 按钮了。我们将使用一个名为 React-PayPal-JS 的包来实现。

2. PayPal Sandbox Setup

PayPal Developer Setup PayPal 开发者设置
Now we are going to start to implement PayPal. In order to do that, you need to go to https://developer.paypal.com/ and log in. 现在我们要开始实现 PayPal。为此,你需要访问 https://developer.paypal.com/ 并登录。
PayPal has a sandbox mode where you can test your application before you go live. The way it works is you create a sandbox account for the "buyer" and a sandbox account for the "seller". You can then use these accounts to test your application. Everything works as if it were in live mode, but you are not actually using real money. PayPal 有一个沙箱模式,你可以在上线之前测试你的应用。其工作方式是你创建一个"买家"沙箱账户和一个"卖家"沙箱账户。然后你可以使用这些账户来测试你的应用。一切都像在生产模式下一样工作,但你实际上并没有使用真实的资金。

Create Sandbox Accounts 创建沙箱账户

Once you log into developer.paypal.com, go to "Testing Tools" and then "Sandbox Accounts". 一旦你登录了 developer.paypal.com,转到"Testing Tools"然后是"Sandbox Accounts"。
You may have a default business and a default personal account. If you do, you don't need to create new ones. 你可能已经有一个默认的企业账户和一个默认的个人账户。如果有的话,你不需要创建新的。
If you don't have any test accounts, click "Create Account" and select "Business". This will be the account that the store will use to receive money. 如果你没有任何测试账户,点击"Create Account"并选择"Business"。这将是商店用于收款的账户。
Now click "Create Account" again and select "Personal". This will be the account that the store will use to pay for things. 现在再次点击"Create Account"并选择"Personal"。这将是商店用于付款的账户。

Create App 创建应用

Under "Apps & Credentials", click on "Create App". Give it a name and keep Merchant selected. 在"Apps & Credentials"下,点击"Create App"。给它起个名字,并保持 Merchant 选中。
Select a test business account to use and click "Create App". 选择一个测试企业账户并点击"Create App"。
You will be taken to a page with your keys. 你将被带到一个包含你的密钥的页面。
Copy the Client ID and the Secret and put them in your .env file along with the api url: 复制 Client ID 和 Secret,并将它们与 API URL 一起放在你的 .env 文件中:
Don't use these keys, they are not real. 不要使用这些密钥,它们不是真实的。
Once you want to go live, you would change the url to https://api-m.paypal.com and change the client id and secret to the live ones. 一旦你想要上线,你需要将 URL 更改为 https://api-m.paypal.com,并将客户端 ID 和密钥更改为生产环境的。
In the next lesson, we will start to implement PayPal. 在下一课中,我们将开始实现 PayPal。

3. Generate PayPal Access Token 生成 PayPal 访问令牌

Now that we have our PayPal keys, we can start to implement PayPal payments. There are a few steps we need to take with the PayPal API. We need to: 现在我们已经有了 PayPal 密钥,可以开始实现 PayPal 支付了。我们需要通过 PayPal API 完成几个步骤:
  • Generate an access token: This is a token that serves as a secure identifier, allowing your app to interact with PayPal's services on behalf of a user or merchant. It grants your application the necessary permissions to perform specific actions, such as creating orders, processing payments, or issuing refunds.
  • 生成访问令牌:这是一个安全标识符,允许你的应用代表用户或商家与 PayPal 服务交互。它授予你的应用执行特定操作所需的权限,例如创建订单、处理支付或发起退款。
  • Create an order: Create an order and set the intent to "capture"
  • 创建订单:创建订单并将 intent 设置为 "capture"
  • Capture payment: To successfully capture payment for an order, the buyer must first approve the order or a valid payment_source must be provided in the request. So typically, once we pay, the status gets set to approve.
  • 捕获支付:要成功捕获订单的支付,买家必须首先批准订单,或者在请求中提供有效的 payment_source。因此通常情况下,一旦我们付款,状态就会被设置为已批准。
That's what we're going to do in a nutshell. We will also write some unit tests with Jest to test these functions out. 简而言之,这就是我们要做的事情。我们还会使用 Jest 编写一些单元测试来测试这些函数。
We are going to start by generating an access token. 我们将从生成访问令牌开始。

Create The PayPal File 创建 PayPal 文件

Let's create a new file at lib/paypal.ts. This will contain the PayPal API calls. 让我们在 lib/paypal.ts 创建一个新文件。这将包含 PayPal API 调用。
Let's start by creating a variable to hold the paypal api url.: 让我们首先创建一个变量来保存 PayPal API URL:
We will use this in all of our requests to the PayPal API. 我们将在所有对 PayPal API 的请求中使用这个变量。

paypal Object paypal 对象

We are going to have an object with a method to create and order and to capture the payment. For now, just create an empty object like this: 我们将创建一个对象,包含创建订单和捕获支付的方法。目前,只需像这样创建一个空对象:

Generate Access Token 生成访问令牌

Before we add the functions in the object, we need an access token. You can find the documentation about this here - https://developer.paypal.com/reference/get-an-access-token/. 在我们向对象中添加函数之前,我们需要一个访问令牌。你可以在这里找到相关文档 - https://developer.paypal.com/reference/get-an-access-token/
Let's create a function to get the access token. Add the following function under the paypal object: 让我们创建一个获取访问令牌的函数。在 paypal 对象下方添加以下函数:
We start by getting the client id and secret from the environment variables. We then create a base64 encoded string of the client id and secret. We then use that to make a request to the PayPal API to get an access token. If the response is ok, we return the access token. If not, we throw an error. 我们首先从环境变量中获取客户端 ID 和密钥。然后我们创建客户端 ID 和密钥的 base64 编码字符串。接着我们使用这个字符串向 PayPal API 发起请求以获取访问令牌。如果响应正常,我们返回访问令牌。否则,我们抛出错误。

Quick Refactor For Handle Response 快速重构:处理响应

Let's actually split this up a bit and create a separate function to handle the response. Because we will re-use this in another function. 让我们把这部分代码拆分一下,创建一个单独的函数来处理响应。因为我们会在另一个函数中重用它。
Replace this code: 替换这段代码:
With this: 用这段代码替换:
Now create a function undernerath the generateAccessToken function: 现在在 generateAccessToken 函数下方创建一个函数:
课程里的写法:
In the next lesson, we will write some simple unit tests to test this out. 在下一课中,我们将编写一些简单的单元测试来测试这些内容。

4. Testing Generate Access Token Function With Jest 使用 Jest 测试生成访问令牌函数

This is optional, but I figured it would be a nice addition to the course. We are going to use Jest to test our PayPal requests. Jest is a testing framework that allows you to write tests for your code. I just want to be able to test certain things such as generating the token for paypal before we write the actual code. 这是可选的,但我认为这是课程的一个很好的补充。我们将使用 Jest 来测试我们的 PayPal 请求。Jest 是一个测试框架,允许你为代码编写测试。我只是希望在编写实际代码之前,能够测试某些功能,例如为 PayPal 生成令牌。
Run the following command to install Jest and some dependencies: 运行以下命令来安装 Jest 和一些依赖项:
Run the following command to initialize Jest: 运行以下命令来初始化 Jest:
You will be asked some questions. Here is what I selected: 你会被问到一些问题。以下是我选择的答案:
  • Would you like to run Jest as your 'test' script in package.json? ... Yes
  • 你是否希望在 package.json 中将 Jest 作为 'test' 脚本运行?... 是的
  • Would you like to use Typescript for the configuration file? ... Yes
  • 你是否希望使用 Typescript 作为配置文件?... 是的
  • Choose the test environment that will be used for testing ... node
  • 选择将用于测试的测试环境 ... node
  • Do you want to add coverage reports? ... No
  • 你是否希望添加覆盖率报告?... 否
  • Which provider should be used to instrument code for coverage? ... v8
  • 应该使用哪个提供程序来检测代码覆盖率?... v8
  • Automatically clear mock calls and instances between every test? ... Yes
  • 在每次测试之间自动清除模拟调用和实例?... 是的
You now should have a jest.config.ts file in your project. 现在你的项目中应该有一个 jest.config.ts 文件。
Open the file and add the following option: 打开文件并添加以下选项:
Make sure you have the following in your package.json: 确保你的 package.json 中有以下内容:

dotenv Setup dotenv 设置

Since Jest runs in a separate environment, we need to set up dotenv to load our environment variables. 由于 Jest 在单独的环境中运行,我们需要设置 dotenv 来加载我们的环境变量。
Install it as a development dependency: 将其安装为开发依赖项:
Create a jest.setup.ts file in the root of your project and add the following: 在项目根目录创建一个 jest.setup.ts 文件并添加以下内容:
Now in the jest.config.ts file, add the following: 现在在 jest.config.ts 文件中,添加以下内容:

Writing The Test 编写测试

Where you put your tests is up to you. A common place is to create a test folder in the folder you are testing. Since we won't have a ton of tests, I'm just going to create a folder in the root of the project called tests. 测试放在哪里由你决定。常见的做法是在你正在测试的文件夹中创建一个测试文件夹。由于我们不会有太多测试,我将在项目根目录创建一个名为 tests 的文件夹。
Let's create a test to make sure we can generate an access token. Create a file at tests/paypal.test.ts file and add the following: 让我们创建一个测试来确保我们可以生成访问令牌。在 tests/paypal.test.ts 创建一个文件并添加以下内容:
You also need to export the function from the file. Open the paypal.ts file and add an export: 你还需要从文件中导出函数。打开 paypal.ts 文件并添加导出:
Now run the following command to run the test: 现在运行以下命令来运行测试:
You should see the log with the token in the console and the test should pass. 你应该在控制台中看到带有令牌的日志,并且测试应该通过。

5. Create Order & Capture Payment Requests 创建订单和捕获支付请求

Now we need to add two functions to the paypal object. 现在我们需要向 paypal 对象添加两个函数。

createOrder Function createOrder 函数

Open the paypal.ts file and add the following to the object: 打开 paypal.ts 文件并向对象添加以下内容:
We are simply making a request to the paypal endpoint and sending the token in the header and the intent and purchase units in the body. Then we return the response. 我们只是向 PayPal 端点发起请求,在头部发送令牌,在主体中发送意图和购买单元。然后返回响应。

capturePayment Function capturePayment 函数

Now add this function below the createOrder: 现在在 createOrder 下方添加这个函数:
We are just sending a request witht the order ID and access token and returning the response. 我们只是发送带有订单 ID 和访问令牌的请求并返回响应。

6. Create Order & Capture Payment Tests with Jest 使用 Jest 测试创建订单和捕获支付

Now we will write two more tests to test the functions that we created in the last lesson. 现在我们将再编写两个测试来测试上一课中创建的函数。

Creating The Order 创建订单

You can test the create order function by adding the following test to thee paypal.test.ts file: 你可以通过向 paypal.test.ts 文件添加以下测试来测试创建订单函数:
You need to import the paypal object as well: 你还需要导入 paypal 对象:
Now run npm run test and you should see an array of objects in the console. The test should pass. 现在运行 npm run test,你应该在控制台中看到一个对象数组。测试应该通过。
You can copy the approve url and go to it in the browser and complete the order if you wanted to. 如果你愿意,你可以复制 approve 网址并在浏览器中访问它来完成订单。

Capture The Payment 捕获支付

This gets a bit more complicated because you need to approve the order first and an actual payment needs to be made. What we can do though is mock the response from PayPal so we can test the capture payment function. 这会变得有点复杂,因为你需要先批准订单,并且需要进行实际支付。不过我们可以模拟 PayPal 的响应,这样我们就可以测试捕获支付函数了。
Add the following test to the paypal.test.ts file: 向 paypal.test.ts 文件添加以下测试:
jest.spyOn(paypal, 'capturePayment') creates a "spy" on the capturePayment method within the paypal object. A spy is a function that monitors and records calls made to another function. With a spy, you can control the return values. In this case, we're using it to simulate a successful API response by returning an object { status: 'COMPLETED' } when capturePayment is called. jest.spyOn(paypal, 'capturePayment') 在 paypal 对象的 capturePayment 方法上创建一个"spy"。spy 是一个监视和记录对另一个函数调用的函数。使用 spy,你可以控制返回值。在这种情况下,我们使用它来模拟成功的 API 响应,当调用 capturePayment 时返回一个 { status: 'COMPLETED' } 对象。
Then we proceed to call the capturePayment method with the mock order ID. The test should pass. 然后我们继续使用模拟订单 ID 调用 capturePayment 方法。测试应该通过。
We now know that our PayPal functions are working as expected. We can now move on to integrating them into our application.

7. Create PayPal Order Action & Payment Result Schema/Type 创建 PayPal 订单操作和支付结果模式/类型

Now that we know our paypal.ts file is working, let's continue on to integrate PayPal with our project. 既然我们知道 paypal.ts 文件可以工作,让我们继续将 PayPal 集成到我们的项目中。
When we call our function to create an order from paypal, it sends back an object called paymentResult. That object includes an id and a status field. So we will be using that in our code to update the order to paid. 当我们调用从 PayPal 创建订单的函数时,它会返回一个名为 paymentResult 的对象。该对象包含 idstatus 字段。因此我们将在代码中使用它来将订单更新为已支付。
We need to create the Zod schema for that paymentResult 我们需要为那个 paymentResult 创建 Zod 模式。

Payment Result Schema & Type 支付结果模式和类型

We need a new Zod schema for the payment result. Open the lib/validator.ts file and add the following: 我们需要一个用于支付结果的新 Zod 模式。打开 lib/validator.ts 文件并添加以下内容:
So when we create a new PayPal order, we will get back an object with an id, status, email_address, and pricePaid. 因此,当我们创建新的 PayPal 订单时,我们将获得一个包含 idstatusemail_addresspricePaid 的对象。
Now opent the types file at types/index.ts and add the following: 现在打开 types/index.ts 中的类型文件并添加以下内容:

PayPal Order Action PayPal 订单操作

We need to create an action to create a new PayPal order. This is different than creating an order in the database. We are basically doing the same thing that we did within our test in the last lesson. 我们需要创建一个操作来创建新的 PayPal 订单。这与在数据库中创建订单不同。我们基本上在做与上一课测试中相同的事情。
This action itself does not mark the order as paid. We will do that in the next lesson. This just creates the order and gets an ID. We want to return that ID. 这个操作本身不会将订单标记为已支付。我们将在下一课中这样做。这只是创建订单并获取一个 ID。我们想要返回那个 ID。
Open the lib/actions/order.actions.ts file and import the paypal object, revalidatePath as well as the PaymentResult type: 打开 lib/actions/order.actions.ts 文件并导入 paypal 对象、revalidatePath 以及 PaymentResult 类型:
Now add the following function: 现在添加以下函数:
This is pretty self-explanatory. We are getting the order from the database, creating a PayPal order, and updating the order with the PayPal order id. 这很容易理解。我们从数据库中获取订单,创建 PayPal 订单,并用 PayPal 订单 ID 更新订单。
We have a couple more actions that we need to create in order to be able to complete the order. We will do that next. 我们还需要创建几个操作才能完成订单。我们接下来会做这件事。

8. Approve & Update Order Actions 批准和更新订单操作

In order to be able to use PayPal, there are a couple other actions that we need to create. We need a approvePayPalOrder action and an updateOrderToPaid function. 为了能够使用 PayPal,我们还需要创建几个其他操作。我们需要一个 approvePayPalOrder 操作和一个 updateOrderToPaid 函数。

Approve Paypal Order Action 批准 PayPal 订单操作

This action is used to approve the order once payment has been made. IN the UI, when a user clicks to pay with PayPal, a 3rd party paypal window opens for the user to enter their PayPal credentials. All the secure payment stuff is done on the PayPal servers, not our application. Once the user has entered their credentials and payment is made, we get back the order id from PayPal and we need to approve the order. That's what this action is for. 这个操作用于在支付完成后批准订单。在 UI 中,当用户点击使用 PayPal 支付时,会打开一个第三方 PayPal 窗口,供用户输入其 PayPal 凭证。所有安全支付工作都在 PayPal 服务器上完成,而不是在我们的应用程序中。一旦用户输入了他们的凭证并完成了支付,我们就会从 PayPal 获得订单 ID,并且需要批准订单。这就是这个操作的目的。
Open the lib/actions/order.actions.js file and add the following function: 打开 lib/actions/order.actions.js 文件并添加以下函数:
We are getting the order from the database, checking if the order is already paid by calling the capturePayment function from the lib/paypal.ts file. Now we need to update the order to paid. Let's create another action below it for that. 我们从数据库中获取订单,通过调用 lib/paypal.ts 文件中的 capturePayment 函数来检查订单是否已经支付。现在我们需要将订单更新为已支付。让我们在下面创建另一个操作来实现这一点。
Add the following function to the same file: 向同一文件添加以下函数:
This function takes in the order ID as well as data from the payment result that PayPal sends us. We are getting the order from the database, checking if the order is already paid, and updating the order to paid. We are also updating the product quantities in the database. We do this in a transaction so that if one of the updates fails, the entire transaction fails. So you don't end up with an order that is paid but the product quantities are not updated. 这个函数接收订单 ID 以及 PayPal 发送给我们的支付结果数据。我们从数据库中获取订单,检查订单是否已经支付,并将订单更新为已支付。我们还在数据库中更新产品数量。我们在一个事务中执行这些操作,这样如果其中一个更新失败,整个事务就会失败。因此,你不会遇到订单已支付但产品数量未更新的情况。
Now we have to call the updateOrderToPaid action from the approvePayPalOrder action. Replace the @todo comment with the following code: 现在我们必须从 approvePayPalOrder 操作中调用 updateOrderToPaid 操作。用以下代码替换 @todo 注释:
We are passing the order ID and a payment result with data from the PayPal capture payment. 我们正在传递订单 ID 和包含来自 PayPal 捕获支付数据的支付结果。
Our server actions are now complete, let's move on to the client and create the UI for the user to pay with PayPal. 我们的服务器操作现在已经完成,让我们转到客户端并为用户创建使用 PayPal 支付的 UI。

9. Implement Paypal Button 实现 PayPal 按钮

We have the backend functionality done to take PayPal payments, but we need to implement the PayPal button and the UI. 我们已经完成了接受 PayPal 支付的后端功能,但我们需要实现 PayPal 按钮和 UI。 https://www.npmjs.com/package/@paypal/react-paypal-js
We are going to use a package called @paypal/react-paypal-js to implement the PayPal button. Let's install it: 我们将使用一个名为 @paypal/react-paypal-js 的包来实现 PayPal 按钮。让我们安装它:
Now let's open the app/(root)/order/[id]/order-details.form.tsx file. 现在让我们打开 app/(root)/order/[id]/order-details.form.tsx 文件。
Add the following imports: 添加以下导入:
Here is an overview of what these are: 以下是这些内容的概述:
  • PayPalButtons: This is the component that will render the PayPal button.
  • PayPalButtons:这是将渲染 PayPal 按钮的组件。
  • PayPalScriptProvider: This is the component that will load the PayPal script. It ensures the SDK is loaded before any buttons or PayPal features are rendered.
  • PayPalScriptProvider:这是将加载 PayPal 脚本的组件。它确保在渲染任何按钮或 PayPal 功能之前加载 SDK。
  • usePayPalScriptReducer: This is a custom React hook that provides access to the state of the PayPal script. It allows you to dynamically control the loading state and settings of the PayPal SDK, which can be useful if you need to adjust configuration or show loading indicators while the SDK is being prepared.
  • usePayPalScriptReducer:这是一个自定义 React 钩子,提供对 PayPal 脚本状态的访问。它允许你动态控制 PayPal SDK 的加载状态和设置,如果你需要在准备 SDK 时调整配置或显示加载指示器,这会很有用。
We also want to import the 2 actions from the order.actions.ts file: 我们还想从 order.actions.ts 文件导入 2 个操作:

Pass In The PayPal Client ID 传入 PayPal 客户端 ID

This component will take the client ID as a prop. Open the file where it is embedded, which is the app/(root)/order/[id]/page.tsx file and add the following: 这个组件将把客户端 ID 作为属性。打开它嵌入的文件,即 app/(root)/order/[id]/page.tsx 文件,并添加以下内容:
We are passing the PayPal client ID as a prop to the OrderDetailsTable component. 我们正在将 PayPal 客户端 ID 作为属性传递给 OrderDetailsTable 组件。
Now add it as a prop to the OrderDetailsTable component: 现在将其作为属性添加到 OrderDetailsTable 组件:
Right above the return, add the following code: 在 return 正上方,添加以下代码:
This code checks the loading status of the PayPal script and returns a status message. 这段代码检查 PayPal 脚本的加载状态并返回状态消息。
Next, add the following function under the PrintLoadingState function: 接下来,在 PrintLoadingState 函数下方添加以下函数:
This calls our createPayPalOrder action and returns the response. 这调用我们的 createPayPalOrder 操作并返回响应。
Finally, we need a function to call the approvePayPalOrder action: 最后,我们需要一个函数来调用 approvePayPalOrder 操作:
Now, down at the bottom, right above the closing </CardContent> tag, add the following: 现在,在底部,在结束标记 </CardContent> 正上方,添加以下内容:
We are checking to see if the order is paid. If it is not and the payment method is PayPal, we will render the PayPal button. THe button takes in the createOrder and onApprove props. 我们正在检查订单是否已支付。如果没有支付并且支付方式是 PayPal,我们将渲染 PayPal 按钮。按钮接收 createOrderonApprove 属性。

Test It Out 测试一下

Now click the pay with paypal button and use your sandbox account. There is no way to use real money while in sandbox mode so don't worry about that. 现在点击使用 PayPal 支付按钮并使用你的沙箱账户。在沙箱模式下无法使用真实资金,所以不用担心。
One paid, you will be redirected to the order details page and you will see the order status as paid. 一旦支付,你将被重定向到订单详情页面,你将看到订单状态为已支付。
notion image
That's it! You have successfully implemented the PayPal button. 就是这样!你已经成功实现了 PayPal 按钮。

📎 参考文章

  • 一些引用
  • 引用文章
 
💡
欢迎您在底部评论区留言,一起交流~
上一篇
第一节 大脑:重新认识你自己
下一篇
Harness Engineering - 搭建Mini Harness
Loading...