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

使用 React 和 Stripe 构建支付系统

使用 React 和 Stripe 构建支付系统

作者:Ovie Okeh ✏️

Stripe是一套 API,可以轻松设置在线支付处理,今天,我们将利用它通过 React 创建一个基本的支付系统。

无论您是要部署订阅服务、电商平台还是众筹解决方案,Stripe 都能提供足够的灵活性助您轻松完成。我们将构建一个小型概念验证支付系统,以实现网站上的一次性购买功能。

完成本教程后,你应该能够在你的 React 应用中设置用于处理在线支付的后端和前端。

参与要求

本教程需要您具备以下条件:

  1. 您的计算机上已安装 Node.js
  2. Stripe开发者帐户
  3. 具备Express的基本知识
  4. React Hooks基础知识

如果您尚未安装Node,可以从官方网站获取最新版本。本教程中编写的所有代码都可以在这里找到

LogRocket 免费试用横幅

Stripe 设置

如果您还没有 Stripe 开发者账号,可以点击此处免费注册。注册后,请按照以下步骤进行设置:

  • 在“您想如何开始? ”模态框中选择“开发者集成” 。
  • 在下一个弹窗中选择“仅接受付款”。
  • 请在下一个弹窗中勾选“一次性付款”选项。
  • 最后,在最后一个模态框中勾选“构建自定义支付流程”。

您现在应该已经设置好了一个基础账户。您可以点击页面左上角的“添加名称”链接来更新账户名称。

您需要从控制面板复制您的可发布密钥和秘密密钥,并将它们保存在某个地方,因为我们很快就需要用到它们。

复制您的测试公开密钥和私钥
复制您的测试用可发布密钥和秘密密钥。

构建支付服务器

在开始构建 React 应用之前,我们需要设置一个服务器来处理支付请求。

我们需要在Express 服务器上搭建一个 RESTful 接口,它将作为我们 React 代码和 Stripe 后端之间的中间层。如果您之前从未构建过 API,请不要担心,这会非常基础,因为我们这里不需要实现生产环境就绪的后端。

我们开始吧。

  1. 创建一个新的项目文件夹,名称随意(我这里用的是react-stripe-payment)。
  2. 在文件夹中打开终端并运行npm init -y
  3. 通过运行以下命令安装依赖项npm install express dotenv body-parser stripe
  4. src通过运行以下命令在根文件夹下创建一个文件夹mkdir src

server.js

让我们创建用于监听支付请求的服务器。在文件夹server.js下创建一个名为 `.ts` 的新文件src,并将以下内容粘贴到其中:

const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const postCharge = require('./stripe')
require('dotenv').config()

const app = express()
const router = express.Router()
const port = process.env.PORT || 7000

router.post('/stripe/charge', postCharge)
router.all('*', (_, res) =>
  res.json({ message: 'please make a POST request to /stripe/charge' })
)
app.use((_, res, next) => {
  res.header('Access-Control-Allow-Origin', '*')
  res.header(
    'Access-Control-Allow-Headers',
    'Origin, X-Requested-With, Content-Type, Accept'
  )
  next()
})
app.use(bodyParser.json())
app.use('/api', router)
app.use(express.static(path.join(__dirname, '../build')))

app.get('*', (_, res) => {
  res.sendFile(path.resolve(__dirname, '../build/index.html'))
})

app.listen(port, () => console.log(`server running on port ${port}`))
Enter fullscreen mode Exit fullscreen mode

让我们逐节分析这个文件。

const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const postCharge = require('./stripe')
require('dotenv').config()
Enter fullscreen mode Exit fullscreen mode

这里,我们正在导入所需的包。你会注意到,除了`<package_name>`之外,其他所有包都是第三方导入的postCharge,`<package_name>`是从一个名为`<file_name>`的文件中导入的stripe。我们稍后会创建这个文件。

dotenv允许我们从 Node 进程中读取敏感信息,这样我们就不必在代码中硬编码秘密值了。

const app = express()
const router = express.Router()
const port = process.env.PORT || 7000
Enter fullscreen mode Exit fullscreen mode

