package lkgateway import ( "context" "log" "strings" "time" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw" ) // pollIncoming периодически опрашивает ИШ на входящие пакеты от НРД // (M2MTransferDecision / Response) и применяет их через svc.ApplyDecision. // Замыкает цикл: bj-server отправил заявку → ИШ → НРД → робот ответил → // ИШ забрал ответ во входящие → этот поллер применяет Decision (статус // заявки переходит в confirmed/rejected, срабатывает callback в ЛК). // // Дедупликация по id обработанных пакетов: ИШ возвращает их повторно, // пока мы не подтвердим, поэтому держим множество уже обработанных. func (s *Server) pollIncoming(ctx context.Context) { const interval = 30 * time.Second ticker := time.NewTicker(interval) defer ticker.Stop() processed := make(map[int]bool) log.Printf("lk-gateway: поллер входящих ИШ запущен (канал %s, интервал %s)", s.igwChannel, interval) for { select { case <-ctx.Done(): return case <-ticker.C: s.fetchAndApply(ctx, processed) } } } // fetchAndApply — один проход поллера: список входящих → для каждого нового // забираем тело, распаковываем, парсим Decision, применяем. func (s *Server) fetchAndApply(ctx context.Context, processed map[int]bool) { cctx, cancel := context.WithTimeout(ctx, 25*time.Second) defer cancel() // Тип не указываем — ИШ вернёт оба (M2MTD + M2MER). Date=сегодня. pkgs, err := s.igwClient.ListIncoming(cctx, igw.ListFilter{ Channel: s.igwChannel, Date: time.Now(), }) if err != nil { log.Printf("lk-gateway: поллер ListIncoming: %v", err) return } for _, p := range pkgs { if processed[p.ID] { continue } if err := s.applyIncoming(cctx, p); err != nil { log.Printf("lk-gateway: поллер пакет id=%d (%s): %v", p.ID, p.Type, err) continue // не помечаем обработанным — повторим в след. раз } processed[p.ID] = true log.Printf("lk-gateway: поллер применил входящий пакет id=%d тип=%s", p.ID, p.Type) } } // applyIncoming забирает тело пакета и применяет M2M-ответ к сделке. // Среди входящих от НРД много служебных пакетов (квитанции ЭДО типа C/Z, // конверты) — они не M2M-документы и пропускаются. Реальные ответы — // M2MTransferDecision (решение принимающей стороны) и M2MTransferResponse // (ответ сервиса МОСТ, в т.ч. ошибки M2Mxx). func (s *Server) applyIncoming(ctx context.Context, p igw.Package) error { zipBytes, err := s.igwClient.GetPackage(ctx, p.ID) if err != nil { return err // сетевая ошибка — повторим в след. раз } unpacked, err := igw.UnpackPackage(zipBytes) if err != nil { // Нет основного .xml — служебный пакет (квитанция/конверт ЭДО). // Не ошибка: помечаем обработанным, чтобы не повторять. log.Printf("lk-gateway: поллер пакет id=%d (%s) — служебный (квитанция/конверт), пропуск", p.ID, p.Type) return nil } doc := string(unpacked.DocXML) switch { case strings.Contains(doc, "M2MTransferDecision"): decision, err := igw.ParseDecision(unpacked.DocXML) if err != nil { log.Printf("lk-gateway: поллер Decision id=%d: разбор: %v", p.ID, err) return nil } return s.svc.ApplyDecision(ctx, decision) case strings.Contains(doc, "M2MTransferResponse"): resp, err := igw.ParseResponse(unpacked.DocXML) if err != nil { log.Printf("lk-gateway: поллер Response id=%d: разбор: %v", p.ID, err) return nil } // Ответ сервиса МОСТ: статус + код (M2Mxx). Применяем к сделке: // INFO — приём в обработку (статус не меняется), ERROR — отказ сервиса // (напр. M2M14 — отправитель не в справочнике), сделка → Отклонена. // Ответ сохраняется в сделке и виден в карточке заявки. var codes string for _, rr := range resp.Responses { codes += string(rr.Code) + " " } log.Printf("lk-gateway: поллер M2MTransferResponse id=%d: статус=%s коды=[%s] GUID=%s", p.ID, resp.StatusCode, strings.TrimSpace(codes), resp.GUID) if err := s.svc.ApplyServiceResponse(ctx, resp, unpacked.DocXML); err != nil { // Сделка может быть не найдена (ответ на чужой/старый GUID) — // логируем, но помечаем обработанным, чтобы не зациклиться. log.Printf("lk-gateway: поллер ApplyServiceResponse id=%d GUID=%s: %v", p.ID, resp.GUID, err) } return nil default: log.Printf("lk-gateway: поллер пакет id=%d (%s) — неизвестный M2M-документ, пропуск", p.ID, p.Type) return nil } }