如何在Spring Boot中开始使用Stripe

1,624 阅读7分钟

在Spring Boot中开始使用Stripe

Stripe是一个几乎与Paypal类似的在线支付系统,它可以利用客户在网上购买产品时提供的信用卡来处理交易。

在本教程中,读者将学习如何通过创建一个简单的Web应用来将Stripe集成到他们的应用程序中,该应用可以收集卡的详细信息并将付款提交给Stripe。

前提条件

要继续学习本教程,你需要具备以下条件。

  • [Spring Boot]方面的知识[。]
  • [Thymeleaf方面的知识。]
  • 在你的电脑上安装了[JDK 11+]。
  • 在你的电脑上安装[Intellij IDEA]。

创建一个stripe账户

创建一个stripe账户是必须的,这样才能访问我们以后用来测试应用程序的公钥和私钥。

当我们登录stripe帐户时,我们将被重定向到仪表板,这就是我们将验证支付是否成功的地方。API密钥部分包含我们的私人和公共密钥,我们可以通过检查密钥上的前缀字符来区分实时密钥和测试密钥。

测试钥匙的前缀是pk_test_ ,表示私钥或sk_test_ ,表示秘钥,而实时钥匙的前缀是pk_live_ ,表示私钥或sk_live_ ,表示秘钥。

项目设置

我们将使用Spring Initialzr来生成一个Spring Boot应用程序,其依赖关系如下。

  • Spring Web
  • Thymeleaf
  • Spring Boot开发工具
  • 验证

Project set up

为了确保应用程序可以使用公钥和私钥,我们在application.properties 文件中添加基本内容,然后我们可以使用@Value 注解注入这些值。@Value 注解用于表达式驱动或属性驱动的依赖注入。

stripe.api.key=sk_test_
stripe.public.key=pk_test_

设置stripe

设置stripe需要在Maven用户的POM.xml文件中添加stripe API的依赖项,该依赖项可以从maven中央仓库获得。Maven会下载所提供的stripe API版本,并将其添加到classpath中,以确保开发过程中可用。

Stripe有一个运行时依赖gson ,这是一个JSON库,我们将把它添加到maven中,以确保运行应用程序时不会出现依赖错误。

<dependency>
    <groupId>com.stripe</groupId>
    <artifactId>stripe-java</artifactId>
    <version>20.77.0</version>
</dependency>

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>

Spring Boot默认使用Jackson,这是一个JSON marshaller,当你在classpath上同时使用它们时,会有一些边缘情况。请确保在你的POM.xml文件中使用以下配置,将Jackson从classpath中排除。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-json</artifactId>
        </exclusion>
    </exclusions>
</dependency>

创建一个请求数据传输对象

这是一个DTO,浏览器将发送至我们的服务器。在一个名为dto 的包内创建一个CreatePayment 类,其字段为amountfeatureRequest 。为这两个字段生成getter和setter方法。

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class CreatePayment {

    @NotNull
    @Min(4)
    private Integer amount;

    @NotNull
    @Size(min = 5, max = 200)
    private String featureRequest;

    public Integer getAmount() {
        return amount;
    }

    public void setAmount(Integer amount) {
        this.amount = amount;
    }

    public String getFeatureRequest() {
        return featureRequest;
    }

    public void setFeatureRequest(String featureRequest) {
        this.featureRequest = featureRequest;
    }
}

@NotNull - 表示该字段是必填的,不能为空。

@Min() - 用于限制所提供的值为一个特定的最小值。

@Size - 通过指定最小值和最大值,提供该字段可以支持的字符串范围。

创建一个PaymentIntent

PaymentIntent 是stripe用来记录客户信息、跟踪收费尝试以及将支付状态从一个阶段改为另一个阶段的对象。下图显示了如何创建一个PaymentIntent ,并跟踪从提供卡的详细信息、尝试付款到最终付款的过程。

Payment intent image

在一个名为controller 的包内创建一个PaymentController 类,并添加一个带有/create-payment-intent 端点的后映射方法。浏览器调用/create-payment-intent 端点,该端点必须调用 stripe 来创建支付意图。

