keirontw/relay-point-bundle

Carrier-agnostic relay point (point relais) selection bundle for Symfony

Maintainers

Package info

github.com/BastienMesnil/relay-point-bundle

Type:symfony-bundle

pkg:composer/keirontw/relay-point-bundle

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-07-07 07:48 UTC

This package is auto-updated.

Last update: 2026-07-07 07:59:22 UTC


README

Carrier-agnostic relay point ("point relais") selection for any Symfony project — no e-commerce framework required. Provides a search/geocoding API, a session-backed selection store, and a ready-to-embed Stimulus widget.

Supported carriers: Mondial Relay, Chronopost, Shop2Shop, Colissimo, InPost, Colis Privé, DPD, DHL, Packeta, PostNL, Bpost, GLS.

Installation

composer require keirontw/relay-point-bundle

Register the bundle in config/bundles.php (skip this step if you use Symfony Flex and it was added automatically):

return [
    // ...
    Keirontw\RelayPointBundle\KeirontwRelayPointBundle::class => ['all' => true],
];

Import the shop routes in config/routes.yaml:

keirontw_relay_point:
    resource: '@KeirontwRelayPointBundle/config/routes/shop.yaml'

Configuration

Create config/packages/keirontw_relay_point.yaml and enable the carriers you need. Every carrier is disabled by default; only enabled: true carriers get wired into the container.

keirontw_relay_point:
    geocoding:
        provider: addok   # addok (default, free, FR-only) | nominatim | google_maps | photon | custom

    providers:
        mondial_relay:
            enabled: true
            account: '%env(MONDIAL_RELAY_ACCOUNT)%'
            password: '%env(MONDIAL_RELAY_PASSWORD)%'
            shipping_method_codes: ['mondial_relay_fr']

        colissimo:
            enabled: true
            account_number: '%env(COLISSIMO_ACCOUNT)%'
            password: '%env(COLISSIMO_PASSWORD)%'
            shipping_method_codes: ['colissimo_relay']

See src/DependencyInjection/Configuration.php for the full tree (every carrier's required options) and src/Provider/** for the corresponding provider implementation.

To plug your own backend instead of Addok/Nominatim/Google/Photon, set geocoding.provider: custom and alias the interface yourself:

services:
    Keirontw\RelayPointBundle\Geocoding\GeocodingProviderInterface: '@App\Geocoding\MyProvider'

To add a carrier the bundle doesn't ship, implement Keirontw\RelayPointBundle\RelayPoint\RelayPointProviderInterface — it's autoconfigured and auto-tagged, no manual service registration needed.

Frontend assets

The widget is a Stimulus controller. Wire it depending on your asset pipeline:

AssetMapper — nothing to do. If symfony/asset-mapper is installed, the bundle registers its assets/shop directory (under the keirontw_relay_point namespace) automatically via prependExtensionConfig. The controller is picked up by symfony/stimulus-bundle's auto-discovery like any other mapped asset.

Webpack Encore — copy assets/shop/controllers/relay-point-picker_controller.js into your own assets/controllers/ and register it in your assets/controllers.json (or symfony_ux autodiscovery if you vendor it via a package).

The widget also expects Leaflet (L global) to be loaded on the page for the map panel.

Embedding the widget

Include the template wherever you want the picker to render (checkout step, an admin order edit page, anywhere):

{% include '@KeirontwRelayPointBundle/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_fr'],
    cartToken:   cart.token,
} %}

methodCodes should be the subset of your configured shipping_method_codes that are actually active for the current order/cart (e.g. the customer's chosen shipping method, if it's a relay-point one). The widget only searches carriers whose method code is in that list.

See the docblock at the top of templates/shop/relay_point_picker.html.twig for the full list of optional variables (pre-filled address, CSS variable theming) and the Twig blocks you can override via {% embed %}.

Reading the customer's selection

The bundle does not assume any order/cart model — you decide when and how to persist the selection. The widget POSTs to keirontw_relay_point_shop_select on every pick, which stores it in the session via RelayPointSessionStorage.

Option A — automatic, via the built-in checkout listener

Implement RelayPointAddressApplierInterface once in your app:

use Keirontw\RelayPointBundle\Checkout\RelayPointAddressApplierInterface;
use Keirontw\RelayPointBundle\RelayPoint\SelectedRelayPoint;

final class RelayPointOrderApplier implements RelayPointAddressApplierInterface
{
    public function getCartToken(object $event): ?string
    {
        return $event->getOrder()->getTokenValue(); // adapt to your event/order model
    }

    public function apply(object $event, SelectedRelayPoint $point): void
    {
        $order = $event->getOrder();
        $order->setShippingAddress(/* map $point->street, ->city, ->postcode, ->countryCode, ... */);
    }
}

Then point the bundle at your own checkout-completed event:

# config/packages/keirontw_relay_point.yaml
keirontw_relay_point:
    checkout_listener:
        enabled: true
        event_class: App\Event\CheckoutCompletedEvent

The bundle registers RelayPointCheckoutListener as a kernel.event_listener for that event class. It reads the session selection, calls your applier, and clears the session — you never touch RelayPointSessionStorage directly. Your RelayPointAddressApplierInterface implementation must be the only service implementing the interface (or aliased explicitly) so autowiring can find it.

Option B — manual

Skip the config above and read the storage yourself wherever you need it:

use Keirontw\RelayPointBundle\RelayPoint\RelayPointSessionStorage;

$selected = $relayPointStorage->get($cartToken);
if ($selected !== null) {
    // ... apply it, then:
    $relayPointStorage->clear($cartToken);
}

SelectedRelayPoint (returned by ->get()) exposes: id, name, street, postcode, city, countryCode, latitude, longitude, carrierCode, shippingMethodCode, distanceInMeters, openingHours.

Development

composer install
composer test        # phpunit — boots a minimal kernel with only this bundle + framework-bundle
vendor/bin/phpstan analyse

License

MIT