Zamówienia to nierozłączny element sklepów internetowych. Najczęściej składa je użytkownik z poziomu frontend, ale są też sytuacje, kiedy zamówienia pochodzą z różnych kanałów sprzedaży i wtedy wymagana jest integracja pomiędzy systemami. Do integracji wykorzystywane jest najczęściej połączenie przez API, dlatego chciałbym dzisiaj przedstawić, jak można stworzyć zamówienie z pomocą Admin API w Shopware 6.
Ten artykuł pomoże Ci znaleźć odpowiedzi na następujące pytania:
- Na co uważać przy tworzeniu zamówienia przez Admin API?
- Jak stworzyć integrację i podłączyć się do Admin API?
- Czym jest w Shopware 6 UUID i jak je tworzyć?
- Jak pobierać dane słownikowe potrzebne do stworzenia zamówienia?
- Z jakich elementów składa się zapytanie tworzące zamówienie?
- Jak wygląda pełen obiekt potrzebny do stworzenia zamówienia?
Admin API i jego możliwości
Oprócz Admin API, w Shopware 6 istnieje również Store API. Zapytasz: dlaczego nie Store API?
Store API umożliwia korzystanie z Shopware jako headless e-commerce. Jest używane m.in. przez Shopware PWA. Złożenie zamówienia przez Store API wymaga sesji użytkownika po stronie frontendu i wykonania kilku zapytań HTTP.
Admin API natomiast jest wykorzystywane przez Panel Administracyjny Shopware i pozwala nam na pełne zarządzanie obiektami w bazie danych (CRUD). Dodatkowo wykorzystuje się je do integracji Shopware 6 z zewnętrznymi systemami np. za pomocą Shopware Apps (alternatywnego rozwiązania dla pluginów) dostarczanych natywnie przez Shopware 6 . Shopware Apps pozwalają na ingerencję w działanie systemu bez rozszerzania kodu źródłowego. W przypadku Shopware Apps nie ma znaczenia, w jakim języku programujesz, ponieważ budujesz osobną aplikację, która łączy się z Shopware 6 właśnie przez wspomniany Admin API.
Wracając do zamówienia – poprzez Admin API wykonujemy (teoretycznie) jedno zapytanie, aby utworzyć zamówienie, co powinno ułatwić pracę nad implementacją, ale czy na pewno? Sprawdźmy to!
Jeżeli jesteś już obeznany w Admin API Shopware i szukasz pełnego body zapytania, to zapraszam Cię do sekcji: TL;DR – Pełny obiekt zamówienia do złożenia przez API.
Na co uważać przy tworzeniu zamówienia?
Admin API Shopware 6 w wielu przypadkach działa jak biała kartka papieru – możesz napisać na niej cokolwiek. Tylko część obiektów i pól jest walidowana, co przy nieodpowiednim użyciu może spowodować dużo problemów. Przykładowo, jeśli dodasz zamówienie przez zapytanie API bez przekazania obiektu użytkownika oraz adresu (obiekty nie są wymagane), można doprowadzić do sytuacji, w której przestanie działać widok listy zamówień w panelu administracyjnym:
Kolejny przykład niewłaściwego użycia – jeśli nie przekażemy poprawnego ID produktu z katalogu produktów, wystąpią błędy JavaScript przy próbie edycji zamówienia:
Nie chcesz przechodzić przez te problemy? Wykorzystaj przykład, który opiszę w dalszej części.
Przedstawię w nim prostą wersję tworzenia poprawnego zamówienia przy zerowych stawkach podatku (w naszym case nie potrzebowaliśmy stawek podatkowych). Pamiętaj – podatki nie są przeliczane automatycznie przy dodawaniu zamówienia do sklepu przez Admin API, wszystkie obliczenia musisz wykonać sam i przesłać ich wartości.
Składanie zamówienia przez Admin API Krok po kroku
- Autoryzacja zapytań Admin API Shopware 6 i przykładowe zapytanie
- Czym jest i jak używać UUID w Shopware 6?
- Przygotowanie słowników
- Wyjaśnienie poszczególnych pól i obiektów zamówienia
- Obiekt całego zamówienia do złożenia przez API
Autoryzacja zapytań Admin API Shopware 6
Shopware 6 Admin API korzysta z autoryzacji typu OAuth i autoryzuje zapytania na podstawie Bearer Token. Istnieją dwa typy autentykacji: Password OAuth Flow oraz Client Credentials OAuth Flow – w moim przykładzie wykorzystamy drugi typ.
Więcej o typach przeczytasz w dokumentacji Admin API Shopware.
Skąd wziąć dane dostępowe?
Należy utworzyć integrację w panelu administracyjnym według oficjalnej dokumentacji na tej stronie: https://docs.shopware.com/en/shopware-6-en/settings/system/integrationen?category=shopware-6-en/settings/system
Pamiętaj, aby zaznaczyć opcję Administrator przy tworzeniu integracji oraz skopiować wartości pól Access key ID i Secret access key – to Twoje dane dostępowe, dzięki którym uzyskasz autoryzację.
Dlaczego poleciłem wybranie opcji Administrator?
Po wybraniu opcji Administrator mamy pełen dostęp do wszystkich endpointów Admin API.
Jeżeli chciałbyś mieć kontrolę nad poszczególnymi zasobami, możesz stworzyć i przypisać rolę do Twojej integracji. Jak to zrobić? Więcej informacji znajdziesz w oficjalnej dokumentacji.
Mamy już dane dostępowe. Teraz czas uzyskać Bearer Token. W tym celu wykonaj następujące zapytanie:
Typ zapytania: POST
Endpoint URL: https://example.com/api/oauth/token
Body:
{
"grant_type": "client_credentials",
"client_id": "XXXXX", // Access key ID
"client_secret": "XXXXX" // Secret access key
}
W odpowiedzi otrzymasz:
{
"token_type": "Bearer",
"expires_in": 600,
"access_token": "xxxxxxxxxxxxxx" // token do autoryzacji zapytań
}
Gotowe, od teraz możesz przez 600 sekund wykonywać zapytania za pomocą otrzymanego Bearer tokena!
Jak wykonać zapytanie? Pokażę na przykładzie pobrania listy wszystkich dostępnych krajów z encji country:
Typ zapytania: GET
Autoryzacja: Bearer token
Endpoint URL: https://example.com/api/country
W odpowiedzi powinieneś otrzymać listę wszystkich dostępnych krajów w formacie JSON.
Jeżeli chciałbyś zapoznać się ze wszystkimi dostępnymi endpointami Admin API, to są one dostępne pod dwoma linkami do oficjalnych zasobów Shopware 6:
- Swagger (wszystkie zapytania CRUD)
Czym jest i jak używać UUID w Shopware 6?
Zanim przejdziemy do body zapytania, chciałbym pokrótce wyjaśnić, czym jest UUID w Shopware.
W skrócie UUID jest to 128-bitowa unikalna etykieta składająca się z ciągu cyfr i liczb. W Shopware 6 jest ona wykorzystywana jako klucz główny do wszystkich tabel w bazie danych, w odróżnieniu od klasycznego podejścia autoinkrementowanego ID.
Jak generować UUID dla Shopware 6?
use Shopware\Core\Framework\Uuid\Uuid;
$uuid = Uuid::randomHex();
W naszym przypadku będziemy odczytywać oraz tworzyć UUID. W body zapytania pojawi się wiele pól, które zawierają przyrostek “Id”, np. salutationId, countryId – są to właśnie wyżej wspomniane UUID rekordów z poszczególnych encji w bazie danych Shopware 6. Poniżej wyjaśnię, jakich dokładnie encji będziemy potrzebowali, aby uzyskać dane słownikowe.
Przygotowanie słowników
Do pobrania wspomnianych powyżej UUID należy wykonać zapytania pobierające listy rekordów z poszczególnych encji, a następnie wybrać odpowiedni rekord, którego UUID posłuży nam do stworzenia body zapytania.
Przykład pobierania listy krajów przedstawiłem wcześniej w sekcji: Autoryzacja zapytań Admin API Shopware 6 – na jego podstawie możesz stworzyć zapytania, aby pobrać dane z innych encji.
Instrukcja budowania zapytania:
Autoryzacja: Bearer token (opisana w sekcji “Autoryzacja zapytań Admin API Shopware 6”)
Endpoint URL: https://example.com/api/{{entity name}} (jako parametr podaj nazwę encji wymienionych poniżej)
Typ zapytania: GET
Lista potrzebnych encji:
- country - lista państw
- salutation - lista zwrotów grzecznościowych
- state_machine - lista maszyn stanów
- state_machine_state - lista stanów per maszyna stanów
- sales_channel - lista kanałów sprzedaży
- currency - lista walut
- payment_method - lista dostępnych metod płatności
- shipping_method - lista dostępnych metod dostawy
Wyjaśnienie poszczególnych pól i obiektów zamówienia
Kiedy zebraliśmy poszczególne UUID encji za pomocą ww. metod, możemy przejść do składowych zapytania. Dla łatwiejszego zrozumienia rozbiłem pełne body zapytania na mniejsze części i postaram się wyjaśnić, jakie wartości powinny być przekazywane do konkretnych pól.
orderCustomer
Obiekt użytkownika zamówienia jest osobną encją w bazie danych (nie mylić z encją użytkownika), która przechowuje informacje o użytkowniku zamówienia. W prezentowanym przeze mnie przypadku tworzę zamówienie dla niezarejestrowanego użytkownika. Jeśli chcemy powiązać zamówienie z istniejącym użytkownikiem w systemie, należy dodać parametr “customerId” zawierający UUID encji użytkownika:
"orderCustomer": {
"email": "email@example.com",
"firstName": "John",
"lastName": "Doe",
"salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b", // UUID pobrane z encji "salutation"
"title": null,
"vatIds": null,
"company": "Macopedia"
}
addresses
W tym obiekcie możemy przekazywać listę adresów. W moim przypadku tworzę tutaj adres płatności, który zostanie przypisany do zamówienia po ID. Adres dostawy znajduje się w obiekcie delivery:
"addresses": [
{
"id": "f01a5f849ecc4056af9036bc2e077bb7", // nowo utworzone UUID potrzebne do stworzenia adresu i przypisania go do adresu płatności zamówienia - parametr "billingAddressId" lub opcjonalnie UUID istniejącego adresu użytkownika (w tym przypadku kolejne pola adresu nie są wymagane)
"salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b", // UUID pobrane z encji "salutation"
"firstName": "John",
"lastName": "Doe",
"street": "Longstreet 3",
"zipcode": "12345",
"city": "Warsaw",
"countryId": "23c2863c6cfd4caf98d77c6572718a53", // UUID pobrane z encji "country"
"company": "",
"vatId": "",
"phoneNumber": "123 123 123"
}
],
price
Obiekt ceny zawiera informację o łącznej sumie zamówienia, cenie netto oraz podatkach. W moim przypadku system, z którym zintegrowałem Shopware 6, nie przesyłał stawek podatkowych. Tak jak wspominałem wcześniej, jest to uproszczony przykład i jeżeli chcesz przekazać ceny z podatkami, wszystkie wartości netto i brutto musisz obliczyć sam. Pamiętaj, że nie jestem twórcą Shopware 6 i niektóre pola też są dla mnie zagadką. Jeśli coś pomieszałem to przepraszam i czekam na uwagi. :)
Zapamiętaj format tego obiektu, ponieważ będzie się on powtarzał dla każdej ceny w zamówieniu:
"price": {
"totalPrice": 13.98, // cena łączna z podatkiem
"netPrice": 13.98, // cena netto
"positionPrice": 13.98, // cena taka sama jak w "netPrice"
"rawTotal": 13.98, // cena taka sama jak w "totalPrice"
"taxStatus": "gross", // typ podatku "net" lub "gross" w zależności, którą cenę traktujemy jako bazową
"calculatedTaxes": [
{
"tax": 0, // wartość podatku dla danej stawki
"taxRate": 0, // stawka podatku w procentach
"price": 13.98 // cena netto lub brutto w zależności od statusu podatku
}
],
"taxRules": [
{
"taxRate": 0, // procent podatku
"percentage": 100 // nieznane mi pole, tutaj zawsze wpisuj wartość 100 :)
}
]
},
shippingCosts
W tym obiekcie przekazujemy cenę za przesyłkę. Obiekt jest podobny do obiektu price, więc nie będę szczegółowo rozpisywał obiektów calculatedTaxes oraz taxRules:
"shippingCosts": {
"unitPrice": 2.99, // cena jednostkowa za dostawę
"totalPrice": 2.99, // cena łączna za dostawę
"quantity": 1, // ilość paczek
"calculatedTaxes": [
{
"tax": 0,
"taxRate": 0,
"price": 2.99
}
],
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
]
},
lineItems
Jest to lista zamówionych przedmiotów. W moim przypadku pokazuję najprostszy przykład z jednym produktem. W celu utworzenia relacji z produktem nie wystarczy Ci UUID produktu. Musisz podać również wartość atrybutu “Numer produktu”. Pamiętaj, że możesz utworzyć zamówienie bez podawania relacji do produktu, ale – jak wspominałem wcześniej – pozbawi Cię to możliwości edytowania zamówienia:
"lineItems": [
{
"productId": "43b379345e2044e4bab948934e78afcc", // UUID produktu
"referencedId": "43b379345e2044e4bab948934e78afcc", // UUID produktu
"payload": {
"productNumber": "ff8a107797c946acb3e1c00219236867" // wartość atrybutu produktu "Numer produktu"
},
"identifier": "43b379345e2044e4bab948934e78afcc", // unikalne UUID, najłatwiej użyć te same UUID produktu
"type": "product",
"label": "Blue T-shirt", // nazwa produktu wyświetlana na zamówieniu (może być inna niż oryginalna nazwa produktu)
"quantity": 1, // ilość produktów
"position": 1, // pozycja na liście w zamówieniu
"price": { // obiekt ceny opisany we wcześniejszych przykładach
"unitPrice": 10.99, // cena jednostkowa
"totalPrice": 10.99, // cena łączna
"quantity": 1, // ilość produktów
"calculatedTaxes": [
{
"tax": 0,
"taxRate": 0,
"price": 10.99
}
],
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
]
},
"priceDefinition": { // obiekt definicji ceny - bez tego obiektu, który jest bardzo podobny do ceny, edycja zamówienia również nie będzie działać
"type": "quantity",
"price": 10.99, // cena jednostkowa
"quantity": 1, // ilość produktów
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
],
"listPrice": null,
"isCalculated": true,
"referencePriceDefinition": null
}
}
],
deliveries
Obiekt dostawy to tak naprawdę adres dostawy, metoda dostawy, status dostawy oraz ponowna informacja o kosztach dostawy (dokładnie takie same pola i wartości jak dla obiektu shippingCosts):
"deliveries": [
{
"stateId": "7aa03d4157e84cf59ba8f30caa6ce3f8", // UUID stanu dostawy pobrane wcześniej z encji "state_machine_state"
"shippingMethodId": "616746f5d82b4474bc804bd7cb913b11", // UUID metody dostawy pobrane wcześniej z encji "shipping_method"
"shippingOrderAddress": {
"id": "071acbb111e3403d99fa31a509537fb1", // nowo utworzone UUID lub UUID istniejącego adresu użytkownika (w tym przypadku kolejne pola adresu nie są wymagane)
"salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b", // UUID pobrane z encji "salutation"
"firstName": "John",
"lastName": "Doe",
"street": "Shortstreet 5",
"zipcode": "12345",
"city": "Warsaw",
"countryId": "23c2863c6cfd4caf98d77c6572718a53", // UUID pobrane z encji "country"
"phoneNumber": "321 321 312"
},
"shippingDateEarliest": "2022-06-08 14:51:01",
"shippingDateLatest": "2022-06-08 14:51:01",
"shippingCosts": { // te same dane co w obiekcie "shippingCosts"
"unitPrice": 8.99,
"totalPrice": 8.99,
"quantity": 1,
"calculatedTaxes": [
{
"tax": 0,
"taxRate": 0,
"price": 8.99
}
],
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
]
}
}
],
transactions
Obiekt ten zawiera informację o transakcjach. W moim przypadku jest to jedna transakcja za całe zamówienie, ale można utworzyć kilka transakcji. Obiekt zawiera wartość transakcji, metodę płatności oraz status transakcji:
"transactions": [
{
"paymentMethodId": "09698e286f394eba8d6fcdfdd72816c1", // UUID metody płatności pobrane wcześniej z encji "payment_method"
"amount": {
"unitPrice": 13.98,
"totalPrice": 13.98,
"quantity": 1,
"calculatedTaxes": [
{
"tax": 0,
"taxRate": 0,
"price": 0
}
],
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
]
},
"stateId": "fcebe9d8e888458cb2f9984259e16c07" // UUID stanu transakcji pobrane wcześniej z encji "state_machine_state"
}
]
Pozostałe pola
Zostało jeszcze kilka pól do wyjaśnienia:
"billingAddressId": "f01a5f849ecc4056af9036bc2e077bb7", // UUID adresu płatności, który został użyty wcześniej w obiekcie "addresses"
"currencyId": "763bcef34142422a8f118cf38c9e00ca", // UUID stanu transakcji pobrane wcześniej z encji "currency"
"salesChannelId": "98432def39fc4624b33213a56b8c944d", // UUID kanału sprzedaży pobrane wcześniej z encji "sales_channel"
"stateId": "f9063cf8c1764db980d8091afc6e8c53", // UUID stanu zamówienia pobrane wcześniej z encji "state_machine_state"
"orderDateTime": "2022-06-08 14:51:01", // data złożenia zamówienia
"orderNumber": "123456", // numer zamówienia
"currencyFactor": 4.33, // wysokość kursu waluty w stosunku do bazowej waluty ustawionej w systemie
TL;DR – Pełny obiekt zamówienia do złożenia przez API
P.S. Jeżeli nie przeczytałeś sekcji Przygotowanie słowników, zapytanie nie zadziała na Twojej instancji Shopware, ponieważ potrzebujesz pobrać najpierw indywidualne dane słownikowe.
Autoryzacja: Bearer token (opisana w sekcji Autoryzacja zapytań Admin API Shopware 6)
Endpoint URL: https://example.com/api/order?_response=detail (parametr “_response=detail” spowoduje zwrócenie dodanego obiektu w odpowiedzi, bez tego odpowiedź będzie pusta)
Typ zapytania: POST
Body zapytania:
{
"billingAddressId": "f01a5f849ecc4056af9036bc2e077bb7",
"currencyId": "763bcef34142422a8f118cf38c9e00ca",
"salesChannelId": "98432def39fc4624b33213a56b8c944d",
"stateId": "f9063cf8c1764db980d8091afc6e8c53",
"orderDateTime": "2022-06-08 14:51:01",
"orderNumber": "123456",
"currencyFactor": 4.33,
"orderCustomer": {
"email": "email@example.com",
"firstName": "John",
"lastName": "Doe",
"salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b",
"title": null,
"vatIds": null,
"company": "Macopedia"
},
"addresses": [
{
"id": "f01a5f849ecc4056af9036bc2e077bb7",
"salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b",
"firstName": "John",
"lastName": "Doe",
"street": "Longstreet 3",
"zipcode": "12345",
"city": "Warsaw",
"countryId": "23c2863c6cfd4caf98d77c6572718a53",
"company": "",
"vatId": "",
"phoneNumber": "123 123 123"
}
],
"price": {
"totalPrice": 13.98,
"positionPrice": 13.98,
"rawTotal": 13.98,
"netPrice": 13.98,
"taxStatus": "gross",
"calculatedTaxes": [
{
"tax": 0,
"taxRate": 0,
"price": 13.98
}
],
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
]
},
"shippingCosts": {
"unitPrice": 2.99,
"totalPrice": 2.99,
"quantity": 1,
"calculatedTaxes": [
{
"tax": 0,
"taxRate": 0,
"price": 2.99
}
],
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
]
},
"lineItems": [
{
"productId": "43b379345e2044e4bab948934e78afcc",
"referencedId": "43b379345e2044e4bab948934e78afcc",
"payload": {
"productNumber": "ff8a107797c946acb3e1c00219236867"
},
"identifier": "43b379345e2044e4bab948934e78afcc",
"type": "product",
"label": "Blue T-shirt",
"quantity": 1,
"position": 1,
"price": {
"unitPrice": 10.99,
"totalPrice": 10.99,
"quantity": 1,
"calculatedTaxes": [
{
"tax": 0,
"taxRate": 0,
"price": 10.99
}
],
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
]
},
"priceDefinition": {
"type": "quantity",
"price": 10.99,
"quantity": 1,
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
],
"listPrice": null,
"isCalculated": true,
"referencePriceDefinition": null
}
}
],
"deliveries": [
{
"stateId": "7aa03d4157e84cf59ba8f30caa6ce3f8",
"shippingMethodId": "616746f5d82b4474bc804bd7cb913b11",
"shippingOrderAddress": {
"id": "071acbb111e3403d99fa31a509537fb1",
"salutationId": "ed643807c9f84cc8b50132ea3ccb1c3b",
"firstName": "John",
"lastName": "Doe",
"street": "Shortstreet 5",
"zipcode": "12345",
"city": "Warsaw",
"countryId": "23c2863c6cfd4caf98d77c6572718a53",
"phoneNumber": "321 321 312"
},
"shippingDateEarliest": "2022-06-08 14:51:01",
"shippingDateLatest": "2022-06-08 14:51:01",
"shippingCosts": {
"unitPrice": 8.99,
"totalPrice": 8.99,
"quantity": 1,
"calculatedTaxes": [
{
"tax": 0,
"taxRate": 0,
"price": 8.99
}
],
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
]
}
}
],
"transactions": [
{
"paymentMethodId": "09698e286f394eba8d6fcdfdd72816c1",
"amount": {
"unitPrice": 13.98,
"totalPrice": 13.98,
"quantity": 1,
"calculatedTaxes": [
{
"tax": 0,
"taxRate": 0,
"price": 0
}
],
"taxRules": [
{
"taxRate": 0,
"percentage": 100
}
]
},
"stateId": "fcebe9d8e888458cb2f9984259e16c07"
}
]
}
Podsumowanie
Admin API Shopware 6 daje nam możliwość tworzenia zamówienia za pomocą jednego zapytania, co jest dużym uproszczeniem. Natomiast musimy pamiętać, że ma to też swoje minusy, ponieważ odpowiedzialność za poprawność danych jest zrzucona na integratora. Wymaga to nierzadko dodatkowych godzin na stworzenie wielu ścieżek testowych mogących generować dodatkowe edge cases do obsłużenia. Dziękuję za przeczytanie tego posta i powodzenia w tworzeniu (poprawnych) zamówień przez Admin API Shopware 6. :)
Chciałbyś coś dodać? Napisz do nas!