Spring Boot整合Stripe订阅支付指南

        在当今的在线支付市场中,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 中进行查看我们具体的交易信息 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言实现Go的defer功能

之前笔者写了一篇博文C实现Go的defer功能&#xff0c;介绍了如何在C语言中实现Go的defer功能&#xff0c;那在C语言中是否也可以实现这样的功能呢&#xff1f;本文就将介绍一下如何在C语言中实现Go的defer功能。 我们还是使用C实现Go的defer功能中的示例&#xff1a; void te…

【每日一题】LeetCode - 判断回文数

今天我们来看一道经典的回文数题目&#xff0c;给定一个整数 x &#xff0c;判断它是否是回文整数。如果 x 是一个回文数&#xff0c;则返回 true&#xff0c;否则返回 false。 回文数 是指从左往右读和从右往左读都相同的整数。例如&#xff0c;121 是回文&#xff0c;而 123 …

nuxt3项目创建

安装 npx nuxilatest init <project-name> 此时会出现报错&#xff0c;需要在host文件中加入 185.199.108.133 raw.githubusercontent.com 再次执行命令&#xff0c;进入安装 此处选择npm&#xff0c;出现下图表示安装成功 启动项目 执行npm run dev&#xff0c;访…

《皮革制作与环保科技》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《皮革制作与环保科技》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《皮革制作与环保科技》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a;中国轻工业联合会 …

深度学习-循环神经网络-LSTM对序列数据进行预测

项目简介: 使用LSTM模型, 对文本数据进行预测, 每次截取字符20, 对第二十一个字符进行预测, LSTM层: units100, activationrelu Dense层: units输入的文本中的字符种类, 比如我使用的文本有644个不同的字符, 那么units64 激活函数: 因为是多分类, 使用softmax 因为这是最…

