1. 概述

Stripe 是一个云服务,让企业和个人能够通过互联网接收支付。它同时提供客户端库(JavaScript 和原生移动端)和服务器端库(Java、Ruby、Node.js 等)。

Stripe 通过抽象层降低了支付处理的复杂性。我们不需要直接处理信用卡细节——而是使用代表扣款授权的令牌(token)

本教程将创建一个 Spring Boot 示例项目,允许用户输入信用卡信息,然后通过 Stripe Java API 对指定金额进行扣款。

2. 依赖配置

要在项目中使用 Stripe Java API,需在 pom.xml 中添加依赖:

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

最新版本可在 Maven 中央仓库 查找。

示例项目使用 spring-boot-starter-parent

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.2</version>
</parent>

同时引入 Lombok 减少样板代码,Thymeleaf 作为模板引擎:

<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>

⚠️ NetBeans 用户注意:需显式指定 Lombok 1.16.16 版本,Spring Boot 1.5.2 自带的版本会导致 NetBeans 报错。

3. API 密钥

与 Stripe 通信执行扣款前,需**注册 Stripe 账户 并获取密钥对**。

登录后访问 Stripe 仪表盘,左侧菜单选择 "API keys":

Stripe 仪表盘 API 密钥

会看到两对密钥——测试密钥和正式密钥。保持此页面开启,后续需要使用这些密钥。

4. 整体流程

信用卡扣款分五步完成,涉及前端(浏览器)、后端(Spring Boot 应用)和 Stripe:

  1. 用户进入结账页面,点击 "Pay with Card"
  2. 弹出 Stripe Checkout 对话框,用户填写信用卡信息
  3. 用户点击 "Pay " 后:
    • 信用卡信息发送至 Stripe
    • 获取令牌(token)并追加到表单
    • 表单提交金额、公钥、邮箱和令牌到后端
  4. 后端使用令牌、金额和私钥联系 Stripe
  5. 后端检查 Stripe 响应并向用户反馈结果

Stripe 支付流程

后续章节将详细展开每一步。

5. 结账表单

Stripe Checkout 是一个可定制、移动端友好、支持多语言的组件,用于渲染信用卡信息表单。通过配置 "checkout.js",它负责:

  • 渲染 "Pay with Card" 按钮
    Pay with Card 按钮
  • 渲染支付弹窗(点击按钮后触发)
    Stripe 结账表单弹窗
  • 信用卡验证
  • "记住我" 功能(关联手机号)
  • 发送信用卡至 Stripe 并替换为令牌(点击支付后触发)

若需更灵活的表单控制,可使用 Stripe Elements

接下来分析表单控制器和表单本身。

5.1. 控制器

创建控制器准备结账表单所需的模型数据

@Controller
public class CheckoutController {

    @Value("${STRIPE_PUBLIC_KEY}")
    private String stripePublicKey;

    @RequestMapping("/checkout")
    public String checkout(Model model) {
        model.addAttribute("amount", 50 * 100); // 单位:分
        model.addAttribute("stripePublicKey", stripePublicKey);
        model.addAttribute("currency", ChargeRequest.Currency.EUR);
        return "checkout";
    }
}

关键点

  • 从 Stripe 仪表盘复制测试公钥,设为环境变量 STRIPE_PUBLIC_KEY
  • 金额(单位:分)和货币仅为演示,实际应用应从产品/订单获取
  • 密钥管理建议:测试/正式环境使用不同环境变量

⚠️ 安全提醒:私钥绝不能提交到版本控制系统!

5.2. 表单

通过配置表单和脚本实现支付功能:

<form action='/charge' method='POST' id='checkout-form'>
    <input type='hidden' th:value='${amount}' name='amount' />
    <label>价格:<span th:text='${amount/100}' /></label>
    <!-- Thymeleaf 动态渲染 data-key/data-amount/data-currency -->
    <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 课程结账'
       data-image
         ='https://www.baeldung.com/wp-content/themes/baeldung/favicon/android-chrome-192x192.png'
       data-locale='auto'
       data-zip-code='false'>
   </script>
</form>

核心机制

  • "checkout.js" 在提交前自动向 Stripe 发送请求
  • 返回的令牌(token)和用户邮箱作为隐藏字段 "stripeToken" 和 "stripeEmail" 追加到表单
  • 脚本属性(如 data-amount)仅用于显示(单位:分),需除以 100 显示实际金额
  • 必须使用公钥(浏览器可见),绝不能使用私钥!

6. 扣款操作

后端需处理表单提交的 POST 请求。以下是扣款操作的核心类:

6.1. 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. 服务层

创建 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);
    }
}

关键实现

  • 从环境变量 STRIPE_SECRET_KEY 读取私钥
  • 初始化时设置全局 API 密钥
  • 返回的 Charge 对象 包含操作 ID 等关键信息

6.3. 控制器

处理表单提交的控制器:

@Controller
public class ChargeController {

    @Autowired
    private StripeService paymentsService;

    @PostMapping("/charge")
    public String charge(ChargeRequest chargeRequest, Model model)
      throws StripeException {
        chargeRequest.setDescription("示例扣款");
        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";
    }
}

流程解析

  • ChargeRequest 自动绑定表单字段(amount/stripeEmail/stripeToken)
  • 成功时将 Charge 对象 的关键信息加入模型
  • 异常处理器统一处理 StripeException(可细分子类如 CardException
  • 返回 "result" 视图展示结果

7. 结果展示

使用 Thymeleaf 模板展示扣款结果:

<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml' xmlns:th='http://www.thymeleaf.org'>
    <head>
        <title>扣款结果</title>
    </head>
    <body>
        <h3 th:if='${error}' th:text='${error}' style='color: red;'></h3>
        <div th:unless='${error}'>
            <h3 style='color: green;'>支付成功!</h3>
            <div>交易ID: <span th:text='${id}' /></div>
            <div>状态: <span th:text='${status}' /></div>
            <div>扣款ID: <span th:text='${chargeId}' /></div>
            <div>余额事务ID: <span th:text='${balance_transaction}' /></div>
        </div>
        <a href='/checkout.html'>重新结账</a>
    </body>
</html>

成功时显示交易详情: 扣款成功

失败时显示 Stripe 返回的错误信息: 扣款错误

8. 总结

本文展示了如何使用 Stripe Java API 实现信用卡扣款。未来可复用后端代码支持原生移动应用。

测试技巧:无需真实信用卡,使用 Stripe 测试卡号 即可验证完整流程。

扣款只是 Stripe Java API 的基础功能,官方 API 文档 涵盖了全部操作。

完整示例代码见 GitHub 项目


原始标题:Introduction to Stripe API for Java