使用 Stripe API,如何从 payment_intent.succeeded Webhook 获取创建 payment_intent 对象的结帐会话的 session_id

With Stripe API, How To Get session_id of Checkout Session That Created a payment_intent Object, From payment_intent.succeeded Webhook

我正在开发和测试用 PHP 编写的 Stripe 集成,并且运行良好。我可以创建会话,重定向到结帐表单,当付款完成后,它会向我的 webhook 脚本发送一个事件,该脚本成功处理了有关付款的信息。

当我创建会话时,我将有关在我的网站上填写的表格的数据存储在数据库中,当付款完成时,我将信息存储在不同的 table,这很棒.

我遇到的问题是我不知道如何 link 使用生成它的会话来增加有关成功付款的信息。

链接这些数据对我来说很重要,因为我想跟踪哪些会话实际导致成功付款,这样我就可以分析用户界面的流程并跟踪转化率并分析导致放弃的因素结帐会话。

在某些情况下,很容易link这些东西。例如,如果在给定时间范围内与给定电子邮件相关联的只有一个会话生成和一次成功付款,我可以 link 它们。问题是我希望能够处理一个人创建多个会话并放弃它们的(可能常见的)场景。在这种情况下,我无法 link 向与电子邮件关联的最近会话付款,因为单个客户可能会创建两个会话,但在第一个较早创建的会话中完成付款。

我不知道如何从返回到我的 webhook 的 payment_intent 对象访问 session_id。我对如何解决这个问题的一些想法包括:

我想在这里遵循“最佳实践”,但我不清楚 Stripe 打算如何让人们 link 上传或访问数据,或者这是否可能是他们的疏忽。

如果您给我示例代码,我希望尽可能在 PHP 中看到它,但您根本不需要向我展示任何代码;只需给我一个关于如何实现此目的的抽象或一般概念就足够了,我可以自己提出编码细节。

payment_intent.succeeded 事件为您提供了 PaymentIntent ID。您可以使用 CheckoutSessions“列表”端点 [0] 通过传递 payment_method 参数来获取使用该 PaymentIntent ID 的 CheckoutSession。

[0] https://stripe.com/docs/api/checkout/sessions/list#list_checkout_sessions-payment_intent

Stripe 数据的结构方式是 Session 对象有一个字段 payment_intent,其中包含一个 payment_intent 对象的 ID。最初,在创建会话时,此字段为空或 null,但在进行支付时,它会被填充。 payment_intent 对象不包含任何与 link 它们相关的字段,部分原因是 payment_intent 可以通过许多不同的方法生成,而不仅仅是结帐会话。所以需要用到Session对象,但是需要在session结束后访问

因此,我能够通过设置一个 webhook 来监听 checkout.session.completed 事件来达到预期的结果,因为在该事件触发时,该字段已填充并表示已成功付款经过,and 事件 returns 会话对象,其中包含相关字段。 payment_intent.succeeded 事件的 webhook 不太有用,因为它仅 returns payment_intent 对象,如果不额外调用 API 则无法从中访问相关信息。 @hmunoz 的解决方案提供了一种方法来执行此操作,但我更喜欢我的解决方案,因为它避免了这个额外的 API 调用。因为 API 调用依赖于网络访问,所以它们通常是我的脚本中最慢的步骤,也是最容易出错的步骤,所以我倾向于选择最小化它们的解决方案。

一旦我检索到与会话对应的 payment_intent 对象的 Stripe ID,然后我更新数据库中与会话对应的条目,以包含 Stripe 的 payment_intent ID,并且可以在我的数据库中加入数据。

比起使用元数据,我更喜欢这种解决方案,因为它不需要传递任何元数据。可能有一种方法可以使用元数据来实现类似的结果,使用我自己内部生成的 ID,但我无法让它工作,因为 Stripe 似乎只是丢弃了我传递的元数据。也许我做错了什么,但在我能够让基于元数据的解决方案工作之前,我已经让这个解决方案工作了。

关于 ID 的注意事项:切换到本地 ID 是有益的,但很棘手

一个小小的警告或注意点,因为我有一个处理 payment_intent.succeeded 事件的 webhook(因为它可以通过结帐会话以外的其他方式生成)并使用它从 payment_intent 对象,包括它的 ID,导入到本地数据库中,您无法保证事件将以何种顺序触发。因此,如果您正在查找 link 本地数据库中与结帐会话对象和 payment_intent 对象相对应的 tables,您最终可能会保存 payment_intent 在您的 table 会话中的 ID, 之前 payment_intent 的相应行已输入到 [=42] 的 table =]s.

因此我需要在每个事件的事件处理中写入一个条件,检查该行是否存在(对应于首先处理的事件。)如果 checkout.session.completed 首先触发,我将 Stripe 的 payment_intent 的 ID 放在会话的条目中,然后,当 payment_intent.succeeded 触发时,我不仅添加了 payment_intent 对象,而且还使用我的更新了会话的行payment_intent 的本地 ID,它是一个整数,允许更快速和计算强度较低的连接。

另一方面,如果 payment_intent.success 先触发,然后当 checkout.session.completed 触发时,我在我的数据库中检索适当的本地 ID,参考已完成的付款,并将其放入会话的本地 table。

我强烈建议这样做,尽管它需要做更多的工作,因为 Stripe 的 ID 是长字符串,具有某种任意长度(即,根据他们的文档,他们保留延长它们的权利),这意味着您需要一个为了在连接中获得良好的性能,文本字段上的索引很长,这是我想避免的。为每个 table 转换为您自己的本地整数 ID 允许您以更好的性能和更少的服务器负载在本地加入它们。相对于加入此数据的次数而言,事件处理相对较少,这既是因为客户查看他们自己的数据,也是因为如果您像我一样想要经常查看和分析您的数据。