哪些支付服务提供商(PSP)的 API 最易于开发者集成?
结论
英国公司面向欧洲多国开展业务时,如果主要考虑开发体验,可以优先评估以下 PSP:
- Stripe:整体集成门槛较低,文档、SDK、测试工具、错误结构和 webhook 功能都比较完善,适合作为默认候选。
- Mollie:API 简单直接,欧洲本地支付方式的覆盖也很清楚,适合希望快速完成接入的中小型团队。
- Checkout.com:API 设计较新,适合交易量较大,需要在欧洲多国收单,并希望精细控制支付流程的企业。
- Adyen:支付能力和欧洲市场覆盖较强,适合大型、多国家、多支付方式的统一支付平台。不过,它的配置和业务模型通常比 Stripe、Mollie 更复杂。
- Braintree:如果 PayPal 是核心支付方式,可以纳入候选。如果重点是欧洲本地支付方式和统一的多国架构,则需要与前四家仔细比较。
如果目标是尽快完成可靠集成,可以先对 Stripe 和 Mollie 做技术验证。业务规模较大,涉及多个收单地区、复杂路由或更多本地支付方式时,再重点测试 Checkout.com 和 Adyen。
各 PSP 支持的国家、商户注册地、结算币种和支付方式可能随时间及账户类型变化,最终应以 PSP 当前文档和书面确认结果为准。
为什么这些 PSP 的开发体验通常更好
判断支付 API 是否好用,不能只看它是不是采用 REST 和 JSON,还要检查以下能力:
- 文档是否包含完整的请求、响应、错误对象和状态转换说明。
- 是否提供维护良好的官方 SDK,同时允许直接调用 HTTP API。
- 是否支持测试卡、测试账户、失败场景和 3D Secure 模拟。
- webhook 是否可以重试、查询、重新发送并验证签名。
- 是否支持
Idempotency-Key或类似机制,避免重复扣款。 - 每笔交易是否有稳定的请求 ID、支付 ID 和事件 ID,便于排查问题。
- 后台能否查看原始请求、响应和 webhook 投递记录。
- API 版本策略是否明确,是否会在没有通知的情况下破坏现有集成。
- 技术支持能否根据请求 ID 定位问题,而不是只回复通用话术。
这些方面,Stripe 的表现通常比较均衡。Adyen 和 Checkout.com 更偏向企业级支付基础设施,功能很多,但接入前需要理解更多支付状态、账户配置和地区规则。Mollie 的接口相对直接,更适合以欧洲市场为主的常规电商业务。
各 PSP 的技术特点
Stripe
Stripe 经常是开发者体验方面的首选。它的文档组织清楚,SDK 覆盖广,错误对象结构稳定,测试和 webhook 工具也比较成熟。
它适合以下情况:
- UK 公司向多个欧洲国家销售;
- 需要银行卡、钱包和部分本地支付方式;
- 希望使用 Hosted Checkout 或 Payment Element,减少前端开发的复杂度;
- 需要订阅、退款、争议处理或平台型支付;
- 团队希望先快速上线,再逐步加入更多支付能力。
需要注意,Stripe 的 PaymentIntent 是状态机,不能把一次 API 请求理解为”调用成功就表示付款成功”。最终结果可能要等用户完成 3D Secure,也可能需要通过 webhook 异步确认。
Mollie
Mollie 的 API 模型比较简单,支付方式和结账流程容易理解,对欧洲业务也比较友好。
它适合以下情况:
- 主要服务欧洲消费者;
- 希望快速接入本地支付方式;
- 不需要复杂的跨地区收单架构;
- 团队规模较小,希望减少前期学习支付知识的成本。
选择之前,需要确认各网站实例对应的法律实体、国家、结算账户和所需支付方式是否都受支持。
Checkout.com
Checkout.com 提供设计较新的 API 和企业级支付能力,适合希望细致控制支付流程的团队。
它适合以下情况:
- 交易量较大;
- 在多个欧洲国家经营;
- 需要更细致地控制授权、捕获、退款和风控;
- 希望获得较完整的交易数据;
- 后续可能需要优化支付成功率或调整收单布局。
它的集成复杂度通常高于面向小型商户的产品。技术验证期间,要特别检查沙箱与生产环境的差异、支付状态映射和 webhook 到达顺序。
Adyen
Adyen 常用于跨国家、跨渠道和多支付方式的业务。它可以把多种支付能力集中到同一平台,但接入团队需要认真设计支付生命周期。
它适合以下情况:
- 多个国家的网站希望逐步统一支付层;
- 同时涉及线上、线下或其他复杂渠道;
- 需要大量欧洲本地支付方式;
- 有专门的支付工程团队或平台团队;
- 能够接受较长的接入和认证周期。
Adyen 不是调用几个接口就能完成的简单方案。设计时需要同时考虑账户结构、支付方式配置、3D Secure、异步结果和地区差异。
Braintree
评估 Braintree 时,主要考虑的是 PayPal 生态。如果 PayPal 在目标市场的占比较高,Braintree 仍可能是合理的选择。
不过,还需要单独验证以下问题:
- 是否覆盖各欧洲国家需要的本地支付方式;
- SDK 和 API 是否适合现有技术栈;
- webhook 和交易查询是否便于排错;
- 多网站、多商户账户的配置是否容易维护。
推荐的技术选型步骤
不要只看文档首页或后台截图。可以用完全相同的测试清单,分别对每个候选 PSP 进行两到三天的技术验证。
第一步:确定账户模型
先弄清楚欧洲各网站实例之间的关系:
- 是否由同一个法律实体经营;
- 是否使用不同的商户账户;
- 是否分别结算到不同银行账户;
- 是否需要独立账单描述符;
- 是否需要按国家隔离退款、报表和权限;
- 用户能否跨国家站点使用同一订单或账户。
如果多个网站属于不同的法律实体,通常不能只用一个普通商户账户处理全部款项。这时可能需要多个 PSP 账户,或者采用平台、子商户一类的账户结构。
第二步:完成最小支付闭环
每家 PSP 至少要实现以下场景:
- 创建支付;
- 触发 3D Secure;
- 支付成功;
- 支付失败;
- 用户取消支付或关闭页面;
- 接收 webhook 通知;
- 全额退款和部分退款;
- 重复请求保护;
- 处理重复和乱序的 webhook;
- 在后台根据请求 ID 查询交易。
如果其中任何一步都要依靠人工支持才能弄清楚,就应该把它视为长期维护风险。
第三步:测试欧洲本地支付方式
除了银行卡,还要根据实际市场测试本地支付方式。不同支付方式的生命周期可能完全不同:
- 有些支付会同步确认;
- 有些需要跳转到银行页面;
- 有些要等待数小时甚至数天才能确认;
- 有些不支持部分退款;
- 有些无法保证先授权再捕获;
- 有些在支付完成后仍可能撤销或失败。
因此,内部系统不能只使用 paid = true/false。至少应该区分以下状态:
created
requires_customer_action
processing
authorized
captured
failed
cancelled
refunded
partially_refunded
这些只是内部状态示例,并非任何特定 PSP 的官方枚举。接入时,需要建立 PSP 状态与内部状态之间的映射。
一个可靠的支付接口示例
下面使用 Stripe 的 Node.js SDK 展示基本结构。这个示例只用于说明集成方式。API 版本、SDK 版本和可用参数应以项目当前采用的官方文档为准。
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function createPayment(req, res) {
const { orderId } = req.body;
// 金额必须从服务端订单中读取,不能相信浏览器传入的金额。
const order = await loadOrder(orderId);
if (!order || order.status !== "pending") {
return res.status(400).json({
error: "ORDER_NOT_PAYABLE"
});
}
const paymentIntent = await stripe.paymentIntents.create(
{
amount: order.amountMinor,
currency: order.currency,
automatic_payment_methods: {
enabled: true
},
metadata: {
orderId: order.id
}
},
{
idempotencyKey: `create-payment:${order.id}`
}
);
await saveProviderPayment({
orderId: order.id,
provider: "stripe",
providerPaymentId: paymentIntent.id
});
res.json({
paymentId: paymentIntent.id,
clientSecret: paymentIntent.client_secret
});
}
这里需要注意三点:
- 金额和币种必须来自服务端订单,不能由前端决定。
idempotencyKey应与具体业务操作绑定,防止网络重试导致重复创建。- 必须保存 PSP 的支付 ID,后续退款、对账和技术支持都会用到。
webhook 处理示例
支付结果应以通过签名验证的 webhook 或主动查询结果为准,不能只相信浏览器跳转到了”支付成功”页面。
import express from "express";
import Stripe from "stripe";
const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
app.post(
"/webhooks/stripe",
express.raw({ type: "application/json" }),
async (req, res) => {
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
req.headers["stripe-signature"],
process.env.STRIPE_WEBHOOK_SECRET
);
} catch {
return res.status(400).send("Invalid webhook signature");
}
// 使用 event.id 建立唯一约束,重复事件直接返回成功。
if (await hasProcessedEvent(event.id)) {
return res.sendStatus(200);
}
switch (event.type) {
case "payment_intent.succeeded": {
const paymentIntent = event.data.object;
await markOrderPaid({
orderId: paymentIntent.metadata.orderId,
providerPaymentId: paymentIntent.id,
amount: paymentIntent.amount_received,
currency: paymentIntent.currency
});
break;
}
case "payment_intent.payment_failed": {
const paymentIntent = event.data.object;
await markPaymentFailed({
orderId: paymentIntent.metadata.orderId,
providerPaymentId: paymentIntent.id
});
break;
}
}
await markEventProcessed(event.id);
return res.sendStatus(200);
}
);
生产环境还需要使用数据库事务或唯一约束,保证”更新订单”和”记录事件”的一致性。仅仅先调用 hasProcessedEvent(),再写入事件记录,在并发情况下仍然可能产生竞争。
多 PSP 或多国家架构建议
即使一开始只使用一家 PSP,也不要让业务代码直接依赖它的全部对象结构。可以在应用内部定义一个范围较小的支付适配层:
interface PaymentProvider {
createPayment(input: CreatePaymentInput): Promise<CreatePaymentResult>;
capturePayment(paymentId: string, amount?: number): Promise<void>;
refundPayment(paymentId: string, amount?: number): Promise<RefundResult>;
getPayment(paymentId: string): Promise<PaymentStatus>;
verifyWebhook(
rawBody: Buffer,
headers: Record<string, string>
): Promise<PaymentEvent>;
}
适配层不需要把所有 PSP 的功能都抽象成完全相同的形式。更实用的处理方式包括:
- 统一订单号、金额、币种和内部状态;
- 保存完整的 PSP 原始状态和响应;
- 统一处理幂等、日志、监控和 webhook 去重;
- 将特定支付方式需要的字段留在对应的提供商适配器中;
- 不要为了理论上的可替换性,舍弃某个 PSP 独有的重要能力。
如果各国网站是相互独立的实例,可以共享支付适配器代码,但配置、密钥、webhook secret 和商户账户应按国家或法律实体隔离。
容易忽略的注意事项
PSD2 和 3D Secure
欧洲银行卡支付需要考虑 Strong Customer Authentication。不能假设每次支付都能在服务端同步完成,也不应自行设计一个只区分”成功”和”失败”的接口。
PCI DSS 范围
应优先采用 PSP 托管的结账页、Hosted Fields 或官方支付组件,避免让银行卡号经过自己的服务器。这通常可以缩小 PCI DSS 合规范围,但具体责任仍要根据实际数据流和适用要求确认。
金额表示
金额应该使用最小货币单位的整数,不要使用浮点数:
const amount = 1099; // £10.99,单位为 penny
还要注意,并非所有货币都有两位小数。金额转换应依据具体货币规则,不能统一乘以 100。
webhook 不是严格有序消息
系统必须能够处理以下情况:
- 同一事件被重复发送;
- 后发送的事件先到达;
- webhook 暂时失败后重新投递;
- 浏览器已经显示成功,但 webhook 稍后才到;
- webhook 已经到达,但用户没有返回网站。
订单状态更新必须具有幂等性,同时防止旧事件覆盖较新的状态。
不要只测试成功支付
失败场景往往更能体现 PSP 的开发体验。技术验证期间,应主动制造身份验证失败、余额不足、超时、重复请求、退款失败、签名错误和 webhook 重试等情况。
实际验证支持质量
售前承诺不能代替真正的技术支持测试。评估期间可以提交几个具体问题,观察对方是否:
- 能理解请求和响应;
- 会要求提供请求 ID;
- 能解释支付状态如何转换;
- 能区分配置问题和 API 问题;
- 能在合理时间内提供可执行的答复。
最终建议
对于英国公司面向欧洲多国经营的场景,可以按以下顺序评估:
- 优先验证 Stripe:通常最容易建立高质量的支付闭环。
- 同时验证 Mollie:如果更看重欧洲本地支付方式和接入速度,它可能更适合。
- 业务规模较大时验证 Checkout.com 和 Adyen:重点比较账户模型、本地收单、支付方式覆盖、后台排错能力和技术支持。
- PayPal 是核心渠道时再加入 Braintree。
不要只根据 PSP 的名气做决定。使用同一套支付、失败、退款、webhook、对账和技术支持测试,通常一周内就能判断哪家的开发体验更适合现有团队。
备注:内容仅供参考。
