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

# Nhúng eContract vào Website (Iframe)

> Hướng dẫn tích hợp econtractid vào website bên thứ ba bằng iframe và accessToken

## Tổng quan

econtractid hỗ trợ **nhúng giao diện** trực tiếp vào website của bên thứ ba thông qua `<iframe>`. Người dùng không cần rời khỏi hệ thống hiện tại mà vẫn có thể:

* Xem danh sách hồ sơ / hợp đồng
* Xem chi tiết và xem PDF hợp đồng
* Thực hiện ký số (ký đơn giản, ký ảnh, ký USB Token, ký Cloud HSM)
* Xử lý công việc liên quan đến hợp đồng

<Info>
  Phương thức này phù hợp cho các hệ thống muốn tích hợp nhanh giao diện
  econtractid mà không cần phát triển lại UI.
</Info>

***

## Luồng hoạt động

```mermaid theme={null}
sequenceDiagram
    participant A as Hệ thống bên thứ ba
    participant B as econtractid

    A->>B: POST /api/auth/login
    B-->>A: accessToken + refreshToken

    Note over A: Tạo iframe với URL:<br/>https://app.econtractid.com/...?at=accessToken

    A->>B: Load iframe
    Note over B: Web nhận accessToken từ URL<br/>→ Gọi API lấy thông tin user<br/>→ Lưu vào localStorage<br/>→ Hiển thị giao diện bình thường
    B-->>A: Giao diện econtractid
```

***

## Bước 1: Lấy Access Token

Có 2 cách để lấy `accessToken` từ hệ thống econtractid:

<Tabs>
  <Tab title="API Login (Username/Password)">
    Đăng nhập bằng tài khoản người dùng (username + password):

    ```bash theme={null}
    POST https://api.econtractid.vn/api/auth/login
    Content-Type: application/json

    {
      "username": "user@example.com",
      "password": "your_password"
    }
    ```

    **Response thành công:**

    ```json theme={null}
    {
      "statusCode": 200,
      "data": {
        "accessToken": "eyJhbGciOiJIUzI1NiIs...",
        "refreshToken": "eyJhbGciOiJIUzI1NiIs...",
        "expiresIn": 3600
      }
    }
    ```

    | Trường     | Kiểu     | Mô tả                    |
    | ---------- | -------- | ------------------------ |
    | `username` | `string` | Email hoặc tên đăng nhập |
    | `password` | `string` | Mật khẩu tài khoản       |

    <Info>
      Phù hợp khi hệ thống bên thứ ba quản lý trực tiếp thông tin đăng nhập của người dùng trên econtractid.
    </Info>
  </Tab>

  <Tab title="API Login Server (Secret Key + CCCD)">
    Đăng nhập phía server bằng **Secret Key** kết hợp **số CCCD** (Căn cước công dân) của người dùng. Phương thức này dành cho tích hợp **server-to-server**, không yêu cầu người dùng nhập mật khẩu.

    ```bash theme={null}
    POST https://api.econtractid.vn/api/auth/login-server
    Content-Type: application/json

    {
      "secretKey": "your_secret_key",
      "cccd": "001234567890"
    }
    ```

    **Response thành công:**

    ```json theme={null}
    {
      "statusCode": 200,
      "data": {
        "accessToken": "eyJhbGciOiJIUzI1NiIs...",
        "refreshToken": "eyJhbGciOiJIUzI1NiIs...",
        "expiresIn": 3600
      }
    }
    ```

    | Trường      | Kiểu     | Mô tả                                                                                        |
    | ----------- | -------- | -------------------------------------------------------------------------------------------- |
    | `secretKey` | `string` | Secret Key được cấp cho hệ thống tích hợp. Lấy tại **Cài đặt** → **Tích hợp** → **API Keys** |
    | `cccd`      | `string` | Số Căn cước công dân của người dùng cần đăng nhập                                            |

    <Info>
      Phù hợp khi hệ thống bên thứ ba đã có thông tin CCCD của người dùng và muốn tự động đăng nhập mà không cần người dùng nhập tài khoản/mật khẩu econtractid.
    </Info>

    <Warning>
      **Secret Key** là thông tin nhạy cảm, chỉ nên gọi API này từ **phía server (backend)**, tuyệt đối không để lộ Secret Key ở phía client (frontend/trình duyệt).
    </Warning>
  </Tab>
</Tabs>

<Warning>
  Access Token có hiệu lực **1 giờ**. Khi token hết hạn, cần gọi API refresh
  hoặc login lại để lấy token mới và cập nhật lại iframe.
</Warning>

Xem chi tiết về xác thực tại [Xác thực & Phân quyền API](/technical/xac-thuc).

***

## Bước 2: Tạo URL nhúng

Tạo URL truy cập econtractid kèm `accessToken` dưới dạng query parameter `at`:

```
https://app.econtractid.com/{đường-dẫn}?at={accessToken}
```

### Các đường dẫn phổ biến

| Đường dẫn          | Mô tả                         |
| ------------------ | ----------------------------- |
| `/dashboard`       | Trang tổng quan               |
| `/ho-so`           | Danh sách hồ sơ hợp đồng      |
| `/ho-so/{id}`      | Chi tiết một hồ sơ cụ thể     |
| `/xu-ly-cong-viec` | Danh sách công việc cần xử lý |
| `/ky-so/{id}`      | Trang ký số hợp đồng          |

