Skip to content

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_name constant 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_vouchers flag marks transporters that support automated voucher/label generation via their API.
  • Cost calculation modes: The eshop_calculated_cost flag determines whether shipping cost is calculated internally (via pricing tables) or externally via the provider's API.

Supported Providers

class_nameDisplay NameSettings MethodKey Credentials
GTGeniki Taxydromiki (v1)settings_gtUSER, PWD
GTV2Geniki Taxydromiki (v2)settings_gt_v2ENV, USER, PWD, APPKEY, FRMT
ACSACS Couriersettings_acsCID, CPWD, UID, UPWD, BCODE, APIKEY, PRINT_TYPE, DELIVERY_OPTION_TYPE
ACSSoapACS SOAPsettings_soapCUS, CPWD, USE, PWD
ELTAELTA Couriersettings_eltaSCD, SSD, SECD, UCD, PS
SPEEDEXSpeedex Couriersettings_speedexENVIRONMENT, USERNAME, PASSWORD, AGREEMENT_ID, CUSTOMER_ID, + 5 more
CENTERCentersettings_centerUSERALIAS, CREDENTIALVALUE, APIKEY, TEMPLATE
EASYMAILEasyMailsettings_easy_mailENVIRONMENT, USERNAME, PASSWORD
FISFIS Couriersettings_fisWEBSERVICE_CODE, PRINT_TYPE
BOXNOWBOX NOW (locker)settings_box_nowENVIRONMENT, CLIENTID, CLIENTSECRET, CONTACTNUMBER, CONTACTEMAIL, CONTACTNAME, LOCATIONID, DELIVERY_OPTION_TYPE, USE_TRANSPORTER_WIDGET
CYPRUSPOSTCyprus Postsettings_cyprus_postAPIKEY
DHLDHL Expresssettings_dhlENVIRONMENT, APIKEY, APISECRET, ACCOUNT_NUMBER, + 11 address/signature fields
TAXYDEMATaxydema (v1)settings_taxydemaPRINT_TYPE, A_PEL_CODE, A_PEL_SUBCODE, A_USER_CODE, A_USER_PASS
DAILYCOURIERDaily Couriersettings_daily_courierBEARER_TOKEN, PRINT_TYPE, POINT_NAME, POINT_ADDRESS, POINT_ZIP_CODE, POINT_PHONE, POINT_EMAIL
SKROUTZSkroutz Last Milesettings_skroutzProduction/Test API tokens, pickup location codes, map/base URLs, DELIVERY_OPTION_TYPE, USE_TRANSPORTER_WIDGET, PAPER_SIZE
ASAPASAP Deliverysettings_asapENVIRONMENT, CLIENT_ID, CLIENT_SECRET, COMPANY_ID, CUTOFF_TIME, CONTACT_NAME, CONTACT_NUMBER
TAXYDEMAV2Taxydema (v2)settings_taxydema_V2USERALIAS, 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.

MethodRouteActionDescription
GETeshop/transporters_adminindex()List all transporters (sorted by sort column)
GETeshop/transporters_admin/add/{provider}add($provider)Show add form for a specific provider class
POSTeshop/transporters_admin/add/{provider}add($provider)Create new transporter
GETeshop/transporters_admin/edit/{id}edit($id)Show edit form for transporter
POSTeshop/transporters_admin/edit/{id}edit($id)Update transporter record + MUI translations
GETeshop/transporters_admin/markActive/{id}markActive($id)Activate transporter and redirect
GETeshop/transporters_admin/markInactive/{id}markInactive($id)Deactivate transporter and redirect
POST (AJAX)eshop/transporters_admin/updateOrderupdateOrder()Reorder transporters via drag-and-drop
GET/POSTeshop/transporters_admin/settings_{method}/{id}settings_{method}($id)View/save provider-specific settings (17 methods)
GETeshop/transporters_admin/pricing/{id}pricing($id)View county + postal code pricing
POSTeshop/transporters_admin/postPricing/{id}postPricing($id)Save county + postal code pricing
GETeshop/transporters_admin/pricingOptions/{id}pricingOptions($id)View weight-based pricing options
POSTeshop/transporters_admin/postPricingOptions/{id}postPricingOptions($id)Save weight-based pricing options
GETeshop/transporters_admin/availabilities/{id}availabilities($id)View county + postal code availability
POSTeshop/transporters_admin/postAvailabilities/{id}postAvailabilities($id)Save county + postal code availability
GET/POSTeshop/transporters_admin/public_marketplace_mappingpublic_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)

