原创

Spring Boot集成Stripe快速入门demo

1.什么是Stripe?

一体化全球支付平台,开启收入增长引擎,针对不同规模业务打造的支付解决方案,满足从初创公司到跨国企业的多维度需求,助力全球范围内线上线下付款。

  • 转化更多客户: 通过内置的优化功能、100 多种支付方式及一键结账来提高转化率。实现线上和线下付款一体化,提供无缝的客户体验。
  • 全球覆盖,本地体验: 引入多样化支付方式,采用当地货币呈现价格,以此快速拓展新市场,实现向 195+ 个国家/地区的跨境销售,有效降低管理多币种的成本。
  • 减少欺诈,增加收入: Stripe 的机器学习优化功能基于数十亿数据点进行深度训练,为您自动降低欺诈风险,同时提升交易授权率。
  • 降本增效,快速拓展: 通过提高开发人员的工作效率,节省时间和工程资源。凭借领先的可靠性避免宕机,并通过替代支付方式和路径降低成本。

2.代码工程

实验目标

1.实现一次支付功能 2.实现订阅支付功能

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springboot-demo</artifactId>
        <groupId>com.et</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>stripe</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.sparkjava</groupId>
            <artifactId>spark-core</artifactId>
            <version>2.9.4</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.stripe</groupId>
            <artifactId>stripe-java</artifactId>
            <version>25.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>
</project>

controller

package com.et.stripe.controller;

import com.et.stripe.common.Response;
import com.et.stripe.service.StripeService;

import com.stripe.model.Coupon;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class PaymentController {

    @Value("${stripe.keys.public}")
    private String API_PUBLIC_KEY;

    private StripeService stripeService;

    public PaymentController(StripeService stripeService) {
        this.stripeService = stripeService;
    }

    @GetMapping("/")
    public String homepage() {
        return "homepage";
    }

    @GetMapping("/subscription")
    public String subscriptionPage(Model model) {
        model.addAttribute("stripePublicKey", API_PUBLIC_KEY);
        return "subscription";
    }

    @GetMapping("/charge")
    public String chargePage(Model model) {
        model.addAttribute("stripePublicKey", API_PUBLIC_KEY);
        return "charge";
    }

    /*========== REST APIs for Handling Payments ===================*/

    @PostMapping("/create-subscription")
    public @ResponseBody
    Response createSubscription(String email, String token, String plan, String coupon) {
        //validate data
        if (token == null || plan.isEmpty()) {
            return new Response(false, "Stripe payment token is missing. Please, try again later.");
        }

        //create customer first
        String customerId = stripeService.createCustomer(email, token);

        if (customerId == null) {
            return new Response(false, "An error occurred while trying to create a customer.");
        }

        //create subscription
        String subscriptionId = stripeService.createSubscription(customerId, plan, coupon);
        if (subscriptionId == null) {
            return new Response(false, "An error occurred while trying to create a subscription.");
        }

        // Ideally you should store customerId and subscriptionId along with customer object here.
        // These values are required to update or cancel the subscription at later stage.

        return new Response(true, "Success! Your subscription id is " + subscriptionId);
    }

    @PostMapping("/cancel-subscription")
    public @ResponseBody
    Response cancelSubscription(String subscriptionId) {
        boolean status = stripeService.cancelSubscription(subscriptionId);
        if (!status) {
            return new Response(false, "Failed to cancel the subscription. Please, try later.");
        }
        return new Response(true, "Subscription cancelled successfully.");
    }

    @PostMapping("/coupon-validator")
    public @ResponseBody
    Response couponValidator(String code) {
        Coupon coupon = stripeService.retrieveCoupon(code);
        if (coupon != null && coupon.getValid()) {
            String details = (coupon.getPercentOff() == null ? "$" + (coupon.getAmountOff() / 100) : coupon.getPercentOff() + "%") +
                    " OFF " + coupon.getDuration();
            return new Response(true, details);
        } else {
            return new Response(false, "This coupon code is not available. This may be because it has expired or has " +
                    "already been applied to your account.");
        }
    }

    @PostMapping("/create-charge")
    public @ResponseBody
    Response createCharge(String email, String token) {
        //validate data
        if (token == null) {
            return new Response(false, "Stripe payment token is missing. Please, try again later.");
        }

        //create charge
        String chargeId = stripeService.createCharge(email, token, 999); //$9.99 USD
        if (chargeId == null) {
            return new Response(false, "An error occurred while trying to create a charge.");
        }

        // You may want to store charge id along with order information

        return new Response(true, "Success! Your charge id is " + chargeId);
    }
}

