Руководство
Markdown-версияИдемпотентность
Как безопасно повторять покупку после таймаута или обрыва связи.
Если ответ на POST /purchase не дошёл, вы не знаете, прошла ли покупка. Повтор без защиты — это вторая покупка и второе списание. Поэтому передавайте idempotencyKey — свой уникальный идентификатор покупки, например ID заказа в вашей системе.
- Первый запрос с ключом создаёт заказ.
- Повтор с тем же ключом и тем же товаром возвращает исход того же заказа — с тем же кодом ответа и полем
"replayed": true. Деньги второй раз не списываются, даже если товар уже закончился. - Тот же ключ с другим товаром —
422 idempotency_key_reused. - Если заказ не был создан (например,
402— не хватило денег), ключ не занят: после пополнения можно повторить с ним же.
Формат
1–128 печатных ASCII-символов (!…~), без пробелов и кириллицы. Регистр важен: ABC и abc — разные покупки. Ключ действует в пределах вашего клиента.
Повтор с тем же ключом
async function buy(productId, idempotencyKey) {
for (let attempt = 1; attempt <= 5; attempt++) {
try {
const res = await fetch(`https://merchant.dbdlobby.com/purchase/${productId}`, {
method: "POST",
headers: { "X-Entity-Key": KEY, "Content-Type": "application/json" },
body: JSON.stringify({ idempotencyKey }), // один и тот же ключ во всех попытках
signal: AbortSignal.timeout(90_000),
});
if (res.status >= 500 && res.status !== 502) throw new Error(`HTTP ${res.status}`);
return { status: res.status, body: await res.json() };
} catch (e) {
// Сеть, таймаут или 5xx: исход неизвестен — повторяем С ТЕМ ЖЕ ключом.
await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
}
}
throw new Error("API недоступен; повторите позже с тем же idempotencyKey");
}Ключ необязателен, но без него повтор после таймаута создаст вторую покупку. В будущем он может стать обязательным.