在Stripe中实施3D安全

689 阅读13分钟

我们生活在一个与许多在线服务互动的世界里,并通过在线支付网关向这些服务付款。而我们,作为开发者,有责任将这些支付网关整合在一起,使其对用户和收款方都是安全的。

在这篇文章中,我们将介绍如何在使用Stripe接受在线支付时实施3D安全保护。

什么是3D安全?

3D安全是Stripe在实际处理付款之前对用户进行认证的一种方式。当用户输入他的银行卡信息时,会有一个弹出窗口或重定向提示他,以验证付款。

这通常是通过OTP验证身份,但这可能取决于发卡的银行。在一些国家,3D安全是没有必要的,但在印度等国家,3D安全是必须的。

你可以在你的Stripe账户中设置雷达规则,要求3D安全认证,但如果你的支付表单中没有代码,使3D安全弹出窗口工作,那就没有用。

在这篇文章中,我们将使用NodeJS、React,当然还有Stripe,创建一个简单的捐赠网络应用。我们将涵盖以下主题。

  • 设置Stripe并获得API密钥
  • 设置NodeJS后端和React前端
  • 在前端创建一个结账表单
  • 以常规方式处理付款
  • 在需要认证的情况下使用3D安全作为后备方案
  • 确认付款
  • 添加经常性付款(订阅)。
  • 测试我们的集成

你需要什么?

  • 一个代码编辑器 - 我更喜欢VSCode,但你可以使用任何你选择的代码编辑器
  • 安装NodeJS
  • 一个Stripe账户
  • 命令行的基本知识
  • ReactJS和NodeJS的基本知识

让我们开始吧!

首先,我们将在后端工作。我更喜欢 "API优先 "的方法,这意味着你首先创建一个API,然后再进行前端的其他工作。

我们将使用NodeJS、Express和一个Stripe包来创建我们的后端,以获取与支付有关的内容。

启动后端

让我们来创建我们的后端。要做到这一点,打开终端/命令提示符,并键入以下命令,在你想要的文件夹中启动一个NodeJS项目。

npm init -y

运行此命令将在该文件夹中生成一个package.json 文件。

现在使用以下命令打开文件夹中的VSCode,以便我们可以开始编辑。

code .

现在VSCode已经打开,你可以使用集成的终端,这将使你的生活更容易。只要在Windows上按Ctrl + J或在Mac上按Command + J就可以在VSCode中打开终端。

让我们安装几个软件包,它们将帮助我们进一步完成项目。在终端输入以下命令,我们将看到这些包的作用。

npm install express cors stripe dotenv

这些是正在安装的软件包。

  • Express 是用来轻松创建HTTP服务器的
  • CORS 帮助我们摆脱客户端应用程序中的跨源错误。
  • Stripe 是与Stripe的实际连接。我们可以使用这个包来获取付款细节并创建付款。
  • Dotenv 帮助我们启用环境变量来存储敏感数据

在环境变量中添加Stripe密匙

在进一步使用这个支付系统之前,让我们在环境变量中设置Stripe的秘密密钥。

所有秘密的API密钥和凭证都必须存储在环境变量中,这样如果实际代码被盗,数据就不会被盗。

要获得您的Stripe秘钥,请打开您的Stripe仪表盘,您会看到一个类似于下图的侧面菜单。

Screenshot of Stripe dashboard

现在,点击 "开发人员",然后点击API密钥。在那里你应该看到你的Stripe可发布和秘密密钥。

现在,我们需要的是秘密密钥。请注意,您不应该与任何人分享您的秘密密钥。分享您的秘密密钥将使其他人能够访问您的Stripe账户。

另一方面,可发布的密钥是我们在前端使用的密钥,如果有人获得了它,也没有关系,因为它是要公开的。

现在,复制您的Stripe密匙并进入VSCode,创建一个名为.env 的新文件,并按以下格式粘贴密钥。

STRIPE_SECRET_KEY=(secret key here)

.env 文件是用来存储环境变量的。dotenv 包将搜索这个文件以加载环境变量。现在,.env 文件已经完成,我们不需要在本教程中再碰环境变量。

安装Nodemon