service

package com.et.stripe.service;

import com.stripe.Stripe;
import com.stripe.model.Charge;
import com.stripe.model.Coupon;
import com.stripe.model.Customer;
import com.stripe.model.Subscription;
import com.stripe.param.SubscriptionCancelParams;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class StripeService {

    @Value("${stripe.keys.secret}")
    private String API_SECRET_KEY;

    public StripeService() {
    }

    public String createCustomer(String email, String token) {
        String id = null;
        try {
            Stripe.apiKey = API_SECRET_KEY;
            Map<String, Object> customerParams = new HashMap<>();
            // add customer unique id here to track them in your web application
            customerParams.put("description", "Customer for " + email);
            customerParams.put("email", email);

            customerParams.put("source", token); // ^ obtained with Stripe.js
            //create a new customer
            Customer customer = Customer.create(customerParams);
            id = customer.getId();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return id;
    }

    public String createSubscription(String customerId, String plan, String coupon) {
        String id = null;
        try {
            Stripe.apiKey = API_SECRET_KEY;
            Map<String, Object> item = new HashMap<>();
            item.put("price", plan);

            Map<String, Object> items = new HashMap<>();
            items.put("0", item);

            Map<String, Object> params = new HashMap<>();
            params.put("customer", customerId);
            params.put("items", items);

            //add coupon if available
            if (!coupon.isEmpty()) {
                params.put("coupon", coupon);
            }

            Subscription sub = Subscription.create(params);
            id = sub.getId();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return id;
    }

    public boolean cancelSubscription(String subscriptionId) {
        boolean status;
        try {
            Stripe.apiKey = API_SECRET_KEY;
            Subscription sub = Subscription.retrieve(subscriptionId);
            sub.cancel((SubscriptionCancelParams) null);
            status = true;
        } catch (Exception ex) {
            ex.printStackTrace();
            status = false;
        }
        return status;
    }

    public Coupon retrieveCoupon(String code) {
        try {
            Stripe.apiKey = API_SECRET_KEY;
            return Coupon.retrieve(code);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public String createCharge(String email, String token, int amount) {
        String id = null;
        try {
            Stripe.apiKey = API_SECRET_KEY;
            Map<String, Object> chargeParams = new HashMap<>();
            chargeParams.put("amount", amount);
            chargeParams.put("currency", "usd");
            chargeParams.put("description", "Charge for " + email);
            chargeParams.put("source", token); // ^ obtained with Stripe.js

            //create a charge
            Charge charge = Charge.create(chargeParams);
            id = charge.getId();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return id;
    }

}

templates

homepage
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Homepage</title>
    <!--Bootstrap 4 CSS-->
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">

    <!--Bootstrap 4 JavaScript-->
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</head>
<body class="bg-light pt-5">

<!--hero section-->
<section class="py-5">
    <div class="container">
        <div class="row">
            <div class="col-lg-7 col-md-10 col-12 my-auto mx-auto text-center">
                <h1>
                    Stripe Payment Examples
                </h1>
                <p class="lead mb-4">
                    What would you like to do?
                </p>
                <a class="btn btn-primary" th:href="@{/subscription}">Create Recurring Subscription</a>
                <a class="btn btn-success" th:href="@{/charge}">Create One-Time Charge</a>
                <p class="mt-5 text-muted">
                    <small>An example project by <a target="_blank"
                                                    th:href="@{https://attacomsian.com}">Atta</a>.</small>
                </p>
            </div>
        </div>
    </div>
</section>

</body>
</html>
charge
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Charge</title>
    <!--Bootstrap 4 CSS-->
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">

    <!--Bootstrap 4 JavaScript-->
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>

    <!--Stripe JavaScript Library-->
    <script src="https://js.stripe.com/v3/"></script>
</head>
<body class="bg-light pt-5">

<!--hero section-->
<section class="py-5">
    <div class="container">
        <div class="row">
            <div class="col-lg-6 col-md-8 col-12 my-auto mx-auto">
                <h1>
                    Stripe One-Time Charge
                </h1>
                <p class="lead mb-4">
                    Please fill the form below to complete the order payment
                </p>
                <div class="card mb-4">
                    <div class="card-body">
                        <h5>Leather Bag</h5>
                        <p class="lead">USD 9.99</p>
                    </div>
                </div>
                <form action="#" id="payment-form" method="post">
                    <input id="api-key" th:value="${stripePublicKey}" type="hidden">
                    <div class="form-group">
                        <label class="font-weight-medium" for="card-element">
                            Enter credit or debit card below
                        </label>
                        <div class="w-100" id="card-element">
                            <!-- A Stripe Element will be inserted here. -->
                        </div>
                    </div>
                    <div class="form-group">
                        <input class="form-control" id="email" name="email"
                               placeholder="Email Address" required type="email">
                    </div>
                    <!-- Used to display Element errors. -->
                    <div class="text-danger w-100" id="card-errors" role="alert"></div>
                    <div class="form-group pt-2">
                        <button class="btn btn-primary btn-block" id="submitButton" type="submit">
                            Pay With Your Card
                        </button>
                        <div class="small text-muted mt-2">
                            Pay securely with Stripe. By clicking the button above, you agree
                            to our <a href="#" target="_blank">Terms of Service</a>,
                            <a href="#" target="_blank">Privacy</a> and
                            <a href="#" target="_blank">Refund</a> policies.

                        </div>
                    </div>


                </form>
                <p class="mt-5 text-muted">
                    <small>An example project by <a target="_blank" th:href="@{https://attacomsian.com}">Atta</a>.
                    </small>
                </p>
            </div>
        </div>
    </div>
</section>

<!--custom javascript for handling subscription-->
<script>
    $(function () {
        var API_KEY = $('#api-key').val();
        // Create a Stripe client.
        var stripe = Stripe(API_KEY);
        // Create an instance of Elements.
        var elements = stripe.elements();
        // Create an instance of the card Element.
        var card = elements.create('card');
        // Add an instance of the card Element into the `card-element` <div>.
        card.mount('#card-element');
        // Handle real-time validation errors from the card Element.
        card.addEventListener('change', function (event) {
            var displayError = document.getElementById('card-errors');
            if (event.error) {
                displayError.textContent = event.error.message;
            } else {
                displayError.textContent = '';
            }
        });
        // Handle form submission.
        var form = document.getElementById('payment-form');
        form.addEventListener('submit', function (event) {
            event.preventDefault();
            // handle payment
            handlePayments();
        });

        //handle card submission
        function handlePayments() {
            stripe.createToken(card).then(function (result) {
                if (result.error) {
                    // Inform the user if there was an error.
                    var errorElement = document.getElementById('card-errors');
                    errorElement.textContent = result.error.message;
                } else {
                    // Send the token to your server.
                    var token = result.token.id;
                    var email = $('#email').val();
                    $.post(
                        "/create-charge",
                        {email: email, token: token},
                        function (data) {
                            alert(data.details);
                        }, 'json');
                }
            });
        }
    });
</script>

</body>
</html>
subscription
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Subscription</title>
    <!--Bootstrap 4 CSS-->
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">

    <!--Bootstrap 4 JavaScript-->
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>

    <!--Stripe JavaScript Library-->
    <script src="https://js.stripe.com/v3/"></script>
</head>
<body class="bg-light pt-5">

<!--hero section-->
<section class="py-5">
    <div class="container">
        <div class="row">
            <div class="col-lg-6 col-md-8 col-12 my-auto mx-auto">
                <h1>
                    Stripe Recurring Subscription
                </h1>
                <p class="lead mb-4">
                    Please fill the form below to complete the payment
                </p>
                <h5 class="mb-2">Choose your payment plan</h5>
                <p class="text-muted">
                    60% OFF when you upgrade to annual plan.
                </p>
                <div class="py-2">
                    <div class="custom-control custom-radio">
                        <input class="custom-control-input" id="monthly-plan" name="premium-plan" type="radio"
                               value="price_1PtRC5KYHXnPEBSj85nrvuKC"/>
                        <label class="custom-control-label" for="monthly-plan">
                            <strong>Monthly $9.99</strong><br/>
                            <small class="text-muted">
                                Pay $9.99 every month and get access to all premium features.
                            </small>
                        </label>
                    </div>
                    <div class="custom-control custom-radio mt-3">
                        <input checked="" class="custom-control-input" id="annually-plan" name="premium-plan"
                               type="radio" value="annual-subscription"/>
                        <label class="custom-control-label" for="annually-plan">
                            <strong>Yearly $49.99</strong>
                            <span class="badge badge-primary ml-1">60% OFF</span>
                            <br/>
                            <small class="text-muted">
                                Pay $49.99 every year and get access to all premium features.
                            </small>
                        </label>
                    </div>
                </div>
                <form action="#" id="payment-form" method="post">
                    <input id="api-key" th:value="${stripePublicKey}" type="hidden">
                    <div class="form-group">
                        <label class="font-weight-medium" for="card-element">
                            Enter credit or debit card below
                        </label>
                        <div class="w-100" id="card-element">
                            <!-- A Stripe Element will be inserted here. -->
                        </div>
                    </div>
                    <div class="form-group">
                        <input class="form-control" id="email" name="email"
                               placeholder="Email Address" required type="email">
                    </div>
                    <div class="form-group">
                        <input class="form-control" id="coupon" name="coupon"
                               placeholder="Coupon code (optional)" type="text">
                    </div>
                    <!-- Used to display Element errors. -->
                    <div class="text-danger w-100" id="card-errors" role="alert"></div>
                    <div class="form-group pt-2">
                        <button class="btn btn-primary btn-block" id="submitButton" type="submit">
                            Pay With Your Card
                        </button>
                        <div class="small text-muted mt-2">
                            Pay securely with Stripe. By clicking the button above, you agree
                            to our <a href="#" target="_blank">Terms of Service</a>,
                            <a href="#" target="_blank">Privacy</a> and
                            <a href="#" target="_blank">Refund</a> policies.

                        </div>
                    </div>


                </form>
                <p class="mt-5 text-muted">
                    <small>An example project by <a target="_blank" th:href="@{https://attacomsian.com}">Atta</a>.
                    </small>
                </p>
            </div>
        </div>
    </div>
</section>

<!--custom javascript for handling subscription-->
<script>
    $(function () {
        var API_KEY = $('#api-key').val();
        // Create a Stripe client.
        var stripe = Stripe(API_KEY);
        // Create an instance of Elements.
        var elements = stripe.elements();
        // Create an instance of the card Element.
        var card = elements.create('card');
        // Add an instance of the card Element into the `card-element` <div>.
        card.mount('#card-element');
        // Handle real-time validation errors from the card Element.
        card.addEventListener('change', function (event) {
            var displayError = document.getElementById('card-errors');
            if (event.error) {
                displayError.textContent = event.error.message;
            } else {
                displayError.textContent = '';
            }
        });
        // Handle form submission.
        var form = document.getElementById('payment-form');
        form.addEventListener('submit', function (event) {
            event.preventDefault();
            //validate coupon if any
            var code = $('#coupon').val().trim();
            if (code.length > 0) {
                $.post(
                    "/coupon-validator",
                    {code: code},
                    function (data) {
                        if (data.status) {
                            handlePayments();
                        } else {
                            alert(data.details);
                        }
                    }, 'json');
            } else {
                handlePayments();
            }
        });

        //handle card submission
        function handlePayments() {
            stripe.createToken(card).then(function (result) {
                if (result.error) {
                    // Inform the user if there was an error.
                    var errorElement = document.getElementById('card-errors');
                    errorElement.textContent = result.error.message;
                } else {
                    // Send the token to your server.
                    var token = result.token.id;
                    var plan = $('input[name="premium-plan"]:checked').val();
                    var email = $('#email').val();
                    var coupon = $('#coupon').val();
                    $.post(
                        "/create-subscription",
                        {email: email, token: token, plan: plan, coupon: coupon},
                        function (data) {
                            alert(data.details);
                        }, 'json');
                }
            });
        }
    });
</script>

</body>
</html>

application.yaml

#Stripe keys - REPLACE WITH ACTUAL KEYS
stripe.keys.public=pk_test_xxxxx
stripe.keys.secret=sk_test_xxxxx
#Don't cache thymeleaf files - FOR TEST PURPOSE ONLY
spring.thymeleaf.cache=false

3.测试

启动Spring Boot应用

一次支付测试

http://127.0.0.1:8080/charge charge 可以在这个地址 https://docs.stripe.com/testing,找到测试卡号, 输入测试卡号,显示支付成功 charge-success

订阅支付测试

http://127.0.0.1:8080/subscription subscription 输入测试卡号,显示订阅成功(注意:是订阅产品上创建多个价格,而不是创建多个产品) subscription-success

stripe控制台交易信息

transactions

4.引用

   
正文到此结束
Loading...