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

【支付】Stripe支付通道Java对接(产品 价格 支付 查询 退款 回调)

Stripe是一家美国科技公司,成立于2010年,由爱尔兰兄弟Patrick Collison和John Collison共同创立。该公司致力于提供高效、简洁的互联网支付收款服务,为开发者或商家提供支付API接口或代码,使商家的网站、移动APP支持信用卡付款。Stripe被誉为“移动时代的PayPal”,因其简便的支付方式而受到广泛欢迎。

Stripe 总共有三种支付方式:
1、Stripe Checkout,pay links 创建支付链接,
2、payment intent,后端预下单,返回秘钥,前端确定订单
3、前端创建支付token ,后端创建Charge,返回支付结果链接

Stripe接口调用时序图

后台服务 Stripe 【初始化】客户端 StripeClient,使用从Stripe平台获取的私钥 初始化成功,创建 StripeClient 实例 【创建产品】 client.products().create() 创建产品成功,返回产品信息 【创建产品价格】 client.prices().create(),给产品创建指定币种的价格 创建产品价格成功,返回价格信息 【下单】 client.checkout().sessions().create() 返回订单信息,包含支付链接 session.getUrl() 和订单id 浏览器打开支付链接【完成支付】 支付完成,跳转到完成页面 【支付完成回调】webhook请求 checkout.session.completed 事件,返回 Session 【支付成功回调】webhook请求 charge.succeeded 事件,返回 Charge 【查询订单】client.charges().retrieve(chargeId) 返回订单信息 【退款】client.refunds().create(),传入 chargeId 返回退款订单信息(退款ID refundId) 【查询退款订单】 client.refunds().retrieve(refundId) 或 client.refunds().list() 返回退款订单信息 后台服务 Stripe

0、初始化客户端

StripeClient client = StripeClient.builder().setConnectTimeout(30 * 1000).setReadTimeout(80 * 1000).setApiKey("sk_test_51PtO7DC6XhwanSnNvGezNPc4hsL2F****").build();

1、产品

查询或创建新产品。

每次交易传入产品名称或描述,自动查询是否已经存在,如果存在则直接使用,如果不存在则新建产品。

