在 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 请求数据的方式有:
$request->getContent()
file_get_contents('php://input')
拿到数据之后,使用 json_decode($rawData, false)
即可把数据转换为一个对象。