Перейти к содержанию

Пайщики

Пайщик - это пользователь, у которого есть принадлежащий ему объект аккаунта пайщика ParticipantAccount, содержащий информацию о его членстве в кооперативе. Аккаунт пайщика выдаётся кооперативом, при прохождении полной процедуры регистрации пайщика, или при добавлении ранее зарегистрированного пайщика в систему.

Регистрация пайщика

Аккаунт пайщика ParticipantAccount выдаётся пользователю смарт-контрактом при прохождении полной процедуры регистрации пайщика. После завершения процедуры аккаунт пайщика будет доступен в объекте Account -> ParticipantAccount при получении аккаунта пользователя.

sequenceDiagram
    autonumber
    participant U as Пользователь
    participant SDK as SDK
    participant SYS as Блокчейн
    participant GOV as Совет Кооператива

    Note over U,SYS: Общий процесс регистрации пайщика

    U->>SDK: Регистрация аккаунта
    SDK->>SYS: Проверка мажоритарности кооператива

    alt Кооператив мажоритарен
        U->>SDK: Выбор кооперативного участка
    else
        Note over U,SYS: Участок не требуется
    end

    U->>SDK: Предгенерация 4х соглашений и заявления на вступление
    SDK->>U: Отображение документов
    U->>SDK: Приём собственноручной подписи
    U->>SDK: Генерация заявления с собственнручной подписью
    SDK->>U: Заявление с собственноручной подписью
    U->>SDK: Цифровая подпись пакета документов

    SDK->>SYS: Отправка пакета документов
    U->>SDK: Оплата вступительного и паевого взноса
    SDK->>SYS: Подтверждение оплаты

    SYS->>GOV: Вынесение вопроса в повестку
    GOV->>SYS: Решение совета
    SYS->>U: Уведомление о принятии в кооператив

1. Регистрация аккаунта

Пользователь заполняет форму и регистрирует аккаунт провайдера, как это описано в разделе Регистрация Аккаунта.

2. Выбор кооперативного участка

Пользователю необходимо выбрать кооперативный участок в том случае, если кооператив перешёл на мажоритарную систему управления, организовав >= 3 кооперативных участков в своём кооперативе. В случае, если кооператив - мажоритарен, пользователю потребуется предоставить выбор идентификатора кооперативного участка, председателю которого он делегирует свой голос на общих собраниях.

Для того, чтобы выяснить, является ли кооператив мажоритарным, необходимо извлечь объект системной информации SystemInfo, обратиться в нём к CooperativeOperatorAccount, и извлечь булево свойство is_branched.

Значение true в is_branched означает, что кооператив - мажоритарный, и пайщику при регистрации будет необходимо предоставить идентификатор имени кооперативного участка, иначе, регистрация пайщика будет отклонена. Извлечение списка кооперативных участков описано в разделе Получить Кооперативные Участки. Для отправки последующей отправки документов на регистрацию пайщика, потребуется выбрать из списка параметр braname - имя аккаунта кооперативного участка, при котором регистрируется пайщик.

3. Предгенерация документов

Для того, чтобы сгенерировать пайщику персональное заявление на вступление и все сопутствующие соглашения, и показать их ему в том виде, в котором он будет подписывать, необходимо произвести предгенерацию пакета документов.

Заявление на вступление

🛠️ SDK: Mutations.Participants.GenerateApplication | 🔗 GraphQL API: Mutation.generateParticipantApplication

Мутация для генерации заявления на вступление пайщика в кооператив вызывается с параметром signature, который может быть пустым для пред-генерации, или содержать png изображение подписи в кодировке base64 для основной генерации. Массив links будет пустым для пред-генерации, но обязательно должен содержать хэши 4-х соглашений для основной генерации.

⚠️ Описание для Mutations.Participants.GenerateApplication не найдено

⚠️ Mutations.Decisions.GenerateApplication не найден

Соглашение о ЦПП "Цифровой Кошелёк"

🛠️ SDK: Mutations.Agreements.GenerateWalletAgreement | 🔗 GraphQL API: Mutation.generateWalletAgreement

Сгенерировать документ соглашения о целевой потребительской программе "Цифровой Кошелёк"

Соглашение о простой электронной подписи

🛠️ SDK: Mutations.Agreements.GenerateSignatureAgreement | 🔗 GraphQL API: Mutation.generateSignatureAgreement

Сгенерировать документ соглашения о порядка и правилах использования простой электронной подписи.

Пользовательское соглашение

