Тема
SDK Java
Библиотека ai.inlog.sdk:postomat-java — клиент интеграционного API постоматов для Java. Страница описывает версию 1.1.1.
- Java 8 или новее.
- Опубликована в Maven Central.
- Зависимость: Jackson (
jackson-databind) — справочные ответы возвращаются какJsonNode. - Используйте библиотеку только на сервере: она работает с токеном компании.
Установка
xml
<dependency>
<groupId>ai.inlog.sdk</groupId>
<artifactId>postomat-java</artifactId>
<version>1.1.1</version>
</dependency>kotlin
implementation("ai.inlog.sdk:postomat-java:1.1.1")groovy
implementation 'ai.inlog.sdk:postomat-java:1.1.1'Создание клиента
java
import ai.inlog.sdk.postomat.PostomatClient;
import ai.inlog.sdk.postomat.SdkConfig;
import java.time.Duration;
PostomatClient client = PostomatClient.create(
SdkConfig.builder()
.apiKey(System.getenv("POSTOMAT_API_KEY")) // YOUR_API_KEY
// baseUrl по умолчанию — продакшн. Для тестового окружения:
.baseUrl("http://api.test.postomat.inlog.ai/api/v1/integration/company")
.timeout(Duration.ofMillis(12_000))
.userAgent("my-company-integration/1.0.0")
.build()
);Или только с токеном (продакшн, настройки по умолчанию):
java
PostomatClient client = PostomatClient.create(System.getenv("POSTOMAT_API_KEY"));Создайте один экземпляр клиента и переиспользуйте его во всём приложении.
Параметры SdkConfig
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
apiKey | String | — | Обязательный. Токен компании |
baseUrl | String | https://postomat-api-1.inlog.ai/api/v1/integration/company | Базовый URL интеграционного API |
timeout / timeoutMs | Duration / long | 12 000 мс | Таймаут подключения и чтения для одной попытки |
maxRetries | int | 3 | Максимум повторов |
baseDelayMs | long | 400 | Базовая задержка бэкоффа, мс |
maxDelayMs | long | 5000 | Верхняя граница задержки, мс |
retry(max, base, maxDelay) | — | — | Задать три параметра ретраев одним вызовом |
authHeader | String | Authorization | Имя заголовка авторизации |
authScheme | String | Bearer | Схема авторизации; "" — передавать токен без схемы |
userAgent | String | postomat-sdk-java/1.x | Заголовок User-Agent |
Параметры отдельного запроса — RequestOptions
java
import ai.inlog.sdk.postomat.RequestOptions;
RequestOptions options = RequestOptions.builder()
.timeoutMs(20_000)
.header("X-Trace-Id", "abc-123")
.build();| Метод | Описание |
|---|---|
timeout(Duration) / timeoutMs(long) | Таймаут этого запроса |
header(name, value) / headers(Map) | Дополнительные заголовки |
idempotencyKey(String) | Передаётся в заголовке Idempotency-Key (см. Ретраи) |
Известные ограничения
Незаданные поля CreateOrderBody отправляются как null
В версии 1.1.1 orders().create(...) сериализует все поля CreateOrderBody, включая незаданные, как null. API не принимает null в необязательных полях и отвечает 400 (ошибка валидации).
Пока это не исправлено в библиотеке, создавайте тело запроса через подкласс, который исключает null-поля:
java
import ai.inlog.sdk.postomat.model.CreateOrderBody;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class NonNullCreateOrderBody extends CreateOrderBody {
}java
CreateOrderBody body = new NonNullCreateOrderBody();
body.setToUserPhone("+996700000000");
body.setPostomatId("tz4a98xxat96iws9zmbrgj3a");
body.setSendMessage(true);
Order order = client.orders().create(body);Билдер CreateOrderBody.builder() создаёт экземпляр базового класса, поэтому для создания заявок используйте сеттеры подкласса.
Заявки — client.orders()
| Метод | HTTP |
|---|---|
create(body) / create(body, options) | POST /order/create |
getById(id) / getById(id, query) / getById(id, query, options) | GET /order/{id} |
list(query) / list(query, options) | GET /order/list |
getDownCode(id) | GET /order/{id}/down-code |
getUpCode(id) | GET /order/{id}/up-code |
getSubtractCode(id) | GET /order/{id}/subtract-code |
Создание
Поля тела совпадают с API (см. Заявки):
| Сеттер | Тип | Обязательное | Описание |
|---|---|---|---|
setToUserPhone | String | да | Телефон получателя, например +996700000000 |
setPostomatId | String | да | ID постомата |
setSendMessage | Boolean | да | Отправить получателю SMS с кодом получения после закладки |
setDays | Integer | нет | Срок хранения в днях |
setWeight | Double | нет | Вес |
setCustomData | String | нет | Ваши данные, например внешний ID заказа |
setPay | Boolean | нет | Оплатить с баланса компании |
SDK сам проверяет toUserPhone и postomatId и бросает IllegalArgumentException, если они пустые.
java
CreateOrderBody body = new NonNullCreateOrderBody();
body.setToUserPhone("+996700000000");
body.setPostomatId("tz4a98xxat96iws9zmbrgj3a");
body.setSendMessage(true);
body.setCustomData("partner-order-12345");
Order order = client.orders().create(body);
System.out.println("Order ID: " + order.getId());Получение и список
java
import ai.inlog.sdk.postomat.model.GetOrderByIdQuery;
import ai.inlog.sdk.postomat.model.GetOrdersListQuery;
import ai.inlog.sdk.postomat.model.OrderStatusType;
import ai.inlog.sdk.postomat.model.OrdersListResponse;
Order order = client.orders().getById(
"gfb0sfr5ahkhu9dns4bja2ly",
GetOrderByIdQuery.builder().withCurrentStatus(true).withStatuses(true).build()
);
OrdersListResponse list = client.orders().list(
GetOrdersListQuery.listBuilder()
.page(1)
.pageSize(20)
.status(OrderStatusType.DELIVERED)
.withCurrentStatus(true)
.build()
);
List<Order> orders = list.getOrders(); // данные из поля data
Long total = list.getTotal();GetOrdersListQuery по умолчанию передаёт page=1, pageSize=20. Флаги with* в GetOrderByIdQuery и GetOrdersListQuery: withToUser, withCell, withPostomat, withCurrentStatus, withStatuses, withRent, withPasswords.
Для фильтра status используйте значения CREATED, DELIVERED, COMPLETED, SUBTRACTED.
Обход всех страниц списка заявок:
java
int page = 1;
int pageSize = 50;
while (true) {
OrdersListResponse result = client.orders().list(
GetOrdersListQuery.listBuilder().page(page).pageSize(pageSize).build()
);
List<Order> orders = result.getOrders();
for (Order o : orders) {
// обработка
}
if (orders.size() < pageSize) {
break;
}
page += 1;
}Коды
java
import ai.inlog.sdk.postomat.model.Code;
Code downCode = client.orders().getDownCode(order.getId()); // код закладки для курьера
Code upCode = client.orders().getUpCode(order.getId()); // код получения для получателя
Code subtractCode = client.orders().getSubtractCode(order.getId()); // код изъятия
System.out.println(upCode.getPassword());Модель Code: getId(), getType() (DOWN, UP, SUBTRACT), getPassword(), getOrderId().
Модель Order
Основные геттеры: getId(), getPostomatId(), getCompanyId(), getCellId(), getDays(), getWeight(), getCustomData(), getSendMessage(), getIsActive(), getPrice(), getPaidAmount(), getNotPaidAmount(), getStartDateTime(), getEndDateTime(), getCurrentStatus() (OrderStatus с getType()), getCell(), getPostomat() (JsonNode), getCreatedAt(), getUpdatedAt(). Неизвестные поля ответа игнорируются.
Постоматы — client.postomats()
java
import ai.inlog.sdk.postomat.model.GetPostomatByIdQuery;
import ai.inlog.sdk.postomat.model.GetPostomatListQuery;
import ai.inlog.sdk.postomat.model.PageResponse;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Arrays;
PageResponse<JsonNode> result = client.postomats().list(
GetPostomatListQuery.builder()
.page(1)
.pageSize(50)
.cityId("pfh0haxfpzowht3oi213cqos")
.withCity(true)
.build()
);
for (JsonNode p : result.getResults()) {
System.out.println(p.get("id").asText() + " " + p.path("title").asText());
}
JsonNode postomat = client.postomats().getById(
"tz4a98xxat96iws9zmbrgj3a",
GetPostomatByIdQuery.builder().withCity(true).withCells(true).build()
);Параметры GetPostomatListQuery: page, pageSize (по умолчанию 1 и 50), ids(List<String>), cityId, districtId, withCity, withDistrict, latitude, longitude. Параметры GetPostomatByIdQuery: withCity, withCells, withBoards. getById возвращает null-узел, если постомат не найден.
PageResponse.getResults() возвращает элементы из поля data ответа, getTotal() — общее количество.
Обход всех постоматов через utils().paginate (размер страницы в запросе должен совпадать с размером, который ожидает пагинатор, — по умолчанию 50):
java
for (JsonNode p : client.utils().<JsonNode>paginate(
page -> client.postomats().list(GetPostomatListQuery.builder().page(page).pageSize(50).build())
)) {
System.out.println(p.get("id").asText());
}Свободные ячейки — client.cells()
java
import ai.inlog.sdk.postomat.model.Cell;
import ai.inlog.sdk.postomat.model.CellSize;
List<Cell> cells = client.cells().getFreeCells("tz4a98xxat96iws9zmbrgj3a");
long mediumCount = cells.stream().filter(c -> c.getSize() == CellSize.M).count();Модель Cell: getId(), getTitle(), getNumber(), getSize() (S, M, L, XL), getIsActive(), getIsOpen(), getForRentAvailable(), getPostomatId(), getBoardId().
Справочники — client.cities(), client.districts()
java
import ai.inlog.sdk.postomat.model.GetCitiesQuery;
import ai.inlog.sdk.postomat.model.GetCityByIdQuery;
import ai.inlog.sdk.postomat.model.GetDistrictsQuery;
PageResponse<JsonNode> cities = client.cities().list(
GetCitiesQuery.builder().page(1).pageSize(50).withCountry(true).build()
);
JsonNode city = client.cities().getById(
"pfh0haxfpzowht3oi213cqos",
GetCityByIdQuery.builder().withCountry(true).build()
);
PageResponse<JsonNode> districts = client.districts().list(
GetDistrictsQuery.builder().cityId("pfh0haxfpzowht3oi213cqos").build()
);Для справочника стран (GET /country) отдельного метода в SDK нет.
Типичный порядок: города → районы города → постоматы по cityId/districtId → свободные ячейки → создание заявки.
Ошибки
Ошибки API и сети выбрасываются как непроверяемое исключение ApiError:
| Метод | Описание |
|---|---|
getStatus() | HTTP-статус; 0 — сетевая ошибка или таймаут |
getMessage() | Значение поля message из тела ответа — для бизнес-ошибок здесь код, например ORDER_NOT_FOUND |
getCode() | Значение поля code из тела. У бизнес-ошибок API такого поля нет — будет null; заполнено у ошибок валидации (FST_ERR_VALIDATION) |
getRequestId() | Заголовок ответа x-request-id, если есть; сейчас API его не передаёт |
getDetails() | Тело ответа (JsonNode или строка) |
java
import ai.inlog.sdk.postomat.ApiError;
try {
client.orders().getById("gfb0sfr5ahkhu9dns4bja2ly");
} catch (ApiError e) {
if (e.getStatus() == 404 && "ORDER_NOT_FOUND".equals(e.getMessage())) {
// заявка не найдена
} else {
System.err.println(e.getStatus() + " " + e.getMessage());
}
}Каталог кодов — на странице Ошибки и ретраи.
Ретраи
SDK автоматически повторяет запрос:
- при ответах
429и5xx; - при сетевых ошибках ввода-вывода.
Таймаут не повторяется: запрос сразу завершается ApiError("Request timeout") со статусом 0. Задержка: baseDelayMs × 2^(попытка − 1), но не больше maxDelayMs; при наличии заголовка Retry-After используется он (тоже не больше maxDelayMs). Остальные ответы 4xx не повторяются.
Ретраи и создание заявок
Автоповторы применяются и к orders().create. SDK может передать Idempotency-Key, но API сейчас не удаляет дубликаты по этому заголовку — повтор после 5xx или обрыва соединения может создать вторую заявку. Для создания заявок используйте отдельный клиент без автоповторов (.maxRetries(0)) и перед ручным повтором проверяйте, не создалась ли заявка, по своему идентификатору в customData.
Заголовки запросов
| Заголовок | Когда |
|---|---|
Authorization: Bearer <apiKey> | Всегда |
Accept: application/json | Всегда |
User-Agent | Всегда |
Content-Type: application/json | Запросы с телом |
Idempotency-Key | Если задан RequestOptions.idempotencyKey |