Spring Boot整合Stripe订阅支付指南

news2024/11/26 20:38:23

        在当今的在线支付市场中,Stripe 作为一款一体化的全球支付平台,因其易用性和广泛的支付方式支持,得到了许多企业的青睐。本文将详细介绍如何在 Spring Boot 项目中整合 Stripe 实现订阅支付功能。

1.Stripe简介

        Stripe 是一家为个人或公司提供网上接受付款服务的科技公司,无需开设商家账户即可在线接受付款。它支持多种支付方式,覆盖全球 195 个以上的国家和地区,具备高度的安全性和稳定性。通过内置的优化功能和一键结账,Stripe 能够显著提高转化率,为商家提供无缝的客户体验。

2.准备工作

2.1.准备工作

        前往 Stripe 官网 注册一个账号,邮箱地址可以是国内的。注册完成后,获取到测试用的秘钥,用于后续的开发和测试。 

2.2.添加Maven依赖

<dependency>  
    <groupId>com.stripe</groupId>  
    <artifactId>stripe-java</artifactId>  
    <version>最新版本号</version>  <!--  博主这里用的是27.0.0版本  -->
</dependency>

3.代码编写

3.1.配置Stripe密钥

        在 Spring Boot 的配置文件中(如 application.properties 或 application.yml,添加 Stripe 的 私钥 

stripe.keys.secret=你的Stripe私钥

3.2.编辑Service层代码

3.2.1.在 StripeService 中使用 Stripe API 创建订阅计划:

@Service
public class StripeService {

    @PostConstruct
    public void init() {
        // 初始化 Stripe API
        Stripe.apiKey = StripeKey.getTestKey();
    }

    /**
     * 创建 Stripe 客户
     */
    public Customer createCustomer(String email, String paymentMethodId) throws StripeException {
        CustomerCreateParams customerParams = CustomerCreateParams.builder()
                .setEmail(email)
                .setPaymentMethod(paymentMethodId)
                .build();

        return Customer.create(customerParams);
    }

    /**
     * 创建 Stripe 订阅
     * @param customerId Stripe 客户 ID
     * @param paymentMethodId 支付方式的 ID
     * @param priceId 订阅计划的价格 ID
     * @return 创建的订阅对象
     */
    public Subscription createSubscription(String customerId, String paymentMethodId, String priceId, String couponId) throws StripeException {
        SubscriptionCreateParams subscriptionParams = SubscriptionCreateParams.builder()
                .setCustomer(customerId)
                .addItem(
                        SubscriptionCreateParams.Item.builder()
                                .setPrice(priceId)
                                .build()
                )
                .setDefaultPaymentMethod(paymentMethodId)
                // 添加优惠券
                .addDiscount(
                        SubscriptionCreateParams.Discount.builder()
                                .setCoupon(couponId) // 关联创建好的 coupon
                                .build()
                )
                .build();

        return Subscription.create(subscriptionParams);
    }

}

通常情况下,我们的连续包月服务首月的价格跟之后每月的价格是不一样的。

  • 这里博主将设置首月价格为$3.99,之后每月的价格为$9.99 
  • 博主通过 一次性优惠券 的方式完成这个首月折扣的功能
  • [如果需要设置首月价格为$9.99,之后每月价格为$3.99,可以考虑使用永久优惠券方案]

3.2.2.通过API向Stripe添加订阅产品和优惠券

@Service
public class StripeService {

    /**
     * 创建 Stripe 订阅产品
     */
    public Product createSubscriptionProduct(String productName, String description) throws Exception {
        ProductCreateParams params = ProductCreateParams.builder()
                .setName(productName)
                .setDescription(description)
                .setType(ProductCreateParams.Type.SERVICE) // 订阅产品通常是服务型产品
                .build();

        // 创建产品
        return Product.create(params);
    }

    /**
     * 创建 Stripe 价格计划
     */
    public Price createMonthlyPrice(String productId, Long unitAmount) throws Exception {
        PriceCreateParams params = PriceCreateParams.builder()
                .setProduct(productId) // 使用创建的产品ID
                .setCurrency("USD") // 设置货币类型,例如USD
                .setRecurring(PriceCreateParams.Recurring.builder()
                        .setInterval(PriceCreateParams.Recurring.Interval.MONTH) // 按月计费
                        .build())
                .setUnitAmount(unitAmount) // 设置金额(以分为单位,10美元即为1000)
                .build();

        // 创建价格计划
        return Price.create(params);
    }

    /**
     * 创建 Stripe 折扣
     */
    public String createdStripeDiscount(DiscountModel model) {

        Map<String, Object> couponParams = new HashMap<>();

        try {
            // 设置折扣方式
            if (Objects.equals(model.getDiscountMethod(), "percent_off")) {
                // 按百分比折扣
                couponParams.put(model.getDiscountMethod(), model.getProportion());
            }

            // 按具体金额折扣
            long price = Math.round(Float.parseFloat(model.getDiscountPrice())) * 100L;
            couponParams.put(model.getDiscountMethod(), price);

            // 设置货币类型
            couponParams.put("currency", "USD");

            if (model.getDiscountType().equals("repeating")) {
                // 有效期: 月份整数
                couponParams.put("duration_in_months", model.getDurationInMonths());
            }
            // 设置折扣券类型
            couponParams.put("duration", model.getDiscountType());

            return Coupon.create(couponParams).getId();

        } catch (Exception e) {

            return e.getMessage();
        }
    }
}

3.3.编辑Controller层代码

3.3.1.通过API创建订阅服务产品

@RestController
@RequestMapping("/api/auth/order/commodity")
@Tag(name = "商品管理")
public class CommodityController {

    private final CommodityService commodityService;

    private final StripeService stripeService;

    @Autowired
    public CommodityController(CommodityService commodityService, StripeService stripeService) {

        this.commodityService = commodityService;

        this.stripeService = stripeService;
    }

    @PostMapping("/created")
    @Operation(summary = "新增商品")
    public Result<Object> created(@RequestHeader("Authorization")String token, @RequestBody CommodityModel model) {

        String jwt = token.substring(11);

        try {
            // Step 1: 创建订阅产品
            Product product = stripeService.createSubscriptionProduct(model.getCommodityName(), model.getDescription());

            // Step 2: 为产品创建价格计划

            // 将 double 价格转换为以分为单位的 long 类型
            Long unitAmountInCents = Math.round(model.getUnitPrice()) * 100;

            Price price = stripeService.createMonthlyPrice(product.getId(), unitAmountInCents);

            // 将 Stripe 产品 ID 与 Stripe 产品价格 ID 存储到商品实体中
            model.setStripeId(product.getId());
            model.setStripePriceId(price.getId());

            // 更新数据库
            int result = commodityService.createdCommodity(jwt, model);

            return result >= 1 ? Result.SUCCESS("Created Success !") : Result.FAILURE("Created Fail !");

        } catch (Exception e) {
            // 错误处理
            return Result.FAILURE(e.getMessage());
        }
        
    }

}

        当我们通过 Spring Boot 向 Stripe 创建服务产品后,登录Stripe Disabled就可以查看到我们所创建的服务产品了

3.3.2.通过API创建优惠券

@RestController
@RequestMapping("/api/auth/order/discount")
@Tag(name = "折扣码管理")
public class DiscountController {

    private final DiscountService discountService;

    @Autowired
    public DiscountController(DiscountService discountService) {
        this.discountService = discountService;
    }

    @PostMapping("/created")
    @Operation(summary = "新增[折扣码]", parameters = {
            @Parameter(
                    name = "Authorization",
                    description = "TOKEN",
                    in = ParameterIn.HEADER,
                    required = true,
                    schema = @Schema(type = "string")
            )
    })
    public Result<Void> createDiscount(@RequestBody DiscountModel model) {

        int result = discountService.createdDiscount(model);

        return result >= 1 ? Result.SUCCESS() : Result.FAILURE();
    }

}

同样的,这里我们创建优惠券[这里博主以一次性优惠券为例]

 

        Stripe 中通过这个优惠券ID自动进行费用折扣计算,Stripe 支持按比例折扣和按具体金额折扣方式进行优惠折算

3.3.3.编写Stripe支付API

@RestController
@RequestMapping("/api/auth/pay/stripe")
@Tag(name = "Stripe-Pay")
public class StripeController {

    private final StripeService stripeService;

    private final OrderCommodityService orderCommodityService;

    @Autowired
    public StripeController(StripeService stripeService, OrderCommodityService orderCommodityService) {

        this.stripeService = stripeService;

        this.orderCommodityService = orderCommodityService;
    }

@PostMapping("/create")
    @Operation(summary = "Stripe_Pay", parameters = {
            @Parameter(
                    name = "Authorization",
                    description = "TOKEN",
                    in = ParameterIn.HEADER,
                    required = true,
                    schema = @Schema(type = "string")
            )
    })
    public Result<Object> createSubscription(@RequestBody PayModel model) {

        try {
            // Step 1: 创建客户
            Customer customer = stripeService.createCustomer(model.getEmail(), model.getPaymentMethodId());

            // Step 2: 创建订阅
            Subscription subscription = stripeService.createSubscription(customer.getId(), model.getPaymentMethodId(), model.getPriceId(), model.getDiscountId());

            // Step 3: 检查支付状态
            Invoice invoice = Invoice.retrieve(subscription.getLatestInvoice());
            PaymentIntent paymentIntent = PaymentIntent.retrieve(invoice.getPaymentIntent());

            if ("succeeded".equals(paymentIntent.getStatus())) {
                // 支付成功,修改订单状态
                int i = orderCommodityService.getBaseMapper().updateOrderById(1, model.getOrderId());

                return i >= 1 ? Result.SUCCESS("Payment succeeded !") : Result.FAILURE("Payment failed !");

            } else {
                // 支付失败或处理中
                return Result.FAILURE("Payment status: " + paymentIntent.getStatus());
            }

        } catch (StripeException e) {

            return Result.FAILURE(e.getMessage());
        }
    }

}

        编写完接口之后,我们就可以在前端生成 paymentMethodId,进行支付的时候将下列的参数上传到 Spring Boot 就可以完成这个支付功能啦!

支付实参

@Data
public class PayModel {

    @Schema(description = "用户邮箱")
    private String email;

    @Schema(description = "订单ID")
    private String orderId;

    @Schema(description = "Stripe 支付方式ID")
    private String paymentMethodId;

    @Schema(description = "Stripe 价格ID")
    private String priceId;

    @Schema(description = "Stripe 客户ID")
    private String discountId;

}

最终支付完成后,可以在 Stripe Dashboard 中进行查看我们具体的交易信息 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220896.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

低代码赋能项目管理系统:打造高效协同的数字化工作环境

项目管理是企业日常运营中的重要环节&#xff0c;其运作效率直接关系到项目的成功交付、资源的优化配置及企业的市场竞争力。然而&#xff0c;传统的项目管理系统却面临着诸多挑战。 传统管理系统开发周期长、耗资大、需要大量时间和资源来定制和优化。同时&#xff0c;高昂的维…

K8s-pod详解3(pod调度)

Pod调度 在默认情况下&#xff0c;一个Pod在哪个Node节点上运行&#xff0c;是由Scheduler组件采用相应的算法计算出来的&#xff0c;这个过程是不受人工控制的。但是在实际使用中&#xff0c;这并不满足的需求&#xff0c;因为很多情况下&#xff0c;我们想控制某些Pod到达某…

基于卷积神经网络的花卉分类系统,resnet50,mobilenet模型【pytorch框架+python源码】

更多目标检测和图像分类识别项目可看我主页其他文章 功能演示&#xff1a; 卷积神经网络&#xff0c;花卉识别系统&#xff0c;resnet50&#xff0c;mobilenet【pytorch框架&#xff0c;python源码】_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于卷积神经网络的…

SpringSecurity源码分析以及如何解决前后端分离出现的跨域问题

解决Security前后端分离出现的跨域问题 一. Security源码分析 首先在看源码之前我们先来看这张图 , 这张图展示了Security执行的全部流程 从上图可知Security执行的入口是UsernamePasswordAuthenticationFilter这个抽象类 , 那我们就先从该类进行分析 1. UsernamePasswordAu…

029_基于nodejs外卖网站设计和实现

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

Java应用程序的测试覆盖率之设计与实现(一)-- 总体设计

一、背景 作为测试&#xff0c;如何保证开发人员提交上来的代码都被测试覆盖到&#xff0c;是衡量测试质量的一个重要指标。 本系列文章将要说一说&#xff0c;如何搭建一套测试覆盖率的系统。 包括以下内容&#xff1a; jacoco agent采集执行覆盖率数据jacoco climaven集成…

基于Multisim的模拟拔河游戏比赛设计与仿真

1.设计一个模拟拔河游戏比赛的逻辑电路 2.使用15个发光二极管表示绳子&#xff0c;开机后只有最中间的发光二极管亮。 3.比赛双方各持一个按钮&#xff0c;快速不断地按动按钮&#xff0c;产生脉冲&#xff0c;谁按的快&#xff0c;发光的二极管就向谁的方向移动&#xff0c;每…

越狱你的 iPhone 安全吗?

越狱 iPhone 并不安全&#xff0c;可能会导致您的个人信息被盗、手机感染恶意软件以及软件出现故障。越狱 iPhone 会增加网络犯罪分子可利用来访问您的私人信息的安全漏洞数量。 继续阅读&#xff0c;了解什么是越狱以及为什么你永远不应该越狱你的 iPhone。 什么是越狱&…

K8S系列-Kubernetes网络

一、Kubernetes网络模型 ​ Kubernetes网络模型设计的一个基础原则是&#xff1a;每个Pod都拥有一个独立的IP地址&#xff0c;并假定所有Pod都在一个可以直接连通的、扁平的网络空间中&#xff0c;不管它们是否运行在同一个Node&#xff08;宿主机&#xff09;中&#xff0c;都…

鸿蒙网络编程系列31-使用RCP调用OpenAI接口实现智能助手

简介 在OpenAI推出GPT系列大模型以后&#xff0c;市场上各种类似的大模型也层出不穷&#xff0c;这些大模型也基本都会兼容OpenAI的接口&#xff0c;在开发基于大模型的应用时&#xff0c;选择使用OpenAI接口作为和后端大模型通讯的标准&#xff0c;可以更好的适配不同厂家的模…

Scala 内部类

一. scala的内部类的定义 它是指定义在类或对象内部的类。 idea实例 二.内部类的基本使用 idea实例 三.内部类的使用案例 四.内部对象 idea实例 五.匿名类 idea实例

Bluetooth Channel Sounding中关于CS Step及Phase Based Ranging相应Mode介绍

目录 BLE CS中Step定义 BLE CS中交互的数据包/波形格式 BLE CS中Step的不同Mode BLE CS中Step的执行过程 Mode0介绍 Mode0 步骤的作用 Mode0步骤的执行过程 Mode0步骤的执行时间 Mode0步骤的时间精度要求 Mode2介绍 Mode2步骤的作用和执行过程 Mode2步骤的执行时间 B…

13.4 Linux_网络编程_套接字属性

概述 什么是选项的级别&#xff1a; socket中可以设置的属性种类很多&#xff0c;比如socke的选项、传输层TCP/UDP的选项、数据链路层的选项。这些选项在不同的层级&#xff0c;这就是选项的级别。常用级别及含义如下&#xff1a; 级别含义SOL_SOCKET作用于套接字本身IPPROT…

MySQL中的优先规则

在图片的例子中&#xff0c;有两个条件&#xff1a; 第一个条件是job_id是AD_PRES并且薪水高于15,000。 第二个条件是job_id是SA_REP。 在图片中的例子有两个条件&#xff1a; 第一个条件是job_id是AD_PRES或者SA_REP。 第二个条件是薪水高于$15,000。

React Componet类组件详解(老项目)

React类组件是通过创建class继承React.Component来创建的&#xff0c;是React中用于构建用户界面的重要部分。以下是对React类组件的详细解释&#xff1a; 一、定义与基本结构 类组件使用ES6的class语法定义&#xff0c;并继承自React.Component。它们具有更复杂的功能&#…

Vscode连接WSL2(Ubuntu20.04)

一.安装WSL插件 在扩展里面搜索WSL,选择安装即可 二.连接到wsl 安装完毕后在左下角看到一个按钮&#xff08;一个>和一个<组成&#xff09;&#xff0c;点击在中间选择"连接到wsl",然后Vscode会弹出一个新窗口&#xff0c;左下角显示WSL子系统名称&#xff0…

vue中如何检测数组变化(vue基础,面试,源码级讲解)

大家有什么不明白的地方可以分享在评论区&#xff0c;大家一起探讨哦~~ &#xff08;如果对数据劫持还有所不明白的小伙伴&#xff0c;可以去看看上一篇文章哦&#xff09; 在vue2中&#xff0c;是如何对数组进行劫持的呢&#xff1f; 简单代码实现&#xff1a; 在vue2中&…

学习中,师傅b站泷羽sec——xss挖掘过程

某职业技术学院网站xss挖掘&#xff1a; 资产归纳 例如&#xff1a;先把功能点都看一遍&#xff0c;大部分都是文章 根据信息搜集第一课学习到一般主站的防御力是比较强的&#xff0c;出现漏洞的点不是对新手不友好。 在资产验证过程中还是把主站看了一遍 没有发现有攻击的机会…

G1 GAN生成MNIST手写数字图像

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 G1 GAN生成MNIST手写数字图像 1. 生成对抗网络 (GAN) 简介 生成对抗网络 (GAN) 是一种通过“对抗性”学习生成数据的深度学习模型&#xff0c;通常用于生成…

如何调试浏览器中的内存泄漏?

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介⭐ 如何调试浏览器中的内存泄漏&#xff1f;1. 什么是内存泄漏&#xff1f;2. 调试内存泄漏的工具3. 如何使用 Memory 面板进行内存调试3.1 获取内存快照&#xff08;Heap Snapshot&#xff09;获取内存快照的步骤&#xff1a;快照…