PaymentIntentCreateParams 使用setCurrency()setAmount() 方法告诉 stripe 要使用的货币、用户要购买的产品以及产品的价格。

import com.example.StripeWithSpringBoot.dto.CreatePayment;
import com.example.StripeWithSpringBoot.dto.CreatePaymentResponse;
import com.stripe.exception.StripeException;
import com.stripe.model.PaymentIntent;
import com.stripe.param.PaymentIntentCreateParams;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
public class PaymentController {
    @PostMapping("/create-payment-intent")
    public CreatePaymentResponse createPaymentIntent(@RequestBody @Valid  CreatePayment createPayment)throws StripeException {
        PaymentIntentCreateParams createParams = new
                PaymentIntentCreateParams.Builder()
                .setCurrency("usd")
                .putMetadata("featureRequest", createPayment.getFeatureRequest())
                .setAmount(createPayment.getAmount() * 100L)
                .build();

        PaymentIntent intent = PaymentIntent.create(createParams);
        return new CreatePaymentResponse(intent.getClientSecret());
    }
}

为产品创建一个模型

model 包内创建一个CheckoutForm 类,其字段为amount,featureRequest, 和email 。接下来,为这些字段生成getter和setter方法。这个类代表了特定付款的客户细节,将在结账过程中提供。

import javax.validation.constraints.*;

public class CheckoutForm {

    @NotNull
    @Min(4)
    private Integer amount;

    @NotNull
    @Size(min = 5,max = 200)
    private String featureRequest;

    @Email
    private String email;

    public Integer getAmount() {
        return amount;
    }

    public void setAmount(Integer amount) {
        this.amount = amount;
    }

    public String getFeatureRequest() {
        return featureRequest;
    }