我们将一个新的 Express 实例初始化到一个名为 `<variable_name>` 的变量中app。然后,我们创建一个新的 Router 实例并将其存储在一个名为 `<variable_name>` 的变量中router。我们将使用它来定义支付端点。

最后,我们初始化一个名为 的新变量port,并从 Node 进程中为其赋值(process.env.PORT),如果该进程为undefined,则赋值为 7000。

router.post('/stripe/charge', postCharge)
router.all('*', (_, res) =>
  res.json({ message: 'please make a POST request to /stripe/charge' })
)
app.use((_, res, next) => {
  res.header('Access-Control-Allow-Origin', '*')
  res.header(
    'Access-Control-Allow-Headers',
    'Origin, X-Requested-With, Content-Type, Accept'
  )
  next()
})
app.use(bodyParser.json())
app.use('/api', router)
app.use(express.static(path.join(__dirname, '../build')))
Enter fullscreen mode Exit fullscreen mode

还记得我们之前初始化的路由器吗?在第一行,我们设置了一个名为 `<endpoint_name>` 的端点/stripe/charge,并将其分配给postCharge`<address_name>` 来处理发送到该路由的所有 POST 请求。

然后,我们捕获所有其他发送到服务器的请求,并以包含消息的 JSON 对象进行响应,该消息将引导用户到相应的端点。

接下来,我们在应用程序实例上定义一个中间件,为所有请求启用 CORS。下一行,我们附加另一个中间件,用于解析请求体中的 JSON 对象。

然后,我们指示应用程序实例使用该router实例来处理所有发送到该/api端点的请求。最后,我们指示 Express 提供该/build文件夹。该文件夹将存放应用程序前端的转译代码。

app.get('*', (_, res) => {
  res.sendFile(path.resolve(__dirname, '../build/index.html'))
})

app.listen(port, () => console.log(`server running on port ${port}`))
Enter fullscreen mode Exit fullscreen mode

这里,我们告诉应用程序实例通过提供index.html位于该/build文件夹中的文件来处理所有 GET 请求。这就是我们在生产环境中提供前端服务的方式。

最后,我们在之前定义的端口上启动服务器,并在启动成功后向控制台记录一条消息。

stripe.js

接下来,我们将创建上面postCharge需要的处理程序server.js。在该src文件夹下,创建一个新文件,stripe.js并将以下内容粘贴到该文件中:

const stripe = require('stripe')(<your_secret_key>)

async function postCharge(req, res) {
  try {
    const { amount, source, receipt_email } = req.body

    const charge = await stripe.charges.create({
      amount,
      currency: 'usd',
      source,
      receipt_email
    })

    if (!charge) throw new Error('charge unsuccessful')

    res.status(200).json({
      message: 'charge posted successfully',
      charge
    })
  } catch (error) {
    res.status(500).json({
      message: error.message
    })
  }
}

module.exports = postCharge
Enter fullscreen mode Exit fullscreen mode

让我们来详细分析一下。

const stripe = require('stripe')(<your_secret_key>)
Enter fullscreen mode Exit fullscreen mode

stripe在这里,我们通过引入Stripe 包并使用之前复制的密钥字符串调用它来初始化一个新的 Stripe 实例。我们将此实例保存在一个名为 `.` 的变量中stripe

async function postCharge(req, res) {
  try {
    const { amount, source, receipt_email } = req.body

    const charge = await stripe.charges.create({
      amount,
      currency: 'usd',
      source,
      receipt_email
    })
Enter fullscreen mode Exit fullscreen mode

然后我们创建一个名为 的新函数postCharge。该函数是一个请求处理程序,因此我们必须接收两个参数:reqres

然后,我们在这个函数内部打开一个try catch代码块。我们从请求对象中解构所有我们期望随请求一起发送的变量;在本例中,这些变量是amountsourcereceipt_email

然后我们创建一个名为 的新变量charge。该变量保存向 Stripe API 异步调用以创建新收费的结果(stripe.charges.create)。

if (!charge) throw new Error('charge unsuccessful')
Enter fullscreen mode Exit fullscreen mode

如果 Stripe 调用的结果是一个假值(undefined在本例中为 false),则表示我们的付款请求失败,因此我们会抛出一个新错误,消息为“收费不成功”。

res.status(200).json({
  message: 'charge posted successfully',
  charge
})
Enter fullscreen mode Exit fullscreen mode

否则,我们将以 200 状态码和包含消息和收费对象的 JSON 对象来响应请求。

} catch (error) {
    res.status(500).json({
      message: error.message
    })
  }
}

