stripe 支付对接

注册 stripe 账户,选择沙箱环境

网址:Stripe 官网

一:

1:保存 公钥和私钥。

2:设置 Webhook

3:添加接收端 勾选 事件 设置 回调地址

4:保存回调验签密钥

二:创建支付意图并支付

Maven 进行引包

复制代码
            <!-- stripe 支付模块 -->
            <dependency>
                <groupId>com.stripe</groupId>
                <artifactId>stripe-java</artifactId>
                <version>29.4.0</version>
            </dependency>


import com.stripe.Stripe;
import com.stripe.exception.StripeException;
import com.stripe.model.PaymentIntent;
import com.stripe.param.PaymentIntentCreateParams;


/**
 * @author: damo
 * @date: 2025-10-14 16:41
 * @description:
 */

public class StripePayService {
    /**
     * 创建支付意图订单
     *
     * @param stripeApiKey 私钥
     * @param customerName 付款姓名
     * @param customerEmail 付款邮箱
     * @param recordId 商品ID
     * @param amount 金额
     * @param currency 货币
     * @return PaymentIntent
     */
    public static PaymentIntent createPaymentIntent(String stripeApiKey, String customerName, String customerEmail, String recordId, Long amount, String currency) throws StripeException {
        Stripe.apiKey = stripeApiKey;

        PaymentIntentCreateParams params = PaymentIntentCreateParams.builder()
                .setAmount(amount)
                .setCurrency(currency)
                .setAutomaticPaymentMethods(
                        PaymentIntentCreateParams.AutomaticPaymentMethods
                                .builder()
                                .setEnabled(true)
                                .build()
                )
                .putMetadata("customer_name", customerName)
                .putMetadata("customer_email", customerEmail)
                .putMetadata("product_id", recordId)
                .build();
        return PaymentIntent.create(params);
    }
}

支付意图创建成功后,将 Id 和 clientSecret 返回给 前端用,注意id名字换为 paymentIntentId,前端调用 stripe 时用的参数名为 paymentIntentId。

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Stripe信用卡支付演示</title>
    <script src="https://js.stripe.com/v3/"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        :root {
            --primary: #6772e5;
            --primary-dark: #5469d4;
            --success: #28a745;
            --danger: #dc3545;
        }

        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e7f4 100%);
            min-height: 100vh;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            padding: 20px;
        }

        .payment-container {
            max-width: 800px;
            margin: 2rem auto;
            background: white;
            border-radius: 16px;
            box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
            overflow: hidden;
        }

        .header-section {
            background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
            color: white;
            padding: 2rem;
            text-align: center;
        }

        .product-card {
            border-radius: 12px;
            overflow: hidden;
            transition: transform 0.3s ease;
            margin: 20px;
        }

        .product-card:hover {
            transform: translateY(-5px);
        }

        .product-image {
            height: 200px;
            object-fit: cover;
            background-color: #f8f9fa;
        }

        .card-element-container {
            background: white;
            padding: 1.5rem;
            border-radius: 12px;
            box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
            margin: 20px;
        }

        .payment-form input {
            padding: 12px;
            border-radius: 6px;
        }

        .btn-pay {
            background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
            border: none;
            padding: 14px;
            font-weight: 600;
            font-size: 1.1rem;
            border-radius: 8px;
            transition: all 0.3s;
            width: 100%;
            color: white;
            margin-top: 20px;
        }

        .btn-pay:hover {
            transform: translateY(-3px);
            box-shadow: 0 7px 14px rgba(103, 114, 229, 0.4);
        }

        .status-card {
            display: none;
            padding: 2rem;
            text-align: center;
        }

        .success-status {
            background-color: rgba(40, 167, 69, 0.1);
            border-left: 4px solid var(--success);
        }

        .error-status {
            background-color: rgba(220, 53, 69, 0.1);
            border-left: 4px solid var(--danger);
        }

        .status-icon {
            font-size: 4rem;
            margin-bottom: 1rem;
        }

        .spinner-border {
            width: 1.5rem;
            height: 1.5rem;
            border-width: 0.2em;
            display: none;
        }

        .test-card-info {
            background-color: #e9ecef;
            border-radius: 6px;
            padding: 10px;
            font-size: 0.85rem;
            margin: 20px;
        }

        .card-element {
            padding: 12px;
            border: 1px solid #ced4da;
            border-radius: 6px;
            margin-bottom: 1rem;
        }

        #card-element {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            margin-bottom: 15px;
        }

        #card-errors {
            color: #dc3545;
            margin-bottom: 15px;
        }
    </style>
</head>
<body>
<div class="payment-container">
    <div class="header-section">
        <h1 class="display-5 fw-bold">信用卡支付集成</h1>
        <p class="lead">使用Stripe处理支付,安全便捷</p>
    </div>

    <div class="row">
        <!-- 产品信息 -->
        <div class="col-md-6">
            <div class="product-card">
                <div class="card">
                    <div class="text-center p-4">
                        <svg xmlns="http://www.w3.org/2000/svg" width="140" height="140" fill="var(--primary)" viewBox="0 0 16 16">
                            <path d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 1l-2.218-.887zm3.564 1.426L5.596 5 8 5.961 14.154 3.5l-2.404-.961zm3.25 1.7-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.82V7.838L1 5.239v7.923l6.5 2.658zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464L7.443.184z"/>
                        </svg>
                    </div>
                    <div class="card-body text-center">
                        <h4 class="card-title">高级订阅服务</h4>
                        <p class="card-text">解锁所有高级功能,尊享专属服务</p>
                        <div class="pricing mb-4">
                            <h2 class="fw-bold">$99.99<span class="fs-6 fw-normal">/年</span></h2>
                        </div>
                        <ul class="list-unstyled text-start">
                            <li class="mb-2">
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="var(--success)" viewBox="0 0 16 16" class="me-2">
                                    <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
                                </svg>
                                无限访问所有功能
                            </li>
                            <li class="mb-2">
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="var(--success)" viewBox="0 0 16 16" class="me-2">
                                    <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
                                </svg>
                                24/7高级技术支持
                            </li>
                            <li class="mb-2">
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="var(--success)" viewBox="0 0 16 16" class="me-2">
                                    <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
                                </svg>
                                专属资源访问权限
                            </li>
                            <li>
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="var(--success)" viewBox="0 0 16 16" class="me-2">
                                    <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
                                </svg>
                                免费产品更新
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>

        <!-- 支付表单 -->
        <div class="col-md-6">
            <div class="card-element-container">
                <h3 class="mb-4">支付信息</h3>

                <form id="payment-form">
                    <div class="mb-3">
                        <label class="form-label">姓名</label>
                        <input type="text" id="name" class="form-control" placeholder="持卡人姓名" value="测试用户">
                    </div>

                    <div class="mb-3">
                        <label class="form-label">邮箱</label>
                        <input type="email" id="email" class="form-control" placeholder="您的邮箱地址" value="test@example.com">
                    </div>

                    <div class="mb-3">
                        <label class="form-label">信用卡信息</label>
                        <div id="card-element" class="card-element"></div>
                        <div id="card-errors" role="alert" class="text-danger mt-2"></div>
                    </div>

                    <div class="mb-4">
                        <label class="form-label">账单地址</label>
                        <div class="row g-3">
                            <div class="col-md-6">
                                <input type="text" class="form-control" placeholder="国家" value="美国">
                            </div>
                            <div class="col-md-6">
                                <input type="text" class="form-control" placeholder="邮编" value="10001">
                            </div>
                        </div>
                    </div>

                    <button id="submit-button" class="btn btn-pay">
                        <span id="button-text">支付 $99.99</span>
                        <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" id="spinner"></span>
                    </button>
                </form>

                <!-- 支付状态 -->
                <div id="payment-status" class="status-card mt-4">
                    <div id="success-message" class="d-none">
                        <div class="success-status p-4 rounded">
                            <svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" fill="var(--success)" viewBox="0 0 16 16" class="status-icon mb-3">
                                <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
                            </svg>
                            <h3>支付成功!</h3>
                            <p class="mb-0">感谢您的购买。交易ID: <span id="order-id" class="fw-bold"></span></p>
                            <button class="btn btn-outline-success mt-3" onclick="resetPaymentForm()">返回</button>
                        </div>
                    </div>

                    <div id="error-message" class="d-none">
                        <div class="error-status p-4 rounded">
                            <svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" fill="var(--danger)" viewBox="0 0 16 16" class="status-icon mb-3">
                                <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
                            </svg>
                            <h3>支付失败</h3>
                            <p id="error-details" class="mb-0"></p>
                            <button class="btn btn-outline-danger mt-3" onclick="resetPaymentForm()">重试</button>
                        </div>
                    </div>
                </div>
            </div>

            <div class="test-card-info">
                <h5>测试卡信息</h5>
                <div class="d-flex flex-wrap gap-2">
                    <div class="badge bg-primary">
                        信用卡: <strong>4242 4242 4242 4242</strong>
                    </div>
                    <div class="badge bg-info">
                        有效期: <strong>未来日期</strong>
                    </div>
                    <div class="badge bg-success">
                        CVC: <strong>任意三位数</strong>
                    </div>
                </div>
                <p class="mt-2 mb-0 small">注意:此演示使用Stripe测试环境,不会产生实际费用</p>
            </div>
        </div>
    </div>