已解决 django.db.utils.OperationalError: (1051, “Unknown table

报错信息&#xff1a; django.db.utils.OperationalError: (1051, "Unknown table bjybolg.tool_submission")python manage.py migrate --fake 命令用于告诉 Django 假装已经应用某个迁移&#xff0c;而不实际执行该迁移的操作。这通常在以下情况下非常有用&#x…

【大模型理论篇】大模型压缩技术之注意力层剪枝以及与MLP层联合剪枝

1. 背景分析 本来打算写一篇关于大模型蒸馏的文章&#xff0c;但刚好看到近期发表的一篇讨论大模型压缩的文章【1】&#xff0c;是关于注意力机制冗余性的讨论&#xff0c;比较有意思&#xff0c;作者分析得出并不是所有的注意力都是必须的&#xff0c;可以通过对模型去除冗余的…

鸿蒙中富文本编辑与展示

富文本在鸿蒙系统如何展示和编辑的&#xff1f;在文章开头我们提出这个疑问&#xff0c;带着疑问来阅读这篇文章。 富文本用途可以展示图文混排的内容&#xff0c;在日常App 中非常常见&#xff0c;比如微博的发布与展示&#xff0c;朋友圈的发布与展示&#xff0c;都在使用富文…

Elasticsearch 中的高效按位匹配

作者&#xff1a;来自 Elastic Alexander Marquardt 探索在 Elasticsearch 中编码和匹配二进制数据的六种方法&#xff0c;包括术语编码&#xff08;我喜欢的方法&#xff09;、布尔编码、稀疏位位置编码、具有精确匹配的整数编码、具有脚本按位匹配的整数编码以及使用 ESQL 进…

Maven 不同环境灵活构建

需求: 使用 Maven根据不同的构建环境&#xff08;如开发、测试、生产&#xff09;来定义不同的配置&#xff0c;实现灵活的构建管理。 需要Demo项目的可以参考&#xff1a;我的demo项目 一、项目分层 一般的初创项目不会有特别多的配置文件&#xff0c;所以使用 spring.profile…

【333基于Java Web的考编论坛网站的设计与实现

毕 业 设 计&#xff08;论 文&#xff09; 考编论坛网站设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计…

linux下gpio模拟spi三线时序

目录 前言一、配置内容二、驱动代码实现三、总结 前言 本笔记总结linux下使用gpio模拟spi时序的方法&#xff0c;基于arm64架构的一个SOC&#xff0c;linux内核版本为linux5.10.xxx&#xff0c;以驱动三线spi(时钟线sclk&#xff0c;片选cs&#xff0c;sdata数据读和写使用同一…

「二叉树进阶题解:构建、遍历与结构转化全解析」

文章目录 根据二叉树创建字符串思路代码 二叉树的层序遍历思路代码 二叉树的最近公共祖先思路代码 二叉搜索树与双向链表思路代码 从前序与中序遍历序列构造二叉树思路代码 总结 根据二叉树创建字符串 题目&#xff1a; 样例&#xff1a; 可以看见&#xff0c;唯一特殊的就…

SCI被「On Hold」意味着什么?会有哪些影响?

本文首发于“生态学者”微信公众号&#xff01; 继Chemosphere在2023年7月被「On Hold」之后&#xff0c;昨晚Science of The Total Environment 被标记为「On Hold」状态在各大公众号和朋友圈被刷屏&#xff01;&#xff08;官方网址&#xff1a;https://mjl.clarivate.com/s…

PouchDB - 免费开源的 JavaScript 数据库,轻量易用,用于离线保存数据的场景

这个 JS 工具库可以让我们很容易地实现数据缓存到本地的需求&#xff0c;要写的代码量也很少。 PouchDB 是一个基于 JavaScript 语言开发的轻量级的数据库&#xff0c;可以在浏览器、Node.js 等环境中使用。作者是一位来自国外的女开发工程师 Alba Herreras。 这是一个运行在浏…

el-datepicker禁用未来日期(包含时分秒)type=‘datetime’

文章目录 实现代码方式1&#xff1a;当选中日期的时候去更新一次。方式2: 优化版本&#xff0c;使用 setTimout 每分钟更新一次。&#xff08;防止选中日期之后过了很久再去选择时分秒时没有根据当前时间去改变&#xff09; el-datepicker 选择器禁用未来日期&#xff0c;动态禁…

重生之“我打数据结构,真的假的?”--2.单链表(无习题)

C语言中的单链表总结 单链表是一种基础的数据结构&#xff0c;广泛应用于C语言编程中。它由节点组成&#xff0c;每个节点包含数据和指向下一个节点的指针。单链表的优点在于动态内存分配和高效的插入与删除操作。本文将详细探讨单链表的定义、基本操作、应用场景以及相关示例…

Gateway 统一网关

一、初识 Gateway 1. 为什么需要网关 我们所有的服务可以让任何请求访问&#xff0c;但有些业务不是对外公开的&#xff0c;这就需要用网关来统一替我们筛选请求&#xff0c;它就像是房间的一道门&#xff0c;想进入房间就必须经过门。而请求想要访问微服务&#xff0c;就必须…

ComfyUI系列——新手安装ComfyUI,就是这么简单

比较Midjoury、WebUI和ComfyUI 在了解ComfyUI的时候&#xff0c;还有其它两款类似的产品&#xff0c;于是就搜集了一下资料&#xff0c;以下是Midjoury、WebUI&#xff08;通常指的是Stable Diffusion Web UI&#xff09;和ComfyUI三者之间的异同点对比表。 特性MidjourneySt…

国内短剧系统源码搭建系统平台小程序新玩法

在数字化内容消费日益普及的今天&#xff0c;短剧小程序作为一种新兴的内容平台&#xff0c;其功能设计至关重要。一个好的短句系统不仅需要提供优质的内容展示&#xff0c;还需要具备一系列优秀功能以满足用户和运营者的需求。以下是一些必备的功能特点&#xff1a; 为大家介…