package m2m import ( "encoding/xml" "errors" "fmt" "regexp" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdxml" ) // Namespaces — целевые namespace из XSD M2MSchemas_260408. const ( NSTypes = "http://nsd.ru/schemas/m2m/types" NSRequest = "http://nsd.ru/schemas/m2m/request" NSDecision = "http://nsd.ru/schemas/m2m/decision" NSResponse = "http://nsd.ru/schemas/m2m/response" NSHandbook = "http://nsd.ru/schemas/m2m/handbook" NSHandbookReq = "http://nsd.ru/schemas/m2m/handbook/request" NSParticipantForm = "http://nsd.ru/schemas/m2m/participant/form" ) // ErrChoice возвращается, если в choice-типе задано не ровно одно поле. var ErrChoice = errors.New("m2m: в choice-типе должно быть задано ровно одно поле") // reDecimal16 — паттерн допустимого десятичного числа с не более чем // 16 знаков после точки. var reDecimal16 = regexp.MustCompile(`^[0-9]+(\.[0-9]{1,16})?$`) // Decimal16 — десятичное число (XSD Decimal16): неотрицательное, до 16 // знаков после точки, до 38 значащих цифр всего. Хранится строкой ради // точности и стабильности round-trip. type Decimal16 string // Validate проверяет формат и общее число цифр. func (d Decimal16) Validate() error { if d == "" { return fmt.Errorf("%w: Decimal16 пустое значение", ErrInvalid) } if !reDecimal16.MatchString(string(d)) { return fmt.Errorf("%w: Decimal16 %q не соответствует формату", ErrInvalid, string(d)) } digits := 0 for _, r := range d { if r >= '0' && r <= '9' { digits++ } } if digits > 38 { return fmt.Errorf("%w: Decimal16 %d значащих цифр, ожидается не более 38", ErrInvalid, digits) } return nil } // IdentityDocument — документ, удостоверяющий личность. type IdentityDocument struct { DocumentType IdentityDocumentCode `xml:"http://nsd.ru/schemas/m2m/types DocumentType"` DocumentSeries *IdentityDocSerial `xml:"http://nsd.ru/schemas/m2m/types DocumentSeries,omitempty"` DocumentNumber IdentityDocSerial `xml:"http://nsd.ru/schemas/m2m/types DocumentNumber"` } // Validate проверяет тип и заполненность номера документа. func (d IdentityDocument) Validate() error { if err := d.DocumentType.Validate(); err != nil { return err } if d.DocumentSeries != nil { if err := d.DocumentSeries.Validate(); err != nil { return err } } return d.DocumentNumber.Validate() } // InvestorInformation — анкета инвестора. type InvestorInformation struct { LastName string `xml:"http://nsd.ru/schemas/m2m/types LastName"` FirstName string `xml:"http://nsd.ru/schemas/m2m/types FirstName"` MiddleName string `xml:"http://nsd.ru/schemas/m2m/types MiddleName,omitempty"` IdentityDocument IdentityDocument `xml:"http://nsd.ru/schemas/m2m/types IdentityDocument"` } // Validate проверяет длину полей и документ. func (i InvestorInformation) Validate() error { if l := len(i.LastName); l < 1 || l > 50 { return fmt.Errorf("%w: LastName длина %d, ожидается 1..50", ErrInvalid, l) } if l := len(i.FirstName); l < 1 || l > 50 { return fmt.Errorf("%w: FirstName длина %d, ожидается 1..50", ErrInvalid, l) } if l := len(i.MiddleName); l > 50 { return fmt.Errorf("%w: MiddleName длина %d, ожидается не более 50", ErrInvalid, l) } return i.IdentityDocument.Validate() } // SettlementRequisites — реквизиты депозитария (содержит только ИНН). type SettlementRequisites struct { INN OrganizationINN `xml:"http://nsd.ru/schemas/m2m/types INN"` } // Validate проверяет ИНН. func (s SettlementRequisites) Validate() error { return s.INN.Validate() } // SettlementDepositoryLocation — реквизиты счёта депо. type SettlementDepositoryLocation struct { DeponentCode string `xml:"http://nsd.ru/schemas/m2m/types DeponentCode"` AccountID AccountID `xml:"http://nsd.ru/schemas/m2m/types AccountId"` SectionID string `xml:"http://nsd.ru/schemas/m2m/types SectionId"` } // Validate проверяет длины и формат счёта. func (s SettlementDepositoryLocation) Validate() error { if l := len(s.DeponentCode); l < 1 || l > 50 { return fmt.Errorf("%w: DeponentCode длина %d, ожидается 1..50", ErrInvalid, l) } if err := s.AccountID.Validate(); err != nil { return err } if l := len(s.SectionID); l < 1 || l > 50 { return fmt.Errorf("%w: SectionID длина %d, ожидается 1..50", ErrInvalid, l) } return nil } // RequestSettlementAccount — счёт в запросе перевода. type RequestSettlementAccount struct { SettlementRequisites SettlementRequisites `xml:"http://nsd.ru/schemas/m2m/types SettlementRequisites"` SettlementLocation SettlementDepositoryLocation `xml:"http://nsd.ru/schemas/m2m/types SettlementLocation"` } // Validate последовательно валидирует реквизиты и место хранения. func (a RequestSettlementAccount) Validate() error { if err := a.SettlementRequisites.Validate(); err != nil { return err } return a.SettlementLocation.Validate() } // DecisionSettlementAccount — счёт в решении (структурно совпадает с // запросом, но именован отдельно в XSD). type DecisionSettlementAccount = RequestSettlementAccount // IIAAgreementDetails — реквизиты договора ИИС. type IIAAgreementDetails struct { AgreementType IIAContractType `xml:"http://nsd.ru/schemas/m2m/types AgreementType"` AgreementNumber string `xml:"http://nsd.ru/schemas/m2m/types AgreementNumber"` AgreementDate string `xml:"http://nsd.ru/schemas/m2m/types AgreementDate"` BrokerINN OrganizationINN `xml:"http://nsd.ru/schemas/m2m/types BrokerINN"` } // Validate проверяет тип, номер договора и ИНН брокера. func (d IIAAgreementDetails) Validate() error { if err := d.AgreementType.Validate(); err != nil { return err } if l := len(d.AgreementNumber); l < 1 || l > 128 { return fmt.Errorf("%w: AgreementNumber длина %d, ожидается 1..128", ErrInvalid, l) } if d.AgreementDate == "" { return fmt.Errorf("%w: AgreementDate пуста", ErrInvalid) } return d.BrokerINN.Validate() } // FundShares — реквизиты пая инвестиционного фонда. type FundShares struct { RegNumber string `xml:"http://nsd.ru/schemas/m2m/types RegNumber"` Class string `xml:"http://nsd.ru/schemas/m2m/types Class,omitempty"` } // Validate проверяет длины полей. func (f FundShares) Validate() error { if l := len(f.RegNumber); l < 1 || l > 256 { return fmt.Errorf("%w: RegNumber длина %d, ожидается 1..256", ErrInvalid, l) } if l := len(f.Class); l > 120 { return fmt.Errorf("%w: Class длина %d, ожидается не более 120", ErrInvalid, l) } return nil } // IdentificationDetails — choice: либо рег.номер выпуска, либо ПИФ. type IdentificationDetails struct { RegNumber *string `xml:"http://nsd.ru/schemas/m2m/types RegNumber,omitempty"` FundShares *FundShares `xml:"http://nsd.ru/schemas/m2m/types FundShares,omitempty"` } // Validate проверяет, что задано ровно одно поле choice. func (i IdentificationDetails) Validate() error { count := 0 if i.RegNumber != nil { if l := len(*i.RegNumber); l > 20 { return fmt.Errorf("%w: RegNumber длина %d, ожидается не более 20", ErrInvalid, l) } count++ } if i.FundShares != nil { if err := i.FundShares.Validate(); err != nil { return err } count++ } if count != 1 { return fmt.Errorf("%w: IdentificationDetails задано %d полей", ErrChoice, count) } return nil } // SecurityDescription — описание ценной бумаги без ISIN. type SecurityDescription struct { SecurityClassification SecurityClassification `xml:"http://nsd.ru/schemas/m2m/types SecurityClassification"` SecurityCategory SecurityCategory `xml:"http://nsd.ru/schemas/m2m/types SecurityCategory"` SecurityType string `xml:"http://nsd.ru/schemas/m2m/types SecurityType,omitempty"` SecuritySeries string `xml:"http://nsd.ru/schemas/m2m/types SecuritySeries,omitempty"` IdentificationDetails IdentificationDetails `xml:"http://nsd.ru/schemas/m2m/types IdentificationDetails"` } // Validate проверяет классификацию, категорию и идентификацию. func (s SecurityDescription) Validate() error { if err := s.SecurityClassification.Validate(); err != nil { return err } if err := s.SecurityCategory.Validate(); err != nil { return err } if l := len(s.SecurityType); l > 256 { return fmt.Errorf("%w: SecurityType длина %d, ожидается не более 256", ErrInvalid, l) } return s.IdentificationDetails.Validate() } // SecurityDetails — choice: либо ISIN, либо описание ценной бумаги. type SecurityDetails struct { ISIN *ISIN `xml:"http://nsd.ru/schemas/m2m/types ISIN,omitempty"` SecurityInfo *SecurityDescription `xml:"http://nsd.ru/schemas/m2m/types SecurityInfo,omitempty"` } // Validate проверяет, что задано ровно одно поле choice. func (s SecurityDetails) Validate() error { count := 0 if s.ISIN != nil { if err := s.ISIN.Validate(); err != nil { return err } count++ } if s.SecurityInfo != nil { if err := s.SecurityInfo.Validate(); err != nil { return err } count++ } if count != 1 { return fmt.Errorf("%w: SecurityDetails задано %d полей", ErrChoice, count) } return nil } // Quantity — choice: целое или дробное количество ценных бумаг. type Quantity struct { Whole *uint64 `xml:"http://nsd.ru/schemas/m2m/types Whole,omitempty"` Fractional *Decimal16 `xml:"http://nsd.ru/schemas/m2m/types Fractional,omitempty"` } // Validate проверяет, что задано ровно одно поле choice. func (q Quantity) Validate() error { count := 0 if q.Whole != nil { if *q.Whole == 0 { return fmt.Errorf("%w: Whole должно быть положительным", ErrInvalid) } count++ } if q.Fractional != nil { if err := q.Fractional.Validate(); err != nil { return err } count++ } if count != 1 { return fmt.Errorf("%w: Quantity задано %d полей", ErrChoice, count) } return nil } // CostInfoYes — тело варианта "учёт ведётся" (DecisionYesType и // RequestYesType структурно совпадают). type CostInfoYes struct { Code DeponentCode `xml:"http://nsd.ru/schemas/m2m/types Code"` } // CostInfoNo — тело варианта "учёт не ведётся" (NoType пустой). type CostInfoNo struct{} // CostInfo — choice: учёт стоимости приобретения ведётся (Yes) или нет. type CostInfo struct { Yes *CostInfoYes `xml:"http://nsd.ru/schemas/m2m/types Yes,omitempty"` No *CostInfoNo `xml:"http://nsd.ru/schemas/m2m/types No,omitempty"` } // Validate проверяет, что задано ровно одно поле choice. func (c CostInfo) Validate() error { count := 0 if c.Yes != nil { if err := c.Yes.Code.Validate(); err != nil { return err } count++ } if c.No != nil { count++ } if count != 1 { return fmt.Errorf("%w: CostInfo задано %d полей", ErrChoice, count) } return nil } // Confirmation — подтверждение приёма ценных бумаг по решению. type Confirmation struct { SettlementAccount DecisionSettlementAccount `xml:"http://nsd.ru/schemas/m2m/types SettlementAccount"` } // Validate валидирует счёт зачисления. func (c Confirmation) Validate() error { return c.SettlementAccount.Validate() } // Rejection — отказ от приёма ценных бумаг по решению. type Rejection struct { Codes []string `xml:"http://nsd.ru/schemas/m2m/types Code"` } // Validate проверяет, что коды отказа заданы и каждый не длиннее 6 символов. func (r Rejection) Validate() error { if len(r.Codes) == 0 { return fmt.Errorf("%w: Rejection без кодов отказа", ErrInvalid) } for _, code := range r.Codes { if l := len(code); l < 1 || l > 6 { return fmt.Errorf("%w: Rejection.Code длина %d, ожидается 1..6", ErrInvalid, l) } } return nil } // DecisionTransfer — choice решения: подтверждение или отказ. type DecisionTransfer struct { Rejection *Rejection `xml:"http://nsd.ru/schemas/m2m/types Rejection,omitempty"` Confirmation *Confirmation `xml:"http://nsd.ru/schemas/m2m/types Confirmation,omitempty"` } // Validate проверяет, что задано ровно одно поле choice. func (t DecisionTransfer) Validate() error { count := 0 if t.Rejection != nil { if err := t.Rejection.Validate(); err != nil { return err } count++ } if t.Confirmation != nil { if err := t.Confirmation.Validate(); err != nil { return err } count++ } if count != 1 { return fmt.Errorf("%w: DecisionTransfer задано %d полей", ErrChoice, count) } return nil } // RequestHeader — заголовок сообщения "Запрос на перевод M2M". type RequestHeader struct { GUID UUID `xml:"http://nsd.ru/schemas/m2m/types GUID"` CreationTimestamp nsdxml.NSDDateTime `xml:"http://nsd.ru/schemas/m2m/types CreationTimestamp"` SenderCode DeponentCode `xml:"http://nsd.ru/schemas/m2m/types SenderCode"` ReceiverCode DeponentCode `xml:"http://nsd.ru/schemas/m2m/types ReceiverCode"` CostInfo CostInfo `xml:"http://nsd.ru/schemas/m2m/types CostInfo"` IIAAgreementDetails *IIAAgreementDetails `xml:"http://nsd.ru/schemas/m2m/types IIAAgreementDetails,omitempty"` } // Validate валидирует поля заголовка запроса. func (h RequestHeader) Validate() error { if err := h.GUID.Validate(); err != nil { return err } if err := h.SenderCode.Validate(); err != nil { return err } if err := h.ReceiverCode.Validate(); err != nil { return err } if err := h.CostInfo.Validate(); err != nil { return err } if h.IIAAgreementDetails != nil { if err := h.IIAAgreementDetails.Validate(); err != nil { return err } } return nil } // DecisionHeader — заголовок сообщения "Решение по запросу M2M". type DecisionHeader struct { GUID UUID `xml:"http://nsd.ru/schemas/m2m/types GUID"` CreationTimestamp nsdxml.NSDDateTime `xml:"http://nsd.ru/schemas/m2m/types CreationTimestamp"` SenderCode DeponentCode `xml:"http://nsd.ru/schemas/m2m/types SenderCode"` ReceiverCode DeponentCode `xml:"http://nsd.ru/schemas/m2m/types ReceiverCode"` CostInfo CostInfo `xml:"http://nsd.ru/schemas/m2m/types CostInfo"` } // Validate валидирует поля заголовка решения. func (h DecisionHeader) Validate() error { if err := h.GUID.Validate(); err != nil { return err } if err := h.SenderCode.Validate(); err != nil { return err } if err := h.ReceiverCode.Validate(); err != nil { return err } return h.CostInfo.Validate() } // RequestSecurity — описание одной ценной бумаги в запросе перевода. type RequestSecurity struct { ReferenceID ReferenceID `xml:"http://nsd.ru/schemas/m2m/types ReferenceId"` SecurityCode SecurityCode `xml:"http://nsd.ru/schemas/m2m/types SecurityCode"` SecurityDetails SecurityDetails `xml:"http://nsd.ru/schemas/m2m/types SecurityDetails"` Quantity Quantity `xml:"http://nsd.ru/schemas/m2m/types Quantity"` SettlementAccount []RequestSettlementAccount `xml:"http://nsd.ru/schemas/m2m/types SettlementAccount"` IsolationStatus IsolationStatus `xml:"http://nsd.ru/schemas/m2m/types IsolationStatus"` } // Validate валидирует все поля ценной бумаги запроса. func (s RequestSecurity) Validate() error { if err := s.ReferenceID.Validate(); err != nil { return err } if err := s.SecurityCode.Validate(); err != nil { return err } if err := s.SecurityDetails.Validate(); err != nil { return err } if err := s.Quantity.Validate(); err != nil { return err } if len(s.SettlementAccount) == 0 { return fmt.Errorf("%w: SettlementAccount должен содержать хотя бы один счёт", ErrInvalid) } for i := range s.SettlementAccount { if err := s.SettlementAccount[i].Validate(); err != nil { return err } } return s.IsolationStatus.Validate() } // RequestTransferredSecurities — список переводимых ценных бумаг. type RequestTransferredSecurities struct { Securities []RequestSecurity `xml:"http://nsd.ru/schemas/m2m/types Security"` } // Validate проверяет непустоту списка и валидирует каждую запись. func (t RequestTransferredSecurities) Validate() error { if len(t.Securities) == 0 { return fmt.Errorf("%w: TransferredSecurities пуст", ErrInvalid) } for i := range t.Securities { if err := t.Securities[i].Validate(); err != nil { return err } } return nil } // RequestData — содержательная часть запроса. IsM2M фиксировано true и // проставляется в MarshalXML, в структуре поле не хранится. type RequestData struct { InvestorInformation InvestorInformation `xml:"http://nsd.ru/schemas/m2m/types InvestorInformation"` TransferringDepository SettlementRequisites `xml:"http://nsd.ru/schemas/m2m/types TransferringDepository"` ReceivingDepository SettlementRequisites `xml:"http://nsd.ru/schemas/m2m/types ReceivingDepository"` TransferredSecurities RequestTransferredSecurities `xml:"http://nsd.ru/schemas/m2m/types TransferredSecurities"` } // requestDataXML — внутренний alias с явным полем IsM2M для XML-кодека. type requestDataXML struct { IsM2M bool `xml:"http://nsd.ru/schemas/m2m/types IsM2M"` InvestorInformation InvestorInformation `xml:"http://nsd.ru/schemas/m2m/types InvestorInformation"` TransferringDepository SettlementRequisites `xml:"http://nsd.ru/schemas/m2m/types TransferringDepository"` ReceivingDepository SettlementRequisites `xml:"http://nsd.ru/schemas/m2m/types ReceivingDepository"` TransferredSecurities RequestTransferredSecurities `xml:"http://nsd.ru/schemas/m2m/types TransferredSecurities"` } // MarshalXML всегда эмитирует IsM2M=true первым элементом. func (d RequestData) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return e.EncodeElement(requestDataXML{ IsM2M: true, InvestorInformation: d.InvestorInformation, TransferringDepository: d.TransferringDepository, ReceivingDepository: d.ReceivingDepository, TransferredSecurities: d.TransferredSecurities, }, start) } // UnmarshalXML принимает и отбрасывает IsM2M, не вынося его в структуру. func (d *RequestData) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) error { var x requestDataXML if err := dec.DecodeElement(&x, &start); err != nil { return err } d.InvestorInformation = x.InvestorInformation d.TransferringDepository = x.TransferringDepository d.ReceivingDepository = x.ReceivingDepository d.TransferredSecurities = x.TransferredSecurities return nil } // Validate валидирует содержательную часть запроса. func (d RequestData) Validate() error { if err := d.InvestorInformation.Validate(); err != nil { return err } if err := d.TransferringDepository.Validate(); err != nil { return err } if err := d.ReceivingDepository.Validate(); err != nil { return err } return d.TransferredSecurities.Validate() } // DecisionSecurity — решение по одной ценной бумаге из запроса. type DecisionSecurity struct { ReferenceID ReferenceID `xml:"http://nsd.ru/schemas/m2m/types ReferenceId"` TransferDecision DecisionTransfer `xml:"http://nsd.ru/schemas/m2m/types TransferDecision"` } // Validate валидирует ReferenceID и решение по бумаге. func (s DecisionSecurity) Validate() error { if err := s.ReferenceID.Validate(); err != nil { return err } return s.TransferDecision.Validate() } // DecisionData — содержательная часть решения. type DecisionData struct { ReceivingDepository SettlementRequisites `xml:"http://nsd.ru/schemas/m2m/types ReceivingDepository"` Securities []DecisionSecurity `xml:"http://nsd.ru/schemas/m2m/types Security"` } // Validate проверяет получателя и каждое решение по бумагам. func (d DecisionData) Validate() error { if err := d.ReceivingDepository.Validate(); err != nil { return err } if len(d.Securities) == 0 { return fmt.Errorf("%w: DecisionData без Security", ErrInvalid) } for i := range d.Securities { if err := d.Securities[i].Validate(); err != nil { return err } } return nil } // Response — элемент комментария НРД к обработке сообщения. type Response struct { ReferenceID *ReferenceID `xml:"http://nsd.ru/schemas/m2m/types ReferenceId,omitempty"` Code string `xml:"http://nsd.ru/schemas/m2m/types Code"` Text string `xml:"http://nsd.ru/schemas/m2m/types Text,omitempty"` } // Validate проверяет ссылку (если задана), код и текст. func (r Response) Validate() error { if r.ReferenceID != nil { if err := r.ReferenceID.Validate(); err != nil { return err } } if l := len(r.Code); l < 1 || l > 5 { return fmt.Errorf("%w: Response.Code длина %d, ожидается 1..5", ErrInvalid, l) } if l := len(r.Text); l > 1024 { return fmt.Errorf("%w: Response.Text длина %d, ожидается не более 1024", ErrInvalid, l) } return nil } // NSDInfo — обёртка над списком комментариев НРД. type NSDInfo struct { Info []Response `xml:"http://nsd.ru/schemas/m2m/types Info"` } // Validate валидирует каждый Response. func (n NSDInfo) Validate() error { for i := range n.Info { if err := n.Info[i].Validate(); err != nil { return err } } return nil } // HandbookRusName — наименования участника на русском языке. type HandbookRusName struct { FullName string `xml:"http://nsd.ru/schemas/m2m/types FullName"` ShortName string `xml:"http://nsd.ru/schemas/m2m/types ShortName,omitempty"` DisplayName string `xml:"http://nsd.ru/schemas/m2m/types DisplayName"` } // HandbookEngName — наименования участника на английском языке. type HandbookEngName = HandbookRusName // HandbookNames — пара RUS+ENG наименований. type HandbookNames struct { Rus HandbookRusName `xml:"http://nsd.ru/schemas/m2m/types Rus"` Eng *HandbookEngName `xml:"http://nsd.ru/schemas/m2m/types Eng,omitempty"` } // DepositoryPlaces — место хранения (депозитарий). type DepositoryPlaces struct { ParticipantCode DeponentCode `xml:"http://nsd.ru/schemas/m2m/types ParticipantCode"` } // BrokerPlaces — место хранения (брокер). type BrokerPlaces struct { ParticipantCode DeponentCode `xml:"http://nsd.ru/schemas/m2m/types ParticipantCode"` } // HandbookParticipant — запись в справочнике участников. type HandbookParticipant struct { INN OrganizationINN `xml:"http://nsd.ru/schemas/m2m/types INN"` Names HandbookNames `xml:"http://nsd.ru/schemas/m2m/types Names"` DepositoryPlace *DepositoryPlaces `xml:"http://nsd.ru/schemas/m2m/types DepositoryPlace,omitempty"` BrokerPlace *BrokerPlaces `xml:"http://nsd.ru/schemas/m2m/types BrokerPlace,omitempty"` } // Validate валидирует ИНН и коды участников (если заданы). func (p HandbookParticipant) Validate() error { if err := p.INN.Validate(); err != nil { return err } if p.DepositoryPlace != nil { if err := p.DepositoryPlace.ParticipantCode.Validate(); err != nil { return err } } if p.BrokerPlace != nil { if err := p.BrokerPlace.ParticipantCode.Validate(); err != nil { return err } } return nil } // HandbookParticipants — обёртка над списком участников. type HandbookParticipants struct { Participants []HandbookParticipant `xml:"http://nsd.ru/schemas/m2m/types Participant"` } // Place — место расчётов в справочнике. type Place struct { INN OrganizationINN `xml:"http://nsd.ru/schemas/m2m/types INN"` ShortName string `xml:"http://nsd.ru/schemas/m2m/types ShortName"` DisplayName string `xml:"http://nsd.ru/schemas/m2m/types DisplayName"` } // SettlementPlaces — обёртка над списком мест расчётов. type SettlementPlaces struct { Places []Place `xml:"http://nsd.ru/schemas/m2m/types Place"` } // M2MTransferRequest — корневой элемент сообщения "Запрос на перевод M2M". type M2MTransferRequest struct { XMLName xml.Name `xml:"http://nsd.ru/schemas/m2m/request M2MTransferRequest"` Header RequestHeader `xml:"http://nsd.ru/schemas/m2m/request Header"` Data RequestData `xml:"http://nsd.ru/schemas/m2m/request Data"` NSDInfo *NSDInfo `xml:"http://nsd.ru/schemas/m2m/request NSDInfo,omitempty"` } // Validate каскадно валидирует заголовок, тело и комментарии НРД. func (m M2MTransferRequest) Validate() error { if err := m.Header.Validate(); err != nil { return err } if err := m.Data.Validate(); err != nil { return err } if m.NSDInfo != nil { return m.NSDInfo.Validate() } return nil } // M2MTransferDecision — корневой элемент сообщения "Решение по запросу M2M". type M2MTransferDecision struct { XMLName xml.Name `xml:"http://nsd.ru/schemas/m2m/decision M2MTransferDecision"` Header DecisionHeader `xml:"http://nsd.ru/schemas/m2m/decision Header"` Data DecisionData `xml:"http://nsd.ru/schemas/m2m/decision Data"` NSDInfo *NSDInfo `xml:"http://nsd.ru/schemas/m2m/decision NSDInfo,omitempty"` } // Validate каскадно валидирует заголовок, тело и комментарии НРД. func (m M2MTransferDecision) Validate() error { if err := m.Header.Validate(); err != nil { return err } if err := m.Data.Validate(); err != nil { return err } if m.NSDInfo != nil { return m.NSDInfo.Validate() } return nil } // M2MTransferResponse — служебный ответ НРД на сообщение M2M. // GUID/StatusCode/Response объявлены локально в M2MTransferResponse.xsd // (elementFormDefault="qualified") — namespace элементов response, не // types, хотя их типы из types. // XMLName без namespace: реальный НРД-ответ присылает корневой // M2MTransferResponse БЕЗ namespace на root (дочерние qualified в response // ns через префикс ns3). Матчим root по локальному имени — толерантно к // тому, объявлен ли default namespace на корне. type M2MTransferResponse struct { XMLName xml.Name `xml:"M2MTransferResponse"` GUID UUID `xml:"http://nsd.ru/schemas/m2m/response GUID"` StatusCode StatusCode `xml:"http://nsd.ru/schemas/m2m/response StatusCode"` Responses []Response `xml:"http://nsd.ru/schemas/m2m/response Response"` } // Validate валидирует GUID, статус и каждый Response. func (m M2MTransferResponse) Validate() error { if err := m.GUID.Validate(); err != nil { return err } if err := m.StatusCode.Validate(); err != nil { return err } if len(m.Responses) == 0 { return fmt.Errorf("%w: M2MTransferResponse без Response", ErrInvalid) } for i := range m.Responses { if err := m.Responses[i].Validate(); err != nil { return err } } return nil } // M2MTransferHandbook — справочник участников M2M. CreationTimestamp, // SettlementPlaces, Participants — локальные элементы из // M2MTransferHandbook.xsd, namespace handbook. type M2MTransferHandbook struct { XMLName xml.Name `xml:"http://nsd.ru/schemas/m2m/handbook M2MTransferHandbook"` CreationTimestamp nsdxml.NSDDateTime `xml:"http://nsd.ru/schemas/m2m/handbook CreationTimestamp"` SettlementPlaces SettlementPlaces `xml:"http://nsd.ru/schemas/m2m/handbook SettlementPlaces"` Participants HandbookParticipants `xml:"http://nsd.ru/schemas/m2m/handbook Participants"` } // Validate валидирует каждого участника и каждое место расчётов. func (m M2MTransferHandbook) Validate() error { for i := range m.SettlementPlaces.Places { if err := m.SettlementPlaces.Places[i].INN.Validate(); err != nil { return err } } for i := range m.Participants.Participants { if err := m.Participants.Participants[i].Validate(); err != nil { return err } } return nil } // M2MTransferHandbookRequest — запрос актуального справочника M2M. // Тело пустое (пустой complexType в XSD). type M2MTransferHandbookRequest struct { XMLName xml.Name `xml:"http://nsd.ru/schemas/m2m/handbook/request M2MTransferHandbookRequest"` } // Validate всегда возвращает nil — содержимое отсутствует по XSD. func (m M2MTransferHandbookRequest) Validate() error { return nil } // M2MTransferParticipantForm — анкета участника M2M. CreationTimestamp // и Participant — локальные элементы из M2MTransferParticipantForm.xsd, // namespace participant/form. type M2MTransferParticipantForm struct { XMLName xml.Name `xml:"http://nsd.ru/schemas/m2m/participant/form M2MTransferParticipantForm"` CreationTimestamp nsdxml.NSDDateTime `xml:"http://nsd.ru/schemas/m2m/participant/form CreationTimestamp"` Participant HandbookParticipant `xml:"http://nsd.ru/schemas/m2m/participant/form Participant"` } // Validate валидирует участника. func (m M2MTransferParticipantForm) Validate() error { return m.Participant.Validate() }