Коротка відповідь
Відокремте публічний магазин від авторитетної системи замовлень. Каталог може кешуватися; прийняття замовлення використовує серверні ціни, залишки й перевірену оплату. Browser-cart — пропозиція, не ledger. Готова commerce-платформа доречна, якщо вже керує правилами.
Практичні кроки реалізації
1. Визначте власників комерційних фактів
Запишіть власників описів, variants, цін, stock, orders і fulfillment. CMS може мати описи, commerce — доступність. Використовуйте стабільні ID. Кеш, frontend-discount і база не мають незалежно визначати одну суму. Валюта й округлення належать pricing-межі.
2. Визначте стани замовлення
Розрізняйте створення, очікування оплати, paid, fulfillment, скасування й refund за потребою. Оплата й доставка — різні події. Success URL не доводить оплату й може не відкритися. Stripe вимагає серверне підтвердження й duplicate-safe fulfillment з delayed payments.
3. Обробляйте повтори й порядок подій
Перевіряйте webhook на raw body, приймайте подію стійко й безпечно повторюйте. Успішне acknowledgement означає стійке прийняття, не memory-task. Provider-idempotency для API й бізнес-idempotency для order/shipment різні. Узгоджуйте записи зі станом provider за затримок.
4. Захистіть залишки за паралельних змін
Двоє покупців останнього товару не мають отримати неможливе виконання. Визначте час резервування, власника й завершення reserve. Прийняття атомарне в головній системі. Availability інформує, а acceptance підтверджує; browser-countdown не блокує stock.
Приклад реалізації
Чиста функція показує серверні цілі minor units і перевірку кількості для однієї валюти. Це не checkout: податки, доставка, знижки, stock й reconciliation мають окремі правила. Ціна читається сервером, не cart. Зберігайте валюту й її exponent. Літерали bigint потребують target ES2020 або новішого.
type Line = { unitMinor: bigint; quantity: number };
export function subtotalMinor(lines: Line[]): bigint {
return lines.reduce((total, line) => {
if (line.unitMinor < 0n ||
!Number.isSafeInteger(line.quantity) ||
line.quantity < 1 || line.quantity > 100) {
throw new Error("Invalid authoritative line item");
}
return total + line.unitMinor * BigInt(line.quantity);
}, 0n);
}
Компроміси та альтернативи
Hosted checkout зменшує платіжний UI й підтримку. Власна вітрина може зберегти hosted-commerce authority. Власна order-система додає reconciliation, stock, refund і support. Власний frontend не означає власний commerce-backend.
Поширені помилки
Не виконуйте з redirect, не довіряйте cart-цінам, не змінюйте raw body до signature й не очікуйте єдиної впорядкованої події. Не підтверджуйте нестійку роботу. Не кешуйте приватні orders публічно й не додавайте зайві персональні дані в analytics.
Перевірка перед випуском
- Ціни, stock, оплата й fulfillment мають власників.
- Сервер перевіряє variant, кількість, валюту й правила.
- Webhooks перевірені та стійкі до повторів.
- Delayed payment і завершення reserve визначено.
- Orders узгоджуються після відмови.
Поширені запитання
Чи товари можуть бути статичними?
Так, за допустимої свіжості й повторної перевірки checkout. Каталог і прийняття order різні.
Чи provider-idempotency запобігає подвійній доставці?
Самостійно ні. Вона захищає provider-запит; shipment і доступ потребують стійкого контролю бізнес-операції.
Наступний крок
Простежте order зі старим каталогом, повтором payment, перерваним acknowledgement і delayed fulfillment. Виберіть відновлювану межу, потім окремо оптимізуйте каталог.
Первинні джерела
- Stripe: виконання замовлень Checkout
- Stripe: перевірка й повтори webhook
- Stripe: ідемпотентні запити
- PostgreSQL: транзакції
Пов’язані матеріали та приклади
Потрібно реалізувати це у вашому продукті?
Перегляньте відповідні приклади продуктів або поділіться контекстом розробки вебзастосунку, розширення Chrome, API чи SaaS.