当前位置: 首页 > news >正文

Stripe Web 购买集成

图片被吞了可以来这里看:https://juejin.cn/post/7346388511338381364

1. 准备事项

  • Stripe 账号
  • 域名以及配套的网站
  • Stripe 账号付款信息
  • 公钥和私钥

2. 配置产品以及价格

可以通过 API 或者 Stripe 管理后台来进行配置

产品:就是商品,只需要配置一个名称和一个类型(用于计算税额)

image.png

价格:价格有定期和一次性两种收费方式,定期其实就是订阅。价格实体非常灵活,适合多种场景,一般就使用固定费率的一次性付款和定期付款。

image.png

3. 设计一下流程

image.png

4. 代码集成

4.1 依赖导入

stripe/stripe-java: Java library for the Stripe API. (github.com)

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

4.2 配置

# 公钥
stripe.key=pk_test_51Nxxxx
# 私钥
stripe.secret=sk_test_51xxxx
# webhook 密钥签名
stripe.endpoint_secret=whsec_Tcxxxx
@Data  
@Configuration  
@ConfigurationProperties(prefix = "stripe")  
public class StripeConfig {  private String key;private String secret;private String endpointSecret;@Bean  public StripeClient stripeClient() {return new StripeClient(secret);}
}

4.3 创建收银

Stripe 中有两种方式能进行收款,Stripe-hosted pageEmbedded form

Stripe-hosted page:指的是收费的时候跳转到 Stripe 提供的一个收银台页面进行付款。

Embedded form:则是需要高度自定义页面的产品使用,或者是客户端。

文档:Stripe Checkout | Stripe 文档

Demo: docs.stripe.com/checkout/quickstart

Web 端一般使用 Stripe-hosted page 来简化开发,像 ChatGPT 也是使用这种方式。

image.png

后端创建收银台

public CheckoutCreateResult create(CheckoutCreateRequest request) {// 查询或者创建客户String customerId = queryOrCreateCustomer();// 查询价格idString priceId = queryPrice();// 构建成功URL和取消URLUriComponents successUrl = UriComponentsBuilder.fromHttpUrl(request.getSuccessUrl()).queryParam("checkout_id", checkoutId).queryParam("receipt", "{CHECKOUT_SESSION_ID}") // 模板变量 https://stripe.com/docs/payments/checkout/custom-success-page#modify-success-url.build();UriComponents cancelUrl = UriComponentsBuilder.fromHttpUrl(request.getCancelUrl()).queryParam("checkout_id", checkoutId).build();// 创建checkout 收银台SessionCreateParams.Builder builder = SessionCreateParams.builder().setSuccessUrl(successUrl.toUriString()).setCancelUrl(cancelUrl.toUriString())// 指定付款用户.setCustomer(customerId)// 自动扣税.setAutomaticTax(SessionCreateParams.AutomaticTax.builder().setEnabled(false).build())// 购买项目:和订单明细类似.addLineItem(SessionCreateParams.LineItem.builder()// 数量.setQuantity(request.getCount().longValue())// 价格.setPrice(priceId).build())// 元数据:额外附加的数据。 webhook 通知的时候可以取出来.putAllMetadata(ImmutableMap.of(MetaDataKey.CHECKOUT_ID, checkoutId,MetaDataKey.APP_ID, request.getAppId()))// 是否允许优惠码.setAllowPromotionCodes(Boolean.TRUE);if (productPrice.getPriceType() == PriceTypeEnum.RECURRING) {// 定期价格,最后会创建订阅对象。可以为付款成功后生成的订阅对象设置一些数据builder.setMode(SessionCreateParams.Mode.SUBSCRIPTION).setSubscriptionData(// 试用期SessionCreateParams.SubscriptionData.builder().putMetadata(MetaDataKey.APP_ID, request.getAppId()).build());} else {// 一次性价格,最后会创建付款对象。可以为付款成功后生成的付款对象设置一些数据builder.setMode(SessionCreateParams.Mode.PAYMENT).setPaymentIntentData(SessionCreateParams.PaymentIntentData.builder().putMetadata(MetaDataKey.APP_ID, request.getAppId()).build());}SessionCreateParams params = builder.build();/*.addDiscount( // 优惠券SessionCreateParams.Discount.builder().setCoupon("bBfCjIMt").build())*/Session session = null;try {session = stripeClient.checkout().sessions().create(params);} catch (StripeException e) {log.error("failed to create checkout session. {}, {}, {}", request, customerId, priceId, e);throw new RuntimeException("failed to create checkout session: "+ e.getMessage());}return new CheckoutCreateThirdResult()// checkout session 的 id.setId(session.getId())// 可供用户进行付款的页面链接,前端直接打开即可跳转到Stripe.setTokenThird(session.getUrl());
}

