支付专用 Webhooks

关于交易的实时更新。

支付专用 Webhooks(之前称为“实时更新”)是应用中的订单通过 Facebook 支付发生更改后,系统向您发送更改相关通知的基本方法。

概览

Webhooks 是 Facebook 及您服务器之间的订阅制系统。订阅之后,您的应用会通过指定 HTTPS 端点从 Facebook 接收更新。如果在您应用中提交的订单有所更新,我们将向该端点发送 HTTPS POST 请求,以便向您的服务器通知该更新。

如发生以下 3 种主要情景,我们会向您的开发者服务器发送更新:

订阅 Webhooks

如要订阅支付 Webhooks,请先创建公开端点网址,用于接收 HTTPS GETPOST。前者用于验证订阅,而后者则用于请求更改数据。下文将对这两种请求类型的结构进行说明。创建网址后,订阅应用的 payment 对象。如要执行此操作,可以采用以下 2 种方式:

无论采用哪种方式,您的端点都会以相同方式收到同样的数据。请查看回调服务器部分,进一步了解您的服务器将收到哪些数据。

通过应用面板订阅

如要为应用设置接收 Webhooks 更新,最简单的方法就是使用应用面板的“支付”窗口。在应用面板中找到您的应用,然后点击 Payments 选项卡。“Webhooks”部分将正好显示在您公司的“设置”部分下方。

支付专用 Webhooks

然后,此屏幕将列出应用的订阅状态,无论是通过应用面板还是通过图谱 API 订阅,均是如此。您可以在这里更改订阅回调网址并对其进行测试。

在“回调”字段中,您必须提供有效且可公开访问的服务器端点。我们将使用订阅回调网址验证订阅及发送更新,而该网址需要按照回调服务器部分中所说明的方式作出响应。

最后,提供“验证口令”。该口令只会在注册阶段发送,以验证相关订阅的来源是安全位置,而不会在常规 Webhooks 更新中发送。

测试设置

在保存订阅前,您应该测试回调设置。此操作会向您的端点发送验证 GET 请求,其中包含 hub.modehub.challengehub.verify_token 参数,并确保您对这些参数的处理方法得当。例如,您必须确保端点会向 Facebook 回显 hub.challenge

测试设置

输入订阅详情后,请务必点击页面底部的“保存更改”按钮。编辑订阅相当简单,只需修改字段内容、重新测试,然后再次保存表单即可。

通过图谱 API 订阅

您也可以通过图谱 API,以编程方式设置订阅并列出订阅清单。您需要应用的 access token,而该口令可通过访问口令工具或图谱 API 的 /oauth 端点获取

您可以在 https://graph.facebook.com/[APP_ID]/subscriptions 端点中获取订阅 API

您可以通过此 API 执行 3 种任务:

  • 添加或修改订阅(通过发送 HTTPS POST 请求)
  • 列出现有的各项订阅(通过发送 HTTPS GET 请求)

添加及修改订阅

如要设置订阅,请发送包含以下参数的 POST。请注意,这些参数对应上文所述表单中的字段:

  • object — 如上所述,此为您想接收相关更新的对象的类型。指定为 payments
  • fields — 使用逗号分隔的清单,其中包含您想接收相关更改更新通知的对象类型的属性。指定为“actions”及“disputes”。
  • callback_url — 有效并且可公开访问的服务器端点。
  • verify_token — 验证订阅时,向您的端点发送的任意字符串。

收到此请求后,与上面的表单配置一样,我们将向您的回调执行 GET,以确保您的回调有效并且已准备好接收更新。您尤其需要注意的是,务必确保端点向 Facebook 回显 hub.challenge

请注意,对于每种对象类型,应用只能有一个订阅,因此如果已存在对该对象类型的订阅,则新发布的数据将取代任何已有数据。

列出您的订阅

如果向订阅 API 发送 HTTP GET,系统将会返回经过 JSON 编码的内容,并在其中列出您的订阅。例如:

[
  {
    "object": "payments",
    "callback_url": "https://www.friendsmash.com/rtu.php",
    "fields": ["actions", "disputes"],
    "active": true
  }
]

您可以使用图谱探索工具直接试用此 API,但是务必记得使用应用的访问口令

回调服务器

您的回调服务器必须处理 2 种类型的请求。请务必确保回调服务器位于公开网址,只有这样我们才能成功发出这些请求。

订阅验证

首先,在您尝试添加或修改订阅时,Facebook 服务器将向您的回调网址发出单独的 HTTPS GET。您的回调网址会附加包含以下参数的查询字符串:

参数 描述

hub.mode

在此参数中传递“subscribe”字符串

hub.challenge

随机字符串

hub.verify_token

创建订阅时指定的 verify_token

端点应首先验证 hub.verify_token。这样做可以确保您的服务器知道该请求是由 Facebook 发出,并且与您刚才配置的订阅相关。

然后,服务器应该回显 hub.challenge 值,以向 Facebook 确认此服务器将配置用于接收回调,并避免出现拒绝服务 (DDoS) 漏洞。

PHP 开发者注意事项:在 PHP 中,查询参数名称中的圆点和空格将自动转换为下划线。因此,如果您在 PHP 中编写回调端点,应该使用 $_GET['hub_mode']$_GET['hub_challenge']$_GET['hub_verify_token'] 访问这些参数。请参阅 PHP 语言手册中的这项说明,了解更多信息。

接收更新

订阅成功后,当您所选择的字段或连接有更改,我们将向您的服务器端点发送 HTTPS POST。您必须使用 HTTP 代码 200 对此请求作出响应。

备注:我们会将除 200 外的任何 HTTP 响应视为错误。在这种情况下,我们将继续尝试重新发送 Webhooks 更新。因此,如果您未能以正确方式作出响应,则可能会多次收到相同更新。

此请求的内容类型为 application/json,其正文将由经过 JSON 编码的字符串组成,其中包含一或多项更改。

PHP 开发者注意事项:在 PHP 中,如要获取经过编码的数据,需要使用以下代码:

$data = file_get_contents("php://input");
$json = json_decode($data);`

请注意,确认订阅之后,系统不会再次发送 hub.modehub.challengehub.verify_token 参数。

以下为向 payments 对象订阅发送的回调典型示例:

{
  "object": "payments",
  "entry": [
    {
      "id": "296989303750203",
      "time": 1347996346,
      "changed_fields": [
        "actions"
      ]
    }
  ]
}

请务必注意,Webhooks 更新只会通知您特定支付(id 字段标识)已发生更改。收到更新后,您接下来需要查询图谱 API 以了解交易详情,从而恰当地处理更改。

备注:虽然其他对象类型的 Webhooks 可以采用批量处理的方法,但是支付更新无法进行批量处理

我们可确保每次交易有所更新时,无论该更新是由用户操作还是开发者操作所致,您都将收到新的更新。

如果向服务器发送的更新失败,我们将立即尝试重新发送,并在随后的 24 小时内以递减的频率再进行多次尝试。

在每个请求中,我们都会发送 X-Hub-Signature-256 HTTP 标头,其中包含请求负载的 SHA256 签名,这类签名将应用密钥用作密钥,并以 sha256= 为前缀。回调端点可以验证此签名,从而验证负载的完整性及来源。

对更新作出响应

服务器收到更新后,您应该使用 id 字段查询图谱 API,以了解交易新状态的详情。接下来,您应该根据状态采取行动。

以下部分列出了会导致系统发送更新的所有状态更改。这些更改大致可分为两类:

  • 操作数组的更改 — 以异步方式完成支付、(您或 Facebook)提出退款或发生拒付时,就会出现此类更改。
  • 异议数组的更改 — 在消费者提出订单异议时,就会出现此类更改。

操作

每个 payment 对象包含一个以 actions 为标题的数组,其中包含各交易阶段的一系列状态更改。actions 数组中的每个条目都有一个名为 type 的属性,用于描述所发生操作的类型。type 可以为以下值:chargerefundchargebackchargeback_reversaldecline点击此处了解详细说明)。

以下示例为图谱 API 对支付对象的响应,其中包含相关操作:

{
   "id": "3603105474213890",
   "user": {
      "name": "Marco Alvarez",
      "id": "500535225"
   },
   "application": {
      "name": "Friend Smash",
      "namespace": "friendsmashsample",
      "id": "241431489326925"
   },
   "actions": [
      {
         "type": "charge",
         "status": "completed",
         "currency": "USD",
         "amount": "0.99",
         "time_created": "2013-03-22T21:18:54+0000",
         "time_updated": "2013-03-22T21:18:55+0000"
      },
      {
         "type": "refund",
         "status": "completed",
         "currency": "USD",
         "amount": "0.99",
         "time_created": "2013-03-23T21:18:54+0000",
         "time_updated": "2013-03-23T21:18:55+0000"
      }
   ],
   "refundable_amount": {
      "currency": "USD",
      "amount": "0.00"
   },
   "items": [
      {
         "type": "IN_APP_PURCHASE",
         "product": "https://www.friendsmash.com/og/friend_smash_bomb.html",
         "quantity": 1
      }
   ],
   "country": "US",
   "created_time": "2013-03-22T21:18:54+0000",
   "payout_foreign_exchange_rate": 1,}`

如果您注册 Webhooks 时订阅 actions 字段,当数组发生如下更改,我们会发送更新:

收费

所有订单刚开始都包含一个带有 "status": "initiated" 的收费条目。“已发起的支付”表示支付仅仅只是被发起,但是尚未完成。我们不会就处于已发起状态的支付发送更新。

成功完成支付后,"status": "initiated" 将更改为 "status": "completed",我们将为此发送更新。看见此更改后,您应该查看支付记录,确认这是笔新交易还是已有交易,并按照以下方式作出响应:

  • 如果是已知订单,而且已经由 JavaScript 回调(最好作为第一选择)履行,则您可以放心地忽略该更新,也可以将其视为额外确认信息。
  • 如果是已知订单,但是处于 initiated 状态,则您可以继续履行订单,向消费者发送相关虚拟商品或货币。之后,可以放心地将该支付标记为完成。
  • 如果是未知订单,则表明客户端流程未完成,这极有可能是因为连接问题,或者消费者在结账过程中关闭浏览器。您仍然可以放心地履行及完成该订单,因为 Facebook 仍是用户结算相关的真正可信源。

您还会收到带有 "status": "failed" 的支付的更新。您不应该履行这类订单。

退款

只要通过图谱 API 发起退款,就会收到更新。与 "type": "charge" 一样,退款也有各种状态,因此您必须加以注意。最需要注意的一点就是,退款有可能失败,而这通常是因为处理或连接出错。如果是因为这些原因,您应该重新尝试发起退款。

拒付、拒付撤销及拒绝

与退款一样,如果有人发起了拒付、拒付撤销或拒绝,您也会收到通知。在支付的图谱 API 响应数据操作数组中,会加入拒付、拒付撤销或拒绝对象。

异议

消费者发起异议后,我们将通过发送更新来通知您。在这种情况下,您将看到 payment 对象中出现新的 "disputes" 数组。此数组将包含异议的发起时间、消费者发起相关操作的理由以及消费者的邮箱,以便您使用该邮箱直接联系消费者解决异议。

以下示例为图谱 API 对有异议的交易的完整响应:

{
   "id": "990361254213890",
   "user": {
      "name": "Marco Alvarez",
      "id": "500535225"
   },
   "application": {
      "name": "Friend Smash",
      "namespace": "friendsmashsample",
      "id": "241431489326925"
   },
   "actions": [
      {
         "type": "charge",
         "status": "completed",
         "currency": "USD",
         "amount": "0.99",
         "time_created": "2013-03-22T21:18:54+0000",
         "time_updated": "2013-03-22T21:18:55+0000"
      }
   ],
   "refundable_amount": {
      "currency": "USD",
      "amount": "0.99"
   },
   "items": [
      {
         "type": "IN_APP_PURCHASE",
         "product": "https://www.friendsmash.com/og/friend_smash_bomb.html",
         "quantity": 1
      }
   ],
   "country": "US",
   "created_time": "2013-03-22T21:18:54+0000",
   "payout_foreign_exchange_rate": 1,
   "disputes": [
      {
         "user_comment": "I didn't receive my item! I want a refund, please!",
         "time_created": "2013-03-24T18:21:02+0000",
         "user_email": "email\u0040domain.com",
         "status": "resolved", 
         "reason": "refunded_in_cash"
      }
   ]
}

如需进一步了解如何响应异议及发起退款,请参阅支付操作说明:处理异议和退款