在 Laravel 中 PayPal Webhook 签名验证失败

背景

最近在工作中需要把集成 PayPal 到系统里。大体流程是:客户在网页上发起付款,跳转到 paypal 页面,客户付款之后,需要去确认用户是否付款成功。 PayPal 提供了 webhook,给调用者发送事件,例如:当客户点了付款的 button,PayPal 会发送 CHECKOUT.ORDER.APPROVED 事件给调用者。当 order 被捕获,付款完成之后,Paypal 会发送 PAYMENT.CAPTURE.COMPLETED 事件给调用者。

由于提供给 PayPal 调用的 API 是公开的,当这个 API 被调用的时候,提供方就需要确认这个请求是否是 PayPal 官方发起的,也就是所谓的验证签名。PayPal 提供了 验证签名的 API,需要按照它的要求,组装好数据,发送给 PayPal。

问题

组装好的数据,发送给 PayPal 之后,如果这个请求确实是 PayPal 发起的,它会返回 {"verification_status":"SUCCESS"},否则会返回 {"verification_status":"FAILURE"}

我所遇到的问题是,有些签名是 SUCCESS,但是有些签名是 FAILURE,这就很奇怪。经过了对比实验,发现了问题所在。

错误的数据

{
"webhook_id": "{{webhook_id}}",
"transmission_id": "{{event_transmission_id}}",
"transmission_time": "{{event_transmission_time}}",
"cert_url": "{{event_cert_url}}",
"auth_algo": "{{event_auth_algo}}",
"transmission_sig": "{{event_transmission_sig}}",
"webhook_event": {
"id": "xxx",
"event_version": "1.0",
"create_time": "2023-12-04T07:06:02.318Z",
"resource_type": "checkout-order",
"resource_version": "2.0",
"event_type": "CHECKOUT.ORDER.APPROVED",
"summary": "An order has been approved by buyer",
"resource": {
"create_time": "2023-12-04T07:05:52Z",
"purchase_units": [
{
"reference_id": "default",
"amount": {
"currency_code": "AUD",
"value": "0.10",
"breakdown": []
}
}
],
"xxx": "xxx"
}
}
}

正确的数据

{
"webhook_id": "{{webhook_id}}",
"transmission_id": "{{event_transmission_id}}",
"transmission_time": "{{event_transmission_time}}",
"cert_url": "{{event_cert_url}}",
"auth_algo": "{{event_auth_algo}}",
"transmission_sig": "{{event_transmission_sig}}",
"webhook_event": {
"id": "xxx",
"event_version": "1.0",
"create_time": "2023-12-04T07:06:02.318Z",
"resource_type": "checkout-order",
"resource_version": "2.0",
"event_type": "CHECKOUT.ORDER.APPROVED",
"summary": "An order has been approved by buyer",
"resource": {
"create_time": "2023-12-04T07:05:52Z",
"purchase_units": [
{
"reference_id": "default",
"amount": {
"currency_code": "AUD",
"value": "0.10",
"breakdown": {}
}
}
],
"xxx": "xxx"
}
}
}

它们不一样的地方是: webhook_event.resource.purchase_units.amount.breakdown,正确的数据是一对 {},而错误的数据是一对 []

为什么会出现这种原因呢?是因为我使用的是 Laravel 框架,而 webhook_event 是通过 $request->all() 得到的。Laravel 默认会把空对象转换为空数组。就导致了这个悲剧发生。

怎么解决

只需要拿到 raw 请求数据,然后把 json 转换为一个对象即可。

拿到 raw 请求数据的方式有:

拿到数据之后,使用 json_decode($rawData, false) 即可把数据转换为一个对象。