diff --git a/.wordlist.txt b/.wordlist.txt index 52b8bef41..719b870ec 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -1277,6 +1277,7 @@ control copyToClipboard copyable cors +countryId createActionResponse createCategoryFixture createCmsFixture @@ -1843,6 +1844,7 @@ salesChannelId salesChannelLoaded saleschannel saleschannelrepositoryfacade +salutationId sandboxed sarven scalability diff --git a/guides/development/integrations-api/flows/create-product.md b/guides/development/integrations-api/flows/create-product.md new file mode 100644 index 000000000..75fab3e75 --- /dev/null +++ b/guides/development/integrations-api/flows/create-product.md @@ -0,0 +1,567 @@ +--- +nav: + title: Create a Product and Complete Checkout + position: 10 + +--- + +# Create a Product and Complete Checkout + +This guide shows a tested local end-to-end flow for Shopware development: + +1. Create a category and product with the Admin API +2. Read the product with the Store API +3. Add the product to a cart +4. Register a customer in the current Store API context +5. Place an order +6. Handle payment if needed + +It is written as a golden path for local development on `http://127.0.0.1:8000`. Troubleshooting tips appear below. + +## What to expect in local development + +A few details matter a lot in local setups: + +- Store API requests use `sw-access-key`, not `sw-access-token` +- On this setup, `/store-api/context` is called with `GET` +- Store API context tokens are ephemeral and may expire during longer debugging sessions +- `register` or `login` may return a new `Sw-Context-Token` +- If the context changes, your cart may no longer contain the items you added earlier +- Product creation requires a price in the **system default currency** +- Customer registration requires real IDs such as `salutationId` and `countryId` + +Because of that, the safest approach is to follow one known good sequence from start to finish. + +## Before you start + +Make sure your local Shopware instance is running: + +- Storefront: `http://127.0.0.1:8000` +- Administration: `http://127.0.0.1:8000/admin` + +You also need: + +- `curl` +- `jq` +- An admin user, for example `admin / shopware` in a local default setup +- A Store API sales channel access key + +## Step 1: Get an Admin API token + +For local development, you can use the Administration password grant shortcut: + +```bash +ADMIN_TOKEN=$(curl -s -X POST "http://127.0.0.1:8000/api/oauth/token" \ + -H "Content-Type: application/json" \ + -d '{ + "grant_type": "password", + "client_id": "administration", + "scopes": "write", + "username": "admin", + "password": "shopware" + }' | jq -r '.access_token') + +printf '%s\n' "$ADMIN_TOKEN" +``` + +## Step 2: Inspect your local API schemas + +Use the generated schemas for two different questions: + +- **OpenAPI spec**: Which endpoint should I call, and what does the payload look like? +- **Entity schema**: Which fields and associations exist on product, category, order, and other entities? + +Download the Admin API schemas: + +```bash +curl -s "http://127.0.0.1:8000/api/_info/openapi3.json" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -o openapi.json +``` + +Download the entity schema as well: + +```bash +curl -s "http://127.0.0.1:8000/api/_info/open-api-schema.json" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -o entity-schema.json +``` + +Quick checks: + +```bash +ls -lh openapi.json entity-schema.json +head -n 5 openapi.json +head -n 5 entity-schema.json +``` + +You can do the same for the Store API later with `/store-api/_info/openapi3.json`. + +## Step 3: Look up the IDs you need before creating products + +A product usually needs related IDs, such as: + +- `taxId` +- `salesChannelId` +- `currencyId` + +Use Admin API search endpoints for this. + +### Find a tax ID + +```bash +curl -s -X POST "http://127.0.0.1:8000/api/search/tax" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "limit": 10, + "sort": [{ "field": "name", "order": "ASC" }] + }' | jq +``` + +Choose the tax rate you want to use — for example, the standard rate. + +### Find a sales channel ID and Store API access key + +```bash +curl -s -X POST "http://127.0.0.1:8000/api/search/sales-channel" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "limit": 10, + "includes": { + "sales_channel": ["id", "name", "accessKey"] + } + }' | jq +``` + +Pick the sales channel you want to test against — for example, `Storefront`. + +### Find a currency ID + +Use the system default currency, because product writes require a price in the default currency: + +```bash +curl -s -X POST "http://127.0.0.1:8000/api/search/currency" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "limit": 10, + "includes": { + "currency": ["id", "name", "isoCode", "isSystemDefault"] + } + }' | jq +``` + +### Save the IDs you want to use + +```bash +TAX_ID="" +SALES_CHANNEL_ID="" +CURRENCY_ID="" +STORE_API_ACCESS_KEY="" +``` + +## Step 4: Create a category with the Admin API + +```bash +CATEGORY_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | tr -d '-') + +curl -s -X POST "http://127.0.0.1:8000/api/category" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"id\": \"$CATEGORY_ID\", + \"name\": \"My Example Category\", + \"active\": true + }" +``` + +Verify it: + +```bash +curl -s -X POST "http://127.0.0.1:8000/api/search/category" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"ids\": [\"$CATEGORY_ID\"], + \"includes\": { + \"category\": [\"id\", \"name\", \"active\"] + } + }" | jq +``` + +The `ids` and `includes` criteria are standard search-criteria features. + +## Step 5: Create a product with the Admin API + +```bash +PRODUCT_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | tr -d '-') +PRODUCT_NUMBER="MyExample-001" + +curl -s -X POST "http://127.0.0.1:8000/api/product" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"id\": \"$PRODUCT_ID\", + \"name\": \"My Example Product\", + \"productNumber\": \"$PRODUCT_NUMBER\", + \"stock\": 10, + \"active\": true, + \"taxId\": \"$TAX_ID\", + \"price\": [ + { + \"currencyId\": \"$CURRENCY_ID\", + \"gross\": 19.99, + \"net\": 16.80, + \"linked\": true + } + ], + \"visibilities\": [ + { + \"salesChannelId\": \"$SALES_CHANNEL_ID\", + \"visibility\": 30 + } + ], + \"categories\": [ + { \"id\": \"$CATEGORY_ID\" } + ] + }" +``` + +### About `visibilities` + +Use `visibilities` to assign a product to one or more sales channels and define how visible it should be in each channel. + +Each visibility entry contains: + +```bash +{ + "salesChannelId": "", + "visibility": 30 +} +``` + +`visibility` is required because product availability is resolved per sales channel. A product is only considered available in a sales-channel context when it has a matching visibility entry for that `salesChannelId`. + +Allowed values: + +- `10` (`VISIBILITY_LINK`): hide in listings and search +- `20` (`VISIBILITY_SEARCH`): hide in listings +- `30` (`VISIBILITY_ALL`): visible everywhere + +Verify it: + +```bash +curl -s -X POST "http://127.0.0.1:8000/api/search/product" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -H "sw-inheritance: 1" \ + -d "{ + \"ids\": [\"$PRODUCT_ID\"], + \"associations\": { + \"categories\": {} + }, + \"includes\": { + \"product\": [\"id\", \"name\", \"productNumber\", \"active\", \"translated\", \"categories\"], + \"category\": [\"id\", \"name\"] + } + }" | jq +``` + +The `sw-inheritance` header tells the API to consider parent-child inheritance when reading products and variants. + +## Step 6: Create a Store API context + +The Store API uses two important headers: + +- `sw-access-key`: authenticates against the Store API sales channel +- `sw-context-token`: identifies the shopper context + +Get a fresh Store API context: + +```bash +curl -i "http://127.0.0.1:8000/store-api/context" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" +``` + +Look for the `Sw-Context-Token` response header and store its value: + +```bash +STORE_CONTEXT_TOKEN="REPLACE_ME" +echo "$STORE_CONTEXT_TOKEN" +``` + +You can also extract it automatically: + +```bash +STORE_CONTEXT_TOKEN=$(curl -si "http://127.0.0.1:8000/store-api/context" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" \ + | tr -d '\r' | awk -F': ' 'tolower($1)=="sw-context-token" {print $2}') + +echo "$STORE_CONTEXT_TOKEN" +``` + +## Step 7: Read the product with the Store API + +Use a Store API search request with search criteria. The following example searches by term and sorts by name: + +```bash +curl -s -X POST "http://127.0.0.1:8000/store-api/search" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" \ + -H "sw-context-token: $STORE_CONTEXT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "term": "My Example Product", + "limit": 5, + "sort": [ + { "field": "name", "order": "ASC", "naturalSorting": true } + ], + "includes": { + "product": ["id", "name", "translated", "calculatedPrice"] + } + }' | jq +``` + +The following example shows how to find the product you created in a storefront-style listing: + +```bash +curl -s -X POST "http://127.0.0.1:8000/store-api/search" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" \ + -H "sw-context-token: $STORE_CONTEXT_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"filter\": [ + { \"type\": \"equals\", \"field\": \"active\", \"value\": true }, + { \"type\": \"equals\", \"field\": \"productNumber\", \"value\": \"$PRODUCT_NUMBER\" } + ], + \"sort\": [ + { \"field\": \"name\", \"order\": \"ASC\" } + ], + \"page\": 1, + \"limit\": 10, + \"includes\": { + \"product\": [\"id\", \"name\", \"productNumber\", \"translated\", \"calculatedPrice\"] + } + }" | jq +``` + +Useful search-criteria options: + +- `includes` restricts the response to the fields you need +- `page` and `limit` control pagination +- `filter` applies exact or nested filtering +- `term` performs a weighted text search +- `sort` controls ordering + +If your project uses a different Store API search/listing endpoint, confirm the exact path in your local `store-api/_info/openapi3.json`. + +## Step 8: Add the product to the cart + +Use the product ID from the previous step: + +```bash +curl -s -X POST "http://127.0.0.1:8000/store-api/checkout/cart/line-item" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" \ + -H "sw-context-token: $STORE_CONTEXT_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"items\": [ + { + \"id\": \"$PRODUCT_ID\", + \"referencedId\": \"$PRODUCT_ID\", + \"type\": \"product\", + \"quantity\": 1 + } + ] + }" | jq +``` + +Verify the cart: + +```bash +curl -s -X GET "http://127.0.0.1:8000/store-api/checkout/cart" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" \ + -H "sw-context-token: $STORE_CONTEXT_TOKEN" | jq +``` + +If your local schema shows slightly different cart payload requirements, trust your local `store-api` OpenAPI spec. + +## Step 9: Prepare checkout state + +Before placing an order, you typically need: + +- a customer in the current Store API context +- active billing and shipping addresses +- a shipping method +- a payment method + +You can inspect your local Store API reference in the browser at `http://127.0.0.1:8000/store-api/_info/stoplightio.html`, or inspect the raw schema: + +```bash +curl -s "http://127.0.0.1:8000/store-api/_info/openapi3.json" -o store-openapi.json +jq -r '.paths | keys[]' store-openapi.json | grep -E 'checkout|account|address|payment|shipping|order|context' +``` + +Typical relevant endpoints include: + +- `/account/register` +- `/account/login` +- `/account/address` +- `/context` +- `/payment-method` +- `/shipping-method` +- `/checkout/cart` +- `/checkout/order` +- `/handle-payment` + +Rule of thumb: + +- Admin API: create and manage data +- Store API: act like a shopper + +## Step 10: Register or log in as a customer, then place the order + +On a typical local setup, `POST /store-api/checkout/order` requires a logged-in customer. If you call it with an anonymous context, Shopware returns `Customer is not logged in.` First, register a customer or log in within the current Store API context. + +### 10.1 Fetch salutationId and countryId + +For registration, fetch real IDs for: + +- `salutationId` from `/store-api/salutation` +- `countryId` from `/store-api/country` + +For example: + +```bash +COUNTRY_ID="019d2b446e29724fa4715c70c9c3eae1" +SALUTATION_ID="019d2b446e2573ba9a3ea2d002050cbe" +``` + +### 10.2 Register the customer + +```bash +curl -i -X POST "http://127.0.0.1:8000/store-api/account/register" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" \ + -H "sw-context-token: $STORE_CONTEXT_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"salutationId\": \"$SALUTATION_ID\", + \"firstName\": \"FirstName\", + \"lastName\": \"LastName\", + \"email\": \"name-test@example.com\", + \"password\": \"shopware123!\", + \"acceptedDataProtection\": true, + \"storefrontUrl\": \"http://127.0.0.1:8000\", + \"billingAddress\": { + \"firstName\": \"FirstName\", + \"lastName\": \"LastName\", + \"street\": \"Teststr. 1\", + \"zipcode\": \"12345\", + \"city\": \"Test City\", + \"countryId\": \"$COUNTRY_ID\" + } + }" +``` + +Use the `Sw-Context-Token` value from the response for subsequent requests: + +```bash +STORE_CONTEXT_TOKEN="REPLACE_ME_WITH_NEW_TOKEN" +``` + +### 10.3 Re-add the product if the context token changed + +After `/store-api/account/register` or `/store-api/account/login`, Shopware may return a new `Sw-Context-Token`. If the token changes, add the product to the cart again in that new context before calling `/store-api/checkout/order`. + +```bash +curl -s -X POST "http://127.0.0.1:8000/store-api/checkout/cart/line-item" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" \ + -H "sw-context-token: $STORE_CONTEXT_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"items\": [ + { + \"id\": \"$PRODUCT_ID\", + \"referencedId\": \"$PRODUCT_ID\", + \"type\": \"product\", + \"quantity\": 1 + } + ] + }" | jq +``` + +Verify that the cart is not empty: + +```bash +curl -s "http://127.0.0.1:8000/store-api/checkout/cart" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" \ + -H "sw-context-token: $STORE_CONTEXT_TOKEN" | jq +``` + +### 10.4 Place the order + +Once the Store API context has: + +- A logged-in customer +- A cart with your product +- Valid billing and shipping data + +You can usually place the order directly on a local default setup. If your instance requires an explicit payment or shipping selection first, inspect `/payment-method`, `/shipping-method`, and `/context`. + +Create the order: + +```bash +curl -s -X POST "http://127.0.0.1:8000/store-api/checkout/order" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" \ + -H "sw-context-token: $STORE_CONTEXT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "customerComment": "Test order from local API walkthrough" + }' | jq +``` + +If the cart is empty, Shopware returns the message: `Cart is empty.` + +In that case, add the product to the cart again in the current context, then retry the order request. + +### 10.5 Handle payment if required + +Some payment methods require an extra payment step after order creation: + +```bash +curl -s -X POST "http://127.0.0.1:8000/store-api/handle-payment" \ + -H "sw-access-key: $STORE_API_ACCESS_KEY" \ + -H "sw-context-token: $STORE_CONTEXT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "orderId": "REPLACE_ME", + "finishUrl": "http://127.0.0.1:8000/checkout/finish", + "errorUrl": "http://127.0.0.1:8000/checkout/confirm" + }' | jq +``` + +If `/store-api/handle-payment` returns `"redirectUrl": null`, the selected payment method does not require an additional redirect-based flow. + +## Troubleshooting + +### Schema endpoints return `500` or missing-table errors + +Your database may not be initialized correctly. Re-run installation and setup. + +### Product does not show up in the Store API + +Check all of the following: + +- The product is `active` +- The product has a valid `price` +- The product has `visibilities` for your sales channel +- You are using the correct Store API access key +- Your Storefront sales channel domain matches your local URL + +### Which headers matter most? + +For this walkthrough: + +- Admin API: `Authorization: Bearer $ADMIN_TOKEN`, optionally `sw-language-id`, `sw-version-id`, `sw-inheritance`, `sw-currency-id` depending on your use case +- Store API: `sw-access-key`, `sw-context-token` diff --git a/guides/development/integrations-api/flows/index.md b/guides/development/integrations-api/flows/index.md new file mode 100644 index 000000000..f32b62e48 --- /dev/null +++ b/guides/development/integrations-api/flows/index.md @@ -0,0 +1,16 @@ +--- +nav: + title: API Flows + position: 10 + +--- + +# API Flows + +These guides focus on concrete end-to-end flows where it helps to see API calls together, rather than as isolated endpoint references. + +## Available flows + +[Create a Product and Complete Checkout](./create-product.md): Create a category and a product with the Admin API, read them through the Store API, add the product to a cart, register a customer, place an order, and handle payment if needed. + +More flows to be added over time.