    public void setFeatureRequest(String featureRequest) {
        this.featureRequest = featureRequest;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

@Email - 表示这个字段应该只接受一个带有电子邮件结构的字符串。

创建支付和结账页面

首先,创建一个名为client.js 的文件,我们将用它来处理从客户到服务器的请求和从服务器到浏览器的响应。当用户进入该页面时,会创建一个支付意向,支付意向会调用stripe,告知客户要付款,并返回一个密匙。

divStripe有一个叫做javascript元素的javascript库,当你在一个特定的elements.create() ,那么stripe会把这个组件插入到div中。

支付发生在confirmCardPayment() 方法中,该方法接受客户的秘密和卡片,一旦你提交,一切都发生在stripe和浏览器之间,没有人看到数据。

// A reference to Stripe.js initialized with your real test publishable API key.
var stripe = Stripe(stripePublicKey);

// The items the customer wants to buy
var purchase = {
    email:email
    amount: amount,
    featureRequest: featureRequest
};

// Disable the button until we have Stripe set up on the page
document.querySelector("button").disabled = true;
fetch("/create-payment-intent", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify(purchase)
})
    .then(function(result) {
        return result.json();
    })
    .then(function(data) {
        var elements = stripe.elements();

        var style = {
            base: {
                color: "#32325d",
                fontFamily: 'Arial, sans-serif',
                fontSmoothing: "antialiased",
                fontSize: "16px",
                "::placeholder": {
                    color: "#32325d"
                }
            },
            invalid: {
                fontFamily: 'Arial, sans-serif',
                color: "#fa755a",
                iconColor: "#fa755a"
            }
        };

        var card = elements.create("card", { style: style });
        // Stripe injects an iframe into the DOM
        card.mount("#card-element");

        card.on("change", function (event) {
            // Disable the Pay button if there are no card details in the Element
            document.querySelector("button").disabled = event.empty;
            document.querySelector("#card-error").textContent = event.error ? event.error.message : "";
        });

        var form = document.getElementById("payment-form");
        form.addEventListener("submit", function(event) {
            event.preventDefault();
            // Complete payment when the submit button is clicked
            payWithCard(stripe, card, data.clientSecret);
        });
    });

// Calls stripe.confirmCardPayment
// If the card requires authentication Stripe shows a pop-up modal to
// prompt the user to enter authentication details without leaving your page.
var payWithCard = function(stripe, card, clientSecret) {
    loading(true);
    stripe
        .confirmCardPayment(clientSecret, {
            receipt_email: email,
            payment_method: {
                card: card,
                billing_details: {
                    email: email
                }
            }
        })
        .then(function(result) {
            if (result.error) {
                // Show error to your customer
                showError(result.error.message);
            } else {
                // The payment succeeded!
                orderComplete(result.paymentIntent.id);
            }
        });
};

/* ------- UI helpers ------- */

// Shows a success message when the payment is complete
var orderComplete = function(paymentIntentId) {
    loading(false);
    document
        .querySelector(".result-message a")
        .setAttribute(
            "href",
            "https://dashboard.stripe.com/test/payments/" + paymentIntentId
        );
    document.querySelector(".result-message").classList.remove("hidden");
    document.querySelector("button").disabled = true;
};

// Show the customer the error from Stripe if their card fails to charge
var showError = function(errorMsgText) {
    loading(false);
    var errorMsg = document.querySelector("#card-error");
    errorMsg.textContent = errorMsgText;
    setTimeout(function() {
        errorMsg.textContent = "";
    }, 4000);
};

// Show a spinner on payment submission
var loading = function(isLoading) {
    if (isLoading) {
        // Disable the button and show a spinner
        document.querySelector("button").disabled = true;
        document.querySelector("#spinner").classList.remove("hidden");
        document.querySelector("#button-text").classList.add("hidden");
    } else {
        document.querySelector("button").disabled = false;
        document.querySelector("#spinner").classList.add("hidden");
        document.querySelector("#button-text").classList.remove("hidden");
    }
};

在资源部分下创建一个名为index.html 的文件,我们将用这个页面向顾客显示一个付款表格。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Accept a card payment</title>
  <meta name="description" content="A demo of a card payment on Stripe" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <link rel="stylesheet" href="global.css" />
  <script src="client.js" defer></script>
</head>

<body>
<!-- Display a payment form -->
<div class="container">
  <div class="form-card">
    <form id="payment-form" th:action="@{/}" th:object="${checkoutForm}" method="post">
      <p><b>Payment details for a product</b></p>
      <div class="form-control">
        <input type="text" th:field="*{email}" placeholder="Email address" required />
        <p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" style="color: red;"></p>
      </div>
      <div class="form-control">
        <input type="text" th:field="*{amount}" placeholder="Amount" required />
        <p th:if="${#fields.hasErrors('amount')}" th:errors="*{amount}" style="color: red;"></p>
      </div>
      <div class="form-control">
        <input type="text" th:field="*{featureRequest}" placeholder="Feature Request" required />
        <p th:if="${#fields.hasErrors('featureRequest')}" th:errors="*{featureRequest}" style="color: red;"></p>
      </div>
      <p><input type="submit" class="btn btn-primary"></p>
    </form>

  </div>

</div>
</body>
</html>

一旦客户填写了所有的细节并按下提交按钮,他们将被重定向到一个结账页面,输入他们的信用卡细节并完成付款。

在资源部分创建一个名为checkout.html 的文件,我们将在其中向客户显示一个结账表格。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Accept a card payment</title>
  <meta name="description" content="A demo of a card payment on Stripe" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <link rel="stylesheet" href="global.css" />
  <script src="https://js.stripe.com/v3/"></script>
  <script src="https://polyfill.io/v3/polyfill.min.js?version=3.52.1&features=fetch"></script>
  <script th:inline="javascript">

    /*<![CDATA[*/

    var stripePublicKey = /*[[${stripePublicKey}]]*/ null;
    var amount = /*[[${amount}]]*/ null;
    var email = /*[[${email}]]*/ null;
    var featureRequest = /*[[${featureRequest}]]*/ null;

