> For the complete documentation index, see [llms.txt](https://developers-apps-in-toss.toss.im/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers-apps-in-toss.toss.im/guide/zh/authentication/migration.md).

# Toss 登录迁移

**Toss 登录(`userKey`)** 的迷你应用 **用户识别键(`hash`)** 切换为该方式的方法。

按照本文档操作后，可以将当前使用 Toss 登录的用户逐步映射到用户识别键，等所有用户迁移完成后，还可以完全移除对 Toss 登录的依赖。

### 什么时候使用本指南？

* 一直以来都在使用 Toss 登录 `userKey` 进行用户识别。
* 今后希望将用户识别键 `hash`值作为标准识别标识。

### 核心概念

* **用户识别键 hash**: `getUserKeyForGame()` 通过调用获取的游戏专用唯一标识符
* **Toss 登录 userKey**: 基于现有 Toss 登录的用户标识符
* **映射**: 同一用户的 `userKey` 和 `hash` 值按 1:1 关联后的状态

{% hint style="info" %}
**请参考**

按各游戏分别 `hash` 值会不同。
{% endhint %}

### 整体迁移流程

1. 在客户端 `getUserKeyForGame()` 通过……获取用户识别键 `hash` 值。
2. `getIsTossLoginIntegratedService()` 确认 Toss 登录是否已集成。
3. 查询合作方服务器上的映射状态。
4. 如果未映射， `appLogin()` 会进行 Toss 登录，并且 `hash` 值发送到服务器。
5. 在服务器端将 Toss 登录 `userKey` 与用户识别键 `hash` 值保存到映射表中。
6. 之后 `hash` 仅凭值就可以识别用户。所有用户映射完成后，请移除对 Toss 登录的依赖。

### 需要预先实现的 API

合作方需要将以下两个 API **自行实现。** 这些 API 不由 App in Toss 提供，请参考下方示例，在合作方服务器上自行开发。

* **查询映射状态**
  * `POST /api/auth/migration/status`
  * **请求**: `{ hash: string }`
  * **响应**: `{ isMapped: boolean }`
* **创建映射**
  * `POST /api/auth/migration/link`
  * **请求**: `{ hash: string; authorizationCode: string; referrer?: string }`
  * **响应**: `{ success: true }`

***

### 客户端实现步骤

#### 1. 引入 SDK

```tsx
import { getUserKeyForGame, getIsTossLoginIntegratedService, appLogin } from '@apps-in-toss/web-framework';
```

#### 2. 发放游戏 hash 值

```tsx
const result = await getUserKeyForGame();
if (!result) return console.warn('不支持的应用版本。');
if (result === 'INVALID_CATEGORY') return console.error('这是非游戏类别的迷你应用。');
if (result === 'ERROR') return console.error('查询用户键时发生错误。');
if (result.type !== 'HASH') return console.error('返回值未知。');
const { hash } = result;
```

#### 3. 确认 Toss 登录集成情况

```tsx
const status = await getIsTossLoginIntegratedService();
if (status === 'INVALID_CLIENT') {
  console.log('这是未集成 Toss 登录的迷你应用。');
  return;
}
```

详细 API 规范请参考下方 [`getIsTossLoginIntegratedService`](#%ED%86%A0%EC%8A%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%B0%EB%8F%99-%EC%97%AC%EB%B6%80-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0) 部分。

#### 4. 在合作方服务器上查询映射状态并进行映射

```tsx
if (status === true) {
  // 已集成 Toss 登录的用户
  const { isMapped } = await fetch('/api/auth/migration/status', {
    // 确认是否已映射
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ hash }),
  }).then((r) => r.json());

  if (!isMapped) {
    const { authorizationCode, referrer } = await appLogin(); // 未映射则先进行 Toss 登录并创建映射
    await fetch('/api/auth/migration/link', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ authorizationCode, referrer, hash }),
    });
  }

  console.log('映射完成或已映射的用户。');
  return;
}

console.log('未集成 Toss 登录的用户。'); // status === false
```

#### 5. 使用用户识别键 hash

现在用户识别可以基于用户识别键 `hash`值来进行。Toss 登录 `userKey` 取而代之， `getUserKeyForGame()` 通过……获取的用户识别键 `hash`值请在服务器和客户端两端都作为用户识别标识使用。

***

### 完整示例代码

```tsx
import { getUserKeyForGame, getIsTossLoginIntegratedService, appLogin } from '@apps-in-toss/web-framework';

async function migrateIfNeeded() {
  const res = await getUserKeyForGame();
  if (!res) return console.warn('不支持的应用版本。');
  if (res === 'INVALID_CATEGORY') return console.error('这是非游戏类别的迷你应用。');
  if (res === 'ERROR') return console.error('查询用户键时发生错误。');
  if (res.type !== 'HASH') return console.error('返回值未知。');
  const { hash } = res;

  let status: boolean;
  try {
    status = await getIsTossLoginIntegratedService();
  } catch (error: any) {
    console.error('确认 Toss 登录集成状态时发生错误：', error);
    return;
  }

  if (status === true) {
    // 查询映射状态
    const { isMapped } = await fetch('/api/auth/migration/status', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ hash }),
    }).then((r) => r.json());

    if (!isMapped) {
      // 未映射则先进行 Toss 登录并创建映射
      const { authorizationCode, referrer } = await appLogin();

      await fetch('/api/auth/migration/link', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ authorizationCode, referrer, hash }),
      });
    }

    console.log('映射完成或已映射的用户。');
    return;
  }

  // status === false : 虽然有 Toss 登录功能，但当前用户未集成
  console.log('这是未集成 Toss 登录的用户。');
}
```

{% hint style="info" %}
**异常处理**

在不使用 Toss 登录的迷你应用中 `getIsTossLoginIntegratedService()`调用时，可能会发生以下异常。

```tsx
@throw {message: "需要设置 oauth2ClientId。"}
```

这种情况下属于没有 Toss 登录功能的环境，因此无需单独处理。
{% endhint %}

***

### 确认 Toss 登录集成状态

**SDK 函数：** `getIsTossLoginIntegratedService`

`getIsTossLoginIntegratedService`是 **用于确认当前用户是否与 Toss 登录集成的 API**。

该函数主要在 **从 Toss 登录 → 用户识别键发放的迁移过程中**使用。可根据是否为既有 Toss 登录用户，分支处理登录流程或数据迁移。

**签名**

```tsx
function getIsTossLoginIntegratedService(): Promise<boolean>;
```

| 返回类型               | 说明                                     |
| ------------------ | -------------------------------------- |
| `Promise<boolean>` | 如果当前服务已与 Toss 登录集成， `true`，否则 `false`。 |

#### 注意事项

* 该 API 仅适用于使用（或曾使用）Toss 登录功能的迷你应用。
* 如果在完全未使用 Toss 登录的迷你应用中调用，可能会发生如下异常。

```tsx
@throw { message: '需要设置 oauth2ClientId。' }
```

**示例：确认 Toss 登录集成状态**

下面的示例展示了先确认用户是否为 Toss 登录集成用户，再根据状态执行不同处理的基本流程。

{% tabs %}
{% tab title="js" %}

```js
import { getIsTossLoginIntegratedService } from '@apps-in-toss/web-framework';

async function handleGetIsTossLoginIntegratedService() {
  try {
    const result = await getIsTossLoginIntegratedService();

    if (result === undefined) {
      console.warn('不支持的应用版本。');
      return;
    }
    if (result === true) {
      console.log('这是集成 Toss 登录的用户。');
      // 可在这里对 Toss 登录集成用户进行处理。
    }
    if (result === false) {
      console.log('这是未集成 Toss 登录的用户。');
      // 可在这里处理非 Toss 登录集成用户的情况。
    }
  } catch (error) {
    console.error(error);
  }
}
```

{% endtab %}

{% tab title="React" %}

```tsx
import { getIsTossLoginIntegratedService } from '@apps-in-toss/web-framework';

function GetIsTossLoginIntegratedServiceButton() {
  async function handleClick() {
    try {
      const result = await getIsTossLoginIntegratedService();

      if (result === undefined) {
        console.warn('不支持的应用版本。');
        return;
      }
      if (result === true) {
        console.log('这是集成 Toss 登录的用户。');
        // 可在这里对 Toss 登录集成用户进行处理。
      }
      if (result === false) {
        console.log('这是未集成 Toss 登录的用户。');
        // 可在这里处理非 Toss 登录集成用户的情况。
      }
    } catch (error) {
      console.error(error);
    }
  }

  return <button onClick={handleClick}>确认 Toss 登录集成服务是否可用</button>;
}
```

{% endtab %}

{% tab title="React Native" %}

```tsx
import { Button } from 'react-native';
import { getIsTossLoginIntegratedService } from '@apps-in-toss/framework';

function GetIsTossLoginIntegratedServiceButton() {
  async function handlePress() {
    try {
      const result = await getIsTossLoginIntegratedService();

      if (result === undefined) {
        console.warn('不支持的应用版本。');
        return;
      }
      if (result === true) {
        console.log('这是集成 Toss 登录的用户。');
        // 可在这里对 Toss 登录集成用户进行处理。
      }
      if (result === false) {
        console.log('这是未集成 Toss 登录的用户。');
        // 可在这里处理非 Toss 登录集成用户的情况。
      }
    } catch (error) {
      console.error(error);
    }
  }

  return <Button onPress={handlePress} title="确认 Toss 登录集成服务是否可用" />;
}
```

{% endtab %}
{% endtabs %}

#### 什么时候适合使用？

* 在基于 Toss 登录的服务中 **切换到用户识别键（迁移）** 时
* 区分现有用户和新用户 **进行数据迁移/补偿处理**时
* 根据 Toss 登录是否集成 **提供不同 UX**时

#### 参考说明

* `getIsTossLoginIntegratedService`是迁移辅助 API。
* 请参考下方的认证/登录功能。
  * Toss 登录
  * [发放用户识别键](https://appsintoss.gitbook.io/appsintoss-docs/documentation/common/authentication/hash-key)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers-apps-in-toss.toss.im/guide/zh/authentication/migration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
