Chaindoc logoChaindoc

Chaindoc SDKs

Chaindoc cung cấp hai SDK TypeScript: Server SDK cho backend (Node.js) và Embed SDK để tích hợp giao diện ký vào web app. Cả hai đều có định nghĩa type đầy đủ, zero runtime dependencies, hoạt động với mọi framework.

Các SDK có sẵn

Xem hướng dẫn cài đặt để thiết lập npm và cấu hình framework cụ thể (biến môi trường, providers, v.v.).

  • Server SDK (@chaindoc_io/server-sdk) - Tích hợp backend cho Node.js 18+
  • Embed SDK (@chaindoc_io/embed-sdk) - Giao diện ký frontend cho ứng dụng web
  • Python SDK - Sắp ra mắt
  • PHP SDK - Sắp ra mắt

Server SDK

Server SDK wrap REST API trong interface Node.js type-safe. Bạn dùng nó để quản lý tài liệu, tạo yêu cầu ký, xử lý upload file, và kích hoạt xác minh blockchain.

Cài đặt

npm install @chaindoc_io/server-sdk

Bắt đầu nhanh

server.ts
import { Chaindoc } from '@chaindoc_io/server-sdk';
import { readFile } from 'fs/promises';

// 1. Khởi tạo SDK
const chaindoc = new Chaindoc({
  secretKey: process.env.CHAINDOC_SECRET_KEY!,
});

// 2. Upload file tài liệu
const buffer = await readFile('./contract.pdf');
const file = new Blob([buffer], { type: 'application/pdf' });
const { media } = await chaindoc.media.upload([file]);

// 3. Tạo tài liệu
const doc = await chaindoc.documents.create({
  name: 'Hợp đồng dịch vụ',
  description: 'Hợp đồng tư vấn',
  media: media[0],
  status: 'published', // Kích hoạt xác minh blockchain
  hashtags: ['#contract', '#2024'],
  meta: [{ key: 'client', value: 'Acme Corp' }],
});

// 4. Tạo yêu cầu ký
const sigRequest = await chaindoc.signatures.createRequest({
  versionId: doc.document.versions[0].uuid,
  recipients: [{ email: 'signer@example.com' }],
  deadline: new Date('2024-12-31'),
  embeddedFlow: true,
});

// 5. Tạo session cho frontend SDK
const session = await chaindoc.embedded.createSession({
  email: 'signer@example.com',
  metadata: {
    documentId: doc.documentId,
    signatureRequestId: sigRequest.signatureRequest.uuid,
  },
});

console.log('Session ID:', session.sessionId);

Tích hợp Express.js

server.ts
import express from 'express';
import { Chaindoc, ChaindocError } from '@chaindoc_io/server-sdk';
import multer from 'multer';

const app = express();
const upload = multer({ storage: multer.memoryStorage() });

const chaindoc = new Chaindoc({
  secretKey: process.env.CHAINDOC_SECRET_KEY!,
});

// Upload và tạo tài liệu
app.post('/api/documents', upload.single('file'), async (req, res) => {
  try {
    const file = new Blob([req.file!.buffer], { type: req.file!.mimetype });
    const { media } = await chaindoc.media.upload([file]);

    const doc = await chaindoc.documents.create({
      name: req.body.name,
      description: req.body.description || '',
      media: media[0],
      status: 'published',
      hashtags: req.body.hashtags || [],
      meta: req.body.meta || [],
    });

    res.json({ documentId: doc.documentId });
  } catch (error) {
    if (error instanceof ChaindocError) {
      res.status(error.statusCode || 500).json({ error: error.message });
    } else {
      res.status(500).json({ error: 'Internal server error' });
    }
  }
});

// Tạo session embedded cho người ký
app.post('/api/signing/session', async (req, res) => {
  try {
    const { email, documentId, signatureRequestId } = req.body;

    const session = await chaindoc.embedded.createSession({
      email,
      metadata: { documentId, signatureRequestId },
    });

    res.json({ sessionId: session.sessionId });
  } catch (error) {
    if (error instanceof ChaindocError) {
      res.status(error.statusCode || 500).json({ error: error.message });
    }
  }
});

app.listen(3000);

Next.js API Routes

