Интеграция за собствен сайт
Ако сайтът ти не е Shopify, WooCommerce или CloudCart — а просто HTML страници, които някой ти е направил — Sendaro пак работи. Слагаш един ред и казваш на кой бутон да се закача. Тази страница е пълната референция: всеки атрибут, всяко събитие, връзката сървър-към-сървър (webhooks и подписана количка), тестовият режим, точните мерни единици и капаните, в които се пада най-често.
- Едно поставяне — какво прави
- 1. Минимална страница
- 2. Кой бутон отваря касата
- 3. Откъде идва продуктът
- ⚠ Мерни единици (цена и тегло)
- 4. Пълна референция
- 5. Събития към твоята страница
- 6. Чистене на количката
- 7. Thank-you страница
- 8. Записване на поръчката при теб
- 9. Готов CSP блок
- 10. Чести грешки
- 11. Бърза проверка преди пускане
- 12. Webhooks — Sendaro звъни на теб
- 13. Подписана количка (цените от твоя сървър)
- 14. Тестов режим
Едно поставяне — какво прави
Редът със скрипта прави четири неща, без да пипа нищо друго по сайта ти:
- 1.Намира бутоните, които си маркирал, и поема клика върху тях.
- 2.Отваря касата на Sendaro върху страницата — с твоите куриери, офиси, карта и цени на доставка.
- 3.Създава поръчката и товарителницата, показва номера на купувача.
- 4.Казва на страницата ти какво се е случило (събития), чисти количката ти и препраща към твоята thank-you страница — ако си му дал къде.
data-store) и по един атрибут на бутоните ти. Всичко останало на тази страница е за когато искаш повече.1. Минимална страница, която работи
Копирай това в празен файл, смени ТВОЕТО-ID с ID-то на магазина си от дашборда и отвори файла в браузър. Кликът върху „Поръчай“ отваря касата.
<!doctype html>
<html lang="bg">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Моят магазин</title>
</head>
<body>
<h1>Кожен портфейл</h1>
<p>129,90 €</p>
<button type="button"
data-vsx-checkout
data-vsx-title="Кожен портфейл"
data-vsx-price="129.90"
data-vsx-weight="0.25"
data-vsx-sku="WALLET-BRN">
Поръчай
</button>
<!-- ЕДИНСТВЕНИЯТ ред, който Sendaro иска. Сложи го точно преди </body>. -->
<script src="https://app.sendaro.bg/checkout.js" data-store="ТВОЕТО-ID" defer></script>
</body>
</html>&.2. Кой бутон отваря касата
На собствен сайт правилото е просто: Sendaro отваря касата само на бутоните, които сам си маркирал. Маркировката е един празен атрибут — без стойност, без стил, без нищо друго.
<!-- 1) Обикновен бутон -->
<button type="button" data-vsx-checkout>Поръчай</button>
<!-- 2) Линк (Sendaro спира навигацията сам) -->
<a href="#" data-vsx-checkout>Купи сега</a>
<!-- 3) Дълго име, ако „vsx" ти изглежда странно — прави същото -->
<button type="button" data-sendaro-checkout>Поръчай</button>
<!-- 4) „Не пипай нищо тук" — важи за елемента И за всичко вътре в него -->
<div data-sendaro-ignore>
<button type="button" id="checkout">Вътрешен бутон, който Sendaro пропуска</button>
</div>- •Работи на
<button>,<a>и<input>. Sendaro сам спира навигацията на линковете. - •Бутони, които се появяват по-късно (рисувани от JavaScript, странична количка, модал) се хващат автоматично — не е нужно да правиш нищо.
- •Бутон в
<iframe>не се хваща. Скриптът вижда само своята страница. - •
data-sendaro-ignoreе аварийният изход: слагаш го на елемент и Sendaro пропуска него и всичко вътре в него.
3. Откъде идва продуктът
Касата трябва да знае какво поръчва купувачът. Има три начина да ѝ кажеш — избери един. Ако сложиш повече от един, Sendaro ги пробва точно в този ред:
data-sendaro-cart). Тогава цените изобщо не минават през браузъра и никой не може да ги пипне. Той бие и трите по-долу — виж раздел 13.А. От дашборда — най-сигурното
Ти пазиш продуктите си в Sendaro; страницата подава само SKU. Цената идва от дашборда, значи никой не може да я подправи от браузъра.
<!-- Заглавието, цената, снимката и теглото идват от продуктовия дашборд на Sendaro. -->
<!-- Страницата НЕ може да ги подправи. Изисква data-store на скрипта. -->
<button type="button" data-vsx-checkout data-vsx-product="WALLET-BRN">
Поръчай
</button>
<!-- Няколко реда наведнъж: до 10 SKU / външни ID-та, разделени със запетая -->
<button type="button" data-vsx-checkout data-vsx-product="WALLET-BRN,BELT-40,BOX-01">
Поръчай комплекта
</button>Б. Инлайн, направо на бутона — най-бързото
Нула настройка. Заглавието и цената са задължителни; останалите са по желание.
<button type="button"
data-vsx-checkout
data-vsx-title="Кожен портфейл"
data-vsx-variant="Кафяв"
data-vsx-sku="WALLET-BRN"
data-vsx-price="129.90"
data-vsx-weight="0.25"
data-vsx-image="https://tvoiat-sait.bg/img/portfeil.jpg"
data-vsx-qty="1">
Поръчай
</button>В. Количка с няколко реда — window.VSX_PRODUCT
Когато имаш истинска количка (сесия, localStorage) и клиентът поръчва няколко неща наведнъж.
<button type="button" data-vsx-checkout id="poracha">Поръчай количката</button>
<script>
// Твоята количка (сесия, localStorage, каквото ползваш) → формата, която Sendaro разбира.
// Sendaro чете window.VSX_PRODUCT В МОМЕНТА НА КЛИКА, затова я обновявай при всяка промяна.
function sendaroSyncCart(cart) {
window.VSX_PRODUCT = cart.lines.map(function (l) {
return {
sku: l.sku,
title: l.title,
variant: l.variant || "",
image: l.image || "",
price: (l.priceCents / 100).toFixed(2), // ← ЕВРО с точка, НЕ центове
weight: l.weightGrams / 1000, // ← КИЛОГРАМИ, НЕ грамове
qty: l.qty // 1..99
};
});
}
sendaroSyncCart(myCart); // при зареждане
// …и пак след „добави", „премахни", „смени количеството"
</script>
<script src="https://app.sendaro.bg/checkout.js" data-store="ТВОЕТО-ID" defer></script>window.VSX_PRODUCT в момента на клика, не при зареждане. Обнови я при всяка промяна в количката, иначе купувачът ще поръча старото съдържание.⚠ Мерни единици — тук се греши най-много
data-vsx-price="129.90" → 129,90 €data-vsx-price="12990" → 12 990,00 € — сто пъти повече, и наложеният платеж тръгва такъв.
| Поле | Мерна единица | Пример | Какво значи |
|---|---|---|---|
price | евро, десетична точка | "129.90" | 129,90 €. Приема се и запетая: "129,90". Интервалите се махат. Нула или отрицателна стойност → артикулът се пропуска. |
weight | килограми, за един брой | "0.25" | 250 грама. Sendaro умножава по количеството. Празно → теглото по подразбиране на магазина ти. |
qty | цели бройки | "2" | Ограничава се до 1–99. |
totalEurв събитието | евро, число | 141.64 | Обща сума на поръчката, вече с доставката. Число, не низ. |
Магазинът работи в евро. Ако твоята система пази цени в центове/стотинки, дели на 100 преди да ги подадеш; ако пази теглата в грамове, дели на 1000. Две деления, които спестяват най-скъпата грешка в цялата интеграция.
4. Пълна референция
Атрибути на <script> тага
| Атрибут | Нужен ли е | Какво прави |
|---|---|---|
src | задължително | Винаги https://app.sendaro.bg/checkout.js. Не си го копирай при теб — виж „Чести грешки". |
data-store | задължително | ID на магазина ти от дашборда. Без него касата се отваря, но БЕЗ твоята тема, твоите куриери и цени, а поръчката не влиза в хъба. Работи и като ?store=… в адреса на скрипта. |
data-cart-clear-url | по избор | Адрес на ТВОЯ сайт. Sendaro прави POST там веднага след успешна поръчка, със същите бисквитки (same-origin) → изпразваш количката си. |
data-vsx-success-url | по избор | Твоята thank-you страница. Sendaro пренасочва там с ?order=… (номерът на поръчката). Псевдоним: data-success-url. |
data-buy | по избор | Текст на плаващ бутон „Поръчай", който Sendaro сам рисува върху страницата. Работи само ако на скрипта има и продукт (data-product или data-title + data-price). |
data-position | по избор | Къде стои плаващият бутон: br (по подразбиране, долу вдясно), bl, tr, tl. |
data-productdata-title data-pricedata-image data-weightdata-variant data-skudata-qty data-qty-lock | по избор | Продуктът за плаващия бутон. Sendaro ги пренася върху него като data-vsx-* — значението им е същото като в таблицата по-долу. |
?sendaro_debug=1 | в адреса | Не е атрибут, а параметър в адреса на страницата. Включва подробен лог в конзолата на браузъра: коя тема е заредена, разпознат ли е магазинът, откъде е разчетена количката и с колко реда. Без него касата не пише нищо излишно. Ползвай го, когато цената или продуктът излизат различни от очакваното. |
data-test | само при проба | Стойност "1" → тестов режим: поръчката минава целия път, но куриер не се вика и купувачът не получава SMS. Виж раздел 14. Работи и като ?sendaro_test=1 в адреса на страницата. Махни го, преди да пуснеш сайта на живо. |
data-lang | по избор | Език на видимия текст в касата: bg или en. Без него Sendaro следва <html lang> на страницата ти; всичко различно от en е български. |
data-consent | по избор | Пикселите чакат съгласие по подразбиране. Meta/Google/Klaviyo и сигналът за изоставени кошници тръгват едва когато твоят cookie банер извика Sendaro.consent({ marketing: true }). Ако банерът ти се изпълнява ПРЕДИ касата (типично за CMP в <head>), сложи window.SENDARO_CONSENT = { marketing: true } — четем и него. Имаш друго основание и не искаш да чакаме? data-consent="off" (или window.SENDARO_CONSENT_NOT_REQUIRED = true). Стойността "required" продължава да работи и значи същото като подразбирането. |
nonce | само при строг CSP | Ако сайтът ти работи с 'nonce-…', сложи същия nonce и на този таг. Sendaro го пренася върху всеки стил и скрипт, който създава → не ти трябва 'unsafe-inline'. |
data-proxy | само Shopify | Подпътят на App Proxy. По подразбиране sendaro. На собствен сайт не се пипа. |
Атрибути на бутона
| Атрибут на бутона | Стойност | Какво прави |
|---|---|---|
data-vsx-checkout | празен | Този бутон отваря касата на Sendaro. Слага се на всеки бутон „Купи / Поръчай / Към плащане". |
data-sendaro-checkout | празен | Точно същото — по-дългото име, ако предпочиташ него. |
data-sendaro-cart | подписан токен | Количка, подписана от твоя сървър (раздел 13). Бие всички останали източници: цените се четат от токена, не от страницата. Псевдоним: data-vsx-cart. |
data-vsx-product | SKU или външно ID | Продуктът се тегли от твоя продуктов дашборд (заглавие, цена, снимка, тегло). До 10, разделени със запетая. Изисква data-store. Страницата не може да подправи цената. |
data-vsx-title | текст | Заглавие на артикула. Задължително за инлайн продукт. |
data-vsx-price | число в евро | 129.90 = 129,90 €. Приема се и запетая (129,90). Интервалите се махат. Задължително за инлайн продукт. Трябва да е по-голямо от нула. |
data-vsx-weight | число в килограми | Тегло на ЕДИН брой. 0.25 = 250 грама. Sendaro сам умножава по количеството. Празно → магазинът ползва теглото по подразбиране. |
data-vsx-image | адрес на снимка | Показва се в списъка с артикули в касата. |
data-vsx-variant | текст | Вариант („Кафяв", „XL", „42") — влиза в името на артикула в поръчката. |
data-vsx-sku | текст | Твоят идентификатор на артикула. Влиза в поръчката. |
data-vsx-qty | цяло число | Количество. Ограничава се до 1–99. По подразбиране 1. |
data-vsx-qty-lock | "1" | Купувачът НЕ може да променя количеството в касата. |
data-sendaro-ignore | празен | Sendaro не пипа този елемент, нито каквото и да е вътре в него. Слага се на бутони и контейнери, които не са „поръчай". |
data-sendaro-hide | празен | Обратната посока: елементът се скрива автоматично, щом касата на Sendaro е на сайта. За стар текст, който вече не важи („ще ви потърсим по телефона, за да потвърдим" и подобни) — маркираш го и той изчезва сам, без да триеш код. Махнеш ли скрипта, текстът просто се показва отново. |
data-sendaro-success | празен | Слага се на контейнер на thank-you страницата (тази от data-vsx-success-url). Пристигне ли купувач с ?order=… в адреса, Sendaro сам изписва вътре „Благодарим! Поръчка №… е приета" на езика на касата — не пишеш и не поддържаш свой текст. Без ?order= не пипа нищо: каквото е в контейнера, остава резервен текст за директно отваряне. |
data-vsx-orders | празен | Отваря панела „Моите поръчки" (клиентът влиза с код на имейл). Псевдоним: data-sendaro-orders. |
Глобални променливи
| Глобална променлива | Стойност | Какво прави |
|---|---|---|
window.VSX_PRODUCT | обект или масив от обекти | Количката, когато няма как да сложиш атрибути по бутона. Полета: title price weight qty image variant sku id. Същите мерни единици: цената в евро, теглото в кг. Чете се в момента на клика. |
window.SENDARO_PRODUCT | същото | Псевдоним на горното. |
window.VSX_STORE | ID на магазина | Резерва за data-store, ако системата ти не дава да сложиш атрибут на скрипта. Задава се преди реда с checkout.js. |
window.SENDARO_STORE | същото | Псевдоним на горното. |
window.SENDARO_CART_URL | адрес | Истинският адрес на твоята количка. Ако купувачът затвори касата, без да поръча, се връща там. Без него Sendaro пробва /cart/. |
window.SENDARO_NO_TRACK | true | Спира сигнала за изоставени кошници (напр. докато клиентът не е приел бисквитките ти). Всичко останало работи както обикновено. |
5. Събития към твоята страница
Sendaro съобщава на страницата ти какво се случва. Събитията се пускат върху document и се качват нагоре, така че можеш да ги слушаш и на window. Никога не хвърлят грешка към твоя код.
| Събитие | Кога | e.detail |
|---|---|---|
sendaro:ready | Скриптът се е зареден и бутоните са закачени. | { store } — ID на магазина или null. |
sendaro:open | Касата се отвори пред купувача. | { store } |
sendaro:order | Поръчката е приета от Sendaro. | { orderNumber, totalEur, waybill } — номерът е низ (напр. "#1410"), сумата е число в евро, товарителницата е низ. Всяко от трите може да е null. |
<script>
// Виджетът е зареден и бутоните са закачени.
document.addEventListener("sendaro:ready", function (e) {
console.log("Sendaro е готов, магазин:", e.detail.store);
});
// Купувачът отвори касата (добро място за „InitiateCheckout" в собствената ти статистика).
document.addEventListener("sendaro:open", function (e) {
console.log("касата се отвори", e.detail.store);
});
// Поръчката е приета от Sendaro.
document.addEventListener("sendaro:order", function (e) {
var d = e.detail || {};
console.log(d.orderNumber); // "#1410" низ или null
console.log(d.totalEur); // 141.64 число (евро) или null
console.log(d.waybill); // "1055183955219" низ или null
});
</script>6. Чистене на количката
След успешна поръчка твоята количка трябва да се изпразни — иначе купувачът се връща и вижда същите артикули. Дай на Sendaro адрес и той ще го извика вместо теб.
<script src="https://app.sendaro.bg/checkout.js"
data-store="ТВОЕТО-ID"
data-cart-clear-url="/api/cart-clear.php"
defer></script><?php
// /api/cart-clear.php — Sendaro прави POST тук веднага след успешна поръчка.
// Адресът е ОТНОСИТЕЛЕН → същият домейн → сесийната бисквитка пътува с заявката.
session_start();
$_SESSION["cart"] = [];
header("Content-Type: application/json; charset=utf-8");
echo json_encode(["ok" => true]);same-origin). Затова адресът трябва да е на твоя домейн — най-добре относителен. Sendaro не чака отговор: ако нещо се обърка при теб, поръчката пак минава.7. Thank-you страница
По подразбиране купувачът вижда екрана „поръчката е приета“ вътре в касата. Ако искаш своя страница — кажи адреса ѝ.
<script src="https://app.sendaro.bg/checkout.js"
data-store="ТВОЕТО-ID"
data-cart-clear-url="/api/cart-clear.php"
data-vsx-success-url="/blagodarim.php"
defer></script><?php
// /blagodarim.php — Sendaro отваря този адрес като /blagodarim.php?order=%231410
// ⚠ Стойността идва от адресната лента. Показвай я ЕКРАНИРАНА и никога не я приемай
// като доказателство за платена поръчка.
$order = isset($_GET["order"]) ? htmlspecialchars($_GET["order"], ENT_QUOTES, "UTF-8") : "";
?>
<h1>Благодарим!</h1>
<?php if ($order !== ""): ?>
<p>Поръчка <strong><?= $order ?></strong> е приета. Ще получиш SMS с номера на пратката.</p>
<?php else: ?>
<p>Поръчката е приета.</p>
<?php endif; ?>data-vsx-success-url Sendaro отива на началната страница (ако адресът съдържа /cart или /checkout) или просто презарежда текущата.8. Записване на поръчката при теб
Поръчката вече е в Sendaro — товарителницата е направена, SMS-ът тръгва, куриерът я вижда. Този раздел е само ако искаш копие и в собствената си база (за твоя списък „моите поръчки“, за статистика, за счетоводството ти).
<script>
document.addEventListener("sendaro:order", function (e) {
var d = e.detail || {};
fetch("/api/sendaro-order.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "same-origin", // сесийната ти бисквитка пътува
keepalive: true, // заявката оцелява при бърз redirect
body: JSON.stringify({
orderNumber: d.orderNumber,
totalEur: d.totalEur,
waybill: d.waybill
})
}).catch(function () {}); // никога не чупи касата
});
</script><?php
// /api/sendaro-order.php — записва номера на поръчката при теб и изпразва количката.
header("Content-Type: application/json; charset=utf-8");
session_start();
$raw = file_get_contents("php://input");
$data = json_decode($raw, true);
if (!is_array($data) || empty($data["orderNumber"])) {
http_response_code(400);
echo json_encode(["ok" => false, "error" => "няма номер на поръчка"]);
exit;
}
$order = [
"number" => substr((string) $data["orderNumber"], 0, 40),
"total" => isset($data["totalEur"]) ? (float) $data["totalEur"] : null,
"waybill" => isset($data["waybill"]) ? substr((string) $data["waybill"], 0, 40) : null,
"items" => isset($_SESSION["cart"]) ? $_SESSION["cart"] : [],
"created" => date("c"),
];
file_put_contents(
__DIR__ . "/../data/orders.jsonl",
json_encode($order, JSON_UNESCAPED_UNICODE) . "\n",
FILE_APPEND | LOCK_EX
);
$_SESSION["cart"] = []; // количката е поръчана
echo json_encode(["ok" => true]);9. Готов CSP блок
Ако сайтът ти има Content-Security-Policy (а ако няма — този раздел не ти трябва), ето кое трябва да е разрешено. Всичко на касата се зарежда от един чужд домейн — този на Sendaro.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://app.sendaro.bg;
style-src 'self' 'unsafe-inline' https://app.sendaro.bg;
img-src 'self' data: https://app.sendaro.bg;
connect-src 'self' https://app.sendaro.bg;
font-src 'self' data:;
form-action 'self';
base-uri 'self';| Домейн | Кога се зарежда | За какво |
|---|---|---|
app.sendaro.bg | винаги | Самият скрипт, стиловете на картата, знамената за телефонния код, логата на куриерите, заявките към Sendaro. |
connect.facebook.netwww.googletagmanager.comstatic.klaviyo.com | само ако си въвел съответния пиксел в дашборда | Маркетинг пиксели. Без въведен пиксел не се зарежда нищо от тях. |
# ДОБАВИ САМО РЕДОВЕТЕ ЗА ПИКСЕЛА, КОЙТО НАИСТИНА СИ ВКЛЮЧИЛ В ДАШБОРДА.
# Изключен пиксел = нула заявки → не отваряй домейни „за всеки случай".
# Meta / Facebook пиксел
script-src … https://connect.facebook.net;
img-src … https://www.facebook.com;
connect-src … https://www.facebook.com;
# Google Ads / GA4 (gtag)
script-src … https://www.googletagmanager.com;
img-src … https://www.google.com https://googleads.g.doubleclick.net;
connect-src … https://www.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com;
# Klaviyo
script-src … https://static.klaviyo.com;
connect-src … https://a.klaviyo.com;'unsafe-inline' в style-src е нужен, защото касата слага стиловете си като вграден блок и ползва inline style атрибути. data: в img-src е за малките иконки в самата каса.'unsafe-inline'? Не се налага. Сложи своя nonce на реда със скрипта — Sendaro го пренася върху всеки стил и скрипт, който създава в страницата ти.<!-- Строг CSP със стойност вместо 'unsafe-inline': -->
<!-- style-src 'self' 'nonce-r4nd0m'; script-src 'self' 'nonce-r4nd0m' https://app.sendaro.bg; -->
<script src="https://app.sendaro.bg/checkout.js"
data-store="ТВОЕТО-ID"
nonce="r4nd0m"
defer></script>
<!-- Стойността на nonce трябва да е НОВА при всяко зареждане на страницата -->
<!-- и да съвпада с тази в хедъра. Сървърът ти я генерира. --># Бутонът „Локализирай ме" (най-близък офис) ползва геолокацията на браузъра.
# Ако сайтът ти забранява геолокация, бутонът просто няма да работи.
Permissions-Policy: geolocation=(self)10. Чести грешки
Всяка от тези е била истински случай при истинска интеграция. Първите две струват пари.
Цената е сложена в центове
Слагаш data-vsx-price="12990", защото системата ти пази цените в центове. Sendaro чете това като 12 990 € — 100 пъти повече. Купувачът вижда абсурдна сума, а наложеният платеж тръгва грешен.
→Поправката: Дели на 100 преди да я подадеш: data-vsx-price="129.90". Същото важи за теглото — data-vsx-weight="750" означава 750 килограма, не 750 грама. Правилното е 0.75.
Липсва data-store
Касата се отваря и изглежда наред, затова грешката се вижда чак когато търговецът каже „поръчах, а в дашборда няма нищо". Без data-store няма твоята тема, няма твоите куриери и цени, data-vsx-product мълчи, а поръчката не влиза в хъба ти.
→Поправката: Сложи data-store="ТВОЕТО-ID" на реда със скрипта (или window.VSX_STORE преди него). Копирай само ID-то от дашборда — не целия адрес и не ред с параметри след &.
Бутонът не отваря касата
Кликът просто не прави нищо или отвежда на стария адрес. Четирите обичайни причини: (1) на бутона липсва data-vsx-checkout; (2) бутонът е вътре в елемент с data-sendaro-ignore; (3) бутонът живее в <iframe> — Sendaro не вижда вътре в чужди рамки; (4) твой собствен onclick спира събитието преди Sendaro.
→Поправката: Провери с реда от „Бърза проверка" по-долу колко бутона намира Sendaro. Ако е 0 — атрибутът липсва или е в рамка. Бутони, които се рисуват от JS по-късно, се хващат автоматично, не се притеснявай за тях.
Кеширан стар скрипт
Копирал си checkout.js при себе си или си го сложил в свой bundle → застива на версията от онзи ден и подминава всяка поправка. Или CDN-ът ти (Cloudflare и подобни) кешира HTML-а и сервира стария data-store.
→Поправката: Зареждай скрипта винаги от https://app.sendaro.bg/checkout.js. Прочисти кеша на CDN-а, презареди с Ctrl+F5 и виж в „Network" (F12) че файлът наистина се тегли от app.sendaro.bg.
Редът е сложен два пъти
Веднъж ръчно в шаблона и веднъж от плъгин/билдър. Sendaro се пази сам и вторият път не се изпълнява, но настройките се четат от тага, който е тръгнал пръв — а той може да е „голият", без твоите атрибути.
→Поправката: Остави един ред с checkout.js в целия сайт и всички атрибути на него. Търси го в шаблона на футъра, в хедъра и в настройките на темата си.
„Thank-you страницата не се отваря"
Поръчката минава, но купувачът си остава на екрана „поръчката е приета" и никъде не отива.
→Поправката: Така и трябва: пренасочването към data-vsx-success-url става, когато купувачът затвори този екран. Без атрибута Sendaro отива на началната страница (ако си на /cart или /checkout) или презарежда текущата.
Количката не се изпразва
Купувачът поръчва, връща се на сайта и количката още е пълна.
→Поправката: Провери, че data-cart-clear-url сочи адрес на твоя домейн (най-добре относителен: /api/cart-clear.php). Към чужд домейн бисквитките ти не пътуват и сесията не се намира.
11. Бърза проверка преди пускане
Три реда в конзолата на браузъра (F12 → Console) отговарят на 90% от въпросите „защо не работи“:
// Пусни това в конзолата на браузъра (F12 → Console) на страницата с бутона.
document.querySelectorAll("[data-vsx-checkout],[data-sendaro-checkout]").length
// 0 → Sendaro няма какво да хване: атрибутът липсва или бутонът е в <iframe>.
document.querySelector("script[src*='checkout.js']").getAttribute("data-store")
// null или "" → липсва data-store. Поръчките няма да влязат в твоя магазин.
document.addEventListener("sendaro:ready", function (e) { console.log("OK", e.detail); });
// Презареди страницата. Няма ли „OK" → скриптът не се е заредил изобщо.И списъкът, който минаваш преди да пуснеш сайта на живо:
12. Webhooks — Sendaro звъни на теб
Събитията от раздел 5 идват от браузъра на купувача: удобни за интерфейса, но могат и да не се случат (човекът затваря раздела) и всеки може да ги подправи. Webhook-ът е другата посока — нашият сървър звъни на твоя, с подпис. Това е сигналът, на който може да стъпи склад, фактура и счетоводство.
- 1.В дашборда отваряш Webhooks → Добави адрес и въвеждаш адрес на твоя сайт (само
https://, публичен домейн). - 2.Показва ти се тайната (
whsec_…) — един-единствен път. Копирай я в конфигурацията на сървъра си сега; после се вижда само последните ѝ 4 знака. Забравиш ли я — бутонът „Нова тайна“ прави друга (старата спира да работи веднага). - 3.Бутонът Тест на реда прави истински POST с демо поръчка — виждаш отговора на екрана, без да чакаш първата истинска поръчка.
Каталогът от твоя сървър
Сървърното препотвърждаване на цените (по-горе) работи само за продукти, които ние познаваме. За да ги познаваме, подай ги веднъж — и после при всяка промяна на цена или наличност:
$telo = json_encode(["products" => [
["sku" => "KAT-100", "title" => "Палетна количка 2500 кг", "priceEur" => 207.00,
"weightKg" => 78.5, "stockQty" => 4, "imageUrl" => "https://tvoiat-sait.bg/img/kat100.jpg"],
]], JSON_UNESCAPED_UNICODE);
$chas = time();
$podpis = "sha256=" . hash_hmac("sha256", $chas . "." . $telo, SENDARO_INT_KEY);
$ch = curl_init("https://app.sendaro.bg/api/catalog");
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_POSTFIELDS => $telo, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Sendaro-Store: " . SENDARO_STORE,
"X-Sendaro-Timestamp: " . $chas,
"X-Sendaro-Signature: " . $podpis,
],
]);
$otgovor = json_decode(curl_exec($ch), true); // created / updated / skipped| Поле | Задължително | Какво е |
|---|---|---|
sku | да | Твоят код на продукта. Той е ключът — по него намираме реда и следващия път го обновяваме, вместо да създаваме втори. Ред без sku се пропуска. |
title | да | Име, каквото искаш да види купувачът в касата. |
priceEur | да | Цена в евро — приема и точка, и запетая. Тя става авторитетната: по-ниска цена, подадена от браузъра, вече не минава. |
stockQty | по избор | Наличност в брой. Не подадеш ли полето — не се пипа. Подадеш ли празно, значи „не следя наличност". Работи само при магазини без собствен склад (Друг сайт, Simvoly) — при Shopify/WooCommerce складът е техен. |
weightKg · lengthCm · widthCm · heightCm | по избор | Тегло и размери за куриера. Без тегло доставката се смята по фиксиран 1 кг и при едра стока доплащаш разликата. |
imageUrl | по избор | Снимка — само https://. |
barcode | по избор | Баркод за пакетиране и печат. |
created, updated и skipped — с причина за всеки пропуснат. Няма мълчаливо изгубени продукти. До 500 реда на заявка; повече качвай на партиди.<час>.<сурово тяло> с ключа за интеграция. Кодът, който вече си написал за количката, върши работа и тук.Версии и заключване (SRI)
По подразбиране скриптът е /checkout.js — обновява се сам, включително с поправките по сигурността. За повечето сайтове това е правилният избор.
Ако правилата ти изискват Subresource Integrity или искаш сам да решаваш кога се обновява касата, ползвай замразена версия. Заключване и авто-обновяване се изключват взаимно — не можеш да имаш и двете:
- A.
/v1/checkout.js— стабилен адрес, съдържанието се обновява в рамките на голяма версия 1. Безintegrity. - Б.
/v/1.0.0/checkout.js— замразено копие. Байтовете никога не се променят, затоваintegrityработи. Не получава нищо автоматично — обновяваш, когато решиш.
Кои версии съществуват и с какъв хеш: /v/versions.json. Оттам вземи стойността sri и я сложи така:
<script src="https://app.sendaro.bg/v/1.0.3/checkout.js"
integrity="sha384-JiTEqfl2+HUlK4ZIn/H6IkROFELpK7hpQSr6qC6Jm7It9eilj3BVIyjF0XR8k1ir"
crossorigin="anonymous"
data-store="ТВОЯТ_КЛЮЧ" defer></script>/v/versions.json — иначе оставаш на стар код, включително при поправка по сигурността.Най-краткият път: готовият файл
Долното в тази глава описва как се проверява подпис на ръка — прочети го, за да знаеш какво става. Но не е нужно да го пишеш сам: дашбордът дава файла готов, с твоите ключове вътре.
- 1.Webhooks → „Свали готовия файл“. Не е нужно да си добавял адрес преди това — бутонът сам създава адреса по стандартния път на домейна ти. Получаваш
sendaro-hook.php— проверката на подписа, отказът на стари заявки, дедупликацията поX-Sendaro-Deliveryи дневникът са вече вътре. - 2.Качваш го в главната папка на сайта си — там, където е
index.php. - 3.„Намери го“ — Sendaro обхожда обичайните адреси на домейна ти, разпознава файла по маркера, който той връща на
GET, записва адреса и веднага праща истинско пробно известие. Зелено означава, че сървърът ти наистина е отговорил.
Твоят код влиза на едно място във файла — функцията sendaro_obrabotka($sabitie, $poracka, $test) най-долу. Дотогава всяко известие се записва ред по ред в sendaro-porachki.jsonl до файла, така че виждаш какво пристига още преди да си написал един ред.
.txt — PHP файл не издава кода си на браузъра, но текстов файл го издава. Всяко сваляне прави нова тайна: старият файл спира да важи в мига, в който качиш новия.Какво пристига при теб
POST /sendaro-hook HTTP/1.1
Host: moiat-sait.bg
Content-Type: application/json
X-Sendaro-Event: order.created
X-Sendaro-Delivery: cm3f8k0a20001x9y7q2v1b8kd
X-Sendaro-Timestamp: 1755780000
X-Sendaro-Signature: sha256=9f2b… (64 шестнайсетични знака)
User-Agent: Sendaro-Webhooks/1
{"event":"order.created","createdAt":"…","order":{…}}| Заглавка | Стойност | За какво ти е |
|---|---|---|
X-Sendaro-Event | името на събитието | Кое се е случило — виж таблицата по-долу. |
X-Sendaro-Delivery | номер на доставката | Един и същ при всички повторни опити. Пази го и пропускай известие, което вече си обработил — иначе едно и също количество може да падне два пъти от склада. |
X-Sendaro-Timestamp | unix секунди | Часът на изпращане. Влиза в подписа. Отказвай всичко по-старо от 5 минути. |
X-Sendaro-Signature | sha256= + подпис | HMAC-SHA256 върху <час>.<тяло> с твоята тайна, в шестнайсетичен вид. Точно това проверяват примерите по-долу. |
<timestamp>.<тяло> — с точка между тях. Само тялото би позволило „презапис“: записваш днешната заявка и я пускаш пак утре с валиден подпис.| Събитие | Кога точно тръгва | За какво ти е |
|---|---|---|
order.created | В мига, в който поръчката се запише при нас — и по двата пътя на касата: и когато магазинът ти е на готова платформа, и когато сайтът е твой собствен. | Ако товарителницата е издадена в същия миг, номерът ѝ вече е в тялото. |
order.paid | Когато поръчката стане платена: при потвърдено картово плащане, и при синхронизация с магазина ти, която я отбелязва като платена. | Тук пускаш фактурата. При наложен платеж не го чакай в момента на поръчката — isCod е true, а парите идват при доставката. |
order.cancelled | Когато поръчката стане отказана: при отказ в опашката „Потвърждение“ в дашборда (клиентът се отказа или номерът е грешен), и при синхронизация, която я анулира. | Тук връщаш количествата в склада и спираш документите. |
order.delivered | Когато проверката на куриерския статус види, че пратката е доставена — часът на доставката идва от куриера, затова събитието тръгва точно веднъж, дори статусът после да се промени. | Тук затваряш поръчката: наложеният платеж е събран, стоката е при клиента. |
order.returned | Когато проверката на куриерския статус види, че пратката е върната на подателя (непотърсена или отказана при доставка). | Тук връщаш количеството в склада и знаеш, че наложеният платеж няма да дойде. |
waybill.created | Когато поръчката получи номер на товарителница: при издаване от дашборда — поединично или наведнъж за няколко поръчки — и при синхронизация, която донася номера. | Тялото носи waybill и waybillTrackUrl — с тях пращаш „пратката тръгна“. |
order.paid. А ако нещо все пак поиска едно и също два пъти, отдолу има втора мрежа: ключът (адрес, събитие, поръчка) е уникален и вторият POST просто не се записва. Двойно известие за едно и също е невъзможно.waybill.created няма — първото известие вече е стигнало при теб. Новият номер се вижда в поръчката в дашборда; ако при теб трябва да е винаги най-актуалният, вземай го оттам, а не от известието.Тялото
{
"event": "order.created",
"createdAt": "2026-08-21T09:14:03.512Z",
"order": {
"id": "cm3f8k0a20001x9y7q2v1b8kd",
"orderNumber": "#1410",
"externalId": "6012345678901",
"storeId": "cm2x9d0110000abcd1234efgh",
"status": "NEW",
"isTest": false,
"financialStatus": "pending",
"isCod": true,
"totalEur": 141.64,
"goodsEur": 135.9,
"shippingEur": 5.74,
"discountEur": 0,
"currency": "EUR",
"customer": {
"name": "Иван Петров",
"phone": "0888123456",
"email": "ivan@example.com"
},
"delivery": {
"type": "OFFICE",
"city": "София",
"officeName": "Офис Младост",
"address": null,
"courier": "ECONT"
},
"items": [
{ "title": "Кожен портфейл (Кафяв)", "sku": "WALLET-BRN", "quantity": 1, "priceEur": 129.9 }
],
"waybill": "1055183955219",
"waybillTrackUrl": "https://app.sendaro.bg/track?n=1055183955219"
}
}| Поле | Тип | Какво е |
|---|---|---|
event | текст | Същото като заглавката X-Sendaro-Event. |
order.id | текст | Нашият номер на поръчката. Стабилен — по него можеш да я намериш и после. |
order.orderNumber | текст | Видимият номер, който купувачът вижда (напр. "#1410"). |
order.isTest | да/не | true = тестова поръчка. Не пипай склад, не издавай документи. Виж раздел 14. |
order.isCod | да/не | Наложен платеж. При false парите вече са платени по друг път. |
order.totalEurgoodsEur shippingEur discountEur | числа | Евро, не центове. Общо, стока, доставка, отстъпка. currency е винаги "EUR". |
order.customer | обект | name, phone, email — всяко може да е null. |
order.delivery | обект | type (OFFICE/DOOR/…), city, officeName, address, courier. |
order.items[] | списък | По артикул: title, sku, quantity, priceEur. |
order.waybillwaybillTrackUrl | текст или null | Номер на товарителницата и линк за проследяване, ако вече са налични. |
Проверка на подписа — PHP
<?php
// /sendaro-hook.php — адресът, който си въвел в дашборда (Webhooks → Добави адрес).
// Тайната е тази, която ти се показа ВЕДНЪЖ при създаването. Пази я като парола.
$SECRET = getenv("SENDARO_WEBHOOK_SECRET");
// 1) СУРОВОТО тяло — точно както е дошло. Никакъв json_decode преди проверката!
$raw = file_get_contents("php://input");
$ts = isset($_SERVER["HTTP_X_SENDARO_TIMESTAMP"]) ? $_SERVER["HTTP_X_SENDARO_TIMESTAMP"] : "";
$sig = isset($_SERVER["HTTP_X_SENDARO_SIGNATURE"]) ? $_SERVER["HTTP_X_SENDARO_SIGNATURE"] : "";
// 2) ЧАСЪТ. По-стар (или по-нов) от 5 минути → отказ. Това спира „презапис“: записана
// стара заявка, пусната пак утре, вече няма да мине, макар подписът ѝ да е верен.
if (!ctype_digit((string) $ts) || abs(time() - (int) $ts) > 300) {
http_response_code(401); echo "stale"; exit;
}
// 3) ПОДПИСЪТ: HMAC-SHA256 върху „<час>.<тяло>" с тайната, с представка „sha256=".
$expected = "sha256=" . hash_hmac("sha256", $ts . "." . $raw, $SECRET);
if (!hash_equals($expected, $sig)) { // ⚠ hash_equals, НЕ == и НЕ ===
http_response_code(401); echo "bad signature"; exit;
}
// 4) Чак СЕГА тялото е доверено.
$data = json_decode($raw, true);
$event = isset($_SERVER["HTTP_X_SENDARO_EVENT"]) ? $_SERVER["HTTP_X_SENDARO_EVENT"] : "";
$id = isset($_SERVER["HTTP_X_SENDARO_DELIVERY"]) ? $_SERVER["HTTP_X_SENDARO_DELIVERY"] : "";
$order = isset($data["order"]) && is_array($data["order"]) ? $data["order"] : [];
// 5) ЕДИН ПЪТ. Същото известие може да дойде пак (повторен опит след мрежов проблем).
// X-Sendaro-Delivery е един и същи при всички опити → ползвай го за отпечатък.
$dir = __DIR__ . "/../data/sendaro-seen";
$seen = $dir . "/" . preg_replace("/[^A-Za-z0-9_-]/", "", $id);
if ($id !== "" && file_exists($seen)) { http_response_code(200); echo "duplicate"; exit; }
// 6) ТЕСТОВА ПОРЪЧКА → нищо реално. Без склад, без фактура, без товар за счетоводството.
if (!empty($order["isTest"])) { http_response_code(200); echo "test ok"; exit; }
// ── тук е твоята работа: сваляш количества, издаваш фактура, записваш поръчката ──
// $order["orderNumber"], $order["totalEur"], $order["items"], $order["customer"] …
@mkdir($dir, 0775, true);
@file_put_contents($seen, $event);
http_response_code(200); // 2xx = прието. Всичко друго връща известието в опашката.
echo "ok";Проверка на подписа — Node / Express
const express = require("express");
const crypto = require("crypto");
const app = express();
const SECRET = process.env.SENDARO_WEBHOOK_SECRET;
const seen = new Set(); // за пример; в истински проект — база или Redis
// ⚠ express.raw, НЕ express.json. Подписът е върху тялото КАКТО Е ДОШЛО; express.json го
// парсва, а JSON.stringify после го връща различен (интервали, ред на полетата) и
// подписът пада „необяснимо". Това е причина №1 за „при мен не работи".
app.post("/sendaro-hook", express.raw({ type: "*/*", limit: "1mb" }), (req, res) => {
const raw = req.body.toString("utf8");
const ts = String(req.get("X-Sendaro-Timestamp") || "");
const sig = String(req.get("X-Sendaro-Signature") || "");
// 1) часът: максимум 5 минути разлика
if (!/^[0-9]+$/.test(ts) || Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > 300) {
return res.status(401).send("stale");
}
// 2) подписът, сравнен по ПОСТОЯННО ВРЕМЕ (timingSafeEqual, не ===)
const expected = "sha256=" + crypto.createHmac("sha256", SECRET)
.update(ts + "." + raw).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(sig);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send("bad signature");
}
// 3) чак сега тялото е доверено
const data = JSON.parse(raw);
const id = req.get("X-Sendaro-Delivery") || "";
const order = data.order || {};
if (id && seen.has(id)) return res.status(200).send("duplicate"); // повторен опит
if (id) seen.add(id);
if (order.isTest) return res.status(200).send("test ok"); // тестова поръчка
// 4) отговори БЪРЗО (чакаме до 12 секунди), а работата свърши после
res.status(200).send("ok");
setImmediate(() => {
// сваляне от склад, фактура, запис в счетоводството…
});
});
app.listen(3000);express.json() вместо express.raw(). Парснатото и после пак сглобено тяло се различава с един интервал или с реда на полетата — подписът пада, а причината изглежда мистична. Подписвай и проверявай суровия низ.Правилата на доставката
| Правило | Стойност |
|---|---|
| Прието е | Кой да е код от 200 до 299. Всичко останало (включително 3xx пренасочване) се брои за неуспех — не следваме пренасочвания. |
| Колко чакаме | 12 секунди. Отговори бързо и свърши работата после — иначе бавната ти фактура ще изглежда като счупен адрес. |
| Повторни опити | 6 опита общо, с паузи 1, 5, 20, 60 и 180 минути. Последният е около 4 часа след събитието. Повтаряме и при 4xx — временно счупен адрес (изтекъл ключ, 401) не бива да ти струва поръчката. |
| След това | Известието се отбелязва като неуспяло и се вижда в дневника на екрана „Webhooks“ (последните 10 на адрес, със статуса и кода на отговора). |
| Размер на отговора | Четем най-много 64 KB. Отговаряй кратко — ok е напълно достатъчно. |
| Ред | Не е гарантиран. Ако получиш две известия за една поръчка, вярвай на това с по-нов createdAt. |
== (ползвай hash_equals / timingSafeEqual); да парсваш тялото преди проверката; и да обработваш едно и също X-Sendaro-Delivery два пъти. Тайната стои в променлива на средата или в конфигурационен файл извън публичната папка — никога в HTML или в git.13. Подписана количка — цените идват от твоя сървър
Дотук цената пътува през страницата: data-vsx-price или window.VSX_PRODUCT. Това е удобно, но всеки може да отвори инструментите на браузъра и да я смени на 0,01 € — и после да поръча с наложен платеж. За отстъпките Sendaro отдавна не вярва на браузъра; с този раздел същото важи и за артикулите.
Идеята е проста: твоят сървър подписва количката и получава кратък токен. Бутонът носи само токена. При поръчка ние четем артикулите и цените от токена — страницата вече не е източник на истината.
- 1.Сървърът ти прави
POST https://app.sendaro.bg/api/cart, подписан с твоя ключ за интеграция. - 2.Отговорът носи токен:
{ "ok": true, "cart": "…" }. - 3.Слагаш го на бутона:
data-sendaro-cart="<токен>". - 4.Купувачът поръчва → сървърът разчита количката от токена и смята по нея.
ik_ и се показва само веднъж. Нов ключ убива стария веднага — смени го и в кода си.Сървърът ти подписва и праща количката
<?php
// /lib/sendaro-cart.php — сървърът ти подписва количката и получава токен за бутона.
// Ключът е от дашборда: Webhooks → „Ключ за сървърна интеграция" → Създай ключ (започва с ik_).
function sendaro_cart_token(array $items, $note = null, $ref = null) {
$STORE = getenv("SENDARO_STORE"); // ID на магазина (същото като data-store)
$KEY = getenv("SENDARO_INTEGRATION_KEY"); // ключът ik_… — НИКОГА в HTML-а!
// ⚠ Цените са в ЕВРО с точка, теглото е в КИЛОГРАМИ за един брой — както навсякъде тук.
$payload = ["items" => $items];
if ($note !== null) $payload["note"] = $note;
if ($ref !== null) $payload["ref"] = $ref; // твоят номер на количката
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
$ts = (string) time(); // ⚠ часът на ТВОЯ сървър; разлика над 5 минути = отказ
$sig = "sha256=" . hash_hmac("sha256", $ts . "." . $body, $KEY);
$ch = curl_init("https://app.sendaro.bg/api/cart");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body, // ⚠ ТОЧНО този низ — подписан е той, знак по знак
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Sendaro-Store: " . $STORE,
"X-Sendaro-Timestamp: " . $ts,
"X-Sendaro-Signature: " . $sig,
],
]);
$res = curl_exec($ch);
curl_close($ch);
$out = json_decode((string) $res, true);
return (is_array($out) && !empty($out["ok"])) ? $out["cart"] : null; // токенът
}| Какво | Стойност |
|---|---|
| Адрес | POST https://app.sendaro.bg/api/cart |
| Заглавки | X-Sendaro-Store — ID на магазина · X-Sendaro-Timestamp — unix секунди · X-Sendaro-Signature — sha256= + HMAC-SHA256 върху <час>.<сурово тяло> с ключа за интеграция. Същият формат като при webhook-ите — един и същи код ти върши работа и в двете посоки. |
| Тяло | items (задължително, до 60 артикула), по избор note (до 500 знака), ref (твоят номер на количката, до 80 знака) и ttlSec. |
| Артикул | title · sku · variantTitle · image (само http(s)://) · quantity (1–999) · priceEur (или price) · weightKg. Евро и килограми, както в целия документ. |
| Срок | ttlSec (в секунди) — по подразбиране 7200 (2 часа), най-малко 60 секунди, най-много 86400 (24 часа). Подадеш ли стойност извън тези граници, тя се стяга до най-близката; липсваща или безсмислена (нула, отрицателна, текст) → 7200. Подписана вчера цена не бива да важи днес. |
| Лимит | До 60 заявки в минута от един адрес. Подписвай количката, когато страницата се рисува, не при всяко кликване. |
{
"ok": true,
"cart": "eyJ2IjoxLCJzIjoiY20yeDlkMDExMDAwMGFiY2QiLCJleHAiOjE3NTU3ODcyMDAsIml0ZW1z…",
"items": 2,
"totalEur": 207.9,
"expiresInSec": 7200
}expiresInSec е срокът, който наистина е използван — не число за украса. Стойността минава през същото стягане (60 … 86400, по подразбиране 7200) и се връща вече стегната, така че токенът и отговорът не могат да се разминат. Поискаш ли "ttlSec": 5, получаваш "expiresInSec": 60; поискаш ли седмица — "expiresInSec": 86400. Смятай срока на количката по това поле, не по число, преписано в кода ти.Токенът стига до бутона
<?php
require __DIR__ . "/lib/sendaro-cart.php";
// Количката, както я пази твоят сървър (сесия, база — все едно).
$items = [
["title" => "Кожен портфейл", "variantTitle" => "Кафяв", "sku" => "WALLET-BRN",
"quantity" => 1, "priceEur" => 129.90, "weightKg" => 0.25,
"image" => "https://tvoiat-sait.bg/img/portfeil.jpg"],
["title" => "Колан", "sku" => "BELT-40", "quantity" => 2, "priceEur" => 39.00, "weightKg" => 0.30],
];
$token = sendaro_cart_token($items, null, "cart-8871");
?>
<?php if ($token): ?>
<button type="button" data-sendaro-checkout
data-sendaro-cart="<?= htmlspecialchars($token, ENT_QUOTES, "UTF-8") ?>">
Поръчай
</button>
<?php else: ?>
<!-- Токенът не се получи (мрежа, изтекъл ключ). Показваме обикновения бутон, за да не -->
<!-- остане купувачът без каса — но цената пак идва от страницата. -->
<button type="button" data-sendaro-checkout
data-vsx-title="Кожен портфейл" data-vsx-price="129.90" data-vsx-weight="0.25">
Поръчай
</button>
<?php endif; ?>| Отговор | Какво значи |
|---|---|
401 Липсва подпис | Няма X-Sendaro-Store, X-Sendaro-Timestamp или X-Sendaro-Signature. |
401 Магазинът няма ключ | Не си създал ключ за интеграция за този магазин. |
401 Подписът не съвпада | Или ключът е грешен/сменен, или часът на сървъра ти бяга с повече от 5 минути, или си подписал един низ, а си изпратил друг. |
400 Количката е празна | Нито един артикул не мина проверката — най-често липсва цена или количеството е 0. |
ik_…, който остава на сървъра ти.data-vsx-price, data-vsx-product, window.VSX_PRODUCT. А ако токенът е нечетим или изтекъл, касата не пада тихо към цените от страницата: поръчката се отказва с ясно съобщение. Тихото падане би направило цялата защита театър.data-store и за срока си. Рисувай го наново при всяко зареждане на страницата. Кешираш ли го, кеширай го по expiresInSec от отговора (и с малко запас), не по константа в твоя код — иначе или сервираш вече изтекъл токен и купувачът вижда „количката е изтекла — презареди страницата“, или изхвърляш още валиден и подписваш количката без нужда.14. Тестов режим — поръчка без пратка
Първата проверка на интеграцията не бива да струва товарителница. В тестов режим поръчката минава целия път — валидация, цени на доставка, отстъпки, запис, събития, webhook — но куриерът не се вика и купувачът не получава нищо.
<!-- Вариант 1: на реда със скрипта — цялата страница е в тестов режим. -->
<script src="https://app.sendaro.bg/checkout.js"
data-store="ТВОЕТО-ID"
data-test="1"
defer></script>
<!-- Вариант 2: без да пипаш кода — отвори страницата си с този параметър: -->
<!-- https://tvoiat-sait.bg/produkt?sendaro_test=1 -->| Какво | В тестов режим |
|---|---|
| Поръчката | Създава се и се вижда в хъба както всяка друга. Носи признак „тестова“ — той идва при теб в тялото на webhook-а като "isTest": true. |
| Куриер и товарителница | НЕ. Пратка не тръгва, номер не се издава, разход не се прави. |
| SMS до купувача | НЕ. Иначе първата ти проба щеше да стресне непознат човек с чужд номер — и да ти струва пари. |
| Webhook към твоя сървър | ДА — точно него тестваш. Тялото носи "isTest": true. |
| Цени, доставка, отстъпки | Смятат се както при истинска поръчка. Затова тестът има смисъл. |
Събитието sendaro:order | Излъчва се както обикновено. |
| Чистене на количката и thank-you | Работят както обикновено. |
data-test="1", преди да пуснеш сайта на живо. Забравен на реда със скрипта, той тихо превръща всяка истинска поръчка в тестова: клиентът поръчва, ти виждаш поръчка — а товарителница няма и никой нищо не изпраща. Това е най-скъпият начин да се провали пускане.?sendaro_test=1 е за бърза проба на живия сайт, без да пипаш кода (важи само за раздела, който си отворил ти). data-test="1" е за копие на сайта — тестов/staging адрес, който така или иначе не е за клиенти.