Appearance
Transporter / Shipping Management (Admin)
Flow ID: AD-06 Module(s): eshop, application Complexity: High Last Updated: 2026-05-19
Business Overview
The Transporter Management system is the central hub for configuring all shipping providers in an Ecommercen installation. It supports 17 courier/logistics providers common in the Greek and Cypriot e-commerce market, each with dedicated API credential management, and provides a three-tier pricing model with geographic availability controls.
Key capabilities:
- Provider lifecycle: Add, edit, activate/deactivate, and reorder shipping providers. Each provider is identified by a
class_nameconstant that links it to its integration code. - Per-provider API settings: Each of the 17 supported providers has a dedicated settings form storing credentials and configuration as key-value pairs in
transporters_settings. - Multi-level pricing: Three independent pricing tiers -- county-level flat rates, postal-code overrides, and weight-based option parameters -- allow fine-grained shipping cost control per country/region.
- Geographic availability: Two-level availability controls (county-level checkboxes and postal-code lists) determine where each transporter can deliver.
- Marketplace mapping: For shops using the Public Marketplace integration, internal transporters can be mapped to standardized marketplace courier codes.
- Voucher generation support: The
has_vouchersflag marks transporters that support automated voucher/label generation via their API. - Cost calculation modes: The
eshop_calculated_costflag determines whether shipping cost is calculated internally (via pricing tables) or externally via the provider's API.
Supported Providers
| class_name | Display Name | Settings Method | Key Credentials |
|---|---|---|---|
GT | Geniki Taxydromiki (v1) | settings_gt | USER, PWD |
GTV2 | Geniki Taxydromiki (v2) | settings_gt_v2 | ENV, USER, PWD, APPKEY, FRMT |
ACS | ACS Courier | settings_acs | CID, CPWD, UID, UPWD, BCODE, APIKEY, PRINT_TYPE, DELIVERY_OPTION_TYPE |
ACSSoap | ACS SOAP | settings_soap | CUS, CPWD, USE, PWD |
ELTA | ELTA Courier | settings_elta | SCD, SSD, SECD, UCD, PS |
SPEEDEX | Speedex Courier | settings_speedex | ENVIRONMENT, USERNAME, PASSWORD, AGREEMENT_ID, CUSTOMER_ID, + 5 more |
CENTER | Center | settings_center | USERALIAS, CREDENTIALVALUE, APIKEY, TEMPLATE |
EASYMAIL | EasyMail | settings_easy_mail | ENVIRONMENT, USERNAME, PASSWORD |
FIS | FIS Courier | settings_fis | WEBSERVICE_CODE, PRINT_TYPE |
BOXNOW | BOX NOW (locker) | settings_box_now | ENVIRONMENT, CLIENTID, CLIENTSECRET, CONTACTNUMBER, CONTACTEMAIL, CONTACTNAME, LOCATIONID, DELIVERY_OPTION_TYPE, USE_TRANSPORTER_WIDGET |
CYPRUSPOST | Cyprus Post | settings_cyprus_post | APIKEY |
DHL | DHL Express | settings_dhl | ENVIRONMENT, APIKEY, APISECRET, ACCOUNT_NUMBER, + 11 address/signature fields |
TAXYDEMA | Taxydema (v1) | settings_taxydema | PRINT_TYPE, A_PEL_CODE, A_PEL_SUBCODE, A_USER_CODE, A_USER_PASS |
DAILYCOURIER | Daily Courier | settings_daily_courier | BEARER_TOKEN, PRINT_TYPE, POINT_NAME, POINT_ADDRESS, POINT_ZIP_CODE, POINT_PHONE, POINT_EMAIL |
SKROUTZ | Skroutz Last Mile | settings_skroutz | Production/Test API tokens, pickup location codes, map/base URLs, DELIVERY_OPTION_TYPE, USE_TRANSPORTER_WIDGET, PAPER_SIZE |
ASAP | ASAP Delivery | settings_asap | ENVIRONMENT, CLIENT_ID, CLIENT_SECRET, COMPANY_ID, CUTOFF_TIME, CONTACT_NAME, CONTACT_NUMBER |
TAXYDEMAV2 | Taxydema (v2) | settings_taxydema_V2 | USERALIAS, CREDENTIALVALUE, APIKEY, TEMPLATE |
API Reference
Admin Routes (Legacy CI)
All admin routes are under the eshop/transporters_admin controller. Authorization requires AUTH_ROLE_ADVISABLE or AUTH_ROLE_ADMIN.
| Method | Route | Action | Description |
|---|---|---|---|
| GET | eshop/transporters_admin | index() | List all transporters (sorted by sort column) |
| GET | eshop/transporters_admin/add/{provider} | add($provider) | Show add form for a specific provider class |
| POST | eshop/transporters_admin/add/{provider} | add($provider) | Create new transporter |
| GET | eshop/transporters_admin/edit/{id} | edit($id) | Show edit form for transporter |
| POST | eshop/transporters_admin/edit/{id} | edit($id) | Update transporter record + MUI translations |
| GET | eshop/transporters_admin/markActive/{id} | markActive($id) | Activate transporter and redirect |
| GET | eshop/transporters_admin/markInactive/{id} | markInactive($id) | Deactivate transporter and redirect |
| POST (AJAX) | eshop/transporters_admin/updateOrder | updateOrder() | Reorder transporters via drag-and-drop |
| GET/POST | eshop/transporters_admin/settings_{method}/{id} | settings_{method}($id) | View/save provider-specific settings (17 methods) |
| GET | eshop/transporters_admin/pricing/{id} | pricing($id) | View county + postal code pricing |
| POST | eshop/transporters_admin/postPricing/{id} | postPricing($id) | Save county + postal code pricing |
| GET | eshop/transporters_admin/pricingOptions/{id} | pricingOptions($id) | View weight-based pricing options |
| POST | eshop/transporters_admin/postPricingOptions/{id} | postPricingOptions($id) | Save weight-based pricing options |
| GET | eshop/transporters_admin/availabilities/{id} | availabilities($id) | View county + postal code availability |
| POST | eshop/transporters_admin/postAvailabilities/{id} | postAvailabilities($id) | Save county + postal code availability |
| GET/POST | eshop/transporters_admin/public_marketplace_mapping | public_marketplace_mapping() | View/save marketplace transporter mappings |
REST API Endpoints
All REST endpoints require JWT authentication. Each sub-entity supports locale-prefixed routes (e.g., /el/rest/transporter).
Transporter (core entity)
| Method | Path | Action | Description |
|---|---|---|---|
GET | /rest/transporter | index | List transporters (paginated, filterable) |
GET | /rest/transporter/{id} | show | Get transporter by ID |
GET | /rest/transporter/item | item | Get single transporter by filter |
POST | /rest/transporter | store | Create transporter |
POST | /rest/transporter/{id} | update | Update transporter |
DELETE | /rest/transporter/{id} | destroy | Delete transporter (cascades MUI) |
Filters: id (exact), slug (partial), active (exact), hasVouchers (exact), sendWeight (exact), eshopCalculatedCost (exact), name.{locale} (partial, MUI join) Sorts: id, slug, active, sort, name.{locale}Relations (via ?with=): translations, settings, pricing, optionPricing, postAvailabilities, postPricing, countyAvailabilities, publicMappings
Setting
| Method | Path | Action | Description |
|---|---|---|---|
GET | /rest/transporter/setting | index | List settings (paginated) |
GET | /rest/transporter/setting/item | item | Get single setting by filter |
POST | /rest/transporter/setting | store | Create setting |
DELETE | /rest/transporter/setting | destroy | Delete setting |
Filters: transporterId (exact), regKey (partial), regValue (partial) Sorts: transporterId, regKeyRelations: transporter (BELONGS_TO)
Pricing (county-level)
| Method | Path | Action | Description |
|---|---|---|---|
GET | /rest/transporter/pricing | index | List pricing entries |
GET | /rest/transporter/pricing/item | item | Get single pricing entry |
POST | /rest/transporter/pricing | store | Create pricing entry |
DELETE | /rest/transporter/pricing | destroy | Delete pricing entry |
Filters: transporterId (exact), countryAlpha2 (exact), countyAlpha (exact) Sorts: transporterId, countryAlpha2, countyAlphaRelations: transporter (BELONGS_TO)
Option Pricing (weight-based parameters)
| Method | Path | Action | Description |
|---|---|---|---|
GET | /rest/transporter/option-pricing | index | List option pricing entries |
GET | /rest/transporter/option-pricing/item | item | Get single entry |
POST | /rest/transporter/option-pricing | store | Create entry |
DELETE | /rest/transporter/option-pricing | destroy | Delete entry |
Filters: transporterId (exact), countryAlpha2 (exact), regKey (partial) Sorts: transporterId, countryAlpha2, regKeyRelations: transporter (BELONGS_TO)
Post Availability (postal-code level)
| Method | Path | Action | Description |
|---|---|---|---|
GET | /rest/transporter/post-availability | index | List postal code availability |
GET | /rest/transporter/post-availability/item | item | Get single entry |
POST | /rest/transporter/post-availability | store | Create entry |
DELETE | /rest/transporter/post-availability | destroy | Delete entry |
Filters: transporterId (exact), countryAlpha2 (exact), post (exact) Sorts: transporterId, countryAlpha2, postRelations: transporter (BELONGS_TO)
Post Pricing (postal-code level)
| Method | Path | Action | Description |
|---|---|---|---|
GET | /rest/transporter/post-pricing | index | List postal code pricing |
GET | /rest/transporter/post-pricing/item | item | Get single entry |
POST | /rest/transporter/post-pricing | store | Create entry |
DELETE | /rest/transporter/post-pricing | destroy | Delete entry |
Filters: transporterId (exact), countryAlpha2 (exact), post (exact) Sorts: transporterId, countryAlpha2, postRelations: transporter (BELONGS_TO)
County Availability
| Method | Path | Action | Description |
|---|---|---|---|
GET | /rest/transporter/county-availability | index | List county availability |
GET | /rest/transporter/county-availability/item | item | Get single entry |
POST | /rest/transporter/county-availability | store | Create entry |
DELETE | /rest/transporter/county-availability | destroy | Delete entry |
Filters: transporterId (exact), countryAlpha2 (exact), countyAlpha (exact) Sorts: transporterId, countryAlpha2, countyAlphaRelations: transporter (BELONGS_TO)
Public Mapping
| Method | Path | Action | Description |
|---|---|---|---|
GET | /rest/transporter/public-mapping | index | List public mappings |
GET | /rest/transporter/public-mapping/item | item | Get single mapping |
POST | /rest/transporter/public-mapping | store | Create mapping |
DELETE | /rest/transporter/public-mapping | destroy | Delete mapping |
Filters: transporterId (exact), code (partial) Sorts: transporterId, codeRelations: transporter (BELONGS_TO)
SmartPoint Catalog — guest-accessible live pickup-point catalog
| Endpoint | Action | Description |
|---|---|---|
GET /rest/transporter/{id}/smart-point | index | Live pickup-point catalog for one transporter. No filters/sorts/relations. Aggregates external provider APIs via Advisable\Domains\Transporter\SmartPointCatalog\Service. |
- Auth: guest (first guest endpoint in this controller family — matches legacy
Adv_order::smartPointsInitialize()exposure) - Controller:
src/Rest/Transporter/Controllers/SmartPoint.php— extends base class directly (noHandlesRestfulActions; no repository) - HTTP envelopes: 200 (success), 404 (unknown/inactive transporter), 409 (smart points disabled — reasons:
no_provider,not_enabled_for_provider,widget_only), 502 (upstream API failure) - Note: distinct from
GET /rest/order/smart-point(per-order saved row,auth=backend)
Total REST endpoints: 35 (6 parent + 4x7 sub-entities + 1 SmartPoint catalog = 35)
Code Flow
Transporter CRUD (Admin)
Admin visits eshop/transporters_admin
|
+--> __construct(): auth check (AUTH_ROLE_ADVISABLE | AUTH_ROLE_ADMIN)
| load transporters_helper, transporters_model
|
+--> index(): model->getAdminList() joins transporters + transporters_mui
| renders sortable list with jQuery drag-and-drop
|
+--> add($provider):
| ├── GET: render create form with class_name = $provider
| └── POST: validation() -> form_validation->run()
| ├── Build $data: active, slug, has_vouchers, send_weight, class_name, eshop_calculated_cost
| ├── Build $dataMui: name per language
| ├── beforeAddData() hook (extensible in subclasses)
| └── model->addRecord($data, $dataMui) -> INSERT transporters + transporters_mui
|
+--> edit($id):
| ├── model->getAdminRecord($id) -> master + captions
| └── POST: validation() -> form_validation->run()
| ├── Build $data (same as add minus class_name)
| ├── Build $dataMui per language
| ├── beforeUpdateData() hook
| └── model->updateRecord() -> UPDATE transporters + UPSERT transporters_mui
|
+--> markActive/markInactive($id): model->update active=true/false -> redirect
|
+--> updateOrder(): AJAX, receives listItem[] -> updates sort column per IDProvider Settings Flow
Admin clicks "Settings" for a transporter
|
+--> settings_{provider}($transporterId):
|
├── Verify transporter exists AND class_name matches expected value
|
├── Load provider-specific validation rules (validateSettings{PROVIDER}())
|
├── POST: form_validation->run()
| └── model->save{Provider}Settings($transporterId, $post)
| ├── DELETE all from transporters_settings WHERE transporter_id
| └── INSERT BATCH new key-value pairs
|
└── GET: Render settings form
└── new {Provider}Config(model->settings($transporterId))
Config class extends BaseTransporterConfig
Uses filterObject() to map DB records to typed propertiesShipping Cost Calculation (Frontend Integration)
Customer selects shipping at checkout
|
+--> Transporters library (AdvTransporters)
|
├── getAvailable($country, $county, $postalCode, $orderAmount, $cartItems)
| └── TransporterAvailability checks county + postal code tables
|
+--> transportCost($transporterId, $country, $county, $postalCode, $weight, $totalWithVat)
|
├── if !eshop_calculated_cost:
| └── calculateCostExternally() via provider API (DHL, ASAP, etc.)
|
├── Check postal code override pricing (transporters_posts_pricing)
| └── If cost = 0 for matching postal code -> return 0
|
├── Get base price from county pricing (transporters_pricing)
|
├── Get options: weightLimit, pricePerKg, transferCostLimit
| └── from transporters_options_pricing
|
├── if totalWithVat > transferCostLimit AND weight < weightLimit:
| └── Free shipping (return 0)
|
├── if weight > weightLimit:
| └── price += pricePerKg * ceil((weight - weightLimit) / 1000)
|
└── Return final priceMarketplace Mapping Flow
Admin visits public_marketplace_mapping
|
├── Guard: registry->value('PUBLIC_MARKETPLACE', 'ENABLED') required
|
├── Load Transporters library + TransporterData
|
├── Fetch: active transporters (internal) + marketplace transporters (via Factories::publicMarketplace())
|
├── Display mapping form: each internal transporter -> dropdown of marketplace codes
| Special options: "Select transporter" (empty), "None of the rest" (code='none'->'other')
|
└── POST: model->savePublicTransportersMapping($selectedTransporters)
├── Validate: all IDs are int, all codes are string
├── Transaction: DELETE existing for these IDs, INSERT BATCH new mappings
└── Redirect with success flashDomain Layer
The Transporter domain is the largest in the system with 8 sub-entities (plus one non-DDD aggregation service), all under src/Domains/Transporter/. Each sub-entity follows the full DDD pattern: Entity, Repository, RepositoryConfigurator, Service, ListRequest, WriteData, WriteRepository, Validator, and WriteService.
Transporter (parent entity)
- Entity:
Advisable\Domains\Transporter\Transporter\Repository\Entity-- ImplementsFilterTranslationfor MUI-aware filtering. Properties:id,slug,class_name,has_vouchers,send_weight,active,eshop_calculated_cost,sort. - MuiEntity:
...\MuiEntity-- Properties:id,transporter_id,name,lang. - Repository: Table
transporters, withRepositoryConfiguratordefining 8ONE_TO_MANYrelations to all sub-entity repositories viatransporter_idFK. - MuiRepository: Table
transporters_mui, configured withNullRelationConfigurator(no further relations). - WriteService: Transactional create/update/delete. On create: insert transporter row, then batch-insert MUI translations. On update: partial update (null-excluded), then replace all translations. On delete: delete MUI first, then master row.
- WriteData: Accepts both snake_case (
class_name) and camelCase (className) input. Required for create:slug. - MuiWriteData: Required fields:
lang,name. - Validator: Validates translation array (lang is required per entry). Create/update validators are extensible but currently pass-through.
- ListRequest: Allowed filters include all boolean flags plus MUI name filtering per locale. Sorts include
id,slug,active,sort, andname.{locale}.
Setting
- Entity: Composite key (
transporter_id,reg_key). Properties:transporter_id,reg_key,reg_value(nullable). - RepositoryConfigurator:
BELONGS_TOrelation to Transporter. - WriteData: Required:
transporterId,regKey. Optional:regValue.
Pricing
- Entity: Composite key (
transporter_id,country_alpha_2,county_alpha). Properties: those three +cost. - RepositoryConfigurator:
BELONGS_TOTransporter. - WriteData: All four fields required for create.
OptionPricing
- Entity: Composite key (
transporter_id,country_alpha_2,reg_key). Properties: those three +reg_value. - RepositoryConfigurator:
BELONGS_TOTransporter. - WriteData: All four fields required.
- The 5 standard option keys per country:
MIN_ORDER_AMOUNT,DELIVERY_COST_MIN_FREE,WEIGHT_LIMIT,PRICE_PER_KG,TRANS_COST_LIMIT.
PostAvailability
- Entity: Composite key (
transporter_id,country_alpha_2,post). Three-column entity marking which postal codes a transporter can deliver to. - WriteData: All three fields required.
PostPricing
- Entity: Composite key (
transporter_id,country_alpha_2,post). Same as PostAvailability but addscostfor postal-code-specific pricing overrides. - WriteData: All four fields required.
CountyAvailability
- Entity: Composite key (
transporter_id,country_alpha_2,county_alpha). Marks which counties a transporter serves. - WriteData: All three fields required.
PublicMapping
- Entity: Composite key (
transporter_id,code). Maps internal transporters to standardized marketplace courier codes. - WriteData: Both fields required.
SmartPointCatalog (non-DDD aggregation service)
No Entity, Repository, or ListRequest — this is a thin aggregation layer over src/SmartPoints/Transporters/* providers.
Service::fetch(int $transporterId): SmartPointDTO[]— applies a 4-stage provider activation gate before calling the external API (see IN-09 Transporter Integrations §Provider activation gating for the full gate logic and HTTP response codes).TransporterSettingsLoader— bridge to legacytransporters_model->settings()viaget_instance()(contained antipattern, acknowledged in method docblock onload()atsrc/Domains/Transporter/SmartPointCatalog/TransporterSettingsLoader.php:7-16).- Three typed exceptions:
UnknownTransporterException,SmartPointsDisabledException(3 reasons),TransporterApiException. - DI registration:
Servicereceives$providerMapfromconfig_item('smartPoints')(src/Domains/Transporter/container.php:89-91); REST controller atsrc/Rest/Transporter/container.php:72-73.
Architecture
Two-Layer Architecture
The transporter system spans both legacy and modern layers:
┌─────────────────────────────────────────────────────────────────────┐
│ Admin UI (Legacy CI) │
│ Adv_transporters_admin (1392 lines) │
│ ├── CRUD: index, add, edit, activate/deactivate, reorder │
│ ├── 17 provider settings methods │
│ ├── Pricing: county + postal code │
│ ├── Availability: county + postal code │
│ └── Marketplace mapping │
├─────────────────────────────────────────────────────────────────────┤
│ Legacy Model Layer │
│ Adv_transporters_model (1071 lines) │
│ ├── CRUD on transporters + transporters_mui │
│ ├── 17 save{Provider}Settings() methods (delete+insert pattern) │
│ ├── Pricing save/get for all 3 tiers │
│ └── Availability save/get for counties + postal codes │
├─────────────────────────────────────────────────────────────────────┤
│ Frontend Cost Calculation │
│ AdvTransporters library + TransporterData + TransporterAvailability│
│ + TransporterPricing + TransporterOptions │
│ Used by checkout flow for real-time shipping cost calculation │
├─────────────────────────────────────────────────────────────────────┤
│ Provider Integration (Modern PSR-4) │
│ src/Transporters/{Provider}/ -- 17 providers │
│ ├── {Provider}Config extends BaseTransporterConfig │
│ ├── {Provider}Helper (tracking URLs, API calls) │
│ └── {Provider} (voucher generation, API integration) │
├─────────────────────────────────────────────────────────────────────┤
│ Modern Domain Layer (PSR-4) │
│ src/Domains/Transporter/ -- 8 sub-entities │
│ Full DDD: Entity, Repository, Service, WriteService, Validator │
├─────────────────────────────────────────────────────────────────────┤
│ REST API Layer │
│ src/Rest/Transporter/ -- 9 controllers, 35 endpoints │
│ HandlesRestfulActions + HandlesWriteActions │
│ OpenAPI annotated, JWT authenticated (SmartPoint: guest auth) │
└─────────────────────────────────────────────────────────────────────┘Provider Config Pattern
Each provider has a Config class extending BaseTransporterConfig:
php
class AcsConfig extends BaseTransporterConfig
{
// Typed properties for each setting
protected $companyId = '';
protected $apiKey = '';
// ...
// Static field configuration for form rendering
public static function getFieldConfiguration(): array { ... }
// Initialize from DB key-value records
protected function initialize(array $settings): void
{
$this->companyId = $this->filterObject($settings, 'CID');
// ...
}
}BaseTransporterConfig provides:
filterObject()-- extracts a value from an array of{reg_key, reg_value}objectscreateField()-- helper to build field configuration arrays (name, label, type, required, options, etc.)- Field type constants:
TYPE_TEXT,TYPE_PASSWORD,TYPE_NUMBER,TYPE_CHECKBOX,TYPE_SELECT
DI Container Registration
Domain layer (src/Domains/Transporter/container.php): Registers all 8 sub-entities' Repositories, RepositoryConfigurators, Services, WriteRepositories, Validators, and WriteServices. The parent Transporter entity's MuiRepository uses a NullRelationConfigurator since translations have no further relations.
REST layer (src/Rest/Transporter/container.php): Registers all 9 REST controllers, each wired with its domain Service, Resource, Collection, ListRequest, and WriteService.
SmartPointCatalog: TransporterSettingsLoader autowired; Service receives $providerMap via config_item('smartPoints') (src/Domains/Transporter/container.php:89-91). REST controller registered at src/Rest/Transporter/container.php:72-73.
Both containers are registered in application/config/container/modules.php.
Settings Save Pattern (Delete-and-Reinsert)
All 17 provider settings methods in the legacy model follow the same pattern:
php
protected function saveSettings($transporterId, $data)
{
$this->db->delete($this->tableSettings, ['transporter_id' => $transporterId]);
if ($data) {
$this->db->insert_batch($this->tableSettings, $data);
}
}This atomic delete-then-insert approach means every save operation replaces ALL settings for a transporter. The individual save{Provider}Settings() methods simply build the correct array of [transporter_id, reg_key, reg_value] rows and call saveSettings().
Data Model
transporters
| Column | Type | Description |
|---|---|---|
id | int (PK, auto-increment) | Transporter ID |
slug | varchar | URL-safe identifier |
class_name | varchar, nullable | Provider integration class (e.g., ACS, DHL, BOXNOW) |
has_vouchers | tinyint(1) | Whether provider supports automated voucher generation |
send_weight | tinyint(1) | Whether to transmit package weight to provider API |
active | tinyint(1) | Whether transporter is currently enabled |
eshop_calculated_cost | tinyint(1) | 1 = use internal pricing tables, 0 = query provider API |
sort | int | Display order (managed via drag-and-drop) |
transporters_mui
| Column | Type | Description |
|---|---|---|
id | int (PK, auto-increment) | Row ID |
transporter_id | int (FK -> transporters.id) | Parent transporter |
name | varchar | Localized transporter name |
lang | varchar | Language code (e.g., el, en) |
transporters_settings
| Column | Type | Description |
|---|---|---|
transporter_id | int (PK part, FK) | Parent transporter |
reg_key | varchar (PK part) | Setting key (e.g., APIKEY, USERNAME) |
reg_value | varchar, nullable | Setting value (may contain API secrets) |
Composite PK: (transporter_id, reg_key)
transporters_pricing
| Column | Type | Description |
|---|---|---|
transporter_id | int (PK part, FK) | Parent transporter |
country_alpha_2 | varchar(2) (PK part) | ISO country code |
county_alpha | varchar (PK part) | County/region code |
cost | decimal | Flat shipping cost for this zone |
Composite PK: (transporter_id, country_alpha_2, county_alpha)
transporters_options_pricing
| Column | Type | Description |
|---|---|---|
transporter_id | int (PK part, FK) | Parent transporter |
country_alpha_2 | varchar(2) (PK part) | ISO country code |
reg_key | varchar (PK part) | Option key (see below) |
reg_value | varchar | Option value |
Composite PK: (transporter_id, country_alpha_2, reg_key)
Standard option keys per country:
MIN_ORDER_AMOUNT-- Minimum order value for this pricing tier to applyDELIVERY_COST_MIN_FREE-- Order threshold for free delivery costWEIGHT_LIMIT-- Maximum weight (grams) before per-kg surchargePRICE_PER_KG-- Surcharge per kg over weight limitTRANS_COST_LIMIT-- Order value threshold for free transport
All five must be present for a country's options to be saved (enforced in savePricingOptions()).
transporters_posts_pricing
| Column | Type | Description |
|---|---|---|
transporter_id | int (PK part, FK) | Parent transporter |
country_alpha_2 | varchar(2) (PK part) | ISO country code |
post | varchar (PK part) | Postal code |
cost | decimal | Shipping cost override for this postal code |
Composite PK: (transporter_id, country_alpha_2, post)
transporters_counties_availabilities
| Column | Type | Description |
|---|---|---|
transporter_id | int (PK part, FK) | Parent transporter |
country_alpha_2 | varchar(2) (PK part) | ISO country code |
county_alpha | varchar (PK part) | County/region code |
Composite PK: (transporter_id, country_alpha_2, county_alpha)
transporters_posts_availabilities
| Column | Type | Description |
|---|---|---|
transporter_id | int (PK part, FK) | Parent transporter |
country_alpha_2 | varchar(2) (PK part) | ISO country code |
post | varchar (PK part) | Postal code |
Composite PK: (transporter_id, country_alpha_2, post)
public_transporters_mapping
| Column | Type | Description |
|---|---|---|
transporter_id | int (PK part, FK) | Parent transporter |
code | varchar (PK part) | Marketplace standard courier code |
Composite PK: (transporter_id, code)
Configuration
Constants
php
// application/config/constants.php
define('TRANSPORTER_CLASSES', [
'GT', 'GTV2', 'ACS', 'ACSSoap', 'ELTA', 'SPEEDEX', 'CENTER',
'EASYMAIL', 'FIS', 'BOXNOW', 'CYPRUSPOST', 'DHL', 'TAXYDEMA',
'DAILYCOURIER', 'SKROUTZ', 'ASAP', 'TAXYDEMAV2'
]);This constant is checked by the hasProviderSettings() helper to determine if a transporter has a dedicated settings page.
Registry Keys
| Group | Key | Purpose |
|---|---|---|
PUBLIC_MARKETPLACE | ENABLED | Enables the marketplace mapping feature; controls visibility of public_marketplace_mapping admin page |
Provider-Specific Settings Keys
Settings are stored as key-value rows in transporters_settings. The exact set of keys depends on the provider (see the "Supported Providers" table above for the full list per provider). Some cross-cutting settings used by the checkout flow:
DELIVERY_OPTION_TYPE-- Controls delivery mode:0= all,1= home delivery,2= pickup point only. Used by ACS, BoxNow, Skroutz.USE_TRANSPORTER_WIDGET-- Boolean flag for providers that offer a frontend widget for point selection (BoxNow, Skroutz). WhenDELIVERY_OPTION_TYPE=2(smart-point-only) ANDUSE_TRANSPORTER_WIDGET=1,GET /rest/transporter/{id}/smart-pointreturns 409widget_only— the catalog is rendered client-side by the vendor widget instead (SmartPointsDisabledException::REASON_WIDGET_ONLY,src/Domains/Transporter/SmartPointCatalog/Service.php:49-51).ENVIRONMENT--productionortestingtoggle for providers with sandbox APIs (GTV2, Speedex, EasyMail, BoxNow, DHL, ASAP).
DHL-Specific Features
DHL settings include a signature image upload flow unique among providers:
- Admin uploads an image file via the settings form
- The image is temporarily stored, read into memory, and base64-encoded
- The encoded string is saved as the
SIGNATUREIMAGEsetting value - The temporary file is deleted
DHL also supports an isEmpty() method on its config class for checking whether required fields are populated, and maintains EU country code lists for customs handling.
Client Extension Points
Subclass Hooks
The admin controller provides two protected hooks for client repos to customize transporter creation/update:
php
// In a client's application/controllers/eshop/Transporters_admin.php:
class Transporters_admin extends Adv_transporters_admin
{
protected function beforeAddData(array $data, array $dataMui): array
{
// Modify data before insert
return [$data, $dataMui];
}
protected function beforeUpdateData(int $transporterId, array $data, array $dataMui): array
{
// Modify data before update
return [$data, $dataMui];
}
}Custom Domain Extensions
Client repos can override domain services via the custom/ directory:
php
// custom/Domains/Transporter/container.php
$services->set(\Custom\Domains\Transporter\Transporter\Repository\RepositoryConfigurator::class);
$services->alias(
\Advisable\Domains\Transporter\Transporter\Repository\RepositoryConfigurator::class,
\Custom\Domains\Transporter\Transporter\Repository\RepositoryConfigurator::class
);This allows adding custom relations, altering default filters, or injecting additional business logic.
Helper Functions
The transporters_helper.php (loaded via ecommercen/helpers/) provides:
hasProviderSettings($provider)-- Checks ifclass_nameis inTRANSPORTER_CLASSESproviderSettingsEditLink($provider)-- Returns the admin URL for a provider's settings page (switch onclass_name)getLinkForTransferProvider($provider, $gtCode)-- Returns external tracking URL via the provider's Helper classgetMinifiedLinkForTransferProvider($provider)-- Returns shortened tracking URL for emails/SMSgetOrdersByTransferProviderId($orders)-- Groups orders bytransport_idgetParcelSize()/getParcelSizeDropDown()-- BoxNow parcel size optionsprepareAsapGetCostData()-- Builds ASAP API request payload for cost estimation
Business Rules
Authorization: Only
AUTH_ROLE_ADVISABLEandAUTH_ROLE_ADMINcan access the transporter admin. (ecommercen/eshop/controllers/Adv_transporters_admin.php,__construct())Provider-class_name binding: When adding a transporter, the provider type (
class_name) is passed as a URL parameter and stored immutably. The edit form does not allow changing it. Each settings method validates that the transporter'sclass_namematches the expected value. (ecommercen/eshop/controllers/Adv_transporters_admin.php,add()/settings_*())Settings replace pattern: Saving provider settings deletes all existing settings for that transporter and re-inserts the full set. This means settings cannot be partially updated through the legacy admin -- only through the REST API's individual setting endpoints. (
ecommercen/eshop/models/Adv_transporters_model.php,saveSettings())Pricing tier priority: During checkout cost calculation, postal-code pricing (
transporters_posts_pricing) takes precedence over county-level pricing (transporters_pricing). If a postal code has a cost of 0, shipping is free regardless of other settings. (application/libraries/Transporters.php,transportCost())Weight surcharge: If package weight exceeds the
WEIGHT_LIMIToption for the country, extra cost is calculated asceil((weight - weightLimit) / 1000) * PRICE_PER_KG. This is added to the base county price (or charged alone if the order exceedsTRANS_COST_LIMIT). (application/libraries/Transporters.php,transportCost())Free shipping logic: Shipping is free when the order total exceeds
TRANS_COST_LIMITAND weight is withinWEIGHT_LIMIT. If weight exceeds the limit, only the overweight surcharge applies. (application/libraries/Transporters.php,transportCost())External cost calculation: When
eshop_calculated_cost = 0, the system attempts to calculate cost via the provider's API (currently supported for DHL and ASAP). If the external call fails, it falls back to internal pricing tables. Uses SY-26 Circuit Breaker pattern for resilient API calls. (application/libraries/Transporters.php,calculateCostExternally())Pricing options validation: All 5 option keys (
MIN_ORDER_AMOUNT,DELIVERY_COST_MIN_FREE,WEIGHT_LIMIT,PRICE_PER_KG,TRANS_COST_LIMIT) must be non-empty for a country's options to be saved. Partially filled options are silently discarded. (ecommercen/eshop/models/Adv_transporters_model.php,savePricingOptions())Marketplace mapping guards: The
public_marketplace_mappingpage is only accessible whenPUBLIC_MARKETPLACE.ENABLEDis set in the registry. The "none" UI option maps to theothercode in the database. (ecommercen/eshop/controllers/Adv_transporters_admin.php,public_marketplace_mapping())Postal code sanitization: When saving postal code availability and pricing, whitespace is stripped via
preg_replace('/\s+/', '', $post). Empty postal codes after sanitization are skipped. (ecommercen/eshop/models/Adv_transporters_model.php)Smart point transporters: Providers with
DELIVERY_OPTION_TYPE = 2are classified as "smart point only" transporters (e.g., BoxNow, Skroutz with pickup points). ThegetIsSmartPointOnlyTransporter()method checks this flag. (application/libraries/Transporters.php)MUI requirement: Transporter name is required for all configured admin languages. The slug field is also required. These are enforced by CodeIgniter form validation on the legacy admin side and by the domain Validator on the REST API side. (
ecommercen/eshop/controllers/Adv_transporters_admin.php,validation()/src/Domains/Transporter/Transporter/Validator.php)DHL signature image encoding: DHL settings include a unique image upload flow where the signature is base64-encoded and stored as a setting value. Temporary file is deleted after encoding. (
ecommercen/eshop/controllers/Adv_transporters_admin.php,settings_dhl())
Known Issues & Security Gaps
[OPEN] Provider map drift: 6 of 17 transporter class names are absent from
$config['smartPoints']—CYPRUSPOST,DAILYCOURIER,ASAP,DHL,TAXYDEMA, andTAXYDEMAV2— and will always return409 no_providerfromGET /rest/transporter/{id}/smart-point(see IN-09 Transporter Integrations §Known Issues for full detail).[OPEN — minor]
TransporterSettingsLoaderusesget_instance()to load the legacytransporters_model(src/Domains/Transporter/SmartPointCatalog/TransporterSettingsLoader.php:19-22). Domain-layer convention break, acknowledged in method docblock onload(). Integration-testable only.[OPEN — minor] Inactive and non-existent transporter IDs both return 404 with
code: unknown_transporter(src/Domains/Transporter/SmartPointCatalog/Service.php:30-32). Callers cannot distinguish "no such ID" from "exists but deactivated".
Related Flows
Customer Flows
- CF-06 Order Preview -- transporter selection and cost calculation during checkout
- CF-07 Order Confirmation -- transporter details shown in order confirmation
- CF-08 Payment Processing -- payment flow determines SENT vs PAID_SENT status
Admin Flows
- AD-03 Order Management -- voucher generation from order detail page, shipment closure
- AD-18 Store Management -- store pickup as alternative to courier delivery
- AD-34 Voucher Generation -- bulk voucher generation workflow
- AD-33 Multi-Carrier Tracking -- tracking URL resolution via provider helpers
Integration Flows
- IN-04 Public Marketplace -- marketplace integration requiring transporter code mapping
- IN-09 Transporter Integrations -- API-level integration details for voucher generation, tracking, and external cost queries
System Flows
- SY-02 Order Status Emails -- email notifications on shipment status transitions
- SY-20 Geolocation Shipping Zones -- geolocation-based transporter availability
- SY-24 Email Dispatch -- email infrastructure for shipping notifications
- SY-23 MUI Translation Pattern — transporters_mui companion table
- SY-26 Circuit Breaker -- resilient external API calls for cost calculation and voucher generation
- SY-28 Storage Abstraction -- file storage for DHL signature images and voucher PDFs
Wiki Guides: DHL Guide | Circuit Breaker Guide