private Product getProduct(StripeOrder stripeOrder) throws StripeException {ProductSearchParams searchParams =ProductSearchParams.builder().setQuery("active:'true' AND name:'" + stripeOrder.getSubject()+ "' AND description:'" + stripeOrder.getBody() + "'").setLimit(1L).build();StripeSearchResult<Product> result = client.products().search(searchParams);Product product;if (result != null && !result.getData().isEmpty()) {product = result.getData().stream().findFirst().get();} else {//创建产品 https://stripe.com/docs/api/products/createProductCreateParams params = ProductCreateParams.builder().setDescription(stripeOrder.getBody()).setName(stripeOrder.getSubject()).build();product = client.products().create(params);}return product;}

2、价格

根据产品ID创建对应币种的价格,指定 lookupKey = 价格+币种+产品ID,作为价格关键字,用于查询是否已经存在。如果价格存在则直接使用,如果不存在则新增新的价格。

private Price getPrice(StripeOrder stripeOrder, String productId) throws StripeException {Long unitAmount = Util.conversionCentAmount(stripeOrder.getPrice());String lookupKey = unitAmount + stripeOrder.getCurrencyCode() + productId;PriceSearchParams params =PriceSearchParams.builder().setQuery("active:'true' AND product:'" + productId+ "' AND currency:'" + stripeOrder.getCurrencyCode()+ "' AND lookup_key:'" + lookupKey + "'").build();StripeSearchResult<Price> result = client.prices().search(params);Price price;if (result != null && !result.getData().isEmpty()) {price = result.getData().stream().findFirst().get();} else {//创建价格 https://stripe.com/docs/api/prices/create
//          PriceCreateParams.Recurring recurring = PriceCreateParams.Recurring.builder()
//                    .setInterval(PriceCreateParams.Recurring.Interval.MONTH).build();PriceCreateParams priceCreateParams = PriceCreateParams.builder().setCurrency(stripeOrder.getCurrencyCode()).setProduct(productId).setLookupKey(lookupKey).setUnitAmount(unitAmount)
//                    .setRecurring(recurring).build();price = client.prices().create(priceCreateParams);}return price;
}

3、创建支付

根据上次返回的价格信息,创建新的支付对象,这里指定银行卡支付,也可以指定别的支付方式;Stripe支持几十种支付方式,可以根据不同国家选择,具体可以在这里查看

下面通过 client.checkout().sessions().create() 创建支付,取得支付链接,在浏览器直接打开即可支付。

public Map<String, Object> orderInfo(PayOrder order) {StripeOrder stripeOrder = (StripeOrder) order;try {Product product = getProduct(stripeOrder);Price price = getPrice(stripeOrder, product.getId());//创建支付信息 得到urlSessionCreateParams sessionCreateParams = SessionCreateParams.builder().setMode(SessionCreateParams.Mode.PAYMENT).addPaymentMethodType(SessionCreateParams.PaymentMethodType.CARD)
//                    .addPaymentMethodType(SessionCreateParams.PaymentMethodType.ALIPAY).setSuccessUrl(payConfigStorage.getReturnUrl()).setCancelUrl(payConfigStorage.getCancelUrl()).setCustomer(stripeOrder.getCustomer()).setClientReferenceId(stripeOrder.getClientReferenceId()).setCustomerEmail(stripeOrder.getCustomerEmail()).addLineItem(SessionCreateParams.LineItem.builder().setQuantity(1L).setPrice(price.getId()).build()).putMetadata("outTradeNo", stripeOrder.getOutTradeNo()).build();Session session = client.checkout().sessions().create(sessionCreateParams);LOG.info("session:{}", JSON.toJSONString(session));return preOrderHandler(Collections.singletonMap("paymentLink", session.getUrl()), order);} catch (StripeException e) {throw new RuntimeException(e);}
}

4、打开支付链接,完成支付

输入Stripe平台提供的测试卡信息,完成支付

test@example.com
4242 4242 4242 4242
12/34
567
Zhang San
United States
12345

在这里插入图片描述

5、支付订单查询

传入回调信息中得到的chargeId,查询订单状态。在返回的json数据了包含支付平台receiptUrl

public Map<String, Object> query(AssistOrder assistOrder) {try {Charge charge = client.charges().retrieve(assistOrder.getTradeNo());// 使用Hutool的BeanUtil将User对象转换为Mapreturn BeanUtil.beanToMap(charge);} catch (Exception e) {throw new RuntimeException(e);}
}

支付凭证

在这里插入图片描述

6、退款

传入回调信息中得到的chargeId,提交退款请求;返回退款订单信息,包含退款订单Id(可用于查询退款订单)

public RefundResult refund(RefundOrder refundOrder) {RefundCreateParams params = RefundCreateParams.builder().setCharge(refundOrder.getTradeNo()).build();try {Refund refund = client.refunds().create(params);LOG.info("refund:{}", JSON.toJSONString(refund));StripeRefundResult refundResult = new StripeRefundResult(refund, refundOrder.getTradeNo());refundOrder.setRefundNo(refundResult.getRefundNo());return refundResult;} catch (StripeException e) {throw new RuntimeException(e);}
}

7、退款查询

传入回调信息中得到的chargeId,查询退款订单;也可以通过退款订单Id查询。

public Map<String, Object> refundquery(RefundOrder refundOrder) {RefundListParams params = RefundListParams.builder().setCharge(refundOrder.getTradeNo()).build();try {StripeCollection<Refund> result = client.refunds().list(params);if (!result.getData().isEmpty()) {Refund refund = result.getData().stream().findFirst().get();// 使用Hutool的BeanUtil将User对象转换为Mapreturn BeanUtil.beanToMap(refund);}} catch (StripeException e) {throw new RuntimeException(e);}return null;
}

8、回调通知

登录Stripe平台,在https://dashboard.stripe.com/workbench/webhooks中配置Webhook;菜单地址“开发人员-Webhook”。
在这里插入图片描述

Webhook回调代码示例

public class StripePayMessageHandler implements PayMessageHandler<PayMessage, StripePayService> {private final Logger LOG = LoggerFactory.getLogger(getClass());private final String endpointSecret = "whsec_HlzC2omyh4V4X3BCgMx5PScTCmwZpvAC";//webhook秘钥签名@Overridepublic PayOutMessage handle(PayMessage payMessage, Map<String, Object> context, StripePayService payService) throws PayErrorException {Map<String, Object> message = payMessage.getPayMessage();NoticeParams noticeParams = (NoticeParams) context.get("noticeParams");try {String sigHeader = noticeParams.getHeader("Stripe-Signature");Event event = Webhook.constructEvent(noticeParams.getBodyStr(), sigHeader, endpointSecret);//验签,并获取事件StripeObject eventObj = event.getDataObjectDeserializer().getObject().get();LOG.info("EventType:{}, Event:{}", event.getType(), JSON.toJSONString(eventObj));PaymentIntent intent;Charge charge;String outTradeNo;String chargeId;String receiptUrl;switch (event.getType()) {case "charge.succeeded"://支付成功//TODO 支付成功,处理业务逻辑charge = (Charge) eventObj;// 取得 chargeId ,在退款时使用chargeId = charge.getId();receiptUrl = charge.getReceiptUrl();message.put("trade_no", chargeId);LOG.info("支付成功 Charge, chargeId:{}, receiptUrl:{}", chargeId, receiptUrl);break;case "checkout.session.completed":// 通过支付链接 支付完成//TODO 支付完成,处理业务逻辑Session session = (Session) eventObj;outTradeNo = session.getMetadata().get("outTradeNo");//自定义订单号LOG.info("支付完成 Session, 订单号为:{}", outTradeNo);message.put("out_trade_no", outTradeNo);break;case "charge.refunded"://退款成功charge = (Charge) eventObj;if (charge.getStatus().equals("succeeded")) {//TODO 退款成功,处理业务逻辑chargeId = charge.getId();receiptUrl = charge.getReceiptUrl();message.put("trade_no", chargeId);LOG.info("退款成功, chargeId:{}, receiptUrl:{}", chargeId, receiptUrl);}break;case "checkout.session.expired"://过期break;case "payment_intent.created"://创建订单 这里事件就是图二选着的事件break;case "payment_intent.canceled"://取消订单break;case "payment_intent.succeeded"://支付成功intent = (PaymentIntent) eventObj;Map<String, String> metaData = intent.getMetadata();//自定义传入的参数outTradeNo = metaData.get("outTradeNo");//自定义订单号message.put("out_trade_no", outTradeNo);LOG.info("支付成功 payment_intent, 订单号为:{}", outTradeNo);//*********** 根据订单号从数据库中找到订单,并将状态置为成功 *********//*break;case "payment_intent.payment_failed"://支付失败intent = (PaymentIntent) eventObj;LOG.info("Failed: " + intent.getId());break;default:break;}} catch (Exception e) {LOG.error("stripe异步通知(webhook事件)", e);}// TODO 支付确认逻辑处理return payService.successPayOutMessage(payMessage);}
}

参考

  • https://docs.stripe.com/api
  • https://docs.stripe.com/js
  • https://docs.stripe.com/webhooks
  • https://docs.stripe.com/search#query-fields-for-products

相关文章:

【支付】Stripe支付通道Java对接(产品 价格 支付 查询 退款 回调)

Stripe是一家美国科技公司&#xff0c;成立于2010年&#xff0c;由爱尔兰兄弟Patrick Collison和John Collison共同创立。该公司致力于提供高效、简洁的互联网支付收款服务&#xff0c;为开发者或商家提供支付API接口或代码&#xff0c;使商家的网站、移动APP支持信用卡付款。S…...

Unity3D 小案例 像素贪吃蛇 01 蛇的移动

Unity3D 小案例 像素贪吃蛇 第一期 蛇的移动 像素贪吃蛇 今天来简单制作一个小案例&#xff0c;经典的像素贪吃蛇。 准备 首先调整一下相机的设置&#xff0c;这里使用灰色的纯色背景&#xff0c;正交视图。 接着&#xff0c;创建一个正方形&#xff0c;保存为预制体&#…...

【STM32 MCU】stm32MCUs 32-bit Arm Cortex-M

stm32MCUs 32-bit Arm Cortex-M...

html+css网页设计 旅游 雪花旅行社5个页面

htmlcss网页设计 旅游 雪花旅行社5个页面 网页作品代码简单&#xff0c;可使用任意HTML辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&#…...

vue3中的实例

实例类型 Vue2&#xff1a;每个Vue应用都是new Vue创建的一个新实例&#xff0c;创建的时候将data作为property添加到响应式系统中 vue3&#xff1a;createApp创建一个Application Instance、应用实例用来注册全局内容&#xff0c;大多数方法支持链式调用&#xff0c;返回实例…...

9.测试计划(包含笔试/面试题)

一、软件测试计划介绍 1.测试计划就是一份测试文档&#xff0c;一份描述测试工作计划的文档&#xff0c;对测试计划进行统筹安排。 2.测试计划的编写者就是测试组长&#xff0c;测试主管。 3.测试计划的查阅者&#xff1a;测试人员&#xff0c;测试主管&#xff0c;产品&#x…...

这 7 款AI应用将让你全新的iPhone 16成为电影制作的强大工具

苹果公司在周一的Glowtime发布会上揭晓了新款的iPhone 16 Pro系列。除了新加入的苹果智能功能和令人印象深刻的硬件升级外&#xff0c;它还获得了一套视频制作工具&#xff0c;让用户能够在一个几乎可以放进口袋的设备上制作整部电影。 这些升级中有一个48MP融合相机。它具有2…...

自注意力机制(self-attention)

自注意力机制&#xff08;self-attention&#xff09; 之前听过吴恩达老师的课&#xff0c;吴恩达老师CNN那一块讲的特别好&#xff0c;但是后面RNN这一部分我听的不是很明白&#xff0c;今天有看了李宏毅老师attention这部分的课&#xff0c;总结一下笔记。 self-attention …...

Nuxt3入门:过渡效果(第5节)

你好同学&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 Nuxt 利用 Vue 的 <Transition> 组件在页面和布局之间应用过渡效果。 一、页面过渡效果 你可以启用页面过渡效果&#xff0c;以便对所有页面应用自动过渡效果。 nuxt.config.js export defaul…...

【开发工具】IntelliJ IDEA插件推荐:Json Helper——让JSON处理更高效

导语&#xff1a;在Java开发过程中&#xff0c;JSON作为一种轻量级的数据交换格式&#xff0c;被广泛应用于前后端数据交互。今天&#xff0c;我要为大家介绍一款IntelliJ IDEA插件——Json Helper&#xff0c;帮助开发者更高效地处理JSON数据。 一、什么是Json Helper&#x…...

Lua垃圾回收机制

Lua垃圾回收机制 在 Lua 中&#xff0c;一共只有8种数据类型&#xff0c;分别为 nil 、boolean 、userdata 、number 、string 、 table 、 function 、 userdata 和 thread 。其中&#xff0c;只有 string table function thread 四种是以引用方式共享&#xff0c;是需要被 G…...

Java学习路线:详细指引

Java学习路线可以分为几个阶段&#xff0c;每个阶段都有其重点和推荐学习的内容。下面我将按照初学者、进阶和高级三个阶段来举例说明&#xff1a; 初学者阶段 目标&#xff1a; 熟悉Java基础语法理解面向对象编程掌握基本数据类型和数据结构学会使用IDE&#xff08;如Intel…...

商家转账到零钱如何开通-微信支付

商家转账到零钱是微信支付的一项实用功能&#xff0c;允许商户将资金从商户号余额直接转账到用户的微信零钱。我们以上万次成功申请的经验整理了本文的详细的步骤和建议以帮助商户可以快速开通该功能。 1. 准备工作 - 确认申请资格&#xff1a;只有公司性质的商户可以申请此功能…...

自研商家如何快速接入电商平台订单数据?

随着电子商务行业的快速发展&#xff0c;越来越多的商家开始寻求高效的订单管理和数据整合方案。对于那些自研系统的商家来说&#xff0c;如何实现与各大电商平台之间的无缝对接&#xff0c;成为了一项重要挑战。点三电商API正是为此类需求量身打造&#xff0c;为商家提供了一站…...

Win10下借助CMake编译OpenMVS

笔者在编译OpenMVS的过程十分曲折。刚开始借助CMake编译,能够把与库生成相关的工程编译出来,但是与可执行文件相关的工程会报错;后来参考官方教程借助VCPKG编译,发现VCPKG并没有想中强大、好用,最终也是遇到了各种问题没有编译成功。但是,笔者在解决问题的过程发现了问题…...

04_定时器与数码管基础

通过上节课的实验&#xff0c;大家会发现&#xff0c;我们逐渐进入比较实质性的学习了&#xff0c;需要记住的内容也更多了&#xff0c;个别地方可能会感觉吃力。但是大家不要担心&#xff0c;要有信心。这个跟小孩学走路一样&#xff0c;刚开始走得不太稳&#xff0c;没关系&a…...

Python 数学建模——方差分析

文章目录 前言单因素方差分析原理核心代码 双因素方差分析数学模型分析依据典型代码 前言 方差分析也是概率论中非常重要的内容&#xff0c;有时数学建模需要用到。方差分析是干什么的&#xff1f;如果说假设检验用于分析两个总体之间的均值 μ 1 , μ 2 \mu_1,\mu_2 μ1​,μ…...

计算机视觉中,什么是上下文信息(contextual information)?

在计算机视觉中&#xff0c;上下文信息&#xff08;contextual information&#xff09;是指一个像素或一个小区域周围的环境或背景信息&#xff0c;它帮助模型理解图像中对象的相对位置、大小、形状&#xff0c;以及与其他对象的关系。上下文信息在图像中提供了全局的语义和结…...

YOLOv5改进 | 模块缝合 | C3 融合RVB + EMA注意力机制【二次融合】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv5入门 改…...

mysql 更改默认端口号 新增用户密码 赋予权限

默认情况下&#xff0c;mysql的端口是3306&#xff0c;超级用户是root&#xff0c;很多情况下会被黑客扫描到&#xff0c;成为肉鸡&#xff08;作者以前就有过经理&#xff09;&#xff0c;数据库表直接丢失&#xff0c;勒索我。 所以我这里介绍下&#xff0c;更改默认端口&am…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

GitHub 趋势日报 (2025年06月06日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解

进来是需要留言的&#xff0c;先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码&#xff0c;输入的<>当成字符串处理回显到页面中&#xff0c;看来只是把用户输…...

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...

鸿蒙(HarmonyOS5)实现跳一跳小游戏

下面我将介绍如何使用鸿蒙的ArkUI框架&#xff0c;实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…...