> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.vpms.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 마일리지 (Mileage)

> 숙소 마일리지 적립 및 사용 API

## 개요

Core 서비스의 마일리지 도메인은 고객의 마일리지 적립, 사용, 취소 및 마일리지 사용자 관리 기능을 제공합니다. SMS 인증을 통한 마일리지 조회와 결제 연동이 가능하며, 숙소별로 마일리지 설정을 관리할 수 있습니다.

## Queries

### getAccommodationMileages

숙소의 마일리지 거래 내역을 조회합니다. (관리자 전용)

#### GraphQL Signature

```graphql theme={null}
query GetAccommodationMileages(
  $accommodationId: ID!
  $filter: MileageFilterInput
  $first: Int
  $after: String
) {
  getAccommodationMileages(
    accommodationId: $accommodationId
    filter: $filter
    first: $first
    after: $after
  ) {
    nodes {
      id
      phone
      amount
      paymentType
      isUsed
      isCancelled
      expireAt
      createdAt
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

#### 파라미터

<ParamField path="accommodationId" type="ID!" required>
  조회할 숙소 ID (관리 권한 필요)
</ParamField>

<ParamField path="filter" type="MileageFilterInput">
  필터 조건:

  * `keyword`: 전화번호 검색 (부분 일치)
  * `paymentType`: 결제 유형 필터
  * `dateFrom`: 시작 날짜
  * `dateTo`: 종료 날짜
</ParamField>

<ParamField path="first" type="Int">
  페이지당 항목 수 (기본값: 20)
</ParamField>

<ParamField path="after" type="String">
  페이지네이션 커서
</ParamField>

#### 응답

<ResponseField name="nodes" type="Mileage[]">
  마일리지 거래 목록

  * `id`: 마일리지 거래 고유 ID
  * `phone`: 고객 전화번호
  * `amount`: 마일리지 금액
  * `paymentType`: 결제 유형 (예: "cash")
  * `isUsed`: 사용 여부
  * `isCancelled`: 취소 여부
  * `expireAt`: 만료일
  * `createdAt`: 생성일
</ResponseField>

<ResponseField name="pageInfo" type="PageInfo">
  페이지네이션 정보

  * `hasNextPage`: 다음 페이지 존재 여부
  * `endCursor`: 다음 페이지 커서
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    getAccommodationMileages(
      accommodationId: "acc_123"
      filter: {
        keyword: "010"
        dateFrom: "2024-01-01"
        dateTo: "2024-12-31"
      }
      first: 10
    ) {
      nodes {
        id
        phone
        amount
        isUsed
        expireAt
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "getAccommodationMileages": {
        "nodes": [
          {
            "id": "01HQKS9V8X2N3P4Q5R6S7T8U9V",
            "phone": "01012345678",
            "amount": 5000,
            "isUsed": false,
            "expireAt": "2025-01-01T00:00:00Z"
          }
        ],
        "pageInfo": {
          "hasNextPage": false,
          "endCursor": "01HQKS9V8X2N3P4Q5R6S7T8U9V"
        }
      }
    }
  }
  ```
</CodeGroup>

***

### getAccommodationMileageUsers

숙소의 마일리지 사용자 목록을 조회합니다. (관리자 전용)

#### GraphQL Signature

```graphql theme={null}
query GetAccommodationMileageUsers(
  $accommodationId: ID!
  $filter: MileageUserFilterInput
  $first: Int
  $after: String
) {
  getAccommodationMileageUsers(
    accommodationId: $accommodationId
    filter: $filter
    first: $first
    after: $after
  ) {
    nodes {
      id
      phone
      name
      currentMileage
      totalVisit
      memo
      createdAt
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

#### 파라미터

<ParamField path="accommodationId" type="ID!" required>
  조회할 숙소 ID (관리 권한 필요)
</ParamField>

<ParamField path="filter" type="MileageUserFilterInput">
  필터 조건:

  * `keyword`: 이름 또는 전화번호 검색 (부분 일치)
  * `totalVisit`: 최소 방문 횟수
  * `currentMileage`: 최소 보유 마일리지
</ParamField>

<ParamField path="first" type="Int">
  페이지당 항목 수 (기본값: 20)
</ParamField>

<ParamField path="after" type="String">
  페이지네이션 커서
</ParamField>

#### 응답

<ResponseField name="nodes" type="MileageUser[]">
  마일리지 사용자 목록

  * `id`: 사용자 고유 ID
  * `phone`: 전화번호
  * `name`: 고객명
  * `currentMileage`: 현재 보유 마일리지
  * `totalVisit`: 총 방문 횟수
  * `memo`: 메모
  * `createdAt`: 생성일
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    getAccommodationMileageUsers(
      accommodationId: "acc_123"
      filter: {
        keyword: "김"
        currentMileage: 1000
      }
      first: 10
    ) {
      nodes {
        id
        phone
        name
        currentMileage
        totalVisit
      }
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "getAccommodationMileageUsers": {
        "nodes": [
          {
            "id": "mu_123",
            "phone": "01012345678",
            "name": "김고객",
            "currentMileage": 5000,
            "totalVisit": 3
          }
        ]
      }
    }
  }
  ```