**Ví dụ URL đầy đủ:**

```
https://app.econtractid.com/ho-so?at=eyJhbGciOiJIUzI1NiIs...
```

<Tip>
  Để kiểm tra nhanh, bạn có thể đăng nhập hệ thống econtractid, lấy accessToken
  từ DevTools (Application → localStorage), sau đó mở trình duyệt mới (hoặc cửa
  sổ ẩn danh) và paste URL kèm accessToken để xác nhận hoạt động đúng.
</Tip>

***

## Bước 3: Nhúng iframe vào website

### HTML thuần

```html theme={null}
<iframe
  id="econtract-frame"
  src="https://app.econtractid.com/ho-so?at=YOUR_ACCESS_TOKEN"
  width="100%"
  height="800px"
  frameborder="0"
  style="border: 1px solid #ddd; border-radius: 8px; background: #fff;"
  allow="clipboard-read; clipboard-write"
>
</iframe>
```

### Angular

```typescript theme={null}
// component.ts
import { Component, OnInit } from "@angular/core";
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";

@Component({
  selector: "app-econtract-embed",
  template: `
    <iframe
      id="econtract-frame"
      [src]="econtractIframeSrc"
      width="100%"
      height="800px"
      frameborder="0"
      style="border: 1px solid #ddd; border-radius: 8px; background: #fff;"
      allow="clipboard-read; clipboard-write"
    >
    </iframe>
  `,
})
export class EcontractEmbedComponent implements OnInit {
  econtractIframeSrc: SafeResourceUrl = "";

  constructor(private sanitizer: DomSanitizer) {}

  ngOnInit(): void {
    const accessToken = "eyJhbGciOiJIUzI1NiIs..."; // Lấy từ API login
    const baseUrl = "https://app.econtractid.com/ho-so";
    const url = `${baseUrl}?at=${accessToken}`;
    this.econtractIframeSrc =
      this.sanitizer.bypassSecurityTrustResourceUrl(url);
  }
}
```

### React

```tsx theme={null}
import React, { useState, useEffect } from "react";

function EcontractEmbed() {
  const [iframeSrc, setIframeSrc] = useState("");

  useEffect(() => {
    // Gọi API login để lấy accessToken
    async function getToken() {
      const response = await fetch(
        "https://api.econtractid.vn/api/auth/login",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            username: "user@example.com",
            password: "your_password",
          }),
        },
      );
      const data = await response.json();
      const accessToken = data.data.accessToken;
      setIframeSrc(`https://app.econtractid.com/ho-so?at=${accessToken}`);
    }
    getToken();
  }, []);

  if (!iframeSrc) return <div>Đang tải...</div>;

  return (
    <iframe
      id="econtract-frame"
      src={iframeSrc}
      width="100%"
      height="800px"
      frameBorder="0"
      style={{
        border: "1px solid #ddd",
        borderRadius: "8px",
        background: "#fff",
      }}
      allow="clipboard-read; clipboard-write"
    />
  );
}

export default EcontractEmbed;
```

### Vue.js

```vue theme={null}
<template>
  <iframe
    v-if="iframeSrc"
    id="econtract-frame"
    :src="iframeSrc"
    width="100%"
    height="800px"
    frameborder="0"
    style="border: 1px solid #ddd; border-radius: 8px; background: #fff;"
    allow="clipboard-read; clipboard-write"
  />
  <div v-else>Đang tải...</div>
</template>

<script setup>
import { ref, onMounted } from "vue";

const iframeSrc = ref("");

onMounted(async () => {
  const response = await fetch("https://api.econtractid.vn/api/auth/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      username: "user@example.com",
      password: "your_password",
    }),
  });
  const data = await response.json();
  const accessToken = data.data.accessToken;
  iframeSrc.value = `https://app.econtractid.com/ho-so?at=${accessToken}`;
});
</script>
```

***

## Xử lý sự cố

| Vấn đề                              | Nguyên nhân                     | Giải pháp                                                   |
| ----------------------------------- | ------------------------------- | ----------------------------------------------------------- |
| Iframe hiển thị trang login         | Token hết hạn hoặc không hợp lệ | Kiểm tra lại token, gọi refresh hoặc login lại              |
| Lỗi `Refused to display in a frame` | Domain chưa được đăng ký        | Liên hệ hỗ trợ để đăng ký domain                            |
| Iframe trắng trống                  | CSP chặn iframe                 | Thêm `frame-src` vào CSP của website                        |
| Không ký được bằng USB Token        | Iframe chặn plugin              | Sử dụng phương thức ký Cloud HSM hoặc ký ảnh thay thế       |
| Token hết hạn liên tục              | Không có cơ chế refresh         | Triển khai auto-refresh token (xem mục Xử lý Token hết hạn) |

***

## Hỗ trợ

Nếu gặp vấn đề khi tích hợp, liên hệ đội ngũ hỗ trợ:

* **Email:** [support@econtractid.vn](mailto:support@econtractid.vn)
* **Điện thoại:** Liên hệ qua kênh hỗ trợ kỹ thuật
