> 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 不由 Apps 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.