</CodeGroup>

***

### queryMileage

SMS 인증을 통한 고객 마일리지 조회 API입니다.

#### GraphQL Signature

```graphql theme={null}
query QueryMileage(
  $phone: String!
  $authToken: String!
  $accommodationId: ID!
) {
  queryMileage(
    phone: $phone
    authToken: $authToken
    accommodationId: $accommodationId
  ) {
    phone
    totalAmount
    usedAmount
  }
}
```

#### 파라미터

<ParamField path="phone" type="String!" required>
  조회할 전화번호
</ParamField>

<ParamField path="authToken" type="String!" required>
  SMS 인증 토큰 (`confirmMileageSMSAuth`에서 발급)
</ParamField>

<ParamField path="accommodationId" type="ID!" required>
  숙소 ID
</ParamField>

#### 응답

<ResponseField name="phone" type="String!">
  조회한 전화번호
</ResponseField>

<ResponseField name="totalAmount" type="Int!">
  총 적립된 마일리지 (만료되지 않은 마일리지)
</ResponseField>

<ResponseField name="usedAmount" type="Int!">
  사용된 마일리지
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    queryMileage(
      phone: "01012345678"
      authToken: "01HQKS9V8X2N3P4Q5R6S7T8U9V"
      accommodationId: "acc_123"
    ) {
      phone
      totalAmount
      usedAmount
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "queryMileage": {
        "phone": "01012345678",
        "totalAmount": 10000,
        "usedAmount": 5000
      }
    }
  }
  ```
</CodeGroup>

#### 에러 처리

<ResponseField name="AUTH_NUMBER_NOT_MATCH">
  인증 토큰이 유효하지 않습니다. 다시 SMS 인증을 진행하세요.
</ResponseField>

<Warning>
  인증 토큰은 30분간 유효하며, 만료 시 `requestMileageSMSAuth`를 통해 재발급 받아야 합니다.
</Warning>

## Mutations

### setMileageSettings

숙소의 마일리지 설정을 변경합니다. (관리자 전용)

#### GraphQL Signature

```graphql theme={null}
mutation SetMileageSettings(
  $accommodationId: ID!
  $inputs: [MileageSettingInput!]
) {
  setMileageSettings(
    accommodationId: $accommodationId
    inputs: $inputs
  ) {
    key
    value
  }
}
```

#### 파라미터

<ParamField path="accommodationId" type="ID!" required>
  설정을 변경할 숙소 ID (관리 권한 필요)
</ParamField>

<ParamField path="inputs" type="MileageSettingInput[]">
  설정 항목 배열:

  * `key`: 설정 키 (예: "expireAfter")
  * `value`: 설정 값
</ParamField>

#### 응답

<ResponseField name="settings" type="MileageSetting[]">
  변경된 설정 목록

  * `key`: 설정 키
  * `value`: 설정 값
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    setMileageSettings(
      accommodationId: "acc_123"
      inputs: [
        { key: "expireAfter", value: "365" }
        { key: "minAmount", value: "1000" }
      ]
    ) {
      key
      value
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "setMileageSettings": [
        { "key": "expireAfter", "value": "365" },
        { "key": "minAmount", "value": "1000" }
      ]
    }
  }
  ```
</CodeGroup>

***

### addMileage

고객에게 마일리지를 수동으로 적립합니다. (관리자 전용)

#### GraphQL Signature

```graphql theme={null}
mutation AddMileage($input: AddMileageInput!) {
  addMileage(input: $input) {
    id
    phone
    amount
    expireAt
    createdAt
  }
}
```

#### 파라미터

<ParamField path="input" type="AddMileageInput!" required>
  마일리지 적립 정보:

  * `accommodationId` (ID!): 숙소 ID
  * `phone` (String!): 고객 전화번호
  * `amount` (Int!): 적립 금액