4.4 完成收银

4.4.1 前端提交

用户付款完成后,Stripe 会将页面重定向到创建 Checkout Session 时设置的 success_url
页面可以从URL中获取到订单id和sessionId来进一步调用后端接口完成收银。

4.4.2 接收 Webhook

用户付款完成后,Stripe 的后台还会将对应的事件通过 WebHook 的方式 POST 我们预先提供的接口。

第一步,先提供一个 Webhook 回调接口

本地测试的方式不是很友好,可以使用内网穿透工具将请求转到本地来进行调试

@RestController
@Slf4j
public class WebhookController {@Resourceprivate StripeConfig stripeConfig;@Resourceprivate List<WebhookHandler> webhookHandlers;@PostMapping("/webhook")public Object handle(@RequestHeader("Stripe-Signature") String sigHeader, @RequestBody String payload) {log.info("stripe webhook payload: {}", payload);return webhook(payload, sigHeader, stripeConfig.endpointSecret());}private Object webhook(String payload, String sigHeader, String endpointSecret) {Event event;try {event = Webhook.constructEvent(payload, sigHeader, endpointSecret);StripeEventType stripeEventType = StripeEventType.convert(event.getType());webhookHandlers.stream().filter(webhookHandler -> webhookHandler.supports(stripeEventType)).findFirst().get().handle(event);} catch (Exception e) {log.error("failed to handle webhook event. {}, {}", sigHeader, payload, e);return ResponseEntity.status(500).body(e.getMessage());}return ResponseEntity.ok().body("OK");}
}

Stripe 事件枚举

public enum StripeEventType implements EnumBase {  // 收银完成  CHECKOUT_SESSION_COMPLETED("checkout.session.completed"),  // 退款  CHARGE_REFUNDED("charge.refunded"),   IGNORED("");  private final String message;  StripeEventType(String message) {  this.message = message;  }  public static StripeEventType convert(String message) {  for (StripeEventType value : StripeEventType.values()) {  if (StringUtils.equals(value.message(), message)) {  return value;  }  }  return IGNORED;  }  @Override  public String message() {  return this.message;  }  @Override  public Number value() {  return null;  }  
}

Webhook 处理器

public interface WebhookHandler {  boolean supports(StripeEventType stripeEventType);  void handle(Event event) throws EventDataObjectDeserializationException;  
}public abstract class WebhookHandlerBase<T> implements WebhookHandler {  @SuppressWarnings("unchecked")  @Override  public void handle(Event event) throws EventDataObjectDeserializationException {  EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();  StripeObject stripeObject = dataObjectDeserializer.deserializeUnsafe();  handle((T) stripeObject);  }  public abstract void handle(T stripeObject);  
}@Component  
@Slf4j  
public class WebhookHandlerDefaultImpl implements WebhookHandler {  @Override  public boolean supports(StripeEventType stripeEventType) {  return stripeEventType.equals(StripeEventType.IGNORED);  }  @Override  public void handle(Event event) {  log.info("ignored event: {} {}", event.getType(), event.toJson());  }  
}@Component
@Slf4j
public class WebhookHandlerCheckoutSessionCompletedImpl extends WebhookHandlerBase<Session> {@Overridepublic boolean supports(StripeEventType stripeEventType) {return stripeEventType.equals(StripeEventType.CHECKOUT_SESSION_COMPLETED);}@Overridepublic void handle(Session session) {// 完成收银}
}@Component
@Slf4j
public class WebhookHandlerChargeRefundImpl extends WebhookHandlerBase<Charge> {@Overridepublic boolean supports(StripeEventType stripeEventType) {return stripeEventType.equals(StripeEventType.CHARGE_REFUNDED);}@Overridepublic void handle(Charge charge) {// 订单退款}
}

配置 Stripe Webhook

管理平台 – FeloTranslator – Stripe [Test]

image.png

4.4.3 完成收银

步骤流程:

  1. 判断对应的订单是否存在
  2. 订单所有者
  3. 对应的Stripe checkout session 状态是否正常
  4. 订单完成
  5. 发送订单完成事件
  6. 事件订阅者处理后续流程
protected Session checkCheckoutSession(String sessionId) {// 查询是否完成Session session = null;try {session = stripeClient.checkout().sessions().retrieve(sessionId);} catch (StripeException e) {log.error("failed to query checkout session. {}", sessionId, e);throw new RuntimeException("failed to query checkout session:" + sessionId);}// https://stripe.com/docs/api/checkout/sessions/object#checkout_session_object-payment_statusString status = session.getPaymentStatus();if (StringUtils.notEquals(status, "paid")) {throw new RuntimeException("Checkout has no completed: " + status);}return session;
}

Ref

Documentation | Stripe 文档

Stripe-hosted page | Stripe 文档

stripe/stripe-java: Java library for the Stripe API. (github.com)

Stripe API Reference

相关文章:

Stripe Web 购买集成

图片被吞了可以来这里看&#xff1a;https://juejin.cn/post/7346388511338381364 1. 准备事项 Stripe 账号域名以及配套的网站Stripe 账号付款信息公钥和私钥 2. 配置产品以及价格 可以通过 API 或者 Stripe 管理后台来进行配置 产品&#xff1a;就是商品&#xff0c;只需…...

加密货币在网络违法犯罪活动中的利用情况调查

一、调查背景 区块链基于分布式共识和经济激励等手段&#xff0c;在开放式、无许可的网络空间中&#xff0c;为价值的确立、存储、转移提供了新的解决方案。然而随着加密生态在过去若干年的快速发展&#xff0c;加密货币也越来越多地被用于各类风险活动&#xff0c;为网络赌博…...

【测试知识】业务面试问答突击版3---bug、测试用例设计

文章目录 一个完整的缺陷报告包含一个完整的测试用例包含一个完整的测试计划包含缺陷严重等级简述等价类划分法并举例简述边界值分析法逻辑覆盖针对具体场景的测试用例设计软件中存在多个分支时如何设计测试用例静态代码检查什么白盒测试是&#xff1f;常用方法是&#xff1f; …...

使用大型语言模型进行实体提取

原文地址&#xff1a;Using A Large Language Model For Entity Extraction LLM 能否比传统 NLP 方法更好地提取实体&#xff1f; 2022 年 7 月 12 日 Large Language Models for Generative Information Extraction: A Survey 实体简介 使用Co:here大型语言模型。 实体可以被视…...

基础:TCP是什么?

1. TCP 是什么&#xff1f; TCP&#xff08;Transmission Control Protocol 传输控制协议&#xff09; 是一种面向连接的、可靠的、基于字节流的传输层通信协议&#xff0c;由IETF的RFC 793 [1]定义。 TCP旨在适应支持多网络应用的分层协议层次结构。连接到不同但互连的计算机…...

el-table中 el-popover 性能优化

场景&#xff1a;在 el-table 中使用 el-popover ,出现了 loading 加载卡顿的问题&#xff0c;接口返回的数据的时间大概是 140ms &#xff0c;所以不是接口慢的原因&#xff1b;通过对表中结构的逐步排查&#xff0c;发现是表中的 某一行 所影响的&#xff1b;并且 其中含有 e…...

java数据结构与算法刷题-----LeetCode46. 全排列

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 暴力回溯2. 分区法回溯 1. 暴力回溯 解题思路&#xff1a;时…...

听说过Nginx反向代理,那正向代理是什么?

Nginx 是一款轻量级的 Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;它以其高性能、稳定性、丰富的功能集、简单的配置和低资源消耗而闻名。在 Nginx 中&#xff0c;正向代理和反向代理是两种常见的代理配置方式&#xff0c;它…...

实现elasticsearch和数据库的数据同步

1. 数据同步 elasticsearch中的酒店数据来自于mysql数据库&#xff0c;因此mysql数据发生改变时&#xff0c;elasticsearch也必须跟着改变&#xff0c;这个就是elasticsearch与mysql之间的数据同步。 1.1. 思路分析 常见的数据同步方案有三种&#xff1a; 同步调用 异步通知…...

SwiftUI的Alert使用方式

SwiftUI的Alert使用方式 记录一下SwiftUI的Alert使用方式&#xff0c;比较简单直接上代码 import SwiftUIstruct AlertBootCamp: View {State var showAlert falsevar body: some View {Button {showAlert.toggle()} label: {Text("alert show")}/// 单按钮 // …...

FPGA高端项目:FPGA基于GS2971的SDI视频接收+GTX 8b/10b编解码SFP光口传输,提供2套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收转HDMI输出应用本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS图像缩放Video Mixer多路视频拼接应用本方案的SDI接收OSD动态字符叠加…...

【源码编译】Apache SeaTunnel-Web 适配最新2.3.4版本教程

Apache SeaTunnel新版本已经发布&#xff0c;感兴趣的小伙伴可以看之前版本发布的文章 本文主要给大家介绍为使用2.3.4版本的新特性&#xff0c;需要对Apache SeaTunnel-Web依赖的版本进行升级&#xff0c;而SeaTunnel2.3.4版本部分API跟之前版本不兼容&#xff0c;所以需要对 …...

数据集下载

一、数据集下载——谷歌Open images 谷歌Open-image-v6是由谷歌出资标注的一个超大型数据集&#xff0c;数据大小达到600多G&#xff0c;类别达到600多种分类&#xff0c;对于普通研究者而言&#xff0c;根本没办法全部下载下来做测试&#xff0c;也没必要。只需要下载与自己任…...

3、设计模式之工厂模式2(Factory)

一、什么是工厂模式 工厂模式属于创建型设计模式&#xff0c;它用于解耦对象的创建和使用。通常情况下&#xff0c;我们创建对象时需要使用new操作符&#xff0c;但是使用new操作符创建对象会使代码具有耦合性。工厂模式通过提供一个公共的接口&#xff0c;使得我们可以在不暴露…...

npm、nodejs和vue之间关系和区别介绍

本文讲解npm、Node.js和Vue.js这三者之间的关系和区别&#xff0c;以及它们各自的特点。 首先&#xff0c;让我们来了解一下Node.js。 **Node.js** 是一个开源的服务器端运行环境&#xff0c;它允许开发者使用JavaScript来编写服务器端的代码。在传统的Web开发中&#…...

DM数据库安装(Windows)

先解压安装包 点击setup安装 下一步 勾选接受然后下一步 下一步 选择典型安装下一步 下一步 搜索DM数据库配置助手然后一直下一步 然后搜索DM管理工具 登录 登录成功 widows版本安装成功...

Python的asyncio 多线程

-- 多线程、进程、协程是什么就不讲了&#xff0c;&#xff08;就是你理解的一边呼吸&#xff0c;一边看文章&#xff09; 仅解决问题的话&#xff0c;下边两篇不用看&#xff0c; Python 中的 async await 概念-CSDN博客 再深一点的看这个 Python中的多线程、进程、协程、…...

【分类讨论】【解析几何】【 数学】【推荐】1330. 翻转子数组得到最大的数组值

作者推荐 视频算法专题 本文涉及知识点 分类讨论 解析几何 LeetCode1330. 翻转子数组得到最大的数组值 给你一个整数数组 nums 。「数组值」定义为所有满足 0 < i < nums.length-1 的 |nums[i]-nums[i1]| 的和。 你可以选择给定数组的任意子数组&#xff0c;并将该子…...

一文了解Spring的SPI机制

文章目录 一文了解Spring的SPI机制Java SPIServiceLoader Spring SPISpringboot利用Spring SPI开发starter 一文了解Spring的SPI机制 Java SPI SPI 全称 Service Provider Interface &#xff0c;是 Java提供的一套用来被第三方实现或者扩展的接口&#xff0c;它可以用来启用…...

django根据时间(年月日)动态修改表名--方法一

方法一&#xff1a; 第一步&#xff1a;在models创建一个类&#xff0c;里边存放数据表中需要的字段&#xff0c;如下 class TemplateModel(models.Model):NowTime models.CharField(max_length5)name models.CharFiedld(max_length5)class Meta:abstract True # 基础类设…...

【收藏干货】2026 版 11 款主流 AI Agent 框架全方位对比!程序员小白入门大模型必备选型指南

本篇整合当下热度顶尖的 11 款 AI Agent 开发框架&#xff0c;囊括 LangChain、AutoGen、CrewAI 等主流工具&#xff0c;新版补充实战落地要点与行业最新应用方向。围绕各框架核心特性、优缺点、适配场景展开深度比对&#xff0c;依托大语言模型搭建智能自主系统&#xff0c;可…...

圆桌探讨AI原生人才:从定义、来源到培养留存,为企业老板提供实战手册

嘉宾介绍真正AI原生的人或不来现场&#xff0c;对于积极转型的20%人群&#xff0c;有实战手册可用于找到、培养并留住他们。目前&#xff0c;仅1%的企业是AI原生企业&#xff0c;20%积极拥抱变化&#xff0c;80%在苦苦挣扎&#xff0c;人才情况也类似。一场圆桌深入探讨企业“赶…...

嘎嘎降AI和率零深度对比:2026年同为低价工具效果差距完整评测报告

嘎嘎降AI和率零深度对比&#xff1a;2026年同为低价工具效果差距完整评测报告 选工具之前做了一周功课&#xff0c;试用了三款&#xff0c;最后定了嘎嘎降AI&#xff08;www.aigcleaner.com&#xff09;。 4.8元&#xff0c;知网AI率从61%降到了5.3%&#xff0c;达标率99.26%…...

【Elasticsearch从入门到精通】第15篇:Elasticsearch删除与更新API——精确操作与脚本更新

上一篇【第14篇】Elasticsearch文档检索API——GET、MGet与字段选择 下一篇【第16篇】Elasticsearch批量操作API——Bulk、Reindex与跨集群索引 摘要 数据的删除和更新是Elasticsearch文档操作中不可或缺的环节。本文全面讲解了Elasticsearch删除与更新API的使用方法&#xff…...

Postman登录接口响应为空?HTTP响应体未刷出的三层根因分析

1. 这不是Postman的问题&#xff0c;是接口通信链路上某个环节“失语”了你用Postman调后端登录接口&#xff0c;请求发出去了&#xff0c;状态码也回来了&#xff08;比如200&#xff09;&#xff0c;但响应体里空空如也——没有JSON数据、没有token字段、甚至Response标签页里…...

3DS GBA硬件直通终极指南:用open_agb_firm获得原生游戏体验

3DS GBA硬件直通终极指南&#xff1a;用open_agb_firm获得原生游戏体验 【免费下载链接】open_agb_firm open_agb_firm is a bare metal app for running GBA homebrew/games using the 3DS builtin GBA hardware. 项目地址: https://gitcode.com/gh_mirrors/op/open_agb_fir…...

LimboAI在Godot 4中实现可维护游戏AI的工程化方案

1. 这不是又一个“AI行为树”教程——LimboAI在Godot 4里真正解决的是什么问题&#xff1f; 你有没有在Godot 4里写过这样的AI逻辑&#xff1a;一个巡逻的守卫&#xff0c;发现玩家后追击&#xff0c;进入攻击距离就挥剑&#xff0c;被击中后后退、喊话、短暂硬直&#xff0c;…...

初创公司如何利用Taotoken快速构建多模型AI应用原型

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 初创公司如何利用Taotoken快速构建多模型AI应用原型 对于资源有限的初创团队而言&#xff0c;验证一个AI产品想法的关键在于速度与…...

3大技术革命:openpilot如何重新定义自动驾驶开源生态

3大技术革命&#xff1a;openpilot如何重新定义自动驾驶开源生态 【免费下载链接】openpilot openpilot is an operating system for robotics. Currently, it upgrades the driver assistance system on 300 supported cars. 项目地址: https://gitcode.com/GitHub_Trending…...

PET模型完全指南:如何利用完形填空问题实现少样本文本分类与自然语言推理

PET模型完全指南&#xff1a;如何利用完形填空问题实现少样本文本分类与自然语言推理 【免费下载链接】pet This repository contains the code for "Exploiting Cloze Questions for Few-Shot Text Classification and Natural Language Inference" 项目地址: htt…...