在遵循本教程时,你可能需要多次重启服务器。为了避免这种情况,我们可以安装一个名为nodemon 的全局包,每当我们保存一个文件时,它就会自动重启我们的服务器。你可以在这里阅读更多关于Nodemon的信息。

在终端键入以下命令。

npm install -g nodemon

如果需要的话,使用sudo ,因为Nodemon应该是全局安装的,所以它将需要root权限。

设置一个Express服务器

让我们来创建将运行我们的服务器的文件。我们可以把它命名为index.js ,因为它在package.json 文件中被默认指定为main 。如果你愿意,你可以改变这个名字,但在本教程中我们将坚持使用index.js

让我们从创建一个Express服务器和一个简单的路由开始。

const express = require("express");
const app = express();
const PORT = process.env.PORT || 5000;
const cors = require("cors");

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get("/", (req, res) => res.json({ status: 200, message: "API Works" }));

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

这将创建一个简单的Express服务器和一个主页路由,它简单地返回一个JSON,说明API的工作。

在这里,我们将端口设置为process.env.PORT || 5000 ,因为如果你决定将这个服务器部署到像Heroku这样的服务中,他们会将其托管在自己的端口上,这些端口存储在他们的环境变量中,所以我们让他们决定端口。如果process.env.PORT 是未定义的,该应用程序在本地运行,将使用5000端口。

我们使用cors 包作为一个Express中间件,这样客户端应用程序就可以与我们的服务器正常互动,而不会出现任何跨源错误。你可以根据你的需要来配置cors 包,但在本教程中,我们将只允许任何流量。

在中间件部分,我们也允许通过请求体的JSON和url编码的数据,Express会自动为我们解析。

