Stripe logo

Contact sales Sign in Dashboard Sign in Sign in Open mobile navigation Stripe logo Back Close mobile navigation

Global payments

Embedded payments and Finance

Revenue and Finance Automation

More

* [ Payment methods  Access to 100+ globally ](https://stripe.com/blog/</payments/payment-methods>)
* [ Link  Accelerated checkout ](https://stripe.com/blog/</payments/link>)
* [ Financial Connections  Linked financial account data ](https://stripe.com/blog/</financial-connections>)
* [ Identity  Online identity verification ](https://stripe.com/blog/</identity>)
* [ Atlas  Startup incorporation ](https://stripe.com/blog/</atlas>)

By stage

By business model

By use case

Ecosystem

Get started

Guides

Sign in Dashboard Sign in Sign in

Global payments

Embedded payments and Finance

Revenue and Finance Automation

More

By stage

By business model

By use case

Ecosystem

Get started

Guides

  1. Blog
  2. Engineering

Stripe on X

Stripe’s payments APIs: The first 10 years

December 15, 2020 Michelle Bu Payments 几年前,《彭博商业周刊》发表了一篇关于Stripe的专题报道。封面中心用了四个大字:“七行代码”,暗示企业只需这几行代码就能通过Stripe支持支付功能。这个说法很吸引人,后来也成了我们的一个主题和梗。

时至今日,我们也不完全清楚文章中提到的这七行代码究竟是什么。普遍的理论是,它指的是创建 Charge 大约需要的七行 curl 代码。2011年,我们着陆页上的代码片段是九行。但如果移除可选的 descriptioncard[cvc],那么视觉上就是七行: blog-payment-api-design-screenshot Stripe.com 的局部截图,大约在 2011 年。图片由 the Internet Archive Wayback Machine 提供。

然而,寻找 七行代码最终会偏离重点:打开终端,运行这段 curl 代码片段,然后_立即_看到信用卡支付成功,感觉就像 七行代码一样。开发人员不太可能相信一个生产级别的支付集成真的只需要七行代码。但是,将像信用卡处理这样复杂的事情简化到只有几行代码,并且在运行后立即返回一个成功的 Charge 对象,这确实非常神奇。

在过去十年里,我们 API 的演变一直受到对支付复杂性进行抽象化的需求的驱动。本文提供了我们 API 设计背后的背景、转折点和概念框架。我们的 API 方法登上商业杂志的封面,这属于极其罕见的情况。本文将分享更多关于我们如何围绕这七行代码发展壮大的故事。

Stripe支付 API 的简要历史

成功的产品往往会随着时间的推移而有机地扩展,从而导致产品债务。与技术债务类似,产品债务会逐渐累积,使得用户更难理解产品,产品团队更难更改产品。对于 API 产品来说,积累产品债务尤其具有诱惑力,因为很难让用户从根本上重构他们的集成;让他们在现有的 API 请求中添加一两个参数要容易得多。

回过头来看,我们可以清楚地看到我们的 API 是如何演变的,以及哪些决策对于塑造它们至关重要。以下是定义我们支付 API 并促成 PaymentIntents API 的里程碑。

支持美国的银行卡支付 (2011-2015)

我们首先在美国推出了 Stripe API,在那里,信用卡曾经是而且现在仍然是主要的支付方式。“七行代码”在很大程度上已经足够了,但实际情况稍微复杂一点。我们还创建了 Stripe.js,这是一个 JavaScript 库,用于从浏览器收集银行卡支付详细信息,并将其安全地存储在 Stripe 中,表示为 Token,稍后可用于创建 Charge。这有助于用户避免繁琐的 PCI 合规性要求。 payment api diagram 1 Token 在客户端创建并发送到服务器。然后使用该 Token 在服务器端创建一个 Charge

这种支付流程遵循传统 Web 应用程序中非常常见的模式。JavaScript 客户端使用可发布的 API 密钥创建一个 Token,并在客户提交支付表单时将其发送到服务器(以及有关订单的其他表单数据)。服务器使用该 Token 和一个密钥 API 密钥同步创建一个 Charge;订单可以有选择地根据支付结果来完成。

ChargeToken 成为我们支付 API 的基础概念。

添加 ACH 和 Bitcoin (2015)

当我们第一次创建 ChargesTokens 时,它们只支持银行卡支付。随着我们扩展到更多国家和类型的用户,我们需要向 API 添加更多 支付方式。2015 年,我们添加了:

当用户对资金有保证有足够的信心时,我们会将付款描述为“已完成”。(当然,即使是已完成的支付,以后也可能由于欺诈或后续退款而被撤销。)在大多数情况下,在完成付款后,用户会放行货物的运输。虽然在银行卡网络上处理的支付由商家发起并且可以立即完成,但这两种支付方式与银行卡截然不同。在 ACH 网络上处理的支付会在_几天后_完成。对于 Bitcoin,客户(而不是商家)决定_何时_创建 Bitcoin 交易。与 ACH 支付一样,Bitcoin 支付也不会立即完成。虽然商家一旦区块拾取 Bitcoin 交易就会知道客户已经创建了该交易,但仍然需要 6 个区块(大约一个小时)才能完成交易。

支付立即完成 | 支付稍后完成 ---|--- 无需客户操作 即可发起资金转移 |

|

需要客户操作 才能发起资金转移 |

Charges API 支持银行卡、ACH debit 和 Bitcoin 作为支付方式。

这前三种支付方式在支付发起方式和资金保证时间上各不相同。这使得创建能够抽象出它们之间差异的 API 变得非常具有挑战性。

我们是这样做的:

ACH debit。由于银行卡支付和 ACH debit 支付都只需要来自客户的静态信息(即,银行卡号或银行帐号),因此我们扩展了 Token 资源来表示银行卡详细信息和银行帐号详细信息。用户仍然可以使用任何类型的 Token 创建 Charge,但是我们在 Charge 中添加了一个 pending 状态,以表示 ACH debit Charge 不会立即完成,并且仍然可能失败。用户会在几天后运行他们的订单履行逻辑,届时他们会收到一个 Webhook,指示 Charge 已成功。 payment api diagram 2 Charge 中添加了一个新的 pending 状态,以表示异步完成支付。

Bitcoin。由于 Bitcoin 不适合我们的抽象,因此我们必须引入一个新的 BitcoinReceiver API 来方便我们在在线支付流程中需要客户采取的客户端操作。对于 Stripe 来说,“接收器”是资金的临时容器。它有一个非常简单的状态机,用于描述接收器的状态:一个布尔值 filled,其值为 true 或 false。一旦接收器被填充,用户就可以使用该 BitcoinReceiver 对象而不是 Token 对象来创建 Charge。这实际上会将资金从接收器转移到用户的余额中。如果用户未在特定时间范围内创建 Charge,则接收器中的资金将退还给客户。与 ACH debit Charges 一样,Bitcoin Charges 也以 pending 状态启动并异步成功。 payment api diagram 3 我们引入了 BitcoinReceiver 资源来表示客户需要采取操作才能完成支付。

使用 ACH debit 和 Bitcoin,集成变得更加复杂。现在,它涉及处理异步支付完成,而在 Bitcoin 的情况下,它涉及管理两个状态机以完成支付:客户端上的 BitcoinReceiver 和服务器上的 Charge

寻求更简单的支付 API (2015 - 2017)

在接下来的两年中,我们添加了更多支付方式。它们中的大多数都更像 Bitcoin,而不是银行卡——它们需要客户操作才能发起支付。我们发现,为每个支付方式引入一个全新的类似于 BitcoinReceiver 的资源对于开发人员来说并不友好——这只会引入太多新的特定于 Stripe 的概念,从而难以在 API 中进行推理。我们渴望设计一个更简单的支付 API,并开始探索如何在一个集成路径上统一这些支付方式:Sources API

支付立即完成 | 支付稍后完成 ---|--- 无需客户操作 即可发起资金转移 | Cards |

需要客户操作 才能发起资金转移 |

|

Sources API 旨在成为一个可以表示多种支付方式的单一客户端 API。

我们将之前设计的两个客户端抽象(TokensBitcoinReceivers)组合成一个称为 Source 的客户端驱动的状态机。创建后,Source 可以立即 chargeable(例如,对于银行卡支付)或 pending(例如,对于需要客户操作的支付方式)。服务器端集成仍然是一个使用密钥 API 密钥创建 Charge 的单一 HTTP 请求。 payment api diagram 4 我们将 Tokens 和接收器的功能合并到一个单一的客户端 API 中:Sources

每种支付方式的支付流程都依赖于相同的两个 API 抽象:SourceCharge。乍一看,这似乎在概念上很简单,因为它类似于美国的银行卡集成。但是,一旦我们了解了此流程如何集成到用户的应用程序中,我们就发现了许多粗糙的边缘。

例如,当用户添加一种无法立即完成的支付方式时,他们无法再在创建 Charge 后立即履行其客户的订单。相反,他们必须等到 Charge 转换为 succeeded 后才能发货。这通常涉及添加一个 Webhook 集成,该集成侦听 charge.succeeded 并将履行逻辑移到那里。

对于其他支付方式来说,SourcesCharges 仍然更加复杂——并且集成问题可能会导致收入损失。例如,使用 iDEAL(荷兰的主要支付解决方案),客户在被重定向到其银行的网站或移动应用程序后发起支付。如果客户端应用程序创建一个 Source,然后浏览器与服务器失去连接,则即使客户认为他们已付款,下一个创建 Charge 的请求也不会通过。(浏览器可能会因任何数量的原因而失去连接:客户在银行网站上付款后关闭了他们的选项卡,支付方式需要客户永远不会返回的重定向,或者客户的互联网连接不稳定。)由于服务器从未创建 Charge,因此我们会在几个小时后退还与 Source 关联的资金。这是一个转化噩梦。

为了减少这种情况发生的可能性,我们建议用户要么从其服务器轮询 Stripe API 直到 Source 变为 chargeable,要么侦听 source.chargeable Webhook 事件以创建 Charge。但是,如果用户的支付应用程序出现故障并且他们使用 SourcesCharges,则这些 Webhook 不会被传递,并且服务器不会创建 Charge。我们会退还客户的资金,并且用户必须让他们重新访问他们的网站才能再次付款。即使该用户正确地实施和维护了此最佳实践,SourcesCharges 的不同可能状态以及不同支付方式类型的路径和要求仍然存在复杂性。 payment api diagram 5 根据支付方式,有很多种实际从 Source 创建 Charge 的方法。

一些 Sources(如银行卡和银行帐户)是 同步可收费的,可以在在线支付表单提交后立即在服务器上收费,而另一些 Sources异步的,只能在几个小时或几天后收费。用户通常使用同步 HTTP 请求和事件驱动的 Webhook 处理程序构建并行集成以支持每种类型。这意味着用户现在有多个地方可以创建 Charge 并履行他们的订单。代码分支因子对于像 OXXO 这样的支付方式会加深,在这种支付方式中,客户打印出实体凭证并将其带到 OXXO 商店以现金支付。资金完全在带外支付,这使得我们监听 source.chargeable Webhook 事件的最佳实践建议对于这些支付方式来说绝对是 必需的。最后,用户必须跟踪每个订单的 Charge ID 和 Source ID。如果同一订单有两个 Sources 变为可收费(例如,客户决定在支付过程中切换他们的支付方式),他们可以确保他们不会为该订单重复收费。

与“七行代码”相比,此工作需要开发人员付出更多的簿记工作和概念理解。我们的用户 需要 了解所有这些边缘情况才能构建一个正常运行的 Stripe 集成。想象一下,推理这两个状态机会造成的混乱,每个状态的定义都因支付解决方案而异。开发人员必须管理两个状态机的成功、失败和待处理状态(这些状态在不同的支付方式中可能有所不同)才能完成单笔支付。 payment api diagram 7 用户必须管理跨客户端和服务器的两个不同的状态机才能完成支付。

让我们回顾一下支付方式表。你可能会注意到,银行卡是左上象限中唯一的支付方式:它们立即完成,并且不需要客户操作即可完成支付。这意味着我们在为所有支付方式中最简单的支付方式(银行卡)设计的一组抽象之上构建了对新支付方式的支持。自然,为银行卡设计的抽象不能很好地表示这些更复杂的支付流程。

支付立即完成 | 支付稍后完成 ---|--- 无需客户操作 即可发起资金转移 |

|

需要客户操作 才能发起资金转移 |

|

全球支付方式并不不同;银行卡是!

引入其他状态并扩展为特定、狭窄用例创建的资源定义导致了一个令人困惑的集成和一组过载的 API 抽象。这就像我们试图通过向汽车添加部件直到它具有宇宙飞船的功能来建造宇宙飞船一样:一个困难_且_可能注定要失败的提议。ChargesTokens 在 API 中是基础,因为它们是我们拥有的第一个 API,而不是因为它们是全球支付的正确抽象。我们需要从根本上重新思考我们的支付抽象。

设计一个统一的支付 API(2017 年底 - 2018 年初

当我们搁置对 SourcesCharges 的进一步更改时,我们能够开始设计我们想要的 API。这要容易得多,因为 我们有机会从用户那里学习多年,并且深入了解了他们在使用我们现有集成路径时遇到的问题。我们还积累了支付领域的专业知识,在我们的 API 上迭代了多年。总而言之,我们的 API 设计有更好的机会不重蹈覆辙。

我们将自己锁在会议室里三个月,目标是设计一个真正统一的支付 API。如果成功,开发人员只需要了解几个基本概念即可构建一个支付集成。即使他们没有听说过该支付方式,他们也应该只需在他们的集成中的几个特定点添加几个参数即可。为了实现这一点,我们 API 的状态和保证必须非常可预测和一致。我们的文档中不应散布着一系列警告和例外情况。

一个由五个人(四名工程师和一名 PM)组成的团队演练了我们支持的每种支付方式以及我们可以想象在未来支持的每种支付方式。我们迭代了一个能够对所有这些方式进行建模的 API 设计。我们忽略了所有现有的抽象,并从第一性原理思考了这个问题。 Payments API conference room Payments API conference room 我们早期的统一支付 API 工作是在一个名为 Lynx 的会议室中完成的。

现在很难准确地记住每天发生的事情,但是一些规则和程序确实帮助了我们: