1. Overview
1.概述
Stripe is a cloud-based service that enables businesses and individuals to receive payments over the internet and offers both client-side libraries (JavaScript and native mobile) and server-side libraries (Java, Ruby, Node.js, etc.).
Stripe是一项基于云的服务,使企业和个人能够通过互联网接收付款,并同时提供客户端库(JavaScript和本地移动)和服务器端库(Java、Ruby、Node.js等)。
Stripe provides a layer of abstraction that reduces the complexity of receiving payments. As a result, we don’t need to deal with credit card details directly – instead, we deal with a token symbolizing an authorization to charge.
Stripe提供了一个抽象层,降低了接收付款的复杂性。因此,我们不需要直接处理信用卡的详细信息 – 相反,我们处理的是一个象征着授权收费的令牌。
In this tutorial, we will create a sample Spring Boot project that allows users to input a credit card and later will charge the card for a certain amount using the Stripe API for Java.
在本教程中,我们将创建一个Spring Boot示例项目,允许用户输入信用卡,随后将使用Stripe API for Java向该卡收取一定的费用。
2. Dependencies
2.依赖性
To make use of the Stripe API for Java in the project, we add the corresponding dependency to our pom.xml:
为了在项目中使用Stripe API for Java,我们在pom.xml中添加相应的依赖项。
<dependency>
<groupId>com.stripe</groupId>
<artifactId>stripe-java</artifactId>
<version>4.2.0</version>
</dependency>
We can find its latest version in the Maven Central repository.
我们可以在Maven Central资源库中找到其最新版本。
For our sample project, we will leverage the spring-boot-starter-parent:
对于我们的示例项目,我们将利用spring-boot-starter-parent。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
</parent>
We will also use Lombok to reduce boilerplate code, and Thymeleaf will be the template engine for delivering dynamic web pages.
我们还将使用Lombok来减少模板代码,而Thymeleaf将作为模板引擎来提供动态网页。
Since we are using the spring-boot-starter-parent to manage the versions of these libraries, we don’t have to include their versions in pom.xml:
由于我们使用spring-boot-starter-parent来管理这些库的版本,我们不必在pom.xml中包含它们的版本。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
Note that if you’re using NetBeans, you may want to use Lombok explicitly with version 1.16.16, since a bug in the version of Lombok provided with Spring Boot 1.5.2 causes NetBeans to generate a lot of errors.
注意,如果你使用NetBeans,你可能要明确使用1.16.16版本的Lombok,因为Spring Boot 1.5.2提供的Lombok版本有一个错误,导致NetBeans产生很多错误。
3. API Keys
3.API密钥
Before we can communicate with Stripe and execute credit card charges, we need to register a Stripe account and obtain secret/public Stripe API keys.
在我们与Stripe通信并执行信用卡收费之前,我们需要注册一个Stripe帐户并获得秘密/公开的Stripe API密钥。
After confirming the account, we will log in to access the Stripe dashboard. We then choose “API keys” on the left side menu:
确认账户后,我们将登录访问Stripe仪表板。然后我们在左侧菜单中选择 “API密钥”。
There will be two pairs of secret/public keys — one for test and one for live. Let’s leave this tab open so that we can use these keys later.
将有两对秘密/公共密钥 – 一个用于测试,一个用于现场。让我们把这个标签打开,以便我们以后可以使用这些钥匙。
4. General Flow
4.一般流程
The charge of the credit card will be done in five simple steps, involving the front-end (run in a browser), back-end (our Spring Boot application), and Stripe:
信用卡的收费将在五个简单的步骤中完成,涉及前端(在浏览器中运行)、后端(我们的Spring Boot应用程序)和Stripe。
- A user goes to the checkout page and clicks “Pay with Card”.
- A user is presented with Stripe Checkout overlay dialog, where fills the credit card details.
- A user confirms with “Pay <amount>” which will:
- Send the credit card to Stripe
- Get a token in the response which will be appended to the existing form
- Submit that form with the amount, public API key, email, and the token to our back-end
- Our back-end contacts Stripe with the token, the amount, and the secret API key.
- Back-end checks Stripe response and provide the user with feedback of the operation.
We will cover each step in greater detail in the following sections.
我们将在以下章节中更详细地介绍每一个步骤。
5. Checkout Form
5.结账表格
Stripe Checkout is a customizable, mobile ready, and localizable widget that renders a form to introduce credit card details. Through the inclusion and configuration of “checkout.js“, it is responsible for:
Stripe Checkout是一个可定制的、可移动的和可本地化的部件,它渲染了一个用于引入信用卡详细信息的表单。通过包含和配置”checkout.js“,它负责。
- “Pay with Card” button rendering
- Payment overlay dialog rendering (triggered after clicking “Pay with Card”)
- Credit card validation
- “Remember me” feature (associates the card with a mobile number)
- Sending the credit card to Stripe and replacing it with a token in the enclosing form (triggered after clicking “Pay <amount>”)
If we need to exercise more control over the checkout form than is provided by Stripe Checkout, then we can use Stripe Elements.
如果我们需要对结账表单进行比Stripe Checkout所提供的更多控制,那么我们可以使用Stripe Elements。。
Next, we will analyze the controller that prepares the form and then the form itself.
接下来,我们将分析准备表单的控制器,然后分析表单本身。
5.1. Controller
5.1.控制器
Let’s start by creating a controller to prepare the model with the necessary information that the checkout form needs.
让我们先创建一个控制器,用结账表单需要的必要信息来准备模型。
First, we’ll need to copy the test version of our public key from the Stripe dashboard and use it to define STRIPE_PUBLIC_KEY as an environment variable. We then use this value in the stripePublicKey field.
首先,我们需要从Stripe仪表板复制我们的公钥测试版本,并使用它来定义STRIPE_PUBLIC_KEY作为一个环境变量。然后我们在stripePublicKey字段中使用这个值。
We’re also setting currency and amount (expressed in cents) manually here merely for demonstration purposes, but in a real application, we might set a product/sale id that could be used to fetch the actual values.
我们还在这里手动设置currency和amount(以美分表示),只是为了演示,但在实际应用中,我们可能会设置一个产品/销售ID,用来获取实际值。
Then, we’ll dispatch to the checkout view which holds the checkout form:
然后,我们将调度到持有结账表单的结账视图。
@Controller
public class CheckoutController {
@Value("${STRIPE_PUBLIC_KEY}")
private String stripePublicKey;
@RequestMapping("/checkout")
public String checkout(Model model) {
model.addAttribute("amount", 50 * 100); // in cents
model.addAttribute("stripePublicKey", stripePublicKey);
model.addAttribute("currency", ChargeRequest.Currency.EUR);
return "checkout";
}
}
Regarding the Stripe API keys, you can define them as environment variables per application (test vs. live).
关于Stripe的API密钥,您可以将它们定义为每个应用程序的环境变量(测试与实时)。
As is the case with any password or sensitive information, it is best to keep the secret key out of your version control system.
正如任何密码或敏感信息一样,最好不要让秘密密钥进入你的版本控制系统。
5.2. Form
5.2.形式
The “Pay with Card” button and the checkout dialog are included by adding a form with a script inside, correctly configured with data attributes:
用卡支付 “按钮和结账对话框是通过添加一个带有脚本的表单来实现的,并正确配置了数据属性。
<form action='/charge' method='POST' id='checkout-form'>
<input type='hidden' th:value='${amount}' name='amount' />
<label>Price:<span th:text='${amount/100}' /></label>
<!-- NOTE: data-key/data-amount/data-currency will be rendered by Thymeleaf -->
<script
src='https://checkout.stripe.com/checkout.js'
class='stripe-button'
th:attr='data-key=${stripePublicKey},
data-amount=${amount},
data-currency=${currency}'
data-name='Baeldung'
data-description='Spring course checkout'
data-image
='https://www.baeldung.com/wp-content/themes/baeldung/favicon/android-chrome-192x192.png'
data-locale='auto'
data-zip-code='false'>
</script>
</form>
The “checkout.js” script automatically triggers a request to Stripe right before the submit, which then appends the Stripe token and the Stripe user email as the hidden fields “stripeToken” and “stripeEmail“.
checkout.js“脚本会在提交前自动触发对Stripe的请求,然后将Stripe令牌和Stripe用户的电子邮件追加为隐藏字段”stripeToken“和”stripeEmail“。
These will be submitted to our back-end along with the other form fields. The script data attributes are not submitted.
这些将和其他表单字段一起被提交到我们的后端。脚本数据属性不会被提交。
We use Thymeleaf to render the attributes “data-key“, “data-amount“, and “data-currency“.
我们使用Thymeleaf来渲染属性”data-key“、”data-amount“和”data-currency“。
The amount (“data-amount“) is used only for display purposes (along with “data-currency“). Its unit is cents of the used currency, so we divide it by 100 to display it.
金额(”data-amount“)仅用于显示目的(与”data-currency“一起)。它的单位是所用货币的美分,所以我们用它除以100来显示它。
The Stripe public key is passed to Stripe after the user asks to pay. Do not use the secret key here, as this is sent to the browser.
在用户要求付款后,Stripe公钥将被传递给Stripe。不要在这里使用秘钥,因为这将被发送到浏览器。
6. Charge Operation
6.充电操作
For server-side processing, we need to define the POST request handler used by the checkout form. Let’s take a look at the classes we will need for the charge operation.
对于服务器端的处理,我们需要定义结账表单所使用的POST请求处理程序。让我们来看看我们在收费操作中需要的类。
6.1. ChargeRequest Entity
6.1.收费请求实体
Let’s define the ChargeRequest POJO that we will use as a business entity during the charge operation:
让我们定义ChargeRequest POJO,我们将在收费操作中作为业务实体使用。
@Data
public class ChargeRequest {
public enum Currency {
EUR, USD;
}
private String description;
private int amount;
private Currency currency;
private String stripeEmail;
private String stripeToken;
}
6.2. Service
6.2.服务
Let’s write a StripeService class to communicate the actual charge operation to Stripe:
让我们写一个StripeService类来向Stripe传达实际收费操作。
@Service
public class StripeService {
@Value("${STRIPE_SECRET_KEY}")
private String secretKey;
@PostConstruct
public void init() {
Stripe.apiKey = secretKey;
}
public Charge charge(ChargeRequest chargeRequest)
throws AuthenticationException, InvalidRequestException,
APIConnectionException, CardException, APIException {
Map<String, Object> chargeParams = new HashMap<>();
chargeParams.put("amount", chargeRequest.getAmount());
chargeParams.put("currency", chargeRequest.getCurrency());
chargeParams.put("description", chargeRequest.getDescription());
chargeParams.put("source", chargeRequest.getStripeToken());
return Charge.create(chargeParams);
}
}
As was shown in the CheckoutController, the secretKey field is populated from the STRIPE_SECRET_KEY environment variable that we copied from the Stripe dashboard.
正如在CheckoutController中所示,secretKey字段是由我们从Stripe仪表板复制的STRIPE_SECRET_KEY环境变量填充的。
Once the service has been initialized, this key is used in all subsequent Stripe operations.
一旦服务被初始化,这个密钥就会在所有后续的Stripe操作中使用。
The object returned by the Stripe library represents the charge operation and contains useful data like the operation id.
由Stripe库返回的对象代表充值操作,并包含有用的数据,如操作ID。
6.3. Controller
6.3.控制器
Finally, let’s write the controller that will receive the POST request made by the checkout form and submit the charge to Stripe via our StripeService.
最后,让我们写一个控制器,它将接收由结账表单发出的POST请求,并通过我们的StripeService将费用提交给Stripe。
Note that the “ChargeRequest” parameter is automatically initialized with the request parameters “amount“, “stripeEmail“, and “stripeToken” included in the form:
请注意,”ChargeRequest“参数会被自动初始化,包含在表单中的请求参数”amount“、”stripeEmail“和”stripeToken“。
@Controller
public class ChargeController {
@Autowired
private StripeService paymentsService;
@PostMapping("/charge")
public String charge(ChargeRequest chargeRequest, Model model)
throws StripeException {
chargeRequest.setDescription("Example charge");
chargeRequest.setCurrency(Currency.EUR);
Charge charge = paymentsService.charge(chargeRequest);
model.addAttribute("id", charge.getId());
model.addAttribute("status", charge.getStatus());
model.addAttribute("chargeId", charge.getId());
model.addAttribute("balance_transaction", charge.getBalanceTransaction());
return "result";
}
@ExceptionHandler(StripeException.class)
public String handleError(Model model, StripeException ex) {
model.addAttribute("error", ex.getMessage());
return "result";
}
}
On success, we add the status, the operation id, the charge id, and the balance transaction id to the model so that we can show them later to the user (Section 7). This is done to illustrate some of the contents of the charge object.
成功后,我们将状态、操作ID、收费ID和余额交易ID添加到模型中,这样我们就可以在以后向用户展示它们(第7节)。这样做是为了说明charge对象的一些内容。
Our ExceptionHandler will deal with exceptions of type StripeException that are thrown during the charge operation.
我们的ExceptionHandler将处理在充电操作中抛出的StripeException类型的异常。
If we need more fine-grained error handling, we can add separate handlers for the subclasses of StripeException, such as CardException, RateLimitException, or AuthenticationException.
如果我们需要更精细的错误处理,我们可以为StripeException的子类添加单独的处理程序,例如CardException,RateLimitException,或AuthenticationException。
The “result” view renders the result of the charge operation.
result“视图渲染了充电操作的结果。
7. Showing the Result
7.显示结果
The HTML used to display the result is a basic Thymeleaf template that displays the outcome of a charge operation. The user is sent here by the ChargeController whether the charge operation was successful or not:
用来显示结果的HTML是一个基本的Thymeleaf模板,用来显示充电操作的结果。用户通过ChargeController将充电操作是否成功发送到这里。
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml' xmlns:th='http://www.thymeleaf.org'>
<head>
<title>Result</title>
</head>
<body>
<h3 th:if='${error}' th:text='${error}' style='color: red;'></h3>
<div th:unless='${error}'>
<h3 style='color: green;'>Success!</h3>
<div>Id.: <span th:text='${id}' /></div>
<div>Status: <span th:text='${status}' /></div>
<div>Charge id.: <span th:text='${chargeId}' /></div>
<div>Balance transaction id.: <span th:text='${balance_transaction}' /></div>
</div>
<a href='/checkout.html'>Checkout again</a>
</body>
</html>
On success, the user will see some details of the charge operation:
一旦成功,用户将看到充电操作的一些细节。
On error, the user will be presented with the error message as returned by Stripe:
出错时,用户将看到由Stripe返回的错误信息。
8. Conclusion
8.结论
In this tutorial, we’ve shown how to make use of the Stripe Java API to charge a credit card. In the future, we could reuse our server-side code to serve a native mobile app.
在本教程中,我们展示了如何利用Stripe Java API来收取信用卡费用。在未来,我们可以重新使用我们的服务器端代码来为本地移动应用程序服务。
To test the entire charge flow, we don’t need to use a real credit card (even in test mode). We can rely on Stripe testing cards instead.
为了测试整个收费流程,我们不需要使用真正的信用卡(即使在测试模式下)。我们可以依靠Stripe测试卡代替。
The charge operation is one among many possibilities offered by the Stripe Java API. The official API reference will guide us through the whole set of operations.
充值操作是Stripe Java API提供的许多可能性中的一种。官方API参考将指导我们完成整套操作。
The sample code used in this tutorial can be found in the GitHub project.
本教程中使用的示例代码可以在GitHub项目中找到。