app/api/signing/create-session/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Chaindoc, ChaindocError } from '@chaindoc_io/server-sdk';

const chaindoc = new Chaindoc({
  secretKey: process.env.CHAINDOC_SECRET_KEY!,
});

export async function POST(request: NextRequest) {
  try {
    const { email, documentId, signatureRequestId } = await request.json();

    const session = await chaindoc.embedded.createSession({
      email,
      metadata: { documentId, signatureRequestId },
    });

    return NextResponse.json({
      sessionId: session.sessionId,
      expiresAt: session.expiresAt,
    });
  } catch (error) {
    if (error instanceof ChaindocError) {
      return NextResponse.json(
        { error: error.message },
        { status: error.statusCode || 500 }
      );
    }
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

Xử lý lỗi

Luôn wrap các API call trong try-catch và xử lý ChaindocError cụ thể:

terminal
import { ChaindocError } from '@chaindoc_io/server-sdk';

try {
  const doc = await chaindoc.documents.create({ /* ... */ });
} catch (error) {
  if (error instanceof ChaindocError) {
    console.error('API Error:', error.message);
    console.error('Status Code:', error.statusCode);
    
    switch (error.statusCode) {
      case 400:
        // Bad request - kiểm tra tham số
        break;
      case 401:
        // Unauthorized - kiểm tra API key
        break;
      case 404:
        // Not found
        break;
      case 429:
        // Rate limited - SDK tự động retry
        break;
    }
  }
}

Embed SDK

Embed SDK cho phép hiển thị giao diện ký Chaindoc trong ứng dụng web. Nó xử lý iframe, xác minh OTP, và giao tiếp giữa app và Chaindoc. Người dùng ký tài liệu mà không rời khỏi site.

Cài đặt

npm install @chaindoc_io/embed-sdk

Cách sử dụng cơ bản

terminal
import { ChaindocEmbed } from '@chaindoc_io/embed-sdk';

// 1. Khởi tạo SDK (một lần mỗi trang)
const chaindoc = new ChaindocEmbed({
  publicKey: 'pk_live_xxxxxxxxxxxxx',
  environment: 'production',
});

// 2. Lấy session từ backend
const response = await fetch('/api/signing/create-session', {
  method: 'POST',
  body: JSON.stringify({ documentId, signerEmail }),
});
const { sessionId } = await response.json();

// 3. Mở flow ký
const instance = chaindoc.openSignatureFlow({
  sessionId,
  
  onReady: () => {
    console.log('Giao diện ký đã load');
  },
  
  onSuccess: (data) => {
    console.log('Tài liệu đã ký:', data.signatureId);
    instance.close();
  },
  
  onError: (error) => {
    console.error('Ký thất bại:', error.code, error.message);
  },
  
  onCancel: () => {
    console.log('Người dùng hủy');
    instance.close();
  },
});

Tích hợp React

components/SignButton.tsx
import { useCallback, useRef, useEffect } from 'react';
import { ChaindocEmbed, EmbedInstance } from '@chaindoc_io/embed-sdk';

function SignButton({ sessionId }: { sessionId: string }) {
  const sdkRef = useRef<ChaindocEmbed | null>(null);
  const instanceRef = useRef<EmbedInstance | null>(null);

  useEffect(() => {
    sdkRef.current = new ChaindocEmbed({
      publicKey: process.env.REACT_APP_CHAINDOC_PUBLIC_KEY!,
    });

    return () => {
      sdkRef.current?.destroy();
    };
  }, []);

  const handleSign = useCallback(() => {
    if (!sdkRef.current) return;

    instanceRef.current = sdkRef.current.openSignatureFlow({
      sessionId,
      onSuccess: (data) => {
        console.log('Đã ký!', data.signatureId);
        instanceRef.current?.close();
      },
      onCancel: () => {
        instanceRef.current?.close();
      },
    });
  }, [sessionId]);

  return <button onClick={handleSign}>Ký Tài Liệu</button>;
}

Tích hợp Vue 3

SignButton.vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { ChaindocEmbed, type EmbedInstance } from '@chaindoc_io/embed-sdk';

const props = defineProps<{ sessionId: string }>();

let sdk: ChaindocEmbed | null = null;
let instance: EmbedInstance | null = null;

onMounted(() => {
  sdk = new ChaindocEmbed({
    publicKey: import.meta.env.VITE_CHAINDOC_PUBLIC_KEY,
  });
});

onUnmounted(() => {
  sdk?.destroy();
});

function openSignature() {
  if (!sdk) return;
  
  instance = sdk.openSignatureFlow({
    sessionId: props.sessionId,
    onSuccess: (data) => {
      console.log('Đã ký!', data.signatureId);
      instance?.close();
    },
    onCancel: () => {
      instance?.close();
    },
  });
}
</script>

<template>
  <button @click="openSignature">Ký Tài Liệu</button>
</template>

Chế độ inline

Thay vì modal, nhúng giao diện ký trực tiếp vào trang:

terminal
const instance = chaindoc.openSignatureFlow({
  sessionId,
  mode: 'inline',
  container: document.getElementById('signature-container'),
  
  onSuccess: (data) => {
    console.log('Đã ký!');
  },
});

Tùy chỉnh giao diện

Tùy chỉnh appearance với theme sáng hoặc tối:

terminal
const instance = chaindoc.openSignatureFlow({
  sessionId,
  theme: 'dark',
  // ... các tùy chọn khác
});

// Thay đổi theme động
instance.changeTheme('light');

Ví dụ workflow đầy đủ

Đây là cách kết hợp cả hai SDK: Server SDK trên backend để tạo tài liệu và sessions, Embed SDK trên frontend để hiển thị UI ký.

1Backend: Upload Tài LiệuDùng Server SDK để upload file và tạo tài liệu

2Backend: Tạo Yêu Cầu KýTạo yêu cầu ký với embedded flow được bật

3Backend: Tạo SessionTạo embedded session cho mỗi người ký

4Frontend: Khởi Tạo Embed SDKKhởi tạo SDK với public key

5Frontend: Mở Flow KýMở giao diện ký với session ID

6Frontend: Xử Lý Thành CôngXử lý tài liệu đã ký và cập nhật UI

// server.ts
import { Chaindoc } from '@chaindoc_io/server-sdk';

const chaindoc = new Chaindoc({
  secretKey: process.env.CHAINDOC_SECRET_KEY!,
});

// Upload & tạo tài liệu
const { media } = await chaindoc.media.upload([pdfFile]);
const doc = await chaindoc.documents.create({
  name: 'Contract',
  description: 'Service agreement',
  media: media[0],
  status: 'published',
  hashtags: ['#contract'],
  meta: [],
});

// Tạo yêu cầu ký
const sigRequest = await chaindoc.signatures.createRequest({
  versionId: doc.document.versions[0].uuid,
  recipients: [{ email: 'signer@example.com' }],
  deadline: new Date('2024-12-31'),
  embeddedFlow: true,
});

// Tạo session
const session = await chaindoc.embedded.createSession({
  email: 'signer@example.com',
  metadata: {
    documentId: doc.documentId,
    signatureRequestId: sigRequest.signatureRequest.uuid,
  },
});

// Trả về sessionId cho frontend
res.json({ sessionId: session.sessionId });

Best practices

  • Khởi tạo SDK một lần mỗi page/component lifecycle
  • Luôn destroy SDK instance khi component unmount
  • Xử lý tất cả callback events (onSuccess, onError, onCancel)
  • Lưu API keys trong environment variables
  • Dùng TypeScript cho type safety tốt hơn
  • Triển khai error handling đúng cách với ChaindocError
  • Test với sandbox keys trước khi deploy production

Cấu hình môi trường

// Backend
const chaindoc = new Chaindoc({
  secretKey: 'sk_live_xxxxx',
});

// Frontend
const embed = new ChaindocEmbed({
  publicKey: 'pk_live_xxxxx',
  environment: 'production',
});

Tiếp theo

  • Cài đặt — Thiết lập npm, cấu hình env, và framework-specific providers
  • Tài liệu API — Tài liệu tham khảo endpoint REST đầy đủ
  • Webhooks — Thông báo sự kiện real-time cho backend
  • Bắt đầu nhanh — Gửi chữ ký đầu tiên trong 10 phút
  • Bảo mật — Quản lý API key và production hardening