</div>

<script>
    // 初始化Stripe - 替换为您的Stripe公钥
    const stripe = Stripe('替换为您的Stripe公钥');

    // 创建Stripe Elements
    const elements = stripe.elements();
    const cardElement = elements.create('card', {
        style: {
            base: {
                fontSize: '16px',
                color: '#32325d',
                '::placeholder': {
                    color: '#aab7c4'
                }
            },
            invalid: {
                color: '#dc3545'
            }
        }
    });

    // 挂载信用卡输入组件
    cardElement.mount('#card-element');

    // 表单元素
    const form = document.getElementById('payment-form');
    const submitButton = document.getElementById('submit-button');
    const buttonText = document.getElementById('button-text');
    const spinner = document.getElementById('spinner');
    const cardErrors = document.getElementById('card-errors');
    const successMessage = document.getElementById('success-message');
    const errorMessage = document.getElementById('error-message');
    const paymentStatus = document.getElementById('payment-status');

    // 监听信用卡输入错误
    cardElement.addEventListener('change', (event) => {
        if (event.error) {
            cardErrors.textContent = event.error.message;
        } else {
            cardErrors.textContent = '';
        }
    });

    // 处理表单提交
    form.addEventListener('submit', async (event) => {
        event.preventDefault();

        // 禁用按钮,显示加载状态
        submitButton.disabled = true;
        buttonText.textContent = '处理中...';
        spinner.style.display = 'inline-block';

        try {
            // 1. 创建支付意图
            const response = await fetch('你创建支付意图的后端接口', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    "operatorId": "1970017596304076800",
                    "orderId": "1977675882809556992",
                    "orderType": "exp",
                    "orderSubtype": "11",
                    "payProvider": "Stripe",
                    "payType": "31",
                    "total": 99,
                    "payParams": {
                        "customerName": "gondar",
                        "customerEmail": "1541998@qq.com"
                    }
                })
            });
            console.log("************************************")
            console.log(response)
            console.log("************************************")
            const data = await response.json();

            if (!response.ok) {
                throw new Error(data.error || '创建支付意图失败');
            }

            // 2. 确认支付
            const { paymentIntent, error } = await stripe.confirmCardPayment(
                data.clientSecret, {
                    payment_method: {
                        card: cardElement,
                        billing_details: {
                            name: document.getElementById('name').value,
                            email: document.getElementById('email').value
                        }
                    }
                }
            );

            if (error) {
                throw new Error(error.message);
            }

            if (paymentIntent.status === 'succeeded') {
                // 3. 显示成功并开始验证
                showSuccess(paymentIntent.id);
            } else {
                throw new Error(`支付状态: ${paymentIntent.status}`);
            }
        } catch (error) {
            showError(error.message);
        } finally {
            spinner.style.display = 'none';
            buttonText.textContent = '支付 $99.99';
        }
    });


    // 显示成功消息
    function showSuccess(orderId) {
        form.style.display = 'none';
        successMessage.classList.remove('d-none');
        document.getElementById('order-id').textContent = orderId;
        paymentStatus.style.display = 'block';
    }

    // 显示错误消息
    function showError(message) {
        submitButton.disabled = false;
        form.style.display = 'block';
        errorMessage.classList.remove('d-none');
        document.getElementById('error-details').textContent = message;
        paymentStatus.style.display = 'block';
    }

    // 重置表单
    function resetPaymentForm() {
        form.style.display = 'block';
        successMessage.classList.add('d-none');
        errorMessage.classList.add('d-none');
        cardElement.clear();
        document.getElementById('name').value = '';
        document.getElementById('email').value = '';
        paymentStatus.style.display = 'none';
        submitButton.disabled = false;
    }
