Commerce
Produkty to rekordy modelu, koszyk to publiczna przestrzeń mutacji wołana przez Twój sklep, a zamówienia żyją w panelu.
cmssy ma koszyk, zamówienia, rabaty i pipeline zamówień. Nic z tego nie jest osobnym obszarem produktu: produkt to rekord modelu, sklep rozmawia z tym samym API dostawczym co reszta, a biurkiem zamówień jest panel.
Ta możliwość to flaga planu - canUseCart w get_workspace_info - więc odczytaj ją, zamiast zakładać.
Produkty to rekordy
W cmssy nie ma typu "produkt". Tworzysz model - product, plan, ticket, cokolwiek jest Twoim katalogiem - a zakładka Produkt tego modelu deklaruje, które jego pola pełnią które role: nazwa, cena, obraz, SKU, stawka podatku, stan magazynowy i osie wariantów. Rola, której nie ustawisz, rozwiązuje się do pola wyświetlanego modelu albo do konwencjonalnego klucza (price, sku, inventory…), więc zwykły katalog nie potrzebuje żadnego mapowania. Konfiguracja koszyka w workspace wymienia potem tylko modele na sprzedaż jako źródła produktów.
Pole ceny trzyma cenę w domyślnej walucie workspace'u. Ceny w innych walutach nie są polem modelu: ustawia się je osobno dla każdej waluty (zobacz Waluty niżej), a model nie może już wskazać pola waluty.
Agenci czytają rozwiązane role z get_model (product.roles) i ustawiają nadpisania przez update_model.
Dlatego katalog może pochodzić z więcej niż jednego modelu naraz i dlatego ten sam blok, który renderuje dowolny rekord, renderuje produkt.
Czytanie katalogu
Produkt wraca z public.model.records jak każdy inny rekord, z kilkoma polami, które mają sens tylko na produkcie.
variants(limit, offset)- jedna strona wariantów produktu, najwyżej 50;variantCountliczy wszystkie. Produkt z większą liczbą wariantów niż jedna strona czyta następną przez offset.variantPriceRange { min max }- najtańszy i najdroższy wariant, w jednostkach drobnych (4900 = 49,00), null na produkcie bez wariantów. Karta na liście z napisem „od 49,00" czyta to zamiast wariantów.records(sku: "TEE-M")- produkt, który ma ten SKU, jako własny albo na jednym z wariantów. Dodajvariants(sku: "TEE-M", limit: 1), żeby dostać tylko ten wariant - formularz szybkiego zamówienia nie potrzebuje niczego więcej.available- ile sztuk kupujący może zamówić teraz: stan minus to, co rezerwują otwarte koszyki, zsumowane po wariantach. Null, gdy produkt nie śledzi stanu, w tym gdy którykolwiek jego wariant go nie śledzi.records(stockState: "in" | "low" | "out")- filtruje po tej samej liczbie względempublic.siteConfig.lowStockThreshold: niski to więcej niż zero i nie więcej niż próg. Koloruj plakietkę stanu zavailablei tego samego progu, a plakietka nie rozjedzie się z filtrem.
Ukryte pole nie wycieka przez inne. Własny SKU produktu jest dopasowywany tylko wtedy, gdy API dostawcze serwuje pole, które go trzyma. Gdy pole stanu jest ukryte, available i inventory każdego wariantu są null, a stockState jest odrzucany.
Co może wołać sklep
Przestrzeń cart jest publiczna: bez tokenu, w zakresie sesji, i jest jedyną częścią API dostawczego, która zapisuje.
cart.get(workspaceId)- pozycje,subtotal,tax,shippingTotal,discountedTotal,totalGross,currency,pricesIncludeTax,availableShippingMethods,appliedDiscount,taxSummary.cart.addItem-{ recordId, quantity, variantSelections, notes }. Pozycja to referencja do rekordu, nie jego kopia.cart.updateItem,cart.removeItem(itemId),cart.clear.cart.applyDiscount(code),cart.removeDiscount,cart.setShippingMethod(shippingMethodId).cart.merge- scala anonimowy koszyk sesji z koszykiem zalogowanego członka. Wołaj raz, zaraz po uwierzytelnieniu.cart.checkout-{ customerEmail, customerNote, poNumber, shippingAddress, customFields }zamienia koszyk w zamówienie.
Sumy wracają policzone. Nie dodawaj cen pozycji w przeglądarce: tryb podatku, rabaty i wysyłka rozstrzygają się po stronie serwera, a druga implementacja we froncie to druga odpowiedź.
Jedna mutacja koszyka na żądanie. Pola wewnątrz cart { … } to pola obiektu-przestrzeni, nie pola główne, więc GraphQL wykonuje je równolegle - addItem i setShippingAddress w jednym dokumencie ścigają się ze sobą, a świeża sesja wybita przez pierwsze wywołanie nie jest widoczna dla drugiego. Każdy zapis koszyka wysyłaj osobnym żądaniem.
Status zamówienia bez konta
public.order.byToken zwraca jedno zamówienie - orderNumber, items, paymentStatus, fulfillmentStatus, amountPaid, balanceDue, customFields, invoiceUrl, shipments (przewoźnik, numer i link śledzenia, status) - dla tokenu wydanego przy checkoucie. Tak działa strona "śledź zamówienie" dla gościa.
Co konfiguruje workspace
public.siteConfig.publicCart to bezpieczna dla sklepu projekcja ustawień koszyka: defaultCurrency, additionalCurrencies, pricesIncludeTax, taxRates, shippingMethods, productSources, maxItemsPerCart, maxQuantityPerItem, orderFields, enableSavedCarts, enableQuoteRequests. Odczytaj ją raz i pozwól jej kształtować UI - format waluty, limity ilości, to czy w ogóle oferujesz zapytanie ofertowe.
Reguły wysyłki
Każda metoda wysyłki ma własne reguły: kraje, które obsługuje (countries, kody ISO, puste = wszędzie), widełki sumy (minSubtotal / maxSubtotal) oraz freeAbove, sumę, od której jest darmowa. availableShippingMethods na koszyku już je stosuje wobec adresu wysyłki i sumy, więc sklep nigdy nie filtruje metod sam. W dodatkowej walucie metoda używa kwot ustawionych dla tej waluty w sekcji Ceny w innych walutach; metoda bez nich nie jest oferowana koszykom w tej walucie.
Pola zamówień
Własne pola na każdym zamówieniu - dedykacja na prezent, okno dostawy, wewnętrzny numer referencyjny. Definiujesz je raz w Ustawienia → Pola zamówień (albo przez update_cart_config z orderFields), najwyżej 30, każde z kluczem, etykietą per język, typem (text, number, date, select, boolean, email, phone, url) i opcjonalnymi limitami.
Każde pole należy albo do klienta, albo tylko do obsługi. publicCart.orderFields wymienia tylko te, które klient może ustawić: wyrenderuj dla każdego pole formularza i wyślij wartości jako customFields w cart.checkout, po kluczu. Serwer je waliduje - typ, limity, wymagalność - i odrzuca klucz pola obsługi; pusta wartość jest pomijana. Wymagane może być tylko pole, które klient może ustawić, więc pole obsługi nigdy nie blokuje checkoutu.
Wartości są zapisywane na zamówieniu jako customFields. public.order.byToken zwraca tylko pola klienta; panel, get_order i webhooki order.* niosą wszystkie. Obsługa edytuje je na zamówieniu albo przez update_order_details: klucz ustawiony na null czyści wartość. Usunięcie definicji nie rusza wartości zapisanych już na zamówieniach.
Waluty
Workspace ma jedną walutę domyślną, bazę katalogu, i do 9 dodatkowych walut. Obie ustawiasz w Ustawienia → Region i podatki albo przez update_cart_config (defaultCurrency, additionalCurrencies). Waluty domyślnej nie da się zmienić, dopóki istnieją dodatkowe - najpierw je usuń. Waluty, w której wciąż sprzedaje kanał sprzedaży, nie da się usunąć.
Każdy kanał sprzedaży sprzedaje w jednej walucie, domyślnej albo dodatkowej (currency w create_sales_channel i update_sales_channel). Kupujący nigdy nie wybiera waluty: sklep wybiera kanał, a waluta kanału wycenia wszystko. Koszyk, jego zamówienie, wysyłka, rabat, płatności i zwroty są w tej jednej walucie. Zmiana waluty kanału porzuca otwarte na nim koszyki.
Nic nie jest przeliczane. cmssy nie ma kursów walut. Każdą kwotę w dodatkowej walucie ustawiasz sam:
- Produkty - Ceny w walutach przy produkcie w katalogu: cena i progi ilościowe w każdej walucie, a dla produktu z wariantami cena każdego wariantu. Zapis nigdy nie zmienia stanu magazynowego. Agenci używają
set_product_currency_prices, które prosi o potwierdzenie, orazcurrencyPriceswset_product_variants. - Cenniki - jeden wiersz stałej ceny na walutę, a procent cennika schodzi z ceny w tej walucie. Zobacz Cenniki.
- Metody wysyłki - cena, widełki sumy i próg darmowej wysyłki w każdej walucie, w sekcji Ceny w innych walutach.
- Kody rabatowe - Minimum w walutach (
minSubtotalByCurrency) na kodach procentowych i z darmową wysyłką. Kod z minimum działa tylko w walutach, które jego minima wymieniają. Kod o stałej kwocie ma jedną walutę i działa tylko na koszyki w niej.
Produkt bez ceny w walucie kanału nie jest sprzedawany w tym kanale: odczyt cen zwraca go z price: null, wyceniony listing go pomija, a cart.addItem odrzuca go z extensions.reason NO_PRICE_IN_CURRENCY. Ceny w walucie, którą wyłączysz, zostają zachowane; niczego nie wyceniają, dopóki waluty znów nie włączysz.
Zamówienia zachowują swoją walutę. Lista zamówień ma kolumnę i filtr waluty (currency w list_orders), zamówienie ręczne wybiera walutę (currency w create_manual_order), a analityka i wydatki klientów są raportowane w rozbiciu na waluty, nigdy sumowane między walutami.
Waluty w sklepie
Ceny kanału czytaj z public.pricing.prices i public.pricing.catalogPrices z kluczem kanału, a dla zalogowanego kupującego z account.prices i account.catalogPrices. Pole ceny rekordu i variantPriceRange trzymają tylko cenę w walucie domyślnej. Każdy wiersz ceny niesie currency: formatuj każdą kwotę walutą jej własnego wiersza, nigdy zahardkodowaną, i dziel przez liczbę miejsc dziesiętnych tej waluty, nie przez 100. W kanale w dodatkowej walucie currency wyniku ją wskazuje, także na pustej stronie. publicCart wymienia defaultCurrency i additionalCurrencies.
Zamówienia to zaplecze
Wszystko po checkoucie wymaga autoryzacji: panel albo serwer MCP z tokenem, którego rola na to pozwala. Zamówienia można listować i czytać, tworzyć ręcznie, edytować pozycja po pozycji, oznaczać jako opłacone w całości lub częściowo, zwracać, anulować, przeprowadzać przez realizację, wystawiać im fakturę i przesuwać po konfigurowalnym pipelinie. Każda wysłana przesyłka jest zapisywana na zamówieniu przez create_shipment i update_shipment - przewoźnik, numer śledzenia, link śledzenia i status od pending do delivered wraz z historią. Dodanie przesyłki oznacza zamówienie jako zrealizowane, a przy dostawie dzielonej jako częściowo zrealizowane. Rabaty tworzy się, aktualizuje i włącza tak samo; produkty da się zmieniać i usuwać hurtem albo nadać im progi cenowe.
Dostawcy płatności nie są tego częścią: cmssy zapisuje, co się stało - mark_order_paid, record_order_payment, refund_order - a Twoja integracja decyduje, kiedy się stało.
Anonimizacja zamówienia
Przy żądaniu usunięcia danych obsługa z uprawnieniem orders:manage anonimizuje zakończone zamówienie - anulowane, zrealizowane, zwrócone albo ze zwrotem pieniędzy - z jego strony, albo wszystkie zakończone zamówienia jednego klienta ze strony klienta. Email kupującego, adres wysyłki, numer PO, notatki, pola zamówienia i notatki dostawy znikają z zamówienia, jego koszyka, zapisanych dostaw webhooków i historii Spotlight, która je pokazała. Pozycje, kwoty, płatności, faktura i numer zamówienia zostają, więc księgi się zgadzają. Tego nie da się cofnąć, zamówienie w toku jest odrzucane, a webhook order.anonymized mówi Twoim systemom, żeby usunęły swoją kopię.
Usuwanie testowego zamówienia
Anulowane zamówienie bez płatności i bez faktury - testowe albo spam - obsługa z orders:manage może usunąć w całości z jego strony. Dane kupującego znikają z koszyka, zapisanych dostaw webhooków i historii Spotlight tak samo jak przy anonimizacji, a log audytu zachowuje numer zamówienia. Każde inne zamówienie jest odrzucane: gdy w grę wchodzą pieniądze albo faktura, zanonimizuj je.
Następne kroki
- Modele danych - rekordy, z których składa się katalog.
- API dostawcze GraphQL - jak ograniczany jest publiczny zapis.
- Klienci - konta stojące za zamówieniami i firmy w B2B.
- Cenniki - ceny B2B dla firmy lub klienta i jak sklep je pokazuje.
- Webhooki - dziesięć zdarzeń
order.*orazshipment.*dla przesyłek.