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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑群晖: 未在 DS918+ 中检测到硬盘 之 解决方案

黑群晖&#xff1a; 未在 DS918 中检测到硬盘 之 解决方案 操作如下&#xff1a; 进入BIOS&#xff0c;将sata operation 设置为 AHCI 即可

Windows,MacOS,Linux下载python并配置环境图文讲解

Windows 打开python官网 点击download 点击黄色按钮 另存为 打开文件 全选 配置安装路径 安装中 关闭路径长度限制 完成 验证 同时按住winr(win就是空格键左边的东西) 输入cmd 键入python,如果出现版本(红框)即安装成功 MacOS 同理打开python官网 点击最新版本 拖…

长安逸动行车记录仪删除恢复方法

行车记录仪的品牌很多&#xff0c;但是主机厂自带的相对较少&#xff0c;之前处理过像特斯拉、极氪、领克等车载的记录仪恢复。今天我们来看一个长安逸动原厂行车记录仪的恢复案例。 故障存储:8G存储卡 fat32文件系统 故障现象: 这个卡的容量不算大&#xff0c;算是小卡&am…

计算机网络笔记(湖科大教书匠版本)

第一章、 ①三种交换方式 电路交换、分组交换、报文交换&#xff08;被分组交换所取代&#xff09; 1.电路交换&#xff1a;会一直占用通道&#xff0c;不适合计算机之间的数据通信 2.分组交换&#xff1a;通常我们把表示该数据的整块数据称为一个报文。 先把较长的报文划…

虚幻引擎5比Maya更好用吗?来看看Maya大神眼中的虚幻引擎5

这两年&#xff0c;大家总在争论&#xff1a; 虚幻引擎5&#xff08;UE5&#xff09;比Maya更好用吗&#xff1f; 未来会替代Maya吗&#xff1f; 虚幻引擎5(UE5)的快速发展&#xff0c;让许多传统Maya动画师感到焦虑和迷茫。但不要担心&#xff0c;这篇文章旨在解决你的困扰。…

基于springboot+vue实现的大学计算机课程管理平台的设计与实现(全套资料)

一、系统架构 前端&#xff1a;vue | antv 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk17 | mysql | maven | node | redis 二、代码及数据库 三、功能介绍 01. 登录页 02. 首页 03. 系统基础模块-用户管理 04. 系统基础模块-部门…

Linux学习之网络

目录 认识协议 网络协议初始 协议分层 OSI七层模型 TCP/IP的四层模型 数据包封装和分用 以太网通信 ip地址与MAC地址 网络编程套接字 端口号&#xff08;port&#xff09; 认识协议 网络字节序 socket接口 网络的产生是计算机历史的必然性&#xff0c;是计算机发展…

ResNet学习笔记

一、residual结构 优点&#xff1a; &#xff08;1&#xff09;超深的网络结构(突破1000层) &#xff08;2&#xff09;提出residual模块 &#xff08;3&#xff09;使用Batch Normalization加速训练(丢弃dropout) 解决问题&#xff1a; &#xff08;1&#xff09; 梯度消失和…

【SQL Server】实验三 高级查询

1 实验目的 掌握SQL的高级查询的使用方法&#xff0c;如分组统计、嵌套查询、集合查询、排序等等。 2 实验内容 2.1 掌握SQL高级查询使用方法 分组统计。嵌套查询&#xff0c;包括IN查询、EXISTS查询。集合查询。排序。 3 实验要求 深入复习教材第三章SQL有关高级查询语句…

WordPress供求插件API文档:获取市场类型

请注意&#xff0c;该文档为&#xff1a; WordPress供求插件&#xff1a;一款专注于同城生活信息发布的插件-CSDN博客文章浏览阅读396次&#xff0c;点赞6次&#xff0c;收藏5次。WordPress供求插件&#xff1a;sliver-urban-life 是一款专注于提供同城生活信息发布与查看的插件…

水果音乐编曲和制作软件 FL Studio 中文汉化破解版下载 FL Studio 中文设置教程

FL Studio 21 也就是 Image-Line 出品的一款功能强大的编曲软件&#xff0c;全名 Fruity Loops Studio 简称“FL Studio”。 FL Studio 21.2汉化破解版是功能强大的音乐制作解决方案&#xff0c;使用旨在为用户提供一个友好完整的音乐创建环境&#xff0c;让您能够轻松创建、管…

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

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