keirontw/sylius-relay-point-plugin
Composer 安装命令:
composer require keirontw/sylius-relay-point-plugin
包简介
Carrier-agnostic relay point (point relais) selection plugin for Sylius 2.x checkout
关键字:
README 文档
README
Carrier-agnostic relay point ("point relais") selection for the Sylius 2.x checkout.
Any carrier — French or international — can plug in by implementing a single PHP interface. Geocoding is equally swappable: Addok (French BAN, default, no API key), Nominatim, Google Maps, or Photon.
Features
- Carrier-agnostic — implement
RelayPointProviderInterfaceto add any carrier without touching the plugin core - Geocoding-agnostic — switch between Addok, Nominatim, Google Maps, Photon, or your own backend via config
- Built-in providers — Mondial Relay, Chronopost, Shop2Shop, Colissimo, InPost, GLS, DPD, DHL, Packeta, PostNL, bpost (skeleton), Colis Privé (skeleton)
- Checkout UX — Stimulus controller + Leaflet map, embeddable in any Sylius checkout template
- Session persistence — selected relay point stored in session, readable server-side during checkout completion
Requirements
- PHP 8.2+
- Sylius 2.0+
- Symfony 7.4+
ext-soap(for SOAP-based carriers: Mondial Relay, Chronopost, Colissimo, Colis Privé)symfony/http-client(for REST-based providers: InPost, Addok, Nominatim, Google Maps, Photon)
Installation
composer require keirontw/sylius-relay-point-plugin
Register the plugin in config/bundles.php:
return [ // ... Keirontw\SyliusRelayPointPlugin\KeirontwSyliusRelayPointPlugin::class => ['all' => true], ];
Import plugin routes in config/routes.yaml:
keirontw_sylius_relay_point_shop: resource: "@KeirontwSyliusRelayPointPlugin/config/routes/shop.yaml"
Configuration
Create config/packages/keirontw_sylius_relay_point.yaml:
keirontw_sylius_relay_point: # ── Geocoding ──────────────────────────────────────────────────────────── geocoding: # addok: French BAN (free, no key, best for France) — default # nominatim: self-hosted OSM (public nominatim.openstreetmap.org forbids SaaS use) # google_maps: commercial worldwide # photon: self-hosted OSM, lightweight # custom: wire your own GeocodingProviderInterface alias in services.yaml provider: addok addok: url: 'https://api-adresse.data.gouv.fr/search/' # or your self-hosted Addok nominatim: url: 'https://your-nominatim.example.com/search' user_agent: 'MyShop (contact@myshop.com)' contact_email: 'contact@myshop.com' secret: ~ # optional X-Nominatim-Secret header for self-hosted instances google_maps: api_key: '%env(GOOGLE_MAPS_API_KEY)%' photon: url: 'https://your-photon.example.com/api' lang: fr # ── Carrier providers ──────────────────────────────────────────────────── providers: mondial_relay: enabled: true account: '%env(MONDIAL_RELAY_ACCOUNT)%' password: '%env(MONDIAL_RELAY_PASSWORD)%' shipping_method_codes: - mondial_relay_france - mondial_relay_belgium chronopost: enabled: true account: '%env(CHRONOPOST_ACCOUNT)%' password: '%env(CHRONOPOST_PASSWORD)%' shipping_method_codes: - chronopost_pickup_france shop2shop: enabled: true account: '%env(SHOP2SHOP_ACCOUNT)%' password: '%env(SHOP2SHOP_PASSWORD)%' shipping_method_codes: - shop2shop_france colissimo: enabled: true account_number: '%env(COLISSIMO_ACCOUNT)%' password: '%env(COLISSIMO_PASSWORD)%' filter_relay: 'A' # A=all, P=relay points only, C=lockers only shipping_method_codes: - colissimo_pickup_france inpost: enabled: true # Country-specific endpoints: # France: https://api.inpost.fr/v1/points # Poland: https://api-pl-points.easypack24.net/v1/points # UK: https://api.inpost.co.uk/v1/points base_url: 'https://api.inpost.fr/v1/points' shipping_method_codes: - inpost_france colis_prive: enabled: false # See note below — relay point WSDL pending confirmation login: '%env(COLIS_PRIVE_LOGIN)%' password: '%env(COLIS_PRIVE_PASSWORD)%' shipping_method_codes: - colis_prive_relay # ── International (REST, no SOAP) ──────────────────────────────────── dpd: enabled: true # API key: https://developer.dpd.com (free, covers all EU countries) api_key: '%env(DPD_API_KEY)%' shipping_method_codes: - dpd_pickup_france - dpd_pickup_germany - dpd_pickup_belgium dhl: enabled: true # API key: https://developer.dhl.com (free) api_key: '%env(DHL_API_KEY)%' # parcel:pick-up → DHL ServicePoints # parcel:drop-off-easy → DHL Packstations (Germany) service_type: 'parcel:pick-up' shipping_method_codes: - dhl_servicepoint_france - dhl_servicepoint_germany packeta: enabled: true # API key: https://client.packeta.com (free for merchants) # Covers: CZ, SK, PL, HU, RO, DE, AT, FR, IT, ES, BE and more api_key: '%env(PACKETA_API_KEY)%' shipping_method_codes: - packeta_czech_republic - packeta_slovakia - packeta_poland post_nl: enabled: true # API key: https://developer.postnl.nl (business account required) # Covers: NL, BE and more api_key: '%env(POSTNL_API_KEY)%' # PG=retail points + lockers (default), PA=lockers only, PG_EX=retail only delivery_options: 'PG' shipping_method_codes: - postnl_netherlands - postnl_belgium bpost: enabled: false # See note below — no public API, requires bpost business agreement api_key: '%env(BPOST_API_KEY)%' base_url: '%env(BPOST_API_BASE_URL)%' shipping_method_codes: - bpost_belgium gls: enabled: true # Credentials provided by your GLS contact upon account setup username: '%env(GLS_USERNAME)%' password: '%env(GLS_PASSWORD)%' # Base URL may differ per country — default covers most EU countries # base_url: 'https://shipit.gls-group.eu/backend/rs/parcelshop' shipping_method_codes: - gls_france - gls_germany - gls_belgium
Note — bpost parcel points: bpost does not expose a public REST API for parcel point search. Access is granted only to verified business partners via the OSP portal (https://osp.bpost.be). Contact bpost at https://www.bpost.be/en/business to request credentials and endpoint documentation. The provider skeleton (
BpostProvider) is ready and only needs the correct endpoint and field mapping.
Note — Colis Privé relay points: Colis Privé's label generation API (
WSCP.asmx) is separate from their relay point search API. The WSDL URL and method name for relay point search must be confirmed with Colis Privé technical support before enabling this provider. The provider skeleton (ColisPriveRelayProvider) is ready and only needs the correct endpoint and field mapping.
Checkout integration
1. Automatic Twig Hook (recommended)
The plugin automatically registers itself into the sylius_shop.checkout.select_shipping hook via PrependExtensionInterface. No template change needed — the relay picker appears automatically when the customer's chosen shipping method matches one of your configured shipping_method_codes.
The hook runs at priority 50 (between the shipping method form and the navigation button). Override the priority in your own twig_hooks.yaml if needed:
sylius_twig_hooks: hooks: 'sylius_shop.checkout.select_shipping': relay_point_picker: priority: 150 # move above the shipping form
1b. Manual embed (alternative)
If you manage checkout templates yourself without Twig Hooks, include the widget directly:
{% include '@KeirontwSyliusRelayPointPlugin/shop/relay_point_picker.html.twig' with {
searchUrl: path('keirontw_relay_point_shop_search'),
geocodeUrl: path('keirontw_relay_point_shop_geocode'),
selectUrl: path('keirontw_relay_point_shop_select'),
methodCodes: ['mondial_relay_france', 'chronopost_pickup_france'],
cartToken: cart.tokenValue,
addressStreet: cart.shippingAddress.street,
addressCity: cart.shippingAddress.city,
addressPostcode: cart.shippingAddress.postcode,
addressCountry: cart.shippingAddress.countryCode,
} %}
The widget handles:
- Address geocoding via the configured provider
- Parallel search across all
methodCodes - Leaflet map with per-carrier colour coding
- Dynamic carrier filter (built from the actual response)
- Opening hours toggle per point
2. Register the Stimulus controller
Install the npm package:
npm install @keirontw/sylius-relay-point-plugin
Then add the controller entry to your app's assets/controllers.json:
{
"controllers": {
"@keirontw/sylius-relay-point-plugin": {
"relay-point-picker": {
"enabled": true,
"fetch": "eager"
}
}
},
"entrypoints": []
}
Alternative (without npm): copy assets/shop/controllers/relay-point-picker_controller.js into your project's assets/controllers/ folder and import it manually in your entrypoint.
3. Handle the selection event
The Stimulus controller dispatches two events that bubble up to the window:
| Event | When | event.detail |
|---|---|---|
relay-point-picker:selected |
User clicks a relay point in the list or map | { point } |
relay-point-picker:confirmed |
User clicks "Confirm" | { point } |
When selectUrl is provided, the plugin automatically POSTs the selection to the session on confirm. Listen to relay-point-picker:confirmed in your own Stimulus controller to trigger the next checkout step:
// assets/controllers/checkout_controller.js import { Controller } from '@hotwired/stimulus'; export default class extends Controller { connect() { this.element.addEventListener('relay-point-picker:confirmed', this.onRelayConfirmed.bind(this)); } onRelayConfirmed(event) { const { point } = event.detail; // e.g. submit the checkout form, update a hidden field, redirect... this.element.querySelector('form').submit(); } }
4. Read the selection server-side
During checkout completion (e.g. in an event subscriber on sylius.order.pre_complete), inject RelayPointSessionStorage and read the selection:
use Keirontw\SyliusRelayPointPlugin\RelayPoint\RelayPointSessionStorage; final class ApplyRelayPointSubscriber implements EventSubscriberInterface { public function __construct( private readonly RelayPointSessionStorage $storage, ) {} public static function getSubscribedEvents(): array { return ['sylius.order.pre_complete' => 'onPreComplete']; } public function onPreComplete(GenericEvent $event): void { $order = $event->getSubject(); $selected = $this->storage->get($order->getTokenValue()); if (null === $selected) { return; } // Update the order shipping address with relay point data $address = $order->getShippingAddress(); $address->setStreet($selected->street); $address->setPostcode($selected->postcode); $address->setCity($selected->city); $address->setCountryCode($selected->countryCode); // Store relay point id for label generation (your own entity field): // $address->setRelayPointId($selected->id); $this->storage->clear($order->getTokenValue()); } }
Customising the widget
CSS variables (theming — no Twig change needed)
Override the CSS custom properties in your stylesheet to match your design system. All defaults are defined on .relay-picker:
.relay-picker { --relay-primary: #7c3aed; /* accent / confirm button */ --relay-primary-hover: #6d28d9; --relay-primary-bg: #f5f3ff; /* selected-point panel background */ --relay-primary-border: #ddd6fe; /* selected-point panel border */ --relay-radius: 0.375rem; /* border-radius for panels and inputs */ --relay-border: #d1d5db; /* general border color */ }
Twig blocks (structural overrides — via {% embed %})
The widget exposes named blocks so you can override individual sections without duplicating the whole template. Use {% embed %} instead of {% include %}:
{% embed '@KeirontwSyliusRelayPointPlugin/shop/relay_point_picker.html.twig' with {
searchUrl: path('keirontw_relay_point_shop_search'),
geocodeUrl: path('keirontw_relay_point_shop_geocode'),
selectUrl: path('keirontw_relay_point_shop_select'),
methodCodes: active_relay_codes,
cartToken: order.tokenValue,
} %}
{# Replace only the confirm button with your own component #}
{% block relay_confirm_button %}
<button type="button"
data-action="click->relay-point-picker#confirmSelection"
class="btn btn-primary w-full">
Valider ce point relais
</button>
{% endblock %}
{% endembed %}
Available blocks:
| Block | Contains |
|---|---|
relay_styles |
<style> tag with CSS variable defaults |
relay_search_bar |
Search input + submit button + filter dropdown |
relay_filter |
Carrier filter dropdown only |
relay_grid |
Two-column map + list layout |
relay_list |
Scrollable relay point list |
relay_map |
Leaflet map |
relay_selected_summary |
Selected point detail panel |
relay_confirm_button |
The confirm CTA inside the summary |
Full template override
Copy the template into your project's bundle override directory — Symfony will use yours instead:
templates/bundles/KeirontwSyliusRelayPointPlugin/shop/relay_point_picker.html.twig
Adding a custom carrier
Implement RelayPointProviderInterface and tag the service. That's it — no YAML mapping, no plugin config change needed.
// src/Shipping/DpdProvider.php use Keirontw\SyliusRelayPointPlugin\RelayPoint\RelayPointProviderInterface; use Keirontw\SyliusRelayPointPlugin\RelayPoint\Model\RelayPoint; use Keirontw\SyliusRelayPointPlugin\RelayPoint\Model\RelayPointSearchCriteria; final class DpdProvider implements RelayPointProviderInterface { public function __construct( private readonly string $apiKey, private readonly array $shippingMethodCodes, ) {} public function supports(string $shippingMethodCode): bool { return in_array($shippingMethodCode, $this->shippingMethodCodes, true); } /** @return RelayPoint[] */ public function search(RelayPointSearchCriteria $criteria): array { // Call DPD Pickup REST API and map results to RelayPoint DTOs return []; } }
Register the service — autoconfiguration applies the tag automatically via _instanceof:
# config/services.yaml App\Shipping\DpdProvider: arguments: $apiKey: '%env(DPD_API_KEY)%' $shippingMethodCodes: ['dpd_pickup_france']
The provider is immediately discoverable by the plugin registry without any further change.
Adding a custom geocoding provider
Implement GeocodingProviderInterface:
use Keirontw\SyliusRelayPointPlugin\Geocoding\GeocodingProviderInterface; use Keirontw\SyliusRelayPointPlugin\Geocoding\Model\GeocodingResult; final class MyGeocoder implements GeocodingProviderInterface { public function geocode(string $query): ?GeocodingResult { // ... return new GeocodingResult(latitude: 48.8, longitude: 2.3, postcode: '75001', city: 'Paris', countryCode: 'FR'); } }
Set provider: custom in the plugin config and alias the interface in your services:
# config/packages/keirontw_sylius_relay_point.yaml keirontw_sylius_relay_point: geocoding: provider: custom # config/services.yaml Keirontw\SyliusRelayPointPlugin\Geocoding\GeocodingProviderInterface: alias: App\Geocoding\MyGeocoder
Licence
MIT. See LICENSE.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-03