# 백엔드 API 통해 구매 수행

> 이벤트 구매를 수신하고 플레이어의 권한을 트래킹하는 자체 백엔드를 구현합니다.

게임에서 서버 권한 권한을 사용하는 경우 다음을 수행하는 백엔드 시스템을 구현합니다.

1. [Webhook 이벤트(JWT) 확인](#validate-webhook-events-\(jwt\))
2. [이벤트 파스](#webhook-event-shape).
3. 시스템의 자격 또는 인벤토리를 [이벤트 유형](#event-types)에 따라 업데이트합니다.
4. 주문을 완료된 것으로 [표시](#mark-orders-as-fulfilled-via-api)합니다.
5. [이벤트 응답 반환](#return-an-event-response)

이렇게 하면 시스템과 Unity IAP가 모두 동기화됩니다.

> **Important:**
>
> 백엔드가 플레이어에게 권한을 부여하더라도 모든 유료 주문을 완료된 것으로 표시해야 합니다. 플레이어는 어쨌든 자격을 유지하지만, 순서가 `paid` 상태로 유지되면 Unity IAP 주문 추적 및 분석이 불완전합니다. SDK와 웹후크를 혼합하는 하이브리드 설정에서 승인되지 않은 주문은 시스템 간에 순서 상태가 움직일 수도 있습니다.

## JWT(Webhook Event) 확인##validate-webhook-events-(jwt)

확인을 활성화하기 위해 Unity IAP는 JWT(JSON 웹 토큰)로 웹후크 요청에 서명합니다. 백엔드에서 이 토큰에 대한 다음 정보를 확인하여 이벤트가 유효한지 확인합니다.

* 인증 헤더
* **SIGNATURE** 비공개 키로 서명됨. 다음을 확인하기 위해 \*\*JWKS(공용 키)\*\*를 가져와 캐시합니다. [`https://services.api.unity.com/webhooks/.well-known/jwks.json`](https://services.api.unity.com/webhooks/.well-known/jwks.json)
* **발급자**: `https://services.api.unity.com/webhooks/`
* **고객 요구 사항(배열)**: `upid`(Unity 프로젝트 ID), `envId`(환경 ID)
* **토큰 만료일**: `exp`( 만료일)가 통과되지 않았는지 확인합니다.

JWT를 검증하는 데 사용할 수 있는 [대부분의 언어 라이브러리](https://www.jwt.io/libraries?programming_language)가 있습니다.

## Webhook 이벤트 셰이프##webhook-event-shape

Unity IAP는 지원되는 모든 결제 제공업체의 이벤트를 이 일관된 포맷으로 정규화합니다. 다음 예를 참고하십시오.

```json
{
  "id": "018d5e5e-5e5e-7e5e-5e5e-5e5e5e5e5e5e",
  "version": "1.0.0",
  "eventType": "order.paid",
  "time": "2024-01-15T14:30:00Z",
  "projectId": "018d5e5e-1111-7e5e-5e5e-111111111111",
  "environmentId": "018d5e5e-2222-7e5e-5e5e-222222222222",
  "dataType": "order",
  "data": {
    "id": "018d5e5e-3333-7e5e-5e5e-333333333333",
    "playerId": "player_12345",
    "paymentProvider": "stripe",
    "paymentProviderResourceId": "cs_test_a1b2c3d4e5f6",
    "url": "https://checkout.stripe.com/pay/cs_test_a1b2c3d4e5f6",
    "lineItems": [
      {
        "sku": "com.game.coins_100",
        "productType": "소모품",
        "price": {
          "amountMicros": 4990000,
          "currency": "USD"
        }
      }
    ],
    "total": {
      "amountMicros": 4990000,
      "currency": "USD",
      "refundedAmountMicros": 0
    },
    "status": "paid",
    "customReferenceId": "order_xyz_789",
    "metadata": {
      "campaign": "summer_sale",
      "platform": "iOS"
    },
    "createdAt": "2024-01-15T14:25:00Z",
    "updatedAt": "2024-01-15T14:30:00Z",
    "paidAt": "2024-01-15T14:30:00Z",
    "fulfilledAt": null
  }
}
```

Webhook 이벤트의 다음 필드를 참조하십시오.

**id** (string, required): 웹후크 이벤트의 고유 식별자입니다.**version** (string, required): Webhook 이벤트 스키마의 버전입니다.**eventType** (string, required): 이벤트 유형입니다. 가능한 값은 [이벤트 타입](#event-types)을 참조하십시오.**time** (string, required): 이벤트가 생성된 시간입니다(ISO 8601 형식).**projectId** (string, required): 이 순서가 속한 Unity 프로젝트 ID입니다.**environmentId** (string, required): 이 주문이 발생한 Unity 환경의 ID입니다.**dataType** (string, required): 이벤트의 데이터 유형입니다.**data** (object, required): 구매에 대한 세부 정보가 포함된 주문 데이터 오브젝트입니다.**id** (string, required): 주문의 고유 식별자입니다.**playerId** (string, required): Unity Authentication 플레이어 ID입니다.**paymentProvider** (string, required): 주문을 처리한 결제 제공업체(예: `stripe` 또는 `coda`)**paymentProviderResourceId** (string, required): 결제 제공업체가 리소스에 할당한 고유 ID입니다.**url** (string, required): 주문의 체크아웃 URL입니다.**lineItems** (object array, required): 주문 시 구매한 상품 목록입니다.**sku** (string, required): 제품의 고유 식별자입니다.**productType** (string, required): 제품 유형(예: `Consumable`)입니다.**price** (object, required): 제품의 가격입니다.**amountMicros** (integer, required): 마이크로미터 단위(1,000,000마이크로미터와 $1.00)입니다. 이 값은 사용자에게 표시된 값입니다.**currency** (string, required): 3개의 글자 ISO 4217 통화 코드입니다.**total** (object, required): 총 주문 금액입니다.**amountMicros** (integer, required): 전체 주문 금액(마이크로)입니다.**currency** (string, required): 3개의 글자 ISO 4217 통화 코드입니다.**refundedAmountMicros** (integer, required): 환불된 총 금액(마이크로)입니다. 부분 환불의 경우, 이는 `amountMicros`보다 작을 수 있습니다.**status** (string, required): 순서의 상태입니다. 가능한 값: `created`, `paid`, `failed`, `fulfilled`, `revoked`.**customReferenceId** (string): 주문에 대한 선택적인 커스텀 레퍼런스 ID입니다.**metadata** (object): 구매 플로 중에 전달된 커스텀 메타데이터를 포함하는 키-값 오브젝트입니다.**createdAt** (string, required): 순서가 생성된 시점의 타임스탬프입니다(ISO 8601 형식).**updatedAt** (string, required): 순서가 마지막으로 업데이트된 시점의 타임스탬프입니다(ISO 8601 형식).**paidAt** (string): 주문이 지급된 시점의 타임스탬프입니다(ISO 8601 형식).**fulfilledAt** (string): 주문이 완료되었을 때의 타임스탬프 또는 아직 완료되지 않은 경우의 `null`입니다.**revokedAt** (string): 주문이 취소되었을 때의 타임스탬프 또는 취소되지 않은 경우의 `null`입니다.

## 이벤트 유형##event-types

Unity IAP는 다음과 같은 웹후크 이벤트 타입을 전송합니다.

### order.paid##order.paid

플레이어가 구매를 성공적으로 완료하면 Unity IAP가 `order.paid` 이벤트를 전송합니다. 이 이벤트를 수신하면 플레이어에게 구매한 자격 또는 화폐를 부여하고 주문이 완료된 것으로 [표시합니다](#mark-orders-as-fulfilled-via-api).

### order.updated##order.updated

Unity IAP는 주문이 업데이트될 때마다 `order.updated` 이벤트를 전송합니다. 여기에는 다음과 같은 변경 사항이 포함됩니다.

* 완료된 것으로 표시되는 순서입니다.
* 처리 중인 환불입니다.
* 기타 순서 수정.

`data.total`의 `refundedAmountMicros` 필드는 현재 환불된 총액을 나타냅니다.

환불은 개발자가 시작합니다. 예를 들어 결제 제공업체의 대시보드를 통해 플레이어에게 환불을 보낼 수 있습니다.

환불이 발생하면 Unity IAP는 자동으로 권한을 취소하지 않습니다. 환불된 자격을 취소하려는 경우 이 이벤트를 수신하고 자격 시스템에서 처리할 수 있습니다.

### order.revoked##order.revoked

주문 상태가 `revoked`로 변경되면 Unity IAP가 `order.revoked` 이벤트를 전송합니다. 이 이벤트를 수신하면 플레이어의 원래 구매에 허용된 자격 또는 재화를 취소합니다.

충전은 플레이어가 시작합니다. 예를 들어 플레이어는 신용 카드 회사 또는 은행을 통해 비용에 대해 논쟁할 수 있습니다.

인보이스 충돌이 종료되고 플레이어가 이기면 Unity IAP는 자동으로 주문을 취소하고 이 이벤트를 전송합니다. 환불과 달리, Unity IAP는 과금에 대한 권한을 취소합니다.

## API 통해 실행된 주문 표시##mark-orders-as-fulfilled-via-api

백엔드에서 권한을 부여한 후 Orders API 사용하여 순서를 완료된 것으로 표시합니다. 이렇게 하면 `fulfilledAt` 타임스탬프가 업데이트되고 주문 상태가 올바르게 추적됩니다.

### Authentication##authentication

이러한 엔드포인트를 호출하려면 다음 방법 중 하나를 사용하여 인증해야 합니다.

* 서비스 계정 Unity Dashboard에서 서비스 계정을 생성하고 이를 사용하여 백엔드 서버를 인증합니다. 자세한 내용은 [서비스 계정 인증](https://services.docs.unity.com/docs/service-account-auth/)을 참고하십시오.
* Cloud Code 프로젝트를 대신하여 인증할 수 있는 Cloud Code 스크립트 또는 모듈에서 엔드포인트를 호출합니다. 자세한 내용은 구매를 위해 [Cloud Code 모듈 사용](./cloud-code-fulfillment.md)을 참고하십시오.

### 주문 가져오기##get-order

주문을 검색하려면 [`GET` 엔드포인트](/oas-iap-client/1.0.0/.md#get-order-status)를 호출합니다.

```http
GET https://iap.services.api.unity.com/v1/projects/{projectId}/environments/{environmentId}/orders/{orderId}
```

#### 주문 상태##order-statuses

| **상태**      | **설명**                                                                  |
| ----------- | ----------------------------------------------------------------------- |
| `created`   | 주문이 생성되었지만 결제가 완료되지 않았습니다. `paid`, `failed` 또는 `cancelled`로 전환할 수 있습니다. |
| `paid`      | 지급 공급자가 지급을 수령하고 확인했습니다. `fulfilled` 또는 `revoked`로 전환 가능                |
| `fulfilled` | 주문이 이행되고 플레이어에게 보상이 제공됩니다. `전환 가능한 상태`revoked\`                         |
| `failed`    | 순서 실패。 이는 터미널 상태입니다.                                                    |
| `revoked`   | 주문이 취소되었습니다. 이는 터미널 상태입니다.                                              |
| `cancelled` | 결제 전에 주문이 취소되었습니다. 이는 터미널 상태입니다.                                        |

#### 응답 예시##example-response-body

`GET` 및 `PATCH` 엔드포인트 모두 전체 주문 객체를 반환합니다.

```json
{
  "id": "018d5e5e-3333-7e5e-5e5e-333333333333",
  "projectId": "018d5e5e-1111-7e5e-5e5e-111111111111",
  "environmentId": "018d5e5e-2222-7e5e-5e5e-222222222222",
  "playerId": "player_12345",
  "paymentProvider": "stripe",
  "paymentProviderResourceId": "cs_test_12345",
  "url": "https://checkout.stripe.com/pay/cs_test_12345",
  "lineItems": [
    {
      "sku": "com.game.coins_100",
      "productType": "Consumable"
    }
  ],
  "status": "paid",
  "fulfilledAt": null,
  "revokedAt": null,
  "customReferenceId": "order_xyz_789",
  "metadata": {
    "campaign": "summer_sale"
  },
  "createdAt": "2024-01-15T14:25:00Z",
  "updatedAt": "2024-01-15T14:30:00Z"
}
```

### 주문 업데이트##update-an-order

주문 상태를 업데이트하려면 [`PATCH` 엔드포인트](/oas-iap-client/1.0.0/.md#update-an-order)를 호출합니다.

```http
PATCH https://iap.services.api.unity.com/v1/projects/{projectId}/environments/{environmentId}/orders/{orderId}
```

#### 요청 본문##request-body

```json
{
  "status": "fulfilled"
}
```

**status** (string, required): 순서에 대해 설정할 새 상태입니다. 허용된 값: .- `fulfilled`: 플레이어에게 권한을 부여한 후 설정합니다. `paid` 주문 시에만 설정할 수 있습니다.
- `revoked`: 을 설정하면 플레이어의 권한이 취소됩니다(예: 환불 후). `paid` 또는 `fulfilled` 주문 시에만 설정할 수 있습니다.
- `cancelled`: 주문을 취소하도록 설정합니다. `created` 주문(지급 전)에만 설정할 수 있습니다.주문이 `revoked` 또는 `cancelled`이면 해당 상태는 변경할 수 없습니다.

[HTTP 응답 상태 코드](/oas-iap-client/1.0.0/.md#http-response-status-codes-for-update-an-order:)는 IAP 클라이언트 API 문서를 참조하십시오.

## 이벤트 응답 반환##return-an-event-response

이벤트 리스폰스를 반환하여 이벤트 처리의 성공 여부를 나타내야 합니다.

* "response":  성공적인 이벤트 처리를 나타냅니다.
* **비`2xx` 응답**: 실패를 나타냅니다. Unity IAP는 리플레이 정책에 따라 [이벤트를 재생](#event-replay-behavior)합니다.

### 이벤트 리플레이 동작##event-replay-behavior

Webhook 엔드포인트가 비`2xx` 응답을 반환하면 Unity IAP가 이벤트 전송을 자동으로 재시도합니다. 리플레이된 이벤트가 중복된 자격으로 이어지지 않도록 웹후크 핸들러가 고정된 상태여야 합니다.

## 다음 단계##next-steps

이 페이지는 IAP를 사용하여 D2C(Direct-to-Consumer) 결제 제공업체를 설정하는 워크플로의 일부입니다. 이 워크플로를 계속하려면 다음 옵션 중 하나를 선택합니다.

[Integrate D2C payment providers](./workflow.md#fulfill-purchases-through-the-backend-api): Integrate D2C payment providers with IAP 워크플로 페이지로 돌아갑니다.
[Test your integration](./test-integration.md): 워크플로의 다음 단계로 이동하여 D2C 결제 제공업체를 설정합니다.