现在,如果你转到Postman或任何其他HTTP客户端,对以下内容执行GET请求 [http://localhost:5000](http://localhost:5000)的GET请求,你会得到以下JSON响应。

{
  "status": 200,
  "message": "API Works"
}

如果你看到这条信息,你的Express服务器已经设置好了。现在让我们进入下一个步骤。

设置dotenv

现在让我们来配置一下dotenv 包,以便它能正确识别我们来自.env 文件的环境变量。在顶部写下以下代码。

require("dotenv").config();

初始化Stripe

现在让我们来设置我们与Stripe的连接。在之前的教程中,我们已经安装了一个名为stripe 的包,它将帮助我们与Stripe通信。但首先,我们需要向它提供我们的Stripe密匙,以便它能与我们的Stripe账户进行交互。

在我们之前创建的文件上面包含这个片段。

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

前面我们处理了环境变量,这里我们使用了我们存储的STRIPE_SECRET_KEY 。现在Stripe识别了您的账户,我们可以与Stripe进一步互动。

整个代码直到现在应该显示如下。

require("dotenv").config();
const express = require("express");
const app = express();
const PORT = process.env.PORT || 5000;
const cors = require("cors");
const Stripe = require("stripe");
const stripe = Stripe(process.env.STRIPE_SECRET_KEY);

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get("/", (req, res) => res.json({ status: 200, message: "API Works" }));

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

收集数据用于支付

让我们考虑一下我们需要从用户那里收集哪些数据来启动支付。为了本教程,我们将保持简单。

  • 电子邮件地址
  • 支付金额
  • paymentMethod ,一个由Stripe在前端生成的ID,将代表一个特定的卡
  • 订阅,onetimemonthly 。如果订阅设置为,我们将设置经常性的付款。monthly

由于我们正在 "创建 "一个付款,我们将使用一个POST请求。使用POST请求的另一个原因是,我们发送给服务器的数据不会显示在URL本身,这与GET请求不同。另外,GET请求可以通过浏览器直接访问,这不是我们想要的。

因此,让我们创建一个POST请求监听器并收集数据。

app.post("/donate", async (req, res) => {
  try {
    let { email, amount, paymentMethod, subscription } = req.body;
    if (!email || !amount || !paymentMethod || !subscription) 
      return res.status(400).json({ status: 400, message: "All fields are required!" });
    amount = parseInt(amount);

    if (subscription === "onetime") {
      // One time payment code here
    }

    if (subscription === "monthly") {
      // Recurring payment code here
    }

    res.status(400).json({ status: 400, message: "Invalid type" });
  } catch(err) {
    console.error(err);
    res.status(500).json({ status: 200, message: "Internal server error" });
  }
});

在上面的代码中,我们正在做以下工作。

  • 当然是在/donate 路由上设置一个POST监听器
  • 从用户那里收集email,amount, 和paymentMethod
  • 验证字段,因此,如果任何字段丢失,将发送一条错误信息
  • 有时,客户端应用程序可能以字符串形式发送amount ,在这种情况下,我们要使用parseInt() 函数将金额转换为整数值

首先,我们要处理的是一次性支付。

尝试简单的HTTP支付

我们只有在需要时才使用3D安全,或者按照我们在Stripe仪表盘中的雷达规则。在我们进入3D安全之前,我们必须尝试HTTP支付,因为有些卡不支持3D安全。

现在是时候联系Stripe了。

const paymentIntent = await stripe.paymentIntents.create({
  amount: Math.round(amount * 100),
  currency: "INR",
  receipt_email: email,
  description: "Payment for donation",
  payment_method: paymentMethod,
  confirm: true
});

这样就可以立即启动支付了。confirm 字段告诉Stripe在收到付款后立即确认。如果你不指定confirm ,它就不会向用户收费,你需要手动确认订单,然后再向Stripe提出请求。

amount 字段中,您指定二级货币单位(例如,美元是美分,印度卢比是派萨)。Math.round() ,这里用于删除任何小数,因为Stripe不喜欢小数。

根据您的Stripe账户位置指定货币。对我来说是,印度,所以我使用INR 的货币。

一旦支付完成,收据将被发送到指定的电子邮件。在这种情况下,我们提到了我们从用户那里收集的电子邮件。

现在让我们检查一下这个简单的HTTP支付是否成功。要做到这一点,我们可以检查paymentIntent 的状态属性。

if (paymentIntent.status === "succeeded") {
  // Payment successful!
  return res.json({
    status: 200,
    message: "Payment Successful!",
    id: paymentIntent.id
  });
}
>

这就是一个简单的HTTP支付的全部内容。在这里,paymentIntent.id 可以作为一个支付ID。而我们用return 来立即停止进一步的执行,这样就不会出现意外的错误。

然而,如果状态不是succeeded ,而是requires_action ,这意味着需要3D安全。因此,我们将在这里处理3D安全问题。

  • 我们将获得一个client_secret ,用于支付意向。
  • 我们将把client_secret 发送给前端
  • 前端将使用client_secret ,使用3D安全进行验证。
  • 我们将在后端制定一个路线,再次检查支付状态

获取client_secret 并将其发送到前端

让我们检查一下我们创建的支付意图是否需要3D安全,然后发送客户秘密。

if (paymentIntent.status === "requires_action") {
  return res.json({ 
    status: 200,
    message: "3D secure required",
    actionRequired: true,
    clientSecret: paymentIntent.client_secret
  });
}

这样一来,我们就把客户秘密发送到前端。一旦我们完成了后端部分,我们将在本文后面处理前端的问题。

最后,如果状态既不是succeeded ,也不是requires_action ,我们将通知用户支付失败。我们在前面的案例中使用了return ,所以我们不需要使用else

return res.status(400).json({
  status: 400,
  message: "Payment failed!"
});

处理经常性支付

在经常性支付中,我们不直接使用支付意图。创建经常性付款的过程有点不同。

  • 首先,我们创建一个价格,这将是我们的捐款金额
  • 接下来我们用用户的电子邮件创建一个Stripe客户
  • 然后,我们创建一个订阅,并向客户收取价格。如果需要认证,Stripe将每月向客户发送一封关于付款的电子邮件。
  • 最后,我们让用户在我们的网站上自己支付第一张发票

之前我们为monthly 订阅类型创建了一个if 报表。所有的经常性付款代码都在那里。

创建一个价格

让我们继续进行第一步,创建一个价格。

const price = await stripe.prices.create({
  unit_amount: Math.round(amount * 100),
  recurring: { interval: "month" },
  currency: "INR",
  product_data: {
    name: "Recurring donation"
  }
});

这里的unit_amount 是实际的金额--我们已经讨论了如何将其发送到Stripe。

我们还为recurring ,提供一个interval 。在这种情况下,我们把它设置为monthproduct_data 对象包含一些关于产品本身的信息。在这种情况下,它只是一个捐款,所以我们只是指定它。

创建一个客户

现在,让我们来创建客户。

const customer = await stripe.customers.create({
  email, 
  description: "Donation customer",
  payment_method: paymentMethod,
  invoice_settings: {
    default_payment_method: paymentMethod
  }
});

在这里我们指定paymentMethod ,这样我们就可以在需要的时候立即向客户收费,而不会有任何麻烦。

创建一个订阅

这是向客户实际收费的地方。当启动订阅时,会产生一张发票,可以由用户支付,但我们将让用户立即支付发票以开始订阅。

我们可以从订阅中获得paymentIntent ,然后我们可以像以前一样做检查。

const subscribe = await stripe.subscriptions.create({
  customer: customer.id,
  items: [{ price: price.id }],
  expand: ["latest_invoice.payment_intent"]
});

我们要传入客户的ID和价格,把所有东西联系在一起。另外,为了获得最新发票的paymentIntent ,我们使用expand 属性。

当我们试图创建一个订阅时,Stripe已经尝试了一个基于HTTP的支付。现在,我们需要像以前一样处理3D安全支付的问题。

if (
  subscribe.latest_invoice.payment_intent.status === "requires_action"
) {
  // proceed to 3ds
  return res.status(200).json({
    status: 200,
    message: "3D Secure required",
    actionRequired: true,
    clientSecret: subscribe.latest_invoice.payment_intent.client_secret,
    id: subscribe.latest_invoice.payment_intent.id,
  });
}
if (subscribe.latest_invoice.payment_intent.status === "succeeded") {
  return res.json({
    status: 200,
    message: "Payment successful!",
  });
}
return res.status(400).json({ status: 400, message: "Payment failed!" });

这与我们用于一次性付款的方法相同。我们已经完成了后台的支付路线。

还有一条路线需要覆盖--检查路线。在前端认证后,我们需要一个路由来检查和验证后端的状态。

app.get("/check/:id", async (req, res) => {
  try {
    const id = req.params.id;
    const paymentIntent = await stripe.paymentIntents.retrieve(id);
    if (paymentIntent?.status === "succeeded") {
      return res.json({
        status: 200,
        message: "Payment successful!",
        id,
      });
    }
    res
      .status(400)
      .json({
        status: 200,
        message: "Payment failed! Please try again later.",
      });
  } catch (err) {
    console.error(err);
    res.status(500).json({ status: 500, message: "Internal server error" });
  }
});

这次我们使用一个GET请求,并检查付款是否真正完成。如果你不想使用webhook,想马上为用户提供一个虚拟服务,可以这样做。

这将是你的应用程序知道支付成功和用户可以使用的地方。但在这种情况下,这是一个捐赠网站,我们不需要在这里做任何特别的事情。

完成index.js 代码

你的index.js 文件现在应该是这样的。

require("dotenv").config();
const express = require("express");
const app = express();
const PORT = process.env.PORT || 5000;
const cors = require("cors");
const Stripe = require("stripe");
const stripe = Stripe(process.env.STRIPE_SECRET_KEY);

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get("/", (req, res) => res.json({ status: 200, message: "API Works" }));

app.post("/donate", async (req, res) => {
  try {
    let { email, amount, paymentMethod, subscription } = req.body;
    if (!email || !amount || !paymentMethod || !subscription) 
      return res.status(400).json({ status: 400, message: "All fields are required!" });
    amount = parseInt(amount);

    if (subscription === "onetime") {
      // One time payment code here
      const paymentIntent = await stripe.paymentIntents.create({
        amount: Math.round(amount * 100),
        currency: "INR",
        receipt_email: email,
        description: "Payment for donation",
        payment_method: paymentMethod,
        confirm: true
      });
      if (paymentIntent.status === "succeeded") {
        // Payment successful!
        return res.json({
          status: 200,
          message: "Payment Successful!",
          id: paymentIntent.id
        });
      }
      if (paymentIntent.status === "requires_action") {
        return res.json({ 
          status: 200,
          message: "3D secure required",
          actionRequired: true,
          clientSecret: paymentIntent.client_secret
        });
      }
      return res.status(400).json({
        status: 400,
        message: "Payment failed!"
      });
    }

    if (subscription === "monthly") {
      // Recurring payment code here
      const price = await stripe.prices.create({
        unit_amount: Math.round(amount * 100),
        recurring: { interval: "month" },
        currency: "INR",
        product_data: {
          name: "Recurring donation"
        }
      });

      const customer = await stripe.customers.create({
        email, 
        description: "Donation customer",
        payment_method: paymentMethod,
        invoice_settings: {
          default_payment_method: paymentMethod
        }
      });

      const subscribe = await stripe.subscriptions.create({
        customer: customer.id,
        items: [{ price: price.id }],
        expand: ["latest_invoice.payment_intent"]
      });

      if (
        subscribe.latest_invoice.payment_intent.status === "requires_action"
      ) {
        // proceed to 3ds
        return res.status(200).json({
          status: 200,
          message: "3D Secure required",
          actionRequired: true,
          clientSecret: subscribe.latest_invoice.payment_intent.client_secret,
          id: subscribe.latest_invoice.payment_intent.id,
        });
      }
      if (subscribe.latest_invoice.payment_intent.status === "succeeded") {
        return res.json({
          status: 200,
          message: "Payment successful!",
        });
      }
      return res.status(400).json({ status: 400, message: "Payment failed!" });
    }

    res.status(400).json({ status: 400, message: "Invalid type" });
  } catch(err) {
    console.error(err);
    res.status(500).json({ status: 200, message: "Internal server error" });
  }
});

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

创建前端

现在让我们转向前端,学习如何触发3D安全认证以及如何启动支付。

我们不会在前端做任何花哨的造型。让我们保持简单,并专注于支付方面的事情。

我们将在前端使用React。创建一个名为frontend 的新文件夹,在该文件夹中打开终端,并输入以下命令。

npx create-react-app .

. 指定我们要在当前文件夹中创建一个React应用程序。

现在让我们安装一些我们在制作这个应用时需要的包。

npm install axios @stripe/react-stripe-js @stripe/stripe-js

  • axios 是一个库,可以轻松地进行HTTP请求,而不需要搞乱fetch API。
  • 这两个Stripe包对于创建Stripe元素和与Stripe通信都很有用。

现在使用以下命令在React应用程序中打开VSCode。

code .

一旦进入集成终端,键入以下命令来启动React应用程序。

npm start

应该打开一个新的浏览器标签,你应该看到以下屏幕。

Screenshot of blank React app

如果你看到这个屏幕,你已经成功启动了一个React应用程序。现在让我们做一些清理工作。

src ,删除以下我们不需要的文件。

  • App.test.js
  • setupTests.js
  • logo.svg

一旦你删除这些文件,你会看到一个错误弹出。这是因为我们破坏了一些东西。

进入App.js ,删除顶部的标识导入和第一个div 下的内容。删除App.css 中的所有内容。

你的App.js 应该看起来像这样。

import "./App.css";
function App() {
  return <div className="app"></div>;
}
export default App;

接下来,让我们创建一个新的组件,名为Checkout 。在src 中创建两个文件:Checkout.jsCheckout.css

由于我们在本教程中不关注样式,我提供了一个CSS文件的内容,但我们不会去看Checkout.css 中实际发生的情况。

.checkout {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  width: 100%;
}
.checkout__container {
  background-color: #f5f5f5;
  padding: 20px;
  width: 25%;
  display: flex;
  flex-direction: column;
}
.checkout__textBox {
  padding: 10px;
  font-size: 18px;
  margin-bottom: 10px;
}
.checkout__radio {
  margin-bottom: 10px;
}
.checkout__btn {
  margin-top: 10px;
  padding: 10px;
  font-size: 18px;
  border: none;
  background-color: #0984e3;
  color: white;
}

现在,打开Checkout.js ,创建一个React功能组件。

import React from "react";
function Checkout() {
  return <div className="checkout"></div>;
}
export default Checkout;

现在让我们导入这个组件并在App.js 中使用它。

import { Elements } from "@stripe/react-stripe-js";
import { loadStripe } from "@stripe/stripe-js";
import "./App.css";
import Checkout from "./Checkout";
const stripePromise = loadStripe("(publishable key here)");
function App() {
  return (
    <div className="app">
      <Elements stripe={stripePromise}>
        <Checkout />
      </Elements>
    </div>
  );
}
export default App;

我们将我们的Checkout 组件包裹在Stripe提供给我们的Elements 中。这个组件充当了我们需要的所有Stripe元素和服务的包装器。

我们使用loadStripe() 函数并传入可发布的密钥,然后在Elements 组件中传入stripePromise 作为stripe 作为道具。

现在让我们去Checkout.js ,并做出我们表单的基本布局。

import { CardElement } from "@stripe/react-stripe-js";
import React, { useState } from "react";
function Checkout() {
  const [email, setEmail] = useState("");
  const [amount, setAmount] = useState("");
  const [subscription, setSubscription] = useState("onetime");
  const handleSubmit = async (e) => {
    try {
      e.preventDefault();
    } catch (error) {
      console.error(error);
      alert("Payment failed!");
    }
  };
  return (
    <div className="checkout">
      <form className="checkout__container" onSubmit={handleSubmit}>
        <input
          type="email"
          value={email}
          className="checkout__textBox"
          onChange={(e) => setEmail(e.target.value)}
          placeholder="E-mail Address"
        />
        <input
          type="number"
          value={amount}
          className="checkout__textBox"
          onChange={(e) => setAmount(e.target.value)}
          placeholder="Amount"
        />
        <div className="checkout__radio">
          <input
            type="radio"
            onChange={(e) => setSubscription("onetime")}
            checked={subscription === "onetime"}
          />
          Onetime
        </div>
        <div className="checkout__radio">
          <input
            type="radio"
            onChange={(e) => setSubscription("monthly")}
            checked={subscription === "monthly"}
          />
          Monthly
        </div>
        <CardElement
          options={{
            style: {
              base: {
                fontSize: "16px",
                color: "#424770",
                "::placeholder": {
                  color: "#aab7c4",
                },
              },
              invalid: {
                color: "#9e2146",
              },
            },
          }}
        />
        <button className="checkout__btn" type="submit">
          Donate
        </button>
      </form>
    </div>
  );
}
export default Checkout;

我们创建了一个基本的表单,要求输入电子邮件和所需的金额。CardElement 组件被用来显示一个小元素,让用户输入卡的详细信息。

现在让我们来处理用户提交表单时的事件。

const handleSubmit = async (e) => {
  try {
    e.preventDefault();
    if (!elements || !stripe) return;
    const cardElement = elements.getElement(CardElement);
    const { error, paymentMethod } = await stripe.createPaymentMethod({
      type: "card",
      card: cardElement,
    });
  } catch (error) {
    console.error(error);
    alert("Payment failed!");
  }
};

首先,我们将检查Stripe和Elements是否已经加载。如果没有,那么表单将不做任何事情。在没有加载Stripe的情况下,你怎么能处理付款?

然后我们进入到cardElement 。之所以太容易找到它,是因为整个表单中只能有一个CardElement

接下来,我们从cardElement 中输入的细节创建一个paymentMethod ,这又会返回一个包含支付方式ID的对象,这是我们在后端需要的。

现在让我们进入后端并处理付款。

首先,让我们导入axios

import axios from "axios"

然后,让我们向后端发出请求,提供关于付款的信息。

const res = await axios.post("http://localhost:5000/donate", {
  amount,
  email,
  subscription,
  stripeToken: paymentMethod.id,
});

如果请求中出现错误或响应代码指向错误,代码将停止执行并转到catch 块来处理错误。

现在后端将尝试执行简单的HTTP支付,我们将得到一个响应。如果我们需要3D安全,actionRequired 将是true

if (res.data.actionRequired) {
  // We perform 3D Secure authentication
  const { paymentIntent, error } = await stripe.confirmCardPayment(
    res.data.clientSecret
  );
  if (error) return alert("Error in payment, please try again later");
  if (paymentIntent.status === "succeeded")
    return alert(`Payment successful, payment ID - ${res.data.id}`);
  const res2 = await axios.get(`http://localhost:5000/check/${res.data.id}`);
  alert(`Payment successful, payment ID - ${res.data.id}`);
} else {
  // Simple HTTP Payment was successful
  alert(`Payment successful, payment ID - ${res.data.id}`);
}

在这里,我们检查actionRequired 是否是true 。如果是,我们需要触发一个3D安全认证弹出窗口。我们通过将我们从服务器得到的clientSecret 传递给confirmCardPayment() 函数,从stripe

然后,我们得到paymentIntent ,并通过向我们的Express服务器的/check 路由发送付款意图ID,从我们的服务器检查付款情况。如果支付成功,路由会返回一个200状态代码,否则我们的代码将通过catch 块,如前所述。

因此,这就是你如何触发3D安全。这里是Checkout.js 的完整代码。

import { CardElement } from "@stripe/react-stripe-js";
import React, { useState } from "react";
import axios from "axios";

function Checkout() {
  const [email, setEmail] = useState("");
  const [amount, setAmount] = useState("");
  const [subscription, setSubscription] = useState("onetime");
  const handleSubmit = async (e) => {
    try {
      e.preventDefault();
      if (!elements || !stripe) return;
      const cardElement = elements.getElement(CardElement);
      const { error, paymentMethod } = await stripe.createPaymentMethod({
        type: "card",
        card: cardElement,
      });
      const res = await axios.post("http://localhost:5000/donate", {
        amount,
        email,
        subscription,
        stripeToken: paymentMethod.id,
      });
      if (res.data.actionRequired) {
        // We perform 3D Secure authentication
        const { paymentIntent, error } = await stripe.confirmCardPayment(
          res.data.clientSecret
        );
        if (error) return alert("Error in payment, please try again later");
        if (paymentIntent.status === "succeeded")
          return alert(`Payment successful, payment ID - ${res.data.id}`);
        const res2 = await axios.get(`http://localhost:5000/check/${res.data.id}`);
        alert(`Payment successful, payment ID - ${res.data.id}`);
      } else {
        // Simple HTTP Payment was successful
        alert(`Payment successful, payment ID - ${res.data.id}`);
      }
    } catch (error) {
      console.error(error);
      alert("Payment failed!");
    }
  };

  return (
    <div className="checkout">
      <form className="checkout__container" onSubmit={handleSubmit}>
        <input
          type="email"
          value={email}
          className="checkout__textBox"
          onChange={(e) => setEmail(e.target.value)}
          placeholder="E-mail Address"
        />
        <input
          type="number"
          value={amount}
          className="checkout__textBox"
          onChange={(e) => setAmount(e.target.value)}
          placeholder="Amount"
        />
        <div className="checkout__radio">
          <input
            type="radio"
            onChange={(e) => setSubscription("onetime")}
            checked={subscription === "onetime"}
          />
          Onetime
        </div>
        <div className="checkout__radio">
          <input
            type="radio"
            onChange={(e) => setSubscription("monthly")}
            checked={subscription === "monthly"}
          />
          Monthly
        </div>
        <CardElement
          options={{
            style: {
              base: {
                fontSize: "16px",
                color: "#424770",
                "::placeholder": {
                  color: "#aab7c4",
                },
              },
              invalid: {
                color: "#9e2146",
              },
            },
          }}
        />
        <button className="checkout__btn" type="submit">
          Donate
        </button>
      </form>
    </div>
  );
}
export default Checkout;

为了测试你的Stripe集成,这里有Stripe提供的一些卡的细节来测试。你需要在测试模式下使用这些卡,而且你不会被收费。

当你用3D安全技术输入卡片时,将打开一个弹出窗口。在生产环境中,用户将被发送一条短信到他们的手机号码,以验证付款。

你可以设置你的雷达规则,对支持的卡强制使用3D安全,只是要注意,雷达规则并不适用所有国家。

下一步是什么?

我建议你查看Stripe的更多内容,比如Apple Pay、Google Pay、保存的卡片、非会话支付以及提供的其他多种支付方式。

你也可以查看Stripe Checkout,在那里你只需要传入产品,付款将由Stripe处理。

The postImplementing 3D Secure in Stripeappeared first onLogRocket Blog.