</script>
</body>
</html>

有前端页面完成支付。

三:回调处理

复制代码
public ResponseEntity<String> handleStripeWebhook(@RequestHeader("Stripe-Signature") String stripeSignature,
                                                      @RequestBody String payload) {
        // 1. 获取Stripe商户密钥
        String webhookSecret = "您的密钥";
        // 2. 格式化参数
        try {
            com.google.gson.Gson gson = new com.google.gson.GsonBuilder().setPrettyPrinting().serializeNulls().create();
            com.google.gson.JsonObject jsonObject = gson.fromJson(payload, com.google.gson.JsonObject.class);
            payload = gson.toJson(jsonObject);
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("处理事件时出错");
        }
        // 3. 验证签名
        Event event;
        try {
            event = Webhook.constructEvent(payload, stripeSignature, webhookSecret);
        } catch (SignatureVerificationException e) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("签名验证失败");
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("处理事件时出错");
        }
        // 4. 解析事件
        PaymentIntent paymentIntent = null;
        try {
            if (event != null && event.getType() != null) {
                if (event.getType().equals("payment_intent.succeeded") || event.getType().equals("payment_intent.payment_failed") || event.getType().equals("payment_intent.canceled")) {
                    EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();
                    if (dataObjectDeserializer != null && dataObjectDeserializer.getObject().isPresent())
                        paymentIntent = (PaymentIntent) dataObjectDeserializer.getObject().get();
                }else {
                    return ResponseEntity.ok(String.format("Webhook 暂未处理 %s 事件", event.getType()));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (event == null || paymentIntent == null)
            return ResponseEntity.ok("Webhook 解析到结果为空");
        // 5. 处理事件
        switch (event.getType()) {
            // 支付成功
            case "payment_intent.succeeded" -> {
                
            }
            // 支付失败
            case "payment_intent.payment_failed" -> {
                            }
            // 取消支付
            case "payment_intent.canceled" -> {
                            }
            // 其他事件
            default -> {
                return ResponseEntity.ok("未处理的事件类型: " + event.getType());
            }
        }
        // 6. 处理支付结果
        return ResponseEntity.ok("Webhook 处理成功");
    }

stripe 用到的JSON 是格式化过的,

目前测试只有 gson 才能验签通过。

相关推荐
麦麦鸡腿堡6 小时前
Java的单例设计模式-饿汉式
java·开发语言·设计模式
假客套6 小时前
Request method ‘POST‘ not supported,问题分析和解决
java
傻童:CPU6 小时前
C语言需要掌握的基础知识点之前缀和
java·c语言·算法
爱吃山竹的大肚肚6 小时前
@Valid校验 -(Spring 默认不支持直接校验 List<@Valid Entity>,需用包装类或手动校验。)
java·开发语言
雨夜之寂6 小时前
mcp java实战 第一章-第一节-MCP协议简介.md
java·后端
皮皮林5516 小时前
蚂蚁又开源了一个顶级 Java 项目!
java
吹晚风吧7 小时前
spring是如何解决循环依赖的(二级缓存不行吗)?
java·spring·循环依赖·三级缓存
九丶弟7 小时前
SpringBoot的cache使用说明
java·spring boot·spring·cache
weixin_445476687 小时前
Java并发编程——synchronized的实现原理与应用
java·开发语言·并发·synchronized