Почему управление инвентарём критично для цифровых товаров
В отличие от физических товаров, цифровые подарочные карты кажутся неограниченным ресурсом — но запасы у поставщика конечны. Пересортица (получение денег за код, которого нет) уничтожает доверие покупателей. Грамотное управление инвентарём гарантирует: вы продаёте только то, что можете доставить мгновенно.
API FoxReload включает данные об остатках в каждом ответе каталога, и вы можете построить живой слой инвентаря, предотвращающий пересортицу на уровне архитектуры.
Данные об остатках из API
Каждый товар в каталоге FoxReload содержит поле stock — количество доступных кодов.
{
"id": "gp-usd-25",
"name": "Google Play Gift Card $25",
"stock": 143,
"delivery_type": "instant"
}
delivery_type: "instant" означает: коды выдаются в момент оформления заказа, без ожидания.
Шаг 1 — Локальный зеркальный снимок остатков
Опрос API каждые 10–15 минут даёт почти реальную картину остатков. Храните это в локальной таблице, чтобы не добавлять задержку при каждой загрузке страницы магазина.
CREATE TABLE inventory_snapshot (
product_id VARCHAR(64) PRIMARY KEY,
stock INT NOT NULL,
last_synced TIMESTAMP NOT NULL
);
def refresh_inventory(conn):
r = requests.get(
"https://api.foxreload.com/api/v1/catalog",
headers={"Authorization": f"Bearer {API_KEY}"},
params={"category": "google-play"}
)
products = r.json()["products"]
cur = conn.cursor()
for p in products:
cur.execute("""
INSERT INTO inventory_snapshot (product_id, stock, last_synced)
VALUES (%s, %s, NOW())
ON CONFLICT (product_id) DO UPDATE
SET stock=EXCLUDED.stock, last_synced=EXCLUDED.last_synced
""", (p["id"], p["stock"]))
conn.commit()
Шаг 2 — Система резервирования кодов
С момента нажатия «Купить» до подтверждения заказа нужно зарезервировать товар, чтобы два параллельных покупателя не получили один и тот же код.
CREATE TABLE order_reservations (
reservation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_id VARCHAR(64) NOT NULL,
quantity INT NOT NULL DEFAULT 1,
reserved_at TIMESTAMP NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL, -- reserved_at + 5 минут
order_id VARCHAR(64) -- устанавливается при завершении заказа
);
import uuid
from datetime import datetime, timedelta
def reserve_stock(conn, product_id: str, quantity: int = 1) -> str | None:
cur = conn.cursor()
# Вычислить фактически доступный остаток
cur.execute("""
SELECT
s.stock - COALESCE(SUM(r.quantity), 0) AS available
FROM inventory_snapshot s
LEFT JOIN order_reservations r
ON r.product_id = s.product_id
AND r.expires_at > NOW()
AND r.order_id IS NULL
WHERE s.product_id = %s
GROUP BY s.stock
""", (product_id,))
row = cur.fetchone()
if not row or row[0] < quantity:
return None # Нет в наличии
reservation_id = str(uuid.uuid4())
cur.execute("""
INSERT INTO order_reservations (reservation_id, product_id, quantity, expires_at)
VALUES (%s, %s, %s, %s)
""", (reservation_id, product_id, quantity,
datetime.utcnow() + timedelta(minutes=5)))
conn.commit()
return reservation_id
Шаг 3 — Освобождение просроченных резервирований
Просроченные резервирования (покупатель бросил корзину) должны очищаться автоматически.
-- Запускать каждую минуту через pg_cron или cron-job
DELETE FROM order_reservations
WHERE expires_at < NOW() AND order_id IS NULL;
Шаг 4 — Оповещения о низком запасе
Предупреждайте себя заранее, когда популярный SKU заканчивается.
LOW_STOCK_THRESHOLD = 20 # кодов
def check_low_stock(conn):
cur = conn.cursor()
cur.execute("""
SELECT product_id, stock FROM inventory_snapshot
WHERE stock < %s AND stock > 0
""", (LOW_STOCK_THRESHOLD,))
low = cur.fetchall()
for product_id, stock in low:
send_alert(f"Низкий запас: {product_id} — осталось {stock} кодов.")
Шаг 5 — Корректная обработка отсутствия товара
Когда остаток равен 0 — скрывайте товар или показывайте бейдж «Нет в наличии». Никогда не позволяйте покупателям завершать оформление заказа на товар, который нельзя выполнить.
def is_available(conn, product_id: str) -> bool:
cur = conn.cursor()
cur.execute(
"SELECT stock FROM inventory_snapshot WHERE product_id=%s",
(product_id,)
)
row = cur.fetchone()
return row is not None and row[0] > 0
Шаг 6 — Сверка после выполнения заказа
После выдачи кода уменьшайте локальный зеркальный остаток, чтобы не показывать устаревшие высокие значения между циклами синхронизации.
def on_order_fulfilled(conn, product_id: str, quantity: int, reservation_id: str):
cur = conn.cursor()
cur.execute(
"UPDATE order_reservations SET order_id=%s WHERE reservation_id=%s",
(order_id, reservation_id)
)
cur.execute(
"UPDATE inventory_snapshot SET stock = stock - %s WHERE product_id=%s",
(quantity, product_id)
)
conn.commit()
Дашборд состояния инвентаря
Рекомендуем построить простой административный вид:
| Метрика | Описание |
|---|---|
| Активные SKU | Товары с остатком > 0 |
| SKU с низким запасом | Товары ниже порога |
| Активные резервирования | Незавершённые резервирования |
| Продано за сутки | Заказы за последние 24 ч |
| Инциденты пересортицы | Отклонённые заказы из-за отсутствия товара |
Итог
Надёжная система управления инвентарём Google Play Gift Cards требует четырёх компонентов: синхронизированного зеркала остатков поставщика, слоя резервирования для предотвращения параллельной пересортицы, автоматических оповещений о низком запасе и чистой сверки после каждого выполненного заказа.
Смотрите также
- Как подключить Google Play Gift Cards к магазину через API
- Как реализовать мгновенную доставку Google Play Gift Cards
- Как настроить вебхуки для доставки цифровых кодов
Никакой пересортицы. API FoxReload даёт актуальные данные об остатках — постройте слой инвентаря поверх и выполняйте каждый заказ мгновенно.