MethodPathActionDescription
GET/rest/transporterindexList transporters (paginated, filterable)
GET/rest/transporter/{id}showGet transporter by ID
GET/rest/transporter/itemitemGet single transporter by filter
POST/rest/transporterstoreCreate transporter
POST/rest/transporter/{id}updateUpdate transporter
DELETE/rest/transporter/{id}destroyDelete 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

MethodPathActionDescription
GET/rest/transporter/settingindexList settings (paginated)
GET/rest/transporter/setting/itemitemGet single setting by filter
POST/rest/transporter/settingstoreCreate setting
DELETE/rest/transporter/settingdestroyDelete setting

Filters: transporterId (exact), regKey (partial), regValue (partial) Sorts: transporterId, regKeyRelations: transporter (BELONGS_TO)

Pricing (county-level)

MethodPathActionDescription
GET/rest/transporter/pricingindexList pricing entries
GET/rest/transporter/pricing/itemitemGet single pricing entry
POST/rest/transporter/pricingstoreCreate pricing entry
DELETE/rest/transporter/pricingdestroyDelete pricing entry

Filters: transporterId (exact), countryAlpha2 (exact), countyAlpha (exact) Sorts: transporterId, countryAlpha2, countyAlphaRelations: transporter (BELONGS_TO)

Option Pricing (weight-based parameters)

MethodPathActionDescription
GET/rest/transporter/option-pricingindexList option pricing entries
GET/rest/transporter/option-pricing/itemitemGet single entry
POST/rest/transporter/option-pricingstoreCreate entry
DELETE/rest/transporter/option-pricingdestroyDelete entry

Filters: transporterId (exact), countryAlpha2 (exact), regKey (partial) Sorts: transporterId, countryAlpha2, regKeyRelations: transporter (BELONGS_TO)

Post Availability (postal-code level)

MethodPathActionDescription
GET/rest/transporter/post-availabilityindexList postal code availability
GET/rest/transporter/post-availability/itemitemGet single entry
POST/rest/transporter/post-availabilitystoreCreate entry
DELETE/rest/transporter/post-availabilitydestroyDelete entry

Filters: transporterId (exact), countryAlpha2 (exact), post (exact) Sorts: transporterId, countryAlpha2, postRelations: transporter (BELONGS_TO)

Post Pricing (postal-code level)

MethodPathActionDescription
GET/rest/transporter/post-pricingindexList postal code pricing
GET/rest/transporter/post-pricing/itemitemGet single entry
POST/rest/transporter/post-pricingstoreCreate entry
DELETE/rest/transporter/post-pricingdestroyDelete entry

Filters: transporterId (exact), countryAlpha2 (exact), post (exact) Sorts: transporterId, countryAlpha2, postRelations: transporter (BELONGS_TO)

County Availability

MethodPathActionDescription
GET/rest/transporter/county-availabilityindexList county availability
GET/rest/transporter/county-availability/itemitemGet single entry
POST/rest/transporter/county-availabilitystoreCreate entry
DELETE/rest/transporter/county-availabilitydestroyDelete entry

Filters: transporterId (exact), countryAlpha2 (exact), countyAlpha (exact) Sorts: transporterId, countryAlpha2, countyAlphaRelations: transporter (BELONGS_TO)

Public Mapping

MethodPathActionDescription
GET/rest/transporter/public-mappingindexList public mappings
GET/rest/transporter/public-mapping/itemitemGet single mapping
POST/rest/transporter/public-mappingstoreCreate mapping
DELETE/rest/transporter/public-mappingdestroyDelete mapping