🛠️ SDK: Mutations.Agreements.GenerateUserAgreement | 🔗 GraphQL API: Mutation.generateUserAgreement

Сгенерировать документ пользовательского соглашения.

Согласие с политикой конфиденциальности

🛠️ SDK: Mutations.Agreements.GeneratePrivacyAgreement | 🔗 GraphQL API: Mutation.generatePrivacyAgreement

Сгенерировать документ согласия с политикой конфиденциальности.

В результате предгенерации будет получены объекты с документами, которые могут быть продемонстрированы пользователю перед запросом его собственноручной подписи. Кроме того, предгенерация необходима, чтобы криптографически связать заявление на вступление с соглашениями на этапе основной генерации после получения собственноручной подписи.

4. Собственноручная подпись

Для получения собственноручной подписи пользователя используется вспомогательный набор методов 🛠️ SDK: Methods.Canvas. Для начала процесса получения собственноручной подписи необходимо создать область для подписи с помощью Canvas.createCanvas(), передав id элемента div, в котором будет размещен canvas.

import { Methods } from '@coopenomics/sdk'

//создаём canvas в контейнере signature-area
const container = document.getElementById('signature-area') as HTMLElement
const { canvas, ctx } = Methods.Canvas.createCanvas(container, 500, 300)

После чего, необходимо подписаться на события и вызывать методы для рисования:

//создаём объект состояния
const drawingState: Methods.Canvas.DrawingState = { drawing: false, lastX: 0, lastY: 0 }

//подписка на нажатие
canvas.addEventListener('mousedown', (e) => Methods.Canvas.startDrawing(e, canvas, drawingState))
//подписка на движение
canvas.addEventListener('mousemove', (e) => Methods.Canvas.draw(e, canvas, ctx, drawingState))
//подписка на отжатие
canvas.addEventListener('mouseup', () => Methods.Canvas.endDrawing(drawingState))

После завершения процесса собственноручной подписи, необходимо извлечь её методом 🛠️ SDK: Methods.Canvas.getSignature и, при необходимости, очистить область с 🛠️ SDK: Methods.Canvas.clearCanvas.

const signature = Methods.Canvas.getSignature(canvas)
Methods.Canvas.clearCanvas(canvas, ctx)
//console.log(signature) // "data:image/png;base64,iVBORw0..."

В результате, у вас будет строковое представление изображения собственноручной подписи пользователя, которое теперь необходимо использовать на основной генерации заявления на вступление в кооператив.

5. Генерация заявления

🛠️ SDK: Mutations.Participants.GenerateApplication | 🔗 GraphQL API: Mutation.generateApplication

Основная генерация заявления производится аналогично предгенерации, однако, в signature необходимо передать полученную ранее собственноручную подпись, а в массиве links указать хэши всех ранее сгенерированных соглашений.

const variables: Mutations.Participants.GenerateApplication.IInput = {
    data: {
      singature: <полученная ранее строка с изображением подписи в png/base64>,
      links: [participantApplication.hash, signatureAgreement.hash, userAgreement.hash, privacyAgreement.hash]
      username: <имя аккаунта пользователя>,
      coopname: <имя аккаунта кооператива>
  }

const { [Mutations.Participants.GenerateApplication.name]: participantApplication2 } = await client.Mutation(
  Mutations.Participants.GenerateApplication.mutation,
  {
    variables,
  }
);

В результате исполнения мутации будет возвращён объект заявления, который сгенерирован с применением его собственноручной подписи. Все документы будут сохранены в MONO и криптографически связаны с пользователем, между собой, и его собственноручной подписью.

5. Подпись документов

Теперь у нас есть полный пакет документов: 4 соглашения с этапа пред-генерации и 1 заявление с предыдущего этапа, которое включает собственноручную подпись пользователя и хэш-ссылки на соглашения.

Теперь необходимо подписать все эти 5 документов. Цифровая подпись осуществляется методом класса 🛠️ Wallet.signDocument, активный инстанс которого есть в SDK-клиенте. Вы можете использовать client.Wallet.signDocument(doc) на действующем экземпляре клиента или создать новый экземпляр класса Wallet, импортировав его из SDK:

//или используем существующий инстанс кошелька в клиенте
client.Wallet.setWif(username, wif)

//последовательно подписываем все документы:
const signedSignatureAgreement = client.Wallet.signDocument(signatureAgreement)
const signedUserAgreement = client.Wallet.signDocument(userAgreement)
const signedWalletAgreement = client.Wallet.signDocument(walletAgreement)
const signedPrivacyAgreement = client.Wallet.signDocument(privacyAgreement)
const signedParticipantApplication = client.Wallet.signDocument(participantApplication2)

Для успешной подписи документов в SDK-клиенте должен быть установлен приватный ключ. В результате, каждый документ будет подписан подготовлен к отправке приведением к необходимому формату.

6. Отправка документов

🛠️ SDK: Mutations.Participants.RegisterParticipant | 🔗 GraphQL API: Mutation.registerParticipant

Регистрация пайщика производится мутацией RegisterParticipant с передачей подписанного заявления и соглашений. Отправленный пакет документов сохраняется в MONO для пользователя и ожидает поступления оплаты взносов перед отправкой в совет на голосование.

import { Mutations } from '@coopenomics/sdk'; 

const variables = {
  data: {
    privacy_agreement: {
      hash: <string>; // Хэш документа
      meta: {
        block_num: <number>; // Номер блока, на котором был создан документ
        coopname: <string>; // Название кооператива, связанное с документом
        created_at: <string>; // Дата и время создания документа
        generator: <string>; // Имя генератора, использованного для создания документа
        lang: <string>; // Язык документа
        links: <string[]>; // Ссылки, связанные с документом
        registry_id: <number>; // ID документа в реестре
        timezone: <string>; // Часовой пояс, в котором был создан документ
        title: <string>; // Название документа
        username: <string>; // Имя пользователя, создавшего документ
        version: <string>; // Версия генератора, использованного для создания документа
      };
      public_key: <string>; // Публичный ключ документа
      signature: <string>; // Подпись документа
    };
    signature_agreement: {
      hash: <string>; // Хэш документа
      meta: {
        block_num: <number>; // Номер блока, на котором был создан документ
        coopname: <string>; // Название кооператива, связанное с документом
        created_at: <string>; // Дата и время создания документа
        generator: <string>; // Имя генератора, использованного для создания документа
        lang: <string>; // Язык документа
        links: <string[]>; // Ссылки, связанные с документом
        registry_id: <number>; // ID документа в реестре
        timezone: <string>; // Часовой пояс, в котором был создан документ
        title: <string>; // Название документа
        username: <string>; // Имя пользователя, создавшего документ
        version: <string>; // Версия генератора, использованного для создания документа
      };
      public_key: <string>; // Публичный ключ документа
      signature: <string>; // Подпись документа
    };
    statement: {
      hash: <string>; // Хэш документа
      meta: {
        block_num: <number>; // Номер блока, на котором был создан документ
        coopname: <string>; // Название кооператива, связанное с документом
        created_at: <string>; // Дата и время создания документа
        generator: <string>; // Имя генератора, использованного для создания документа
        lang: <string>; // Язык документа
        links: <string[]>; // Ссылки, связанные с документом
        registry_id: <number>; // ID документа в реестре
        signature: <string>; // Изображение собственноручной подписи (base-64)
        skip_save: <boolean>; // Флаг пропуска сохранения документа (используется для предварительной генерации и демонстрации пользователю)
        timezone: <string>; // Часовой пояс, в котором был создан документ
        title: <string>; // Название документа
        username: <string>; // Имя пользователя, создавшего документ
        version: <string>; // Версия генератора, использованного для создания документа
      };
      public_key: <string>; // Публичный ключ документа
      signature: <string>; // Подпись документа
    };
    user_agreement: {
      hash: <string>; // Хэш документа
      meta: {
        block_num: <number>; // Номер блока, на котором был создан документ
        coopname: <string>; // Название кооператива, связанное с документом
        created_at: <string>; // Дата и время создания документа
        generator: <string>; // Имя генератора, использованного для создания документа
        lang: <string>; // Язык документа
        links: <string[]>; // Ссылки, связанные с документом
        registry_id: <number>; // ID документа в реестре
        timezone: <string>; // Часовой пояс, в котором был создан документ
        title: <string>; // Название документа
        username: <string>; // Имя пользователя, создавшего документ
        version: <string>; // Версия генератора, использованного для создания документа
      };
      public_key: <string>; // Публичный ключ документа
      signature: <string>; // Подпись документа
    };
    username: <string>; // Имя аккаунта пайщика
    wallet_agreement: {
      hash: <string>; // Хэш документа
      meta: {
        block_num: <number>; // Номер блока, на котором был создан документ
        coopname: <string>; // Название кооператива, связанное с документом
        created_at: <string>; // Дата и время создания документа
        generator: <string>; // Имя генератора, использованного для создания документа
        lang: <string>; // Язык документа
        links: <string[]>; // Ссылки, связанные с документом
        registry_id: <number>; // ID документа в реестре
        timezone: <string>; // Часовой пояс, в котором был создан документ
        title: <string>; // Название документа
        username: <string>; // Имя пользователя, создавшего документ
        version: <string>; // Версия генератора, использованного для создания документа
      };
      public_key: <string>; // Публичный ключ документа
      signature: <string>; // Подпись документа
    };
  };
};

const { [Mutations.Participants.RegisterParticipant.name]: result } = await client.Mutation(
  Mutations.Participants.RegisterParticipant.mutation,
  { variables }
);

7. Оплата взносов

🛠️ SDK: Mutations.Payments.CreateInitial | 🔗 GraphQL API: Mutation.createInitial

Перед тем, как принятые документы будут отправлены в повестку совета на голосование, новому пайщику необходимо оплатить вступительный и минимальный паевый взносы (одним платежом).

⚠️ Описание для Mutations.Payments.CreateInitial не найдено

Подробнее о доступных платежных провайдерах и управлении платежами смотри раздел Платежи. После получения подтверждения о поступлении платежа (статус платежа PAID), пользователю будет зарегистрирован аккаунт в блокчейне, а пакет документов пайщика поставлен в повестку голосования совета кооператива.

8. Получение решения совета

После поступления платежа вопрос добавляется в повестку голосования совета, где каждый член совета голосует за решение о приёме нового пайщика, а председатель - утверждает это решение. Процесс принятия решения советом описан в разделе Решения.

После того, как совет примет решение, оно будет автоматически исполнено: обновятся все реестры, будут созданы счета, открыты кошельки и т.д., а пользователь получит уведомление на электронную почту о том, что он принят в кооператив. С этого момента он является пайщиком, обладает объектом ParticipantAccount в Account, и может участвовать во всех целевых потребительских программах кооператива.

Добавление пайщика

🛠️ SDK: Mutations.Participants.AddParticipant | 🔗 GraphQL API: Mutation.addParticipant

Иногда необходимо добавить в цифровую систему уже действующего пайщика кооператива. В таком случае, нам не нужно проводить регистрацию пайщика по полному процессу, а достаточно сразу добавить его в систему.

Данная мутация должна использоваться ТОГДА И ТОЛЬКО ТОГДА, когда решение совета кооператива уже принято и оформлено в бумажном протоколе, а пайщик фактически совершил взносы в кооператив. Т.е. юридически пользователь уже является пайщиком кооператива и его необходимо только добавить в систему, переведя взаимоотношения с ним в цифровую форму.

  import { Mutations, Zeus} from '@coopenomics/sdk'

  const variables: Mutations.Participants.AddParticipant.IInput = {
      data: {
        created_at: "<фактическая дата регистрации пайщика>",
        email: "<email пайщика>",
        initial: <внесенный вступительного взнос (100.0000 RUB)>,
        minimum: <внесенный мин. паевый взнос (300.0000 RUB)>,

        //флаг распределения вступительного взноса по фондам указывает то, 
        // следует ли системе добавить вступительный взнос в фонд для дальнейшего списания (true), 
        // или он был ранее добавлен и списан на хозяйственные расходы (false).
        spread_initial: false | true, 
        //тип добавляемого аккаунта - физлицо, юрлицо или организация
        type: <Zeus.AccountType.Individual | Zeus.AccountType.Entrepreneur | Zeus.AccountType.Organization>

        //передать один из объектов с данными      
        individual_data: {<объект с данными физлица> },
        organization_data: {<объект с данными ИП> },
        entrepreneur_data: {<объект с данными организации> },  
      }
    }

  const { [Mutations.Account.AddParticipant.name]: result } = await client.Mutation(
    Mutations.Account.AddParticipant.mutation,
    {
      variables,
    }
  );

/** Добавить активного пайщика, который вступил в кооператив, не используя платформу (заполнив заявление собственноручно, оплатив вступительный и минимальный паевый взносы, и получив протокол решения совета)

В результате исполнения мутации, пайщик будет добавлен в кооператив, а ему на электронную почту выслано приглашение на получение приватного ключа доступа к системе. Воспользовавшись им, он сможет войти в систему.

Однако, для получения полной функциональности, ему необходимо предоставить 4 базовых подписанных соглашения, как это описано в разделе Соглашения. Без наличия подписанных соглашений, функционал платформы для него может быть ограничен.