Перейти к основному содержимому

API-контракты

Endpoint coverage

api-map.md является endpoint registry. Этот файл не дублирует полный DTO для каждой CRUD-строки; все endpoints из api-map.md покрываются platform envelope, pagination, error и idempotency rules плюс DTO/feature docs из таблицы.

API map sectionEndpoint rows coveredContract source
Accounts/accounts*Account, Timeline; features/user-card.md
Sales/leads*, /service/leads/from-storefront, /deals*, /orders*Lead from storefront, SourceRef, common list/mutation DTO rules; features/user-card.md
Billing/invoices*, /payments*, /refunds, /adjustments, /accounts/{accountId}/balanceInvoice, Payment webhook, Refund, Adjustment; features/billing.md
Entitlements/products*, /price-plans, /entitlements*Entitlement, MoneyDto, SourceRef; features/billing.md
Support and interactions/interactions*, /tickets*, /notesTimeline, common envelope/list DTO rules; features/user-card.md
Payroll/teacher-rates*, /teacher-payouts*, /me/teacher-payoutsPayroll; features/teacher-payroll.md
Admin/sync-logs, /audit-logs, /exports, /settingscommon envelope/error/idempotency rules; features/admin.md

Общие DTO

type PaginationQuery = {
page?: number;
perPage?: number;
sort?: string;
};

type MoneyDto = {
amount: string;
currency: 'RUB';
};

type SourceRef = {
domain: 'storefront' | 'identity' | 'lms' | 'competitions' | 'task-bank' | 'management' | 'crm' | 'payment_provider';
type: string;
id: string;
};

type ApiEnvelope<TData, TMeta = Record<string, unknown>> = {
data: TData | null;
meta?: TMeta;
error?: {
code: string;
message: string;
details?: Record<string, unknown>;
};
};

Account

type CrmAccountDto = {
id: string;
type: 'family' | 'person' | 'organization' | 'lead';
displayName: string;
status: 'new' | 'active' | 'paused' | 'archived' | 'blocked';
ownerUserId?: string;
riskFlags: string[];
};

type CreateAccountRequest = {
type: CrmAccountDto['type'];
displayName: string;
ownerUserId?: string;
initialContact?: {
type: 'email' | 'phone' | 'messenger';
value: string;
};
};

Validation:

  • displayName обязателен;
  • contact нормализуется;
  • duplicate candidates возвращаются как warning, а не скрыто объединяются.

Lead from storefront

type CreateLeadFromStorefrontRequest = {
storefrontSubmissionId: string;
formKey: string;
pageId?: string;
campaign?: Record<string, string>;
contact: {
name?: string;
email?: string;
phone?: string;
};
payload?: Record<string, unknown>;
};

type CreateLeadResponse = {
leadId: string;
accountId: string;
duplicateOfLeadId?: string;
};

Rules:

  • service endpoint требует service scope;
  • idempotency key: storefront:{submissionId};
  • payload минимизируется и не хранит лишнюю PII.

Invoice

type CreateInvoiceRequest = {
accountId: string;
orderId?: string;
dueAt?: string;
items: Array<{
title: string;
productRef?: string;
amount: MoneyDto;
metadata?: Record<string, unknown>;
}>;
};

type InvoiceDto = {
id: string;
accountId: string;
orderId?: string;
status: 'draft' | 'issued' | 'partially_paid' | 'paid' | 'overdue' | 'cancelled' | 'void';
amountTotal: MoneyDto;
amountPaid: MoneyDto;
dueAt?: string;
issuedAt?: string;
};

Validation:

  • amount > 0;
  • issued invoice нельзя менять без void + new invoice или adjustment;
  • issue action пишет audit.

Payment webhook

type PaymentWebhookRequest = {
providerEventId: string;
providerPaymentId: string;
invoiceId?: string;
accountId?: string;
status: 'pending' | 'succeeded' | 'failed' | 'refunded' | 'disputed';
amount: MoneyDto;
paidAt?: string;
rawPayload: Record<string, unknown>;
};

type PaymentWebhookResponse = {
accepted: true;
paymentId?: string;
ignoredDuplicate?: boolean;
};

Rules:

  • signature проверяется до DTO processing;
  • duplicate webhook возвращает success без повторной операции;
  • successful payment создаёт payment, balance entry и может обновить invoice.

Refund

type CreateRefundRequest = {
paymentId: string;
amount: MoneyDto;
reason: string;
};

type RefundDto = {
id: string;
paymentId: string;
status: 'requested' | 'approved' | 'sent' | 'succeeded' | 'failed' | 'cancelled';
amount: MoneyDto;
reason: string;
};

Validation:

  • amount <= refundable amount;
  • reason обязателен;
  • approval требует crm.refunds.manage;
  • succeeded refund создаёт balance entry.

Adjustment

type CreateAdjustmentRequest = {
accountId: string;
type: 'adjustment_credit' | 'adjustment_debit' | 'writeoff';
amount: MoneyDto;
reason: string;
source?: SourceRef;
};

Rules:

  • reason обязателен;
  • adjustment не меняет payment/refund;
  • создаёт balance entry и audit.

Entitlement

type CommercialSubjectRef = {
type: 'student_profile' | 'user' | 'family_group' | 'organization' | 'manual';
ref?: string;
};

type EnrollmentSubjectRef = {
type: 'student_profile' | 'user' | 'organization_student' | 'manual';
ref: string;
};

type CreateEntitlementRequest = {
accountId: string;
purchaserUserId?: string;
beneficiary: CommercialSubjectRef;
enrollmentSubject?: EnrollmentSubjectRef;
productRef: string;
targetDomain: 'lms' | 'competitions' | 'task-bank';
targetRef?: string;
source: {
type: 'invoice' | 'payment' | 'order' | 'manual';
id: string;
};
startsAt?: string;
endsAt?: string;
};

type EntitlementDto = {
id: string;
accountId: string;
purchaserUserId?: string;
beneficiary: CommercialSubjectRef;
enrollmentSubject?: EnrollmentSubjectRef;
productRef: string;
targetDomain: 'lms' | 'competitions' | 'task-bank';
targetRef?: string;
status: 'pending' | 'active' | 'suspended' | 'expired' | 'revoked';
startsAt?: string;
endsAt?: string;
};

Payroll

type TeacherPayoutDto = {
id: string;
teacherUserId: string;
periodStart: string;
periodEnd: string;
status: 'draft' | 'calculated' | 'review' | 'approved' | 'paid' | 'cancelled';
amountTotal: MoneyDto;
approvedAt?: string;
paidAt?: string;
};

Timeline

type TimelineItemDto = {
id: string;
type: 'lead' | 'deal' | 'invoice' | 'payment' | 'refund' | 'entitlement' | 'interaction' | 'ticket' | 'note';
occurredAt: string;
title: string;
source?: SourceRef;
visibility: 'default' | 'financial' | 'restricted';
};