Filters: transporterId (exact), code (partial) Sorts: transporterId, codeRelations: transporter (BELONGS_TO)

SmartPoint Catalog — guest-accessible live pickup-point catalog

EndpointActionDescription
GET /rest/transporter/{id}/smart-pointindexLive 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 (no HandlesRestfulActions; 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 ID

Provider 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 properties

Shipping 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 price

Marketplace 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 flash

Domain 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 -- Implements FilterTranslation for 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, with RepositoryConfigurator defining 8 ONE_TO_MANY relations to all sub-entity repositories via transporter_id FK.
  • MuiRepository: Table transporters_mui, configured with NullRelationConfigurator (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, and name.{locale}.

Setting

  • Entity: Composite key (transporter_id, reg_key). Properties: transporter_id, reg_key, reg_value (nullable).
  • RepositoryConfigurator: BELONGS_TO relation 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_TO Transporter.
  • 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_TO Transporter.
  • 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 adds cost for 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 legacy transporters_model->settings() via get_instance() (contained antipattern, acknowledged in method docblock on load() at src/Domains/Transporter/SmartPointCatalog/TransporterSettingsLoader.php:7-16).
  • Three typed exceptions: UnknownTransporterException, SmartPointsDisabledException (3 reasons), TransporterApiException.
  • DI registration: Service receives $providerMap from config_item('smartPoints') (src/Domains/Transporter/container.php:89-91); REST controller at src/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} objects
  • createField() -- 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

ColumnTypeDescription
idint (PK, auto-increment)Transporter ID
slugvarcharURL-safe identifier
class_namevarchar, nullableProvider integration class (e.g., ACS, DHL, BOXNOW)
has_voucherstinyint(1)Whether provider supports automated voucher generation
send_weighttinyint(1)Whether to transmit package weight to provider API
activetinyint(1)Whether transporter is currently enabled
eshop_calculated_costtinyint(1)1 = use internal pricing tables, 0 = query provider API
sortintDisplay order (managed via drag-and-drop)

transporters_mui

ColumnTypeDescription
idint (PK, auto-increment)Row ID
transporter_idint (FK -> transporters.id)Parent transporter
namevarcharLocalized transporter name
langvarcharLanguage code (e.g., el, en)

transporters_settings

ColumnTypeDescription
transporter_idint (PK part, FK)Parent transporter
reg_keyvarchar (PK part)Setting key (e.g., APIKEY, USERNAME)
reg_valuevarchar, nullableSetting value (may contain API secrets)

Composite PK: (transporter_id, reg_key)

transporters_pricing

ColumnTypeDescription
transporter_idint (PK part, FK)Parent transporter
country_alpha_2varchar(2) (PK part)ISO country code
county_alphavarchar (PK part)County/region code
costdecimalFlat shipping cost for this zone

Composite PK: (transporter_id, country_alpha_2, county_alpha)

transporters_options_pricing

ColumnTypeDescription
transporter_idint (PK part, FK)Parent transporter
country_alpha_2varchar(2) (PK part)ISO country code
reg_keyvarchar (PK part)Option key (see below)
reg_valuevarcharOption 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 apply
  • DELIVERY_COST_MIN_FREE -- Order threshold for free delivery cost
  • WEIGHT_LIMIT -- Maximum weight (grams) before per-kg surcharge
  • PRICE_PER_KG -- Surcharge per kg over weight limit
  • TRANS_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

ColumnTypeDescription
transporter_idint (PK part, FK)Parent transporter
country_alpha_2varchar(2) (PK part)ISO country code
postvarchar (PK part)Postal code
costdecimalShipping cost override for this postal code

Composite PK: (transporter_id, country_alpha_2, post)

transporters_counties_availabilities

ColumnTypeDescription
transporter_idint (PK part, FK)Parent transporter
country_alpha_2varchar(2) (PK part)ISO country code
county_alphavarchar (PK part)County/region code

Composite PK: (transporter_id, country_alpha_2, county_alpha)

transporters_posts_availabilities

ColumnTypeDescription
transporter_idint (PK part, FK)Parent transporter
country_alpha_2varchar(2) (PK part)ISO country code
postvarchar (PK part)Postal code

Composite PK: (transporter_id, country_alpha_2, post)

public_transporters_mapping

ColumnTypeDescription
transporter_idint (PK part, FK)Parent transporter
codevarchar (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

GroupKeyPurpose
PUBLIC_MARKETPLACEENABLEDEnables 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). When DELIVERY_OPTION_TYPE=2 (smart-point-only) AND USE_TRANSPORTER_WIDGET=1, GET /rest/transporter/{id}/smart-point returns 409 widget_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 -- production or testing toggle 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:

  1. Admin uploads an image file via the settings form
  2. The image is temporarily stored, read into memory, and base64-encoded
  3. The encoded string is saved as the SIGNATUREIMAGE setting value
  4. 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 if class_name is in TRANSPORTER_CLASSES
  • providerSettingsEditLink($provider) -- Returns the admin URL for a provider's settings page (switch on class_name)
  • getLinkForTransferProvider($provider, $gtCode) -- Returns external tracking URL via the provider's Helper class
  • getMinifiedLinkForTransferProvider($provider) -- Returns shortened tracking URL for emails/SMS
  • getOrdersByTransferProviderId($orders) -- Groups orders by transport_id
  • getParcelSize() / getParcelSizeDropDown() -- BoxNow parcel size options
  • prepareAsapGetCostData() -- Builds ASAP API request payload for cost estimation

Business Rules

  1. Authorization: Only AUTH_ROLE_ADVISABLE and AUTH_ROLE_ADMIN can access the transporter admin. (ecommercen/eshop/controllers/Adv_transporters_admin.php, __construct())

  2. 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's class_name matches the expected value. (ecommercen/eshop/controllers/Adv_transporters_admin.php, add() / settings_*())

  3. 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())

  4. 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())

  5. Weight surcharge: If package weight exceeds the WEIGHT_LIMIT option for the country, extra cost is calculated as ceil((weight - weightLimit) / 1000) * PRICE_PER_KG. This is added to the base county price (or charged alone if the order exceeds TRANS_COST_LIMIT). (application/libraries/Transporters.php, transportCost())

  6. Free shipping logic: Shipping is free when the order total exceeds TRANS_COST_LIMIT AND weight is within WEIGHT_LIMIT. If weight exceeds the limit, only the overweight surcharge applies. (application/libraries/Transporters.php, transportCost())

  7. 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())

  8. 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())

  9. Marketplace mapping guards: The public_marketplace_mapping page is only accessible when PUBLIC_MARKETPLACE.ENABLED is set in the registry. The "none" UI option maps to the other code in the database. (ecommercen/eshop/controllers/Adv_transporters_admin.php, public_marketplace_mapping())

  10. 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)

  11. Smart point transporters: Providers with DELIVERY_OPTION_TYPE = 2 are classified as "smart point only" transporters (e.g., BoxNow, Skroutz with pickup points). The getIsSmartPointOnlyTransporter() method checks this flag. (application/libraries/Transporters.php)

  12. 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)

  13. 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

  1. [OPEN] Provider map drift: 6 of 17 transporter class names are absent from $config['smartPoints']CYPRUSPOST, DAILYCOURIER, ASAP, DHL, TAXYDEMA, and TAXYDEMAV2 — and will always return 409 no_provider from GET /rest/transporter/{id}/smart-point (see IN-09 Transporter Integrations §Known Issues for full detail).

  2. [OPEN — minor] TransporterSettingsLoader uses get_instance() to load the legacy transporters_model (src/Domains/Transporter/SmartPointCatalog/TransporterSettingsLoader.php:19-22). Domain-layer convention break, acknowledged in method docblock on load(). Integration-testable only.

  3. [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".

Customer Flows

Admin Flows

Integration Flows

System Flows

Wiki Guides: DHL Guide | Circuit Breaker Guide