silvershop-suite / shipping-myparcel
MyParcel shipping-label provider for silvershop-suite/shipping-labels — creates carrier labels (PostNL; carrier-extensible) with tracking, from an order.
Package info
github.com/silvershop-suite/silvershop-shipping-myparcel
Type:silverstripe-vendormodule
pkg:composer/silvershop-suite/shipping-myparcel
Requires
- php: ^8.3
- myparcelnl/sdk: ^3.0
- silvershop-suite/shipping-labels: @dev
- silvershop/core: ^6
- silverstripe/framework: ^6.0
- silverstripe/siteconfig: ^6.0
Requires (Dev)
- cambis/silverstan: ^2.1
- phpunit/phpunit: ^11.0
- silverstripe/sqlite3: ^4
- squizlabs/php_codesniffer: ^3.7
Suggests
- silvershop-suite/address-formats: Supplies the discrete house number the delivery-options widget's pickup lookup needs (NL/BE); without it the number is parsed from the street.
- silvershop-suite/store-profile: Central shop identity + sender address, shared with the shipping-labels packing slip.
- silvershop/shipping: Derives the parcel weight automatically from the order's products (createShippingPackage); otherwise a configurable fallback weight is used.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-19 19:35:32 UTC
README
A MyParcel label provider for silvershop-suite/shipping-labels.
It creates a real carrier label (PostNL) for an order through the MyParcel SDK,
returning the label PDF + barcode/tracking; the shipping-labels framework stores and serves the PDF.
MyParcel is an NL/BE-account aggregator (ships NL/BE domestic + EU/international). Merchants outside NL/BE should use a different adapter.
Install
composer require silvershop-suite/shipping-myparcel
Pulls in silvershop-suite/shipping-labels and myparcelnl/sdk. Install silvershop/shipping too if you want the parcel
weight derived automatically from your products.
Configure
Put your MyParcel API key in .env (never in config/SiteConfig):
SS_MYPARCEL_API_KEY="your-myparcel-key"
Everything else is optional config on SilverShop\ShippingMyParcel\MyParcelProvider:
SilverShop\ShippingMyParcel\MyParcelProvider: default_service: 'postnl_parcel' default_weight_grams: 1000 # fallback when a parcel has no product weight weight_to_grams: 1 # set 1000 if your shop stores product weight in kilograms services: postnl_parcel: { title: 'PostNL — Parcel', carrier_id: 1, package_type: 1 } postnl_mailbox: { title: 'PostNL — Mailbox parcel', carrier_id: 1, package_type: 2 } # Add DHL/DPD here once the installed myparcelnl/sdk major provides the carrier — config only, no code.
Use
Create a label for an order (the only entry point; the shipping-labels framework persists the result):
$label = $order->createShippingLabel('myparcel', ['service' => 'postnl_parcel']); // $label->LabelFile (PDF), $label->TrackingNumber (barcode), $label->TrackingUrl, $label->ProviderRef (consignment id)
The label PDF is viewable at /shippinglabels/label/{ID}. track() returns the barcode + public MyParcel
track-trace URL.
Checkout delivery options + pickup points (afhaalpunt)
The module also adds a pre-purchase step: at checkout the customer can choose home delivery or a nearby
carrier parcel point (PostNL pickup location). The choice is stored on the order and routed into the consignment, so
the package is sent to the chosen point (delivery_type 4).
It uses MyParcel's official @myparcel/delivery-options
v6 widget, loaded from a pinned jsDelivr CDN build and mounted into the single-page checkout via the module's own
Requirements + the updateOrderForm controller hook — no theme edits, the same injection pattern as
silvershop-suite/conversion-tracking. The widget's address lookups (delivery options + nearby pickup points) are keyless;
only consignment creation uses your SS_MYPARCEL_API_KEY.
How it works
- When MyParcel is configured and the cart is not pickup-only, the widget mount + a hidden field are added to the
checkout form (via a
CheckoutComponent), and the widget JS + a small glue script are injected. - The glue script builds the widget config from the live shipping-address fields and only activates it for an
NL/BE address with a postcode + house number. (Install
silvershop-suite/address-formatsfor a discrete house-number field; otherwise it is parsed from the street.) - The customer's selection (
myparcel_updated_delivery_options) is written into the hidden field and, on submit, stored on the order. MyParcelProvider::createLabel()reads the stored choice and, for a pickup, setsdelivery_type = 4+ signature + thepickupobject (canonicallocation_code+retail_network_id+ the point's address).
Nothing chosen, an incomplete address, or a non-NL/BE destination → the order falls back to plain home delivery.
What is stored on the order (carrier-neutral field names, so a future generic delivery-options layer can lift them):
| Field | Meaning |
|---|---|
MyParcelDeliveryOptions |
The raw widget payload JSON (source of truth) |
DeliveryType |
standard / morning / evening / pickup / … |
DeliveryDate |
The carrier's chosen date (when offered) |
PickupLocationCode / PickupNetworkId |
The canonical MyParcel pickup identifiers |
PickupLocation (has_one Address) |
The chosen point, mirrored for admin display |
Checkout wiring (no core edit)
Core's SinglePageCheckoutComponentConfig hard-codes its component list with no add-component hook. Rather than swap the
class (an Injector class: override doesn't compose — only one module can win), the module appends its component with an
Injector calls: entry (addComponent). Injector merges service definitions, so this coexists with another module that
overrides the same config (e.g. silvershop-suite/vat-compliance). CheckoutComponentConfig::create() resolves through the
Injector, so no silvershop/core edit is needed. A small upstream updateComponents extend hook in the core config would
be cleaner still; it is flagged as a recommended future PR rather than bundled here.
Widget configuration
Defaults live on SilverShop\ShippingMyParcel\Extension\MyParcelCheckoutExtension (CDN URLs, platform, carrier,
locale, pickup_default_view) and SilverShop\ShippingMyParcel\Extension\MyParcelOrderExtension.allowed_countries
(default ['NL', 'BE']). Override them in config as needed, e.g.:
SilverShop\ShippingMyParcel\Extension\MyParcelCheckoutExtension: pickup_default_view: 'list'
Cancelling labels
cancelLabel() asks MyParcel to delete the shipment and then verifies it is actually gone before reporting success,
so it never reports a cancellation that didn't happen.
Limitation: MyParcel's API can only delete shipments still in concept status. Because this module produces the
label PDF when it creates the label — which registers / pre-announces the shipment with the carrier — a created label
is no longer a concept, so cancelLabel() on it returns false and leaves the label unchanged. To remove such a
shipment, delete it in the MyParcel dashboard (possible the same day, before it is collected).
Making created labels cancellable through the API would need a concept-first flow (register the shipment as a concept and produce the PDF only when printing) — a planned v2 change.
Scope
- v1 — labels (PostNL, carrier-extensible via config): a real carrier label + tracking from a placed order.
- v2 (this release) — checkout delivery-options + carrier pickup-point selection: home-vs-pickup chosen with the official MyParcel widget, stored on the order, routed into the consignment. Flat pricing (no per-option surcharge), NL/BE gate, graceful degrade.
Planned (later): per-option surcharges (pickup fee / signature / delivery windows) as a shipping modifier, carrier delivery dates/time windows on the consignment, multi-carrier (DPD/bpost/DHL), opt-in auto-create on payment, bulk label creation, concept-first create (API-cancellable labels).
Credits
Built and maintained by Remy Vaartjes (remy@xd.nl) — part of the SilverShop-Suite.