module.exports = postCharge
Enter fullscreen mode Exit fullscreen mode

在 catch 块中,我们拦截所有其他错误,并将它们连同 500 状态码和包含错误消息的消息一起发送给客户端。

在文件末尾,我们postCharge使用 . 导出函数module.exports

这就是支付服务器的全部内容。当然,它尚未达到生产就绪状态,应该用于处理真实支付的实际应用程序,但对于我们目前的用例来说已经足够了。接下来我们来看前端。

构建前端

支付服务器搭建完毕,现在该完善前端了。由于本教程力求简洁易懂,所以前端不会太复杂。以下是应用程序的各个组件:

  • 路由器组件
  • 产品列表组件
  • 结账表单组件

我们开始吧。

  1. 运行以下命令安装所需的软件包:
npm install axios babel-polyfill history parcel parcel-bundler react react-dom react-router-dom react-stripe-elements
Enter fullscreen mode Exit fullscreen mode
  1. 在项目根目录下,运行以下命令:
mkdir public && touch public/index.html
Enter fullscreen mode Exit fullscreen mode

这将创建一个名为“文件夹”的文件夹public,并在该新文件夹中创建一个index.html文件。打开该index.html文件并粘贴以下内容:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="description" content="React + Stripe" />
    <title>React and Stripe Payment</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script src="https://js.stripe.com/v3/"></script>
    <script src="../src/index.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

如果您已经熟悉 React,这应该没什么新鲜的;这只是我们应用程序的入口点。另外请注意,我们在第一个<script>标签中导入了 Stripe SDK——Stripe SDK 的导入必须放在我们自己的代码之前。

在文件夹内src,运行以下命令:

touch src/index.js && touch src/products.js
Enter fullscreen mode Exit fullscreen mode

打开index.js并粘贴以下内容:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'
import 'babel-polyfill'

const rootNode = document.querySelector('#root')
ReactDOM.render(<App />, rootNode)
Enter fullscreen mode Exit fullscreen mode

现在我们需要从某个地方获取产品列表。通常情况下,我们会从数据库或 API 获取,但对于这个简单的用例,我们可以直接在 JavaScript 文件中硬编码两三个产品。这就是为什么我们需要…… products.js。打开它并粘贴以下内容:

export const products = [
  {
    name: 'Rubber Duck',
    desc: `Rubber ducks can lay as many eggs as the best chicken layers, and they
  are fun to watch with their antics in your backyard, your barnyard, or
  your pond.`,
    price: 9.99,
    img:
      'https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcSqkN8wkHiAuT2FQ14AsJFgihZDzKmS6OHQ6eMiC63rW8CRDcbK',
    id: 100
  },
  {
    name: 'Chilli Sauce',
    desc: `This Chilli Sauce goes well with some nice roast rubber duck. Flavored with
    the best spices and the hottest chillis, you can rest assured of a tasty Sunday
    rubber roast.`,
    price: 12.99,
    img:
      'https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcRTREm1dEzdI__xc6O8eAz5-4s88SP-Gg9dWYMkBKltGMi84RW5',
    id: 101
  }
]
Enter fullscreen mode Exit fullscreen mode

这里列出了可供购买的产品。您可以添加任意数量的产品,然后继续创建组件。

在项目根目录运行以下命令:mkdir src/components。这将components在项目根目录下创建一个名为 `<components_name>` 的新文件夹src,用于存放我们的 React 组件。接下来,我们来创建第一个组件。

App.jsx

这是根组件,它将负责将请求路由到我们应用程序中的各个页面。在文件夹App.jsx内创建一个名为 `<filename>` 的新文件components,并将以下内容粘贴到该文件中:

import React, { useState } from 'react'
import { Router, Route, Switch } from 'react-router-dom'
import { createBrowserHistory } from 'history'
import Products from './Products'
import Checkout from './Checkout'
import { products } from '../products'

const history = createBrowserHistory()

const App = () => {
  const [selectedProduct, setSelectedProduct] = useState(null)

  return (
    <Router history={history}>
      <Switch>
        <Route
          exact
          path="/"
          render={() => (
            <Products
              products={products}
              selectProduct={setSelectedProduct}
              history={history}
            />
          )}
        />
        <Route
          path="/checkout"
          render={() => (
            <Checkout
              selectedProduct={selectedProduct}
              history={history}
            />
          )}
        />
      </Switch>
    </Router>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

让我们来详细分析一下。

import React, { useState } from 'react'
import { Router, Route, Switch } from 'react-router-dom'
import { createBrowserHistory } from 'history'
import Products from './Products'
import Checkout from './Checkout'
import { products } from '../products'

const history = createBrowserHistory()
Enter fullscreen mode Exit fullscreen mode

第一部分只是一些依赖导入。前三个导入是任何单页 React 应用都必需的。接下来的两个导入是自定义组件,我们稍后会编写。最后一个导入是我们之前创建的硬编码产品数据。我们会将其作为 prop 传递给组件Products

最后,我们从该包中创建一个新的历史实例history,并将其保存在一个名为 history 的变量中。

const App = () => {
  const [selectedProduct, setSelectedProduct] = useState(null)

  return (
    <Router history={history}>
      <Switch>
        <Route
          exact
          path="/"
          render={() => (
            <Products
              products={products}
              selectProduct={setSelectedProduct}
              history={history}
            />
          )}
        />
        <Route
          path="/checkout"
          render={() => (
            <Checkout
              selectedProduct={selectedProduct}
              history={history}
            />
          )}
        />
      </Switch>
    </Router>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

然后,我们创建一个名为 `app` 的新功能组件App。该应用程序有一个名为 ` product` 的状态变量selectedProduct,用于保存当前选定的待购买产品。

我们返回一个Router实例,该实例定义了所有路由及其各自的组件。

在第一条路由中/,我们渲染Products组件并传入三个属性:硬编码的产品列表、用于在 App 状态中设置产品的函数以及历史记录对象,以便我们能够在不破坏浏览器历史记录的情况下导航到新页面。

在第二个路由中,/checkout我们渲染Checkout组件并传入几个属性:当前选定的产品和history对象。

在文件末尾,我们将App组件导出为默认导出项。

Products.jsx

该组件负责将产品列表渲染到 DOM 中,它非常简单。Products.jsxcomponents文件夹中创建一个名为 `<filename>` 的新文件,并将以下内容粘贴到该文件中:

import React from 'react'
import './Products.scss'

const Products = ({ products, selectProduct, history }) => {
  const handlePurchase = prod => () => {
    selectProduct(prod)
    history.push('/checkout')
  }

  return products.map(prod => (
    <div className="product" key={prod.id}>
      <section>
        <h2>{prod.name}</h2>
        <p>{prod.desc}</p>
        <h3>{'$' + prod.price}</h3>
        <button type="button" onClick={handlePurchase(prod)}>
          PURCHASE
        </button>
      </section>
      <img src={prod.img} alt={prod.name} />
    </div>
  ))
}

export default Products
Enter fullscreen mode Exit fullscreen mode

注:您可以从这里Products.scss获取内容

让我们来详细分析一下。

const Products = ({ products, selectProduct, history }) => {
  const handlePurchase = prod => () => {
    selectProduct(prod)
    history.push('/checkout')
  }
Enter fullscreen mode Exit fullscreen mode

我们首先定义一个函数式组件,它接收三个属性:

  1. products
  2. selectProduct
  3. history

products这是我们之前硬编码的产品数组。稍后我们将遍历这个数组,将各个产品渲染到 DOM 中。

selectProduct这是一个接收单个产品对象的函数。它会更新App组件的状态以保存该产品,以便Checkout组件可以通过其 props 访问它。

history是历史对象,它将使我们能够安全地导航到其他路线。

然后我们定义一个handlePurchase函数,当用户想要购买某个产品时,该函数将被调用。它接收一个参数,prodselectProduct使用该参数调用另一个函数。调用该函数后selectProduct,它会/checkout通过调用另一个函数导航到相应的路由history.push

return products.map(prod => (
    <div className="product" key={prod.id}>
      <section>
        <h2>{prod.name}</h2>
        <p>{prod.desc}</p>
        <h3>{'$' + prod.price}</h3>
        <button type="button" onClick={handlePurchase(prod)}>
          PURCHASE
        </button>
      </section>
      <img src={prod.img} alt={prod.name} />
    </div>
  ))
}

export default Products
Enter fullscreen mode Exit fullscreen mode

现在是时候将产品渲染到 DOM 中了。我们遍历数组products,并为数组中的每个产品返回一系列 JSX 代码。这些 JSX 代码应该非常简单明了,最终会在屏幕上绘制出以下图像:

橡皮鸭和辣椒酱产品图片

Checkout.jsx

接下来,我们要创建结账页面,用户点击产品上的“购买”按钮后将被引导至该页面。

Checkout.jsx在该文件夹下创建一个文件components,并将以下内容粘贴到该文件中:

import React, { useEffect } from 'react'
import { StripeProvider, Elements } from 'react-stripe-elements'
import CheckoutForm from './CheckoutForm'

const Checkout = ({ selectedProduct, history }) => {
  useEffect(() => {
    window.scrollTo(0, 0)
  }, [])

  return (
    <StripeProvider apiKey="pk_test_UrBUzJWPNse3I03Bsaxh6WFX00r6rJ1YCq">
      <Elements>
        <CheckoutForm selectedProduct={selectedProduct} history={history} />
      </Elements>
    </StripeProvider>
  )
}

export default Checkout
Enter fullscreen mode Exit fullscreen mode

这时我们开始引入 Stripe。在第二行代码中,我们导入了一个名为 `stripe` 的组件StripeProvider,以及另一个名为 `stripe` 的组件,Elements它们都来自react-stripe-elements我们在本节开头安装的软件包。

StripeProvider我们的应用程序需要访问 Stripe 对象,任何与 Stripe 对象交互的组件都必须是该组件的子组件StripeProvider

Elements这是一个 React 组件,它封装了实际的结账表单。它有助于将 Stripe 元素(稍后会详细介绍)组合在一起,并简化每个 Stripe 元素的数据标记化过程。

这个Checkout组件本身相当简单。它接收两个属性,selectedProduct并将history它们传递给CheckoutForm我们接下来要创建的另一个组件。

此外,还有一个useEffect调用会在页面首次加载时将文档滚动到顶部。这是必要的,因为react-router-dom切换路由时需要保留之前的滚动状态。

另请注意,我们向 `<prop> apiKey`传递了一个属性StripeProvider。此键是您之前设置 Stripe 时复制的可发布密钥。请注意,此属性是必需的,因为它用于向 Stripe 服务器验证您的应用程序。

CheckoutForm.jsx

这是我们要创建的最后一个组件,也是最重要的一个。该CheckoutForm组件将接收用户银行卡信息的输入,并实际调用后端服务器来处理支付交易。

CheckoutForm.jsx在目录下创建一个名为 `.htm` 的新文件components。我们将逐节阅读此文件的内容。

import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import {
  CardNumberElement,
  CardExpiryElement,
  CardCVCElement,
  injectStripe
} from 'react-stripe-elements'
import axios from 'axios'
import './CheckoutForm.scss'
...to be continued below...
Enter fullscreen mode Exit fullscreen mode

首先,我们将需要用到的包导入到文件中。请注意react-stripe-elements包的导入语句。现在正好可以详细讨论一下 Stripe Elements。

Stripe Elements是一组预构建的 UI 元素,可让您收集用户的银行卡信息,而无需自行管理此类敏感信息。

react-stripe-elements软件包是 Stripe Elements 的封装,它将这些元素作为 React 组件公开,您可以直接将其插入到您的应用程序中——无需从头开始创建它们。

我们将其中一些组件以及一个 HOC 组件导入到此文件中injectStripe

injectStripe简单来说,它会获取组件中初始化的 Stripe 对象StripeProvider,并将该对象“注入”到任何被它包裹的组件中。这就是我们访问 Stripe 对象的方式。

然后我们导入一个名为 Axios 的包axios。Axios 只是一个基于 Promise 的浏览器 HTTP 客户端,我们将使用它与我们的支付服务器进行通信。

CheckoutForm.scss您可以从这里获取内容

...continued...
const CheckoutForm = ({ selectedProduct, stripe, history }) => {
  if (selectedProduct === null) history.push('/')

  const [receiptUrl, setReceiptUrl] = useState('')

  const handleSubmit = async event => {
    event.preventDefault()

    const { token } = await stripe.createToken()

    const order = await axios.post('http://localhost:7000/api/stripe/charge', {
      amount: selectedProduct.price.toString().replace('.', ''),
      source: token.id,
      receipt_email: 'customer@example.com'
    })

    setReceiptUrl(order.data.charge.receipt_url)
  }
...to be continued...
Enter fullscreen mode Exit fullscreen mode

接下来是CheckoutForm组件本身。它接收三个属性:

  1. selectedProduct
  2. stripe
  3. history

selectedProduct这是用户点击购买的产品。它来自根App组件的状态,并作为 props 传递下去。

stripe是我们导入的高阶组件(HOC)以属性形式“注入”的实际 Stripe 对象injectStripe。您已经知道它的作用history了。

组件首先会检查页面是否selectedProduct存在。如果不存在,则将用户重定向到首页。在生产级应用中,这通常会由路由守卫高阶组件 (HOC) 来处理。

然后,我们定义一个新的状态来保存成功支付的收据 URL。它最初为空。

接下来,我们定义一个名为 `pay` 的函数handleSubmit,该函数将在结账表单提交时(即点击“支付”按钮时)被调用。让我们来详细了解一下这个函数。

首先,我们阻止元素的默认行为,form以防止页面刷新。

然后,我们token从异步调用结果中解构出一个值stripe.createTokencreateToken该调用会对表单中的卡片信息进行标记化处理,并将其发送到 Stripe 服务器。之后,它会返回一个token对象,您可以从中获取一个token.id值,该值是实际卡片信息的别名。这确保您永远不会将用户的实际卡片详细信息发送到您的支付服务器。

其次,我们向目标服务器发送一个 HTTP POST 请求,localhost:7000/api/stripe/charge请求体包含以下三项内容:

  1. amount
  2. source
  3. receipt_email

amount是所购商品的价格。我们需要将其转换为字符串,并删除所有特殊字符,例如“.”和“,”。这意味着 9.99 美元的价格将以字符串的形式发送到支付服务器999

source付款将从这里扣除。在本例中,它将是我们刚刚生成的令牌的 ID。

receipt_email付款收据将发送到此处。通常是客户的电子邮件地址,但由于我们没有实现身份验证,所以这里我们直接将其硬编码到代码中。

请求完成后,我们从响应对象中获取收据的 URL 并将其设置到状态中。这里假设没有错误,因此在生产级应用中,通常需要实现错误处理。

...continued...
if (receiptUrl) {
    return (
      <div className="success">
        <h2>Payment Successful!</h2>
        <a href={receiptUrl}>View Receipt</a>
        <Link to="/">Home</Link>
      </div>
    )
  }
...to be continued...
Enter fullscreen mode Exit fullscreen mode

函数执行完毕后handleSubmit,我们会立即if检查状态中是否存在某个receiptUrl值。如果存在,则需要渲染一个div包含成功消息、查看收据链接以及返回首页链接的元素。

...continued...
  return (
    <div className="checkout-form">
      <p>Amount: ${selectedProduct.price}</p>
      <form onSubmit={handleSubmit}>
        <label>
          Card details
          <CardNumberElement />
        </label>
        <label>
          Expiration date
          <CardExpiryElement />
        </label>
        <label>
          CVC
          <CardCVCElement />
        </label>
        <button type="submit" className="order-button">
          Pay
        </button>
      </form>
    </div>
  )
}

export default injectStripe(CheckoutForm)
Enter fullscreen mode Exit fullscreen mode

否则,我们将渲染实际的结账表单。我们使用预构建的 Elements 组件,而不是从头开始重新创建,从而避免管理敏感信息。

在本文件的末尾,我们将CheckoutForm组件包装在injectStripeHOC 中,以便我们可以访问在组件中使用的 Stripe 对象。

测试我们的应用程序

让我们回顾一下目前为止我们取得的成就。

  1. 我们创建了一个与 Stripe 通信的支付服务器。
  2. 我们创建了一个主页来列出我们的产品。
  3. 我们创建了一个结账页面,用于收集用户的付款信息。
  4. 我们创建了一个handleSubmit函数,用于向服务器发送请求以处理支付费用。

我们已经基本完成所有设置,现在是时候运行我们的应用程序,看看能否成功购买一只橡皮鸭了。首先我们需要添加脚本,所以请打开文件package.json,并将“scripts”部分替换为以下内容:

"scripts": {
    "build": "parcel build public/index.html --out-dir build --no-source-maps",
    "dev": "node src/server.js & parcel public/index.html",
    "start": "node src/server.js"
  },
Enter fullscreen mode Exit fullscreen mode

打开终端并运行命令npm run dev。这将启动支付服务器并在 1234 端口上开放前端。打开浏览器,访问http://localhost:1234,然后按照以下步骤操作:

  • 点击任意产品上的“购买”按钮
  • 在结账页面,请在“卡片详情”字段中填写 4242 4242 4242 4242
  • 填写任意到期日期并选择一个随机的CVC值
  • 点击“支付”

如果一切顺利,您应该会看到“付款成功”的消息,其中包含查看收据和返回主页的链接。

要确认付款,请登录您的 Stripe 控制面板,点击“付款”,您应该会在那里看到您的付款。

Stripe支付控制面板

结论

这是一个使用 Stripe 的支付系统简化版(绝对不适用于生产环境)。如果您想尝试一下,我们来总结一下实现一个真正可用于生产环境的系统所需的必要组件。

  1. 一个更强大的支付服务器,具备适当的身份验证(例如 JWT)和验证功能。
  2. 用于采集和保存客户详细信息以便日后更轻松地进行计费的流程
  3. 利用 Stripe 的欺诈检测服务来决定哪些付款应该被处理。
  4. 客户端的UI和UX有了显著提升
  5. 客户端强大的错误处理机制

虽然本教程足以让你掌握基础知识,但远远不足以构建一个功能齐全的支付解决方案,因此请花些时间阅读Stripe 文档,因为它们编写得非常出色。


编者按:发现本文有误?您可以在这里找到正确版本。

插件:LogRocket,一款用于 Web 应用的 DVR

 
LogRocket 控制面板免费试用横幅
 
LogRocket是一款前端日志工具,可让您重现问题,如同在您自己的浏览器中发生一样。无需猜测错误原因,也无需用户提供屏幕截图和日志转储,LogRocket 即可让您重现会话,快速了解问题所在。它与任何框架的应用程序完美兼容,并提供插件来记录来自 Redux、Vuex 和 @ngrx/store 的额外上下文信息。
 
除了记录 Redux 操作和状态之外,LogRocket 还会记录控制台日志、JavaScript 错误、堆栈跟踪、包含标头和正文的网络请求/响应、浏览器元数据以及自定义日志。它还会对 DOM 进行插桩,记录页面上的 HTML 和 CSS,即使是最复杂的单页应用程序,也能生成像素级精确的视频。
 
免费试用


这篇文章《使用 React 和 Stripe 构建支付系统》最初发表在LogRocket 博客上。

文章来源:https://dev.to/bnevilleoneill/building-a- payments-system-with-react-and-stripe-5djc