결제용 Webhooks

거래에 대한 실시간 업데이트입니다.

결제용 Webhooks(이전 명칭: 실시간 업데이트)는 앱에서 Facebook 결제를 통해 이루어진 주문에 대한 변경 사항을 파악하는 데 매우 중요한 수단입니다.

개요

Webhooks는 Facebook과 서버 사이의 구독 기반 시스템입니다. 앱은 지정된 HTTPS 엔드포인트를 통해 Facebook에서 업데이트를 수신하기 위한 구독을 합니다. 앱에서 이루어진 주문이 업데이트되면 Facebook에서 해당 엔드포인트로 HTTPS POST 요청을 보내 서버에 변경 사항을 알립니다.

개발자 서버로 업데이트가 전송되는 시나리오는 기본적으로 3가지가 있습니다.

Webhooks 구독

결제 Webhooks를 구독하려면 먼저 구독 인증을 위한 HTTPS GET와 변경 데이터 요청을 위한 POST를 모두 수신하는 공개 엔드포인트 URL을 만듭니다. 이 두 가지 요청 유형의 구조는 아래에 설명되어 있습니다. 다음으로 앱의 payment 개체에 대한 구독을 설정합니다. 구독을 설정하는 방법은 두 가지가 있습니다.

어떤 방법을 사용하든 엔드포인트는 동일한 방식으로 동일한 데이터를 수신하게 됩니다. 서버에서 수신하는 정보에 대한 자세한 내용은 콜백 서버를 참조하세요.

앱 대시보드를 통한 구독

앱이 Webhooks 업데이트를 수신하도록 설정하는 가장 쉬운 방법은 앱 대시보드의 결제 패널을 사용하는 것입니다. 대시보드에서 앱을 찾은 다음, Payments 탭을 클릭합니다. Webhooks 섹션은 회사의 설정 섹션 바로 아래에 있습니다.

결제용 Webhooks

그러면 이 화면에 이 패널을 통해 구독이 추가되었는지 혹은 API를 통해 구독이 추가되었는지 여부를 나타내는 앱의 구독 상태가 표시됩니다. 이곳에서 구독 콜백 URL을 변경하고 테스트할 수 있습니다.

'콜백' 필드에 유효하고 공개적으로 액세스 가능한 서버 엔드포인트를 입력해야 합니다. 이 주소는 구독을 인증하고 업데이트를 전송하는 데 사용되며, 콜백 서버에 나와 있는 설명대로 응답해야 합니다.

마지막으로 '인증 토큰'을 입력합니다. 이 토큰은 등록 단계에서 구독이 안전한 위치에서 시작되는지 확인하기 위해서만 전송됩니다. 이 토큰은 정기 Webhooks 업데이트 시에는 전송되지 않습니다.

설정 테스트

구독을 저장하기 전에 콜백 설정을 테스트해야 합니다. 그러면 hub.mode, hub.challengehub.verify_token 매개변수가 포함된 인증 GET 요청이 엔드포인트로 전송되어 매개변수를 올바르게 처리하는지 확인합니다. 예를 들어 엔드포인트가 Facebook으로 hub.challenge를 에코 백하도록 해야 합니다.

settinfs 테스트

구독 상세 정보를 입력하고 나면 페이지 아래에 있는 '변경 사항 저장' 버튼을 클릭해야 합니다. 구독은 필드 내용을 변경하고 다시 테스트한 다음, 양식을 다시 저장하는 간단한 과정을 통해 수정합니다.

그래프 API를 통한 구독

그래프 API를 통해 프로그래밍 방식으로 구독을 설정하고 나열할 수도도 있습니다. 앱의 access token이 필요한데, 이는 액세스 토큰 도구나 그래프 API의 /oauth 엔드포인트를 사용해서 얻을 수 있습니다.

구독 API는 https://graph.facebook.com/[APP_ID]/subscriptions 엔드포인트에서 제공됩니다.

이 API를 사용하여 수행할 수 있는 작업은 3가지가 있습니다.

  • 구독을 추가하거나 수정(HTTPS POST 요청 전송)
  • 각각의 기존 구독 나열(HTTPS GET 요청 전송)

구독 추가 및 수정

구독을 설정하려면 다음 매개변수를 포함하여 POST를 전송합니다. 이러한 매개변수는 위에서 설명한 양식에 나와 있는 필드에 상응합니다.

  • object - 위에서 설명한 대로 업데이트를 받고자 하는 개체의 유형입니다. payments를 지정하세요.
  • fields - 변경 사항에 대한 업데이트를 받고자 하는 개체 유형의 속성을 쉼표로 구분하여 나열한 리스트입니다. 'actions'와 'disputes'를 지정하세요.
  • callback_url - 유효하고 공개적으로 액세스할 수 있는 서버 엔드포인트입니다.
  • verify_token - 구독을 인증할 때 엔드포인트로 전송되는 임의의 문자열입니다.

Facebook에서 이 요청을 수신하면 위의 양식 구성에서와 마찬가지로 콜백으로 GET을 보내어 요청이 유효하고 업데이트를 수신할 준비가 되도록 합니다. 특히 엔드포인트가 Facebook으로 hub.challenge를 에코 백하도록 해야 합니다.

앱이 각 개체 유형에 대해 하나만 구독할 수 있으므로 이 개체 유형에 대한 구독이 존재할 경우, 새로 게시된 데이터가 기존 데이터를 대체합니다.

구독 리스트 표시

HTTP GET을 구독 API로 보내면 구독이 나열된 JSON 인코딩 콘텐츠가 반환됩니다. 예를 들면 다음과 같습니다.

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

그래프 탐색기를 사용하여 이 API를 직접 실험할 수 있으며, 앱의 액세스 토큰을 사용해야 합니다.

콜백 서버

콜백 서버는 2가지 유형의 요청을 처리해야 합니다. 요청을 성공적으로 보낼 수 있도록 콜백 서버는 공개 URL에 있어야 합니다.

구독 인증

구독을 추가하거나 수정하려고 시도하면 Facebook 서버가 먼저 하나의 HTTPS GET을 콜백 URL로 보냅니다. 쿼리 문자열이 다음 매개변수와 함께 콜백 URL에 첨부됩니다.

매개변수 설명

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 응답은 오류로 간주합니다. 이 경우 Facebook에서 Webhooks 업데이트 전송을 계속 다시 시도합니다. 그러므로 올바르게 응답하지 않으면 동일한 업데이트를 여러 번 수신할 수 있습니다.

이 요청은 콘텐츠 유형이 application/json이며, 본문은 하나 이상의 변경 사항을 포함한 JSON 인코딩 문자열로 구성됩니다.

PHP 개발자 관련 참고 사항: PHP에서는 인코딩된 데이터를 가져오려면 다음 코드를 사용해야 합니다.

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

구독이 확인되고 나면 hub.mode, hub.challengehub.verify_token 매개변수는 다시 전송되지 않습니다.

payments 개체 구독에 대해 전송되는 일반적인 콜백의 예는 다음과 같습니다.

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

Webhooks 업데이트는 id 필드에서 식별한 특정 결제가 변경되었다는 정보만 제공한다는 점에 유의해야 합니다. 업데이트를 수신하고 나면 그래프 API를 쿼리하여 거래에 대한 상세 정보를 받아보고 변경 사항을 적절히 처리해야 합니다.

참고 - 다른 개체 유형에 대한 Webhooks는 일괄 처리할 수 있지만 결제 업데이트는 일괄 처리할 수 없습니다.

사용자 행동이나 개발자 행동을 통해 거래가 업데이트될 때마다 반드시 새로운 업데이트를 수신하게 됩니다.

서버로 보낸 업데이트가 실패하는 경우 Facebook은 즉시 다시 시도하고 이후 24시간 동안 빈도를 줄이면서 몇 차례 더 시도합니다.

요청을 보낼 때마다 Facebook은 앱 시크릿 코드를 키로 사용하고 프리픽스로 sha256=을 붙여서 요청 페이로드의 SHA256 서명을 포함하는 X-Hub-Signature-256 HTTP 헤더를 전송합니다. 콜백 엔드포인트는 이 서명을 인증하여 페이로드의 무결성과 진위를 검증할 수 있습니다.

업데이트에 대한 응답

서버에서 업데이트를 수신하고 나면 id 필드를 사용하여 그래프 API를 쿼리하고 거래의 새로운 상태에 대한 상세 정보를 얻어야 합니다. 그런 다음, 상태에 따라 조치를 취해야 합니다.

아래의 섹션에는 업데이트 전송을 트리거하는 모든 잠재적 상태 변경 사항이 나와 있습니다. 변경 사항은 크게 다음과 같이 나눌 수 있습니다.

  • actions(조치) 배열의 변경 사항: 결제가 비동기식으로 완료되거나, (개발자가 또는 Facebook에서) 환불을 처리하거나, 결제가 취소되었을 때 발생합니다.
  • disputes(이의 제기) 배열의 변경 사항: 고객이 주문에 대해 이의를 제기했을 때 발생합니다.

조치

payment 개체에는 거래를 진행하는 동안의 상태 변경 사항 컬렉션이 포함된 actions라는 이름의 배열이 있습니다. actions 배열의 각 항목에는 수행한 조치의 유형을 나타내는 type이라는 이름의 속성이 있습니다. typecharge, refund,chargeback, chargeback_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"가 있는 청구 항목이 포함됩니다. 시작된 결제는 결제가 시작되었을 뿐, 아직 완전히 완료되지 않았다는 것을 나타냅니다. Facebook에서는 시작 상태인 결제에 대해서는 업데이트를 전송하지 않습니다.

결제가 성공적으로 완료되면 "status": "initiated""status": "completed"로 변경되고 Facebook에서 업데이트를 전송합니다. 이 변경 사항이 보이면 결제 기록을 확인하여 신규 거래인지 기존 거래인지 파악한 후 다음과 같이 대응하세요.

  • 이미 알고 있는 주문이고 JavaScript 콜백에서 이미 이행한 경우(가장 적절한 옵션으로 권장), 안심하고 업데이트를 무시하거나 추가적인 확인으로 사용할 수 있습니다.
  • 이미 알고 있는 주문이지만 initiated 상태일 경우, 주문을 계속 이행하고 관련 가상 항목이나 통화를 고객에게 발행할 수 있습니다. 그러면 이 결제는 안전하게 '완료'로 표시할 수 있습니다.
  • 모르는 주문인 경우 클라이언트 측의 플로가 완료되지 않았음을 나타냅니다. 이는 연결에 문제가 있거나 사용자가 결제 진행 도중 브라우저를 종료했기 때문일 가능성이 큽니다. 그래도 Facebook이 사용자 청구와 관련하여 최종적인 정보 출처가 되므로 여전히 이 주문을 안전하게 이행하고 완료할 수 있습니다.

또한 "status": "failed"가 있는 결제에 대한 업데이트도 수신하게 됩니다. 이러한 주문은 이행해서는 안 됩니다.

환불

그래프 API를 통해 환불을 처리할 때마다 업데이트를 수신합니다. "type": "charge"와 마찬가지로 환불도 여러 가지 상태가 있을 수 있고 이를 알고 있어야 합니다. 특히, 환불이 실패할 수도 있습니다. 일반적으로는 처리 또는 연결 오류로 인해 실패하며, 이 경우 다시 환불 처리를 시도해야 합니다.

결제 취소, 결제 취소 번복 및 거절

환불과 마찬가지로 결제 취소, 결제 취소 번복이나 거절의 경우에도 알림을 받게 됩니다. 결제에 대한 그래프 API 반환 데이터의 actions 배열에 결제 취소, 결제 취소 번복 또는 거절 개체가 추가됩니다.

이의 제기

이의 제기가 시작되면 Facebook에서 업데이트를 전송하여 알려드립니다. 이 경우 새로운 "disputes" 배열이 payment 개체의 일부로 표시됩니다. 이 배열에는 이의 제기가 시작된 시간, 소비자가 대응을 시작한 이유, 소비자의 이메일 주소가 포함되며, 이 이메일 주소를 사용하여 소비자에게 직접 연락해 이의 제기를 해결할 수 있습니다.

아래는 이의가 제기된 거래에 대한 그래프 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"
      }
   ]
}

이의 제기에 대응하고 환불을 처리하는 방법에 대한 자세한 내용은 결제 방법: 이의 제기 및 환불 처리를 참조하세요.