    /*]]>*/
  </script>
  <script src="client.js" defer></script>
</head>

<body>
<!-- Display a payment form -->
<div class="container">
  <div class="form-card">
    <form id="payment-form">
      <p><b>Credit card details to complete payment</b></p>
      <p>Press the pay now button to complete your payment of <span th:text="${amount}"></span>USD.</p>
          <div id="card-element" class="form-control"><!--Stripe.js injects the Card Element--></div>
      <button id="submit" class="btn btn-primary">
        <div class="spinner hidden" id="spinner"></div>
        <span id="button-text">Pay now</span>
      </button>
      <p id="card-error" role="alert"></p>
      <p class="result-message hidden">
        Payment succeeded, see the result in your
        <a href="" target="_blank">Stripe dashboard.</a> Refresh the page to pay again.
      </p>
    </form>

  </div>

</div>
</body>
</html>

创建一个名为global.css 的文件,我们将用它来为我们的支付和结账页面着色。

/* Variables */
:root{
    --primary-color: #047aed;
}
.container{
    max-width: 1100px;
    margin: 0 auto;
    overflow: auto;
    padding: 0 40px;
}
.form-card{
    background-color: #fff;
    color: #333;
    border-radius: 10px;
    box-shadow: 0 3px 10px rgba(0,0,0,0.2);
    padding: 20px;
    margin: 10px;
}
.btn{
    display: inline-block;
    padding: 10px 30px;
    cursor: pointer;
    background: var(--primary-color);
    color: #fff;
    border: none;
    border-radius: 5px;
}
.btn-primary{
    background-color: var(--primary-color);
    color: #fff;
}
.form-control{
    margin: 30px 0;
}
.form-card input[type='text']{
    border: 0;
    border-bottom: 1px solid #b4becb;
    width: 100%;
    padding: 3px;
    font-size: 16px;
}
.form-card input:focus{
    outline: none;
}
.hidden{
    display: none;
}

为付款创建一个控制器

  • controller 包内创建一个WebController 类,并使用@Value 注释注入 stripe 公钥。
  • 添加一个方法,当客户向/ 端点发出请求时返回支付表单。
  • 创建另一个方法,在输入付款细节并点击提交按钮后返回结账页面。
import com.example.StripeWithSpringBoot.model.CheckoutForm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import javax.validation.Valid;

@Controller
public class WebController {

    @Value("${stripe.public.key}")
    private String stripePublicKey;

    @GetMapping("/")
    public String home(Model model){
        model.addAttribute("checkoutForm",new CheckoutForm());
        return "index";
    }

    @PostMapping("/")
    public String checkout(@ModelAttribute
                                       @Valid CheckoutForm checkoutForm,
                           BindingResult bindingResult,
                           Model model){
        if (bindingResult.hasErrors()){
            return "index";
        }

        model.addAttribute("stripePublicKey",stripePublicKey);
        model.addAttribute("amount",checkoutForm.getAmount());
        model.addAttribute("email", checkoutForm.getEmail());
        model.addAttribute("featureRequest", checkoutForm.getFeatureRequest());
        return "checkout";
    }
}

测试应用程序

Stripe提供了几个测试号码,我们可以用来测试支付,在本教程中,我们将利用4242 4242 4242 4242 ,这是一个Visa卡号码。为CVC提供任何3 数字,为日期字段提供任何未来日期,这样当我们运行应用程序并导航到localhost:8080 ,浏览器就会显示以下支付页面。

payment form

当我们输入所有的输入字段并按下提交按钮时,我们将被重定向到结账页面,以输入付款细节,因此输入4242 4242 4242 4242 作为测试卡号,任何三个CVC号码和任何未来日期,并按下提交按钮。

checkout form

当你按下现在支付按钮时,付款将被成功处理,并且我们的Stripe仪表盘的链接将被添加到表单中。Stripe仪表板会显示支付的金额、日期、客户和支付方式,如下图所示。

stripe dashboard

总结

在本教程中,我们学习了如何通过利用Stripe支付意图在Spring Boot应用程序中集成Stripe,确保每笔交易至少有一笔付款。下面的教程将帮助读者学习如何通过创建一个包含详细信息的webhook,将stripe返回的详细信息存储到我们的数据库。