kommandhub / chowdeck-relay-sw
Same-day Chowdeck rider delivery: live Relay delivery prices at checkout, rider requests from the order, tracking links and status updates, all on the merchant's own Chowdeck account.
Package info
github.com/KommandHub-Org/KmhChowdeckRelaySW
Type:shopware-platform-plugin
pkg:composer/kommandhub/chowdeck-relay-sw
Requires
- php: >=8.2
- shopware/administration: ~6.7.0
- shopware/core: ~6.7.0
- shopware/storefront: ~6.7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
- rregeer/phpunit-coverage-check: ^0.3.1
- shopware/dev-tools: ^1.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-04 07:52:32 UTC
README
Same-day rider delivery with Chowdeck Relay for Shopware 6.7: live delivery prices at checkout, rider requests from the order and tracking — all on the merchant's own Chowdeck account. Free and open source.
Status: pre-release (0.1.0). Everything below is built and tested. A real rider request has not yet been made end to end, because Chowdeck's sandbox currently refuses to create deliveries; v1.0.0 follows once it has.
Table of contents
- What it does
- Requirements
- Installation
- Configuration
- Architecture
- Features
- Local development
- Makefile commands
- Testing
- Code quality
- CI/CD
- Release process
- Logging and debugging
- Security
- Contributing
- License
What it does
- Checkout: "Same-day rider delivery (Chowdeck)" shows a live Chowdeck price for the customer's address (plus your optional markup). The price always comes from the plugin's own Chowdeck quote, never from the browser.
- Orders: a Chowdeck card on the order requests the rider, shows the rider, delivery PIN and tracking link, refreshes the status and cancels. You press Request rider when the parcel is packed; a rider is never called automatically, because Chowdeck sends one the moment a delivery is created.
- Tracking: the Chowdeck tracking link becomes the order's tracking code, shown in the customer account and mails.
- Status updates: Chowdeck webhooks move the order's delivery status (shipped when picked up, cancelled) and trigger mails — no polling.
- No manual setup: the flows it needs are installed with the plugin.
You pay riders from your own Chowdeck Relay wallet; the plugin never collects or relays delivery fees. Customers stay your customers: they order in your shop, not in the Chowdeck app.
Requirements
| Shopware | ~6.7.0 |
| PHP | 8.2 or newer |
| Database | MySQL 8.0+ / MariaDB 10.11+ |
| Chowdeck | a merchant account with Relay enabled and an API key (dashboard.chowdeck.com), and money in the Relay wallet |
| Shop | NGN currency, customer phone number in addresses, a message queue worker — see Requirements on your shop |
Installation
The package is not on Packagist; add the GitHub repository first:
composer config repositories.kmh-chowdeck vcs https://github.com/KommandHub-Org/KmhChowdeckRelaySW.git composer require kommandhub/chowdeck-relay-sw:dev-main bin/console plugin:refresh bin/console plugin:install --activate KmhChowdeckRelaySW bin/console cache:clear
From v1.0.0 the plugin is also available from the Shopware Store and as a zip on the GitHub releases page. Then follow Configuration and activate the shipping method.
Configuration
Settings > Extensions > Chowdeck Relay Rider Delivery
Every setting is sales-channel scoped: use the sales-channel selector at the top of the configuration page to override a value for one channel only.
- Chowdeck account: enter the integration key from the Chowdeck dashboard
(Settings > Developer settings; the "Merchant ref" shown there is not
needed) and the webhook signing secret, save, then click
Test connection. It shows your Relay wallet balance, or Chowdeck's
error. A test key (
sk_test_…) talks to the Chowdeck sandbox — no real riders; its signing secret is in the sandbox console (console.chowdeck.com) under Setup & Credentials. A live key calls real riders. - Webhook URL: copy the URL shown under the keys into Chowdeck — dashboard Settings > Developer settings for a live key, the sandbox console's test webhook URLs for a test key. Rider status updates then reach the shop by themselves. Updates without a valid signature are refused, so the signing secret must be set; if your live dashboard shows none, ask Chowdeck support for it. The page also shows when the last update arrived.
- Pickup: the address the rider collects from, as one line, and the contact the rider asks for. Optional coordinates make prices more exact.
- Address lookup (optional): switch on "Look up coordinates with Google Maps" and enter a Google Maps key with the Geocoding API. Pickup and customer addresses are then turned into coordinates before Chowdeck is asked for a price — more exact prices, and required for the Chowdeck sandbox, which cannot read text addresses. Results are cached for 30 days; Google bills each lookup to your account. Off, or without a key, addresses go to Chowdeck as text.
- Delivery: item type, delivery PIN (the customer gives it to the rider), how Chowdeck notifies the customer (SMS, email, WhatsApp), and whether to declare the order value (Chowdeck's loss and damage cover, 2% of the value).
- Checkout price: optional markup, an optional fallback price used when Chowdeck cannot be reached, and how long a price is reused.
- Activate the shipping method "Same-day rider delivery (Chowdeck)" under Settings > Shipping. The plugin creates it inactive and never changes it again (except adding the tracking link when the field is empty), so renaming it or changing its sales channels is safe.
Nothing has to be set up in Flow Builder: the plugin installs its flows (see Flows).
Requirements on your shop
- Currency NGN in the sales channel: Chowdeck prices in naira. In another currency the method shows "only available in naira".
- Customer phone number in the shipping address: the rider calls the customer. Turn on the phone field (Settings > Login & sign-up); without a phone the method asks the customer to complete the address.
- Within Chowdeck's service area: Chowdeck only serves the cities shown in your Chowdeck dashboard. Outside them, the method says Chowdeck does not deliver to the address.
- A message queue worker (
bin/console messenger:consume, as Shopware recommends for production): webhooks are processed in the background.
Dispatching an order
- Pack the parcel.
- Open the order; on the Chowdeck card press Request rider. The plugin asks Chowdeck for a fresh price first. If it equals the checkout price, the rider is requested at once; if it changed, you confirm the new price (the customer's shipping price never changes; the difference is yours).
- Chowdeck sends a rider immediately and charges your Relay wallet. The customer gets the tracking link and delivery PIN by email.
- Webhooks update the card: finding a rider, on the way to pickup, on the way to the customer (the order delivery becomes shipped), delivered.
- Cancel delivery works until the rider has collected the parcel; Chowdeck refunds the fee to your wallet.
If the request fails (for example an empty wallet), the card shows Chowdeck's reason and you get the "rider request failed" email. The Flow Builder action "Request Chowdeck rider" exists for shops that want to dispatch from their own flow — choose a trigger that fires when the parcel is packed.
When Chowdeck cannot price a delivery
Checkout never breaks: the method shows a reason and the customer picks another method; the order is never placed with a wrong price.
| Situation | Customer sees |
|---|---|
| No phone or incomplete address | "complete your delivery address, including a phone number" |
| Address Chowdeck cannot find or does not serve | "Chowdeck does not deliver to this address" |
| Shop currency is not NGN | "only available in naira" |
| Chowdeck unreachable | your fallback price if set (you confirm the real price when you request the rider), else "temporarily unavailable"; checkout then skips Chowdeck for a minute |
| Key missing, pickup missing or account without Relay | "currently unavailable"; you get an admin notification (at most once an hour) |
Do not make Chowdeck the only or the default shipping method of a sales channel: when Chowdeck cannot price an address, customers need another option.
Flows installed with the plugin
All active from the start; edit, switch off or delete them in Flow Builder — updates never bring them back.
| Flow | When | Does |
|---|---|---|
| Chowdeck: rider on the way email | rider requested | emails the customer the tracking link and delivery PIN |
| Chowdeck: rider request failed alert | rider request fails | emails you Chowdeck's reason |
| Chowdeck: delivered email | Chowdeck reports delivered | confirms the delivery to the customer |
Further triggers (rider assigned, picked up, cancelled) are available for your own flows, e.g. SMS or WhatsApp messages.
Uninstalling
Deactivating or uninstalling switches the shipping method off; it is never deleted, because orders refer to it. Uninstalling without "keep user data" also removes the delivery and webhook tables, the plugin's flows (and any flow using its triggers or action) and their mail templates. With "keep user data" everything stays.
Architecture
Feature-first modules under src/, following Shopware's own plugin layout. A
top-level directory is a boundary; inside it, flat Symfony-idiomatic folders
(Service, Subscriber, Handler, Struct, Event, Enum, Controller).
No Application/Domain/Infrastructure nesting — one obvious home per class.
| Path | Responsibility |
|---|---|
src/KmhChowdeckRelaySW.php |
plugin bootstrap and lifecycle hooks (installer, uninstall clean-up) |
src/Client/ |
the only code that talks HTTP to Chowdeck: transport, resources, DTOs |
src/Shipping/ |
cart processor, checkout errors, shipping method installer |
src/Geocoding/ |
optional Google address → coordinates lookup, cached |
src/Quote/ |
checkout fee: pickup and customer address, cache, markup, failure notices |
src/Delivery/ |
delivery status, request / refresh / cancel rider, order delivery state updates |
src/Webhook/ |
webhook endpoint, signature check, de-duplication, async handler |
src/Flow/ |
Flow Builder triggers, the Request rider action, flow installer |
src/Administration/Controller/ |
Admin API: Test connection, webhook status, rider actions |
src/Core/Content/ |
DAL entities kmh_chowdeck_delivery, kmh_chowdeck_webhook_event |
src/Setting/Service/Config.php |
typed, sales-channel-aware settings reader |
src/Logging/ConfigurableLogger.php |
PSR-3 wrapper gated on the debug settings |
src/ScheduledTask/ |
daily clean-up of old webhook events |
src/Resources/ |
config/ (services, routes, config.xml, packages), admin app, snippets |
tests/Unit |
mirrors src/ |
Features
Administration
Vue/JS sources live in src/Resources/app/administration/src/. Built output in
src/Resources/public/ is generated — never hand-edit it. Rebuild it with
bin/build-administration.sh in the container after changing a source file and
commit the result: make zip ships the committed bundle as is (asset builds are
off in .shopware-extension.yml).
main.js— entry point: registers locales, ACL, components and services.acl/index.js— order permissionskmh_chowdeck_delivery.viewer/.editor.component/kmh-chowdeck-connection-test.js— the Test connection button.component/kmh-chowdeck-webhook-info.js— webhook URL and last update.component/kmh-chowdeck-order-delivery.js— the Chowdeck card on the order.extension/— order detail and Flow Builder extensions.service/chowdeck-config.service.js— client for the plugin's Admin API.snippet/*.json— en-GB, de-DE, fr-FR, mirroring the same key tree.
Any privilege enforced here must also be enforced server-side via _acl on
the route.
Chowdeck Relay API client
src/Client/ is the only place that talks HTTP.
| Layer | Responsibility |
|---|---|
Http/ChowdeckHttpClient |
bearer API key, 3 s timeout; live keys go to https://api.chowdeck.com, test keys (sk_test_…) to the sandbox https://console-api.chowdeck.com |
Resource/Deliveries |
fee quote, create (calls a rider), get, cancel |
Resource/Wallet |
wallet balance (Test connection) |
Dto/* |
typed requests and results; Chowdeck's kobo amounts are converted to naira |
ChowdeckClient |
facade services depend on |
Every failure becomes a ChowdeckApiException with an ApiErrorReason
(unauthorized, bad request, insufficient balance, rate limited, server error,
timeout, network, invalid response). Messages never contain the API key. Add a
new call by adding a method to the matching Resource/ class — never by
issuing HTTP from a service.
Checkout
When the customer selects "Same-day rider delivery (Chowdeck)", the cart
processor Shipping/ChowdeckDeliveryProcessor (priority -6000, after the core
delivery processor) prices the delivery through Quote/QuoteService:
- The customer's street, city and phone are required (the rider calls).
- The pickup address and the customer address go to Chowdeck as text, with coordinates added when known (your pickup coordinates, or Google address lookup when it is on).
- The fee is cached for the price cache time, keyed by pickup, address and declared value; the shipping cost is the Chowdeck total plus your markup.
- On order placement the fee id, Chowdeck price and charged price are written
to
kmh_chowdeck_delivery(statusquoted, orneeds_quotewhen the fallback price was charged).
When Chowdeck cannot price the address, checkout explains why and the customer picks another method; the order is never placed with a wrong price:
| Situation | Customer sees |
|---|---|
| No phone or incomplete address | "complete your delivery address, including a phone number" |
| Address Chowdeck cannot find or does not serve | "Chowdeck does not deliver to this address" |
| Shop currency is not NGN | "only available in naira" |
| Chowdeck unreachable | your fallback price if set, else "temporarily unavailable" |
| Key missing, pickup missing or account without Relay | "currently unavailable"; you get an admin notification (at most once an hour) |
Do not make Chowdeck the only or the default shipping method of a sales channel. The Chowdeck sandbox cannot read text addresses — switch on address lookup with a Google Maps key to get sandbox prices.
Data model
| Table | Holds |
|---|---|
kmh_chowdeck_delivery |
one Chowdeck delivery per order delivery: fee, prices, reference, status, tracking, PIN, rider |
kmh_chowdeck_webhook_event |
received webhooks, unique per body hash (redeliveries are processed once) |
order_delivery.kmhChowdeckDelivery exposes the delivery on the order delivery
(loaded only when associated).
Webhooks
POST /kmh-chowdeck/webhook/{salesChannelId} (storefront scope, no login).
The raw body is checked against x-chowdeck-signature (HMAC-SHA256 hex with
the sales channel's webhook secret); anything unsigned or wrong gets 401. A
valid body is stored once per body hash in kmh_chowdeck_webhook_event
(INSERT IGNORE, so redeliveries are processed once), answered with 200 and
handled on the message queue: the delivery's status, rider and tracking link
are updated, picked up ships the order delivery, cancelled cancels it, and
the matching Flow Builder trigger fires.
Scheduled tasks
kmh_chowdeck.webhook_event_cleanup runs daily and deletes stored webhook
events older than 90 days. It only runs when a worker is running
(bin/console messenger:consume).
Local development
The plugin is developed inside a Docker stack that runs a full Shopware install
with this directory mounted at custom/static-plugins/KmhChowdeckRelaySW.
git clone https://github.com/KommandHub-Org/KmhChowdeckRelaySW.git cd KmhChowdeckRelaySW make up # build the image, start Shopware, install dependencies make shell # bash into the container # inside the container bin/console plugin:refresh bin/console plugin:install --activate KmhChowdeckRelaySW
Storefront: http://localhost · Administration: http://localhost/admin
(admin / shopware).
Makefile commands
| Command | What it does |
|---|---|
make up / make down |
start / tear down the stack |
make restart |
down then up |
make shell |
shell into the container |
make test |
PHPUnit; filter with make test FILTER=SomeTest |
make test-coverage |
coverage text report |
make analyse |
PHPStan on src/ |
make cs / make cs-fix |
php-cs-fixer dry-run / apply |
make validate-plugin |
shopware-cli store-compliance check |
make changelog |
render CHANGELOG.md as the Store will |
make zip |
build a distributable zip into build/ |
make cli ARGS="..." |
any other shopware-cli command |
Testing
make test make test FILTER=ConfigTest make test-coverage
tests/Unit/mirrorssrc/. No kernel, no database — fast, and what CI runs.tests/Integration/needs a booted Shopware kernel. Mark those tests#[Group('kernel')]; CI runs--exclude-group kernel.- Add or update a test with every behaviour change.
Code quality
make cs-fix && make analyse && make test
All three must pass before a commit. PHPStan runs at the level pinned in
phpstan.dist.neon; php-cs-fixer enforces PSR-12 plus the rules in
.php-cs-fixer.dist.php.
CI/CD
.github/workflows/php.yml runs on every push to main and on every pull
request: composer validate, PHP lint, PHPStan, php-cs-fixer (dry-run),
PHPUnit with coverage, and a coverage threshold gate.
CI runs without a Shopware kernel, so kernel-dependent tests are excluded
there and the plugin bootstrap is excluded from coverage in phpunit.dist.xml.
Release process
- Make sure the local gate passes and CI is green on
main. - Bump
versionincomposer.json. - Add a
# <version>section at the top ofCHANGELOG.mdandCHANGELOG_de-DE.md. Check the rendering withmake changelog. make validate-plugin— must be clean for a Store submission.make zip— the artefact lands inbuild/.- Tag
v<version>onmain, create the GitHub release with the zip, and upload the same zip to the Shopware Store.
Logging and debugging
Enable debug logging in the plugin configuration; entries land in
var/log/kmh_chowdeck_<env>.log (rotating, 7 files).
error and above are always written regardless of the toggle, so production
keeps a trail of failures. Both the toggle and the level filter are
sales-channel scoped — pass the sales channel id in the log context so it
resolves against the right scope:
$this->logger->info('something happened', [ ConfigurableLogger::CONTEXT_SALES_CHANNEL_ID => $salesChannelId, ]);
Security
Report vulnerabilities privately — see SECURITY.md. Never open a public issue for one, and never paste real credentials into an issue.
Contributing
See CONTRIBUTING.md. Pull requests target main, commits
are signed off (git commit -s), and the local gate must pass.
License
Apache-2.0 — see LICENSE and NOTICE. The KommandHub name and logo are covered by TRADEMARKS.md.
Chowdeck is a trademark of its owner. This plugin is an independent integration by Kommandhub Limited and is not affiliated with or endorsed by Chowdeck.