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

caz*_*ort 8 php checkout stripe-payments

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

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

我遇到的问题是我不知道如何将有关成功付款的信息与生成它的会话链接起来。

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

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

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

  • 监听我的 webhook 脚本中发生的其他事件,这可能允许我链接这两个记录。
  • 将元数据传递到会话,例如唯一生成的 ID,然后从 payment_intent 对象访问相同的元数据。然而,我无法通过阅读 Stripe 文档弄清楚元数据是如何工作的,即使元数据从会话传递到 payment_intent 对象(文档没有明确说明这一点或解释它,并且 session_id 没有传递的事实让我想知道元数据是否会被传递)。我不想这样做,因为它需要在生成会话之前生成唯一 ID 的额外步骤,这将需要我做更多的工作,并且还会使我的代码更加复杂,并涉及更多的数据库调用和更多潜在的步骤可能会出错(当前我正在生成会话,然后存储信息以响应会话的成功创建),但如果确实没有更好的选择,我可以容忍它。

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

如果你给我示例代码,如果可能的话,我更愿意在 PHP 中看到它,但你根本不需要向我展示任何代码;只需给我一个关于如何实现这一目标的抽象或一般想法就足够了,我可以自己想出编码细节。

caz*_*ort 9

Stripe 数据的结构方式是 Session 对象有一个字段payment_intent,其中包含 payment_intent 对象的 id。最初,当创建会话时,该字段为空或为 null,但是当执行付款时,该字段将被填充。payment_intent 对象不包含任何链接它们的相关字段,部分原因是 payment_intent 可以通过许多不同的方法生成,而不仅仅是结帐会话。所以我需要使用Session对象,但是我需要在会话完成后访问它。

因此,我能够通过设置一个 webhook 来侦听该checkout.session.completed事件来实现所需的结果,因为当该事件触发时,该字段已填充并引用已成功完成的付款,并且该事件返回会话对象,其中包含相关字段。该事件的 Webhookpayment_intent.succeeded不太有用,因为它只返回 payment_intent 对象,如果不调用额外的 API 就无法从中访问相关信息。@hmunoz 的解决方案提供了一种方法来执行此操作,但我更喜欢我的解决方案,因为它避免了这种额外的 API 调用。由于 API 调用依赖于网络访问,因此它们通常是我的脚本中最慢的步骤,并且最容易出错,因此我倾向于选择将它们最小化的解决方案。

一旦我检索到引用与会话相对应的 payment_intent 对象的 Stripe 的 ID,我就会更新数据库中与该会话相对应的条目,以包含 Stripe 的 payment_intent ID,并且可以将数据连接到我的数据库中。

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

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

需要注意的一点是,因为我有一个 webhook 来处理该payment_intent.succeeded事件(因为它可以通过结帐会话以外的其他方式生成),并使用它来将 payment_intent 对象中的一些详细信息(包括其 ID)输入到本地数据库中,您无法保证事件将按什么顺序触发。因此,如果您希望链接本地数据库中对应于结账会话对象和 payment_intent 对象的表,您最终可能会在该 payment_intent 的相应行之前在会话表中保存 payment_intent 的 ID。已输入到 payment_intents 表中。

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

另一方面,如果 payment_intent.success 首先触发,那么当 checkout.session.completed 触发时,我会在数据库中检索相应的本地 ID(引用已完成的付款),并将其放入会话的本地表中。

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


hmu*_*noz 7

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

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