</ParamField>

#### 응답

<ResponseField name="mileage" type="Mileage">
  생성된 마일리지 정보

  * `id`: 마일리지 거래 ID
  * `phone`: 고객 전화번호
  * `amount`: 적립 금액
  * `expireAt`: 만료일 (설정된 유효기간 적용)
  * `createdAt`: 생성일
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    addMileage(input: {
      accommodationId: "acc_123"
      phone: "01012345678"
      amount: 5000
    }) {
      id
      phone
      amount
      expireAt
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "addMileage": {
        "id": "01HQKS9V8X2N3P4Q5R6S7T8U9V",
        "phone": "01012345678",
        "amount": 5000,
        "expireAt": "2025-12-31T00:00:00Z"
      }
    }
  }
  ```
</CodeGroup>

<Note>
  전화번호의 하이픈(-)은 자동으로 제거되며, 만료일은 숙소의 마일리지 설정(`expireAfter`)에 따라 자동 계산됩니다.
</Note>

***

### cancelMileage

적립된 마일리지를 취소합니다. (관리자 전용)

#### GraphQL Signature

```graphql theme={null}
mutation CancelMileage($id: ID!) {
  cancelMileage(id: $id) {
    id
    isCancelled
  }
}
```

#### 파라미터

<ParamField path="id" type="ID!" required>
  취소할 마일리지 거래 ID
</ParamField>

#### 응답

<ResponseField name="mileage" type="Mileage">
  취소된 마일리지 정보

  * `id`: 마일리지 거래 ID
  * `isCancelled`: 취소 여부 (true)
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    cancelMileage(id: "01HQKS9V8X2N3P4Q5R6S7T8U9V") {
      id
      isCancelled
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "cancelMileage": {
        "id": "01HQKS9V8X2N3P4Q5R6S7T8U9V",
        "isCancelled": true
      }
    }
  }
  ```
</CodeGroup>

#### 에러 처리

<ResponseField name="DATA_NOT_FOUND_ON_ID">
  해당 ID의 마일리지를 찾을 수 없습니다.
</ResponseField>

<ResponseField name="ALREADY_PAID">
  이미 사용된 마일리지는 취소할 수 없습니다.
</ResponseField>

<ResponseField name="ALREADY_CANCELLED">
  이미 취소된 마일리지입니다.
</ResponseField>

***

### updateMileageUser

마일리지 사용자 정보를 수정합니다. (관리자 전용)

#### GraphQL Signature

```graphql theme={null}
mutation UpdateMileageUser($input: UpdateMileageUserInput!) {
  updateMileageUser(input: $input) {
    id
    phone
    name
    memo
  }
}
```

#### 파라미터

<ParamField path="input" type="UpdateMileageUserInput!" required>
  수정할 사용자 정보:

  * `id` (ID!): 사용자 ID
  * `accommodationId` (ID!): 숙소 ID
  * `phone` (String): 전화번호
  * `name` (String): 고객명
  * `memo` (String): 메모
</ParamField>

#### 응답

<ResponseField name="mileageUser" type="MileageUser">
  수정된 사용자 정보
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    updateMileageUser(input: {
      id: "mu_123"
      accommodationId: "acc_123"
      name: "김고객"
      memo: "VIP 고객"
    }) {
      id
      name
      memo
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "updateMileageUser": {
        "id": "mu_123",
        "name": "김고객",
        "memo": "VIP 고객"
      }
    }
  }
  ```
</CodeGroup>

## 사용 흐름

### 마일리지 조회 흐름 (고객용)

1. **SMS 인증 요청**: `requestMileageSMSAuth`로 인증번호 발송
2. **인증 확인**: `confirmMileageSMSAuth`로 인증 토큰 발급
3. **마일리지 조회**: `queryMileage`로 보유 마일리지 확인

### 마일리지 관리 흐름 (관리자용)

1. **설정 관리**: `setMileageSettings`로 유효기간 등 설정
2. **수동 적립**: `addMileage`로 고객에게 마일리지 지급
3. **내역 조회**: `getAccommodationMileages`로 거래 내역 확인
4. **취소 처리**: `cancelMileage`로 잘못된 적립 취소

## 관련 API

* [인증 API](/api-reference/core-svc/auth) - SMS 인증 토큰 발급
* [예약 API](/api-reference/core-svc/reservation) - 예약 시 마일리지 적립/사용
* [결제 API](/api-reference/core-svc/payment) - 결제 연동
