> ## Documentation Index
> Fetch the complete documentation index at: https://playwave.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# DELETE /session/end

> 게임 세션을 종료하고 과금을 중지합니다

<Badge color="red">DELETE</Badge> `/v1/game/session/end`

플레이어가 게임을 떠날 때 호출합니다. 과금을 중지하고 세션을 정리합니다.

## 요청

### Headers

<ParamField header="X-Api-Key" type="string" required>
  게임별 API Key.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  `application/json`
</ParamField>

### Body

<ParamField body="game_session_id" type="string" required>
  종료할 게임 세션 ID.
</ParamField>

<ParamField body="play_duration_sec" type="integer">
  클라이언트 측정 플레이 시간 (초). 선택 — 서버 값과 비교용.
</ParamField>

```json theme={null}
{
  "game_session_id": "gs_550e8400-e29b-41d4-a716-446655440000",
  "play_duration_sec": 1795
}
```

## 응답

### 성공 — <Badge color="green">200</Badge>

```json theme={null}
{
  "success": true,
  "request_id": "req_abc123",
  "data": {
    "result": "SUCCESS",
    "play_duration_sec": 1800,
    "client_play_duration_sec": 1795
  }
}
```

<ResponseField name="result" type="string" required>
  항상 `"SUCCESS"`.
</ResponseField>

<ResponseField name="play_duration_sec" type="integer" required>
  **서버 기준** 플레이 시간 (초) — 정산에 사용되는 공식 값.
</ResponseField>

<ResponseField name="client_play_duration_sec" type="integer | null">
  요청에서 보낸 `play_duration_sec` 값. 미전달 시 `null`.
</ResponseField>

### 에러

| HTTP                              | 코드                  | 설명               | 처리 |
| --------------------------------- | ------------------- | ---------------- | -- |
| <Badge color="yellow">404</Badge> | `SESSION_NOT_FOUND` | 세션 없음 (이미 만료/삭제) | 무시 |
| <Badge color="orange">409</Badge> | `ALREADY_ENDED`     | 이미 종료된 세션        | 무시 |

<Note>
  404와 409 에러는 세션이 이미 정리된 상태이므로 무시해도 됩니다. 로깅만 하고 정상 흐름을 유지하세요.
</Note>

## Luau 예제

```lua theme={null}
local function endSession(gameSessionId, playDurationSec)
    local requestBody = {
        game_session_id = gameSessionId,
    }

    if playDurationSec then
        requestBody.play_duration_sec = playDurationSec
    end

    local success, response = pcall(function()
        return HttpService:RequestAsync({
            Url = API_URL .. "/game/session/end",
            Method = "DELETE",
            Headers = {
                ["X-Api-Key"] = API_KEY,
                ["Content-Type"] = "application/json",
            },
            Body = HttpService:JSONEncode(requestBody),
        })
    end)

    if not success then
        warn("[Playwave] End session failed:", response)
        return
    end

    -- 404/409는 이미 정리된 상태 — 무시
    if response.StatusCode == 404 or response.StatusCode == 409 then
        return
    end

    local body = HttpService:JSONDecode(response.Body)
    return body.data
end
```

## 호출 시점

### PlayerRemoving

개별 플레이어 퇴장 시 호출합니다.

```lua theme={null}
Players.PlayerRemoving:Connect(function(player)
    local session = activeSessions[player.UserId]
    if not session then return end

    activeSessions[player.UserId] = nil
    endSession(session.gameSessionId)
end)
```

### BindToClose

서버 종료 시 모든 활성 세션을 정리합니다.

```lua theme={null}
game:BindToClose(function()
    for userId, session in pairs(activeSessions) do
        activeSessions[userId] = nil
        pcall(function()
            endSession(session.gameSessionId)
        end)
    end
end)
```

<Tip>
  `BindToClose`는 30초 제한이 있습니다. HTTP 호출이 실패하더라도 <Tooltip tip="Time-To-Live — Redis가 하트비트 없이 4분 후에 세션 키를 자동 삭제합니다">TTL</Tooltip> 만료로 4분 후 자동 정리됩니다.
</Tip>
