Повторы и externalId
Главное правило: один externalId — один фискальный чек. Сколько бы раз кассовая программа ни отправила чек с тем же номером и тем же содержимым, ФМ пробьёт его не больше одного раза.
Как это устроено
Заголовок раздела «Как это устроено»- Приложение получает чек и сначала записывает его в журнал на диске, а потом отправляет в ФМ.
- Пока чек пробивается, повтор получает
409 BRIDGE_RECEIPT_IN_PROGRESS. - Когда ФМ ответил, результат сохраняется в журнале. Повтор получает тот же результат с
"duplicate": true. - Если ФМ отказал (код
OFD1_*илиOFD2_*), чек точно не пробит. Когда причина вне чека (смена не открыта, ФМ не вставлен), устраните её и отправьте тот же чек с тем же номером. Если нужно изменить сам чек — это новый чек с новымexternalId. - Если ФМ не ответил на шаге регистрации, неизвестно, пробит ли чек. Повтор получает
409 BRIDGE_RECEIPT_STATE_UNKNOWN, пока кассир не проверит чек и не нажмёт в приложении «Не пробит».
Журнал переживает перезапуск приложения и компьютера: чек, который пробивался в момент сбоя, после запуска считается «исход неизвестен».
Что делать кассовой программе
Заголовок раздела «Что делать кассовой программе»| Ответ | Что делать |
|---|---|
200, duplicate: false |
Чек пробит — сохраните result, напечатайте чек |
200, duplicate: true |
Чек был пробит раньше — сохраните result, не пробивайте заново |
| Нет ответа, обрыв, таймаут | Повторите тот же запрос с тем же externalId |
409 BRIDGE_RECEIPT_IN_PROGRESS |
Подождите 2–5 секунд и повторите |
409 BRIDGE_RECEIPT_STATE_UNKNOWN |
Покажите кассиру title и action; повторите после его проверки |
409 BRIDGE_EXTERNAL_ID_CONFLICT |
Номер уже занят другим чеком — это ошибка в кассовой программе |
422 OFD1_* / OFD2_* |
Покажите кассиру текст, после исправления повторите с тем же номером |
402 LICENSE_* |
Касса отключена — покажите текст; чек не записан, после оплаты отправьте снова |
Пример на JavaScript:
async function registerWithRetry(receipt, attempts = 5) { for (let attempt = 1; ; attempt++) { let response; try { response = await fetch(`${API}/v1/receipts`, { method: 'POST', headers: { 'X-Api-Key': KEY, 'Content-Type': 'application/json' }, body: JSON.stringify(receipt), signal: AbortSignal.timeout(60_000), }); } catch { // Обрыв или таймаут: тот же чек, тот же externalId — второй раз он не пробьётся if (attempt < attempts) continue; throw new Error('Приложение не отвечает'); }
const body = await response.json(); if (response.ok) return body.result; if (body.error.code === 'BRIDGE_RECEIPT_IN_PROGRESS' && attempt < attempts) { await new Promise((resolve) => setTimeout(resolve, 3000)); continue; } throw Object.assign(new Error(body.error.title), body.error); }}Каким должен быть externalId
Заголовок раздела «Каким должен быть externalId»- Уникальный для каждого чека на этой кассе: номер документа в вашей программе, UUID и т.п. До 64 символов.
- Постоянный: при повторе — тот же номер. Не генерируйте новый номер на каждую попытку — иначе защита от двойного чека не сработает.
- Возврат — это отдельный чек со своим
externalId, а ссылка на продажу — в полеrefund.
«Тем же содержимым» приложение считает тот же JSON чека. Если номер совпал, а позиции или суммы другие — это 409 BRIDGE_EXTERNAL_ID_CONFLICT: приложение не знает, какой чек вы имели в виду.
Узнать состояние чека
Заголовок раздела «Узнать состояние чека»GET /v1/receipts/{externalId} возвращает чек из журнала: status (registered, failed, unknown, pending), result и текст ошибки. Это удобно, если кассовая программа перезапустилась и не знает, чем кончилась последняя попытка.