Skip to content

Shipping Voucher Generation

Flow ID: AD-34 Module(s): eshop, Transporters Complexity: High Last Updated: 2026-04-04

Business Overview

Shipping voucher generation is the process of creating carrier shipping labels for accepted orders. An admin selects one or more orders in the order management panel and triggers voucher creation, which calls the carrier's API to generate a shipment with a tracking number. The order status transitions to PENDING_ACCEPTED_VOUCHER, and the tracking number (gtcode) is stored for subsequent tracking (AD-33) and label printing.

The system supports 16 carriers, with per-carrier logic for shipment creation, voucher cancellation, and label printing (single and batch). Each carrier has distinct API payloads, but the voucher libraries provide a unified interface via a class_name switch dispatcher.

API Reference

Admin Endpoints

Voucher operations are triggered from the admin order management controller (Adv_orders_admin):

ActionTriggerLibrary
Generate voucher(s)Admin bulk action on selected ordersAdvSetPendingWithVoucher::set()
Cancel voucher(s)Admin bulk action on selected ordersAdvCancelVoucher::cancel()
Print single voucherAdmin order detailAdvPrintVoucher::print()
Print batch vouchersAdmin bulk action on selected ordersAdvPrintVoucher::printBatch()

REST Endpoints

No dedicated voucher REST endpoint. Order status reflecting voucher state is exposed via:

MethodPathNotes
GET/rest/order/order/{id}status field shows PENDING_ACCEPTED_VOUCHER

Code Flow

Voucher Creation

Admin selects orders → "Generate Voucher" action
  |
  +--> AdvSetPendingWithVoucher($languageAbbr)::set($provider, $providerSettings, $orders)
       |
       +--> Switch on provider->class_name (16 carriers)
       |
       +--> For each order:
            |
            +--> canCreateVoucher($order) guard check
            |    ├── COD orders: must be PENDING_ACCEPTED
            |    └── Prepaid orders: must be PAID
            |
            +--> recipientName($order) -- shipping or billing fallback
            +--> recipientPostal($order) -- shipping or billing fallback
            |
            +--> Build carrier-specific payload (address, COD amount, weight, etc.)
            +--> Call carrier SDK: createJob() / createShipment() / createVoucher()
            |
            +--> On success:
                 +--> order_model->update_order(id, {
                        gtcode: tracking_number,
                        gtjobcode: job_id,
                        status: 'PENDING_ACCEPTED_VOUCHER'
                      })

Voucher Cancellation

Admin selects orders → "Cancel Voucher" action
  |
  +--> AdvCancelVoucher::cancel($provider, $providerSettings, $orders)
       |
       +--> Switch on provider->class_name (15 carriers)
       +--> For each order:
            +--> Call carrier SDK: cancelJob() / deleteVoucher() / cancelVoucher() / cancelShipment()
            +--> On success:
                 +--> order_model->setOrderCanceledVoucher($orderId, revertedStatus)
                 +--> Status reverts based on payment method:
                      COD → PENDING_ACCEPTED, Prepaid → PAID

Voucher Printing

Admin → "Print Voucher" (single or batch)
  |
  +--> AdvPrintVoucher::print() or ::printBatch()
       |
       +--> Switch on provider->class_name
       +--> Call carrier SDK: getVoucherPdf() / getVouchersPdf() / printVoucherLabel()
       +--> Output PDF to browser (Content-Type: application/pdf)
       |
       +--> Batch: Some carriers support multi-voucher PDFs natively;
            ACS uses FPDI to merge individual PDFs

Domain Layer

Key Files

FileResponsibility
ecommercen/libraries/vouchers/AdvSetPendingWithVoucher.phpVoucher creation (16 carriers, ~1370 lines)
ecommercen/libraries/vouchers/AdvCancelVoucher.phpVoucher cancellation (15 carriers)
ecommercen/libraries/vouchers/AdvPrintVoucher.phpSingle + batch voucher label printing (16 carriers)
ecommercen/libraries/vouchers/AdvTrackAndTrace.phpOn-demand tracking (see AD-33)
ecommercen/eshop/controllers/Adv_orders_admin.phpAdmin UI trigger point
ecommercen/eshop/models/Adv_order_model.phpOrder update + DHL voucher storage
src/Transporters/{Carrier}/{Carrier}.phpCarrier SDK (API client)
src/Transporters/{Carrier}/{Carrier}Config.phpPer-carrier config DTO

Supported Carriers (16 for creation)

class_nameCarrierCreateCancelPrintBatch Print
GTGeniki v1YesYesNo (legacy)No
GTV2Geniki v2YesYesYesYes
ACSACS (REST)YesYesYesYes (FPDI merge)
ACSSoapACS (SOAP)NoNoNoNo
ELTAELTAYesYes (local only)YesNo
SPEEDEXSpeedexYesYesYesYes
CENTERCenterYesYesYesYes (max 20)
EASYMAILEasyMailYesYesYesYes
FISFIS CourierYesYesRedirectYes (redirect)
BOXNOWBoxNowYesYesYesNo
DHLDHL ExpressYesYes (+ file cleanup)Yes (local file)No
TAXYDEMATaxydema v1YesYesYesNo
DAILYCOURIERDailyCourierYesNoYesYes
SKROUTZSkroutz Last MileYesYesYesNo
ASAPASAPYes (batch)Yes (local)YesNo
TAXYDEMAV2Taxydema v2YesYesYesNo

Architecture

Admin Order View (Adv_orders_admin)
  |
  +--> ecommercen/libraries/vouchers/
  |    ├── AdvSetPendingWithVoucher.php   -- creation
  |    ├── AdvCancelVoucher.php           -- cancellation
  |    ├── AdvPrintVoucher.php            -- label printing
  |    └── AdvTrackAndTrace.php           -- on-demand tracking
  |
  +--> src/Transporters/{Carrier}/
       ├── {Carrier}.php       -- API client (createJob, createVoucher, etc.)
       ├── {Carrier}Config.php -- Config from transporters_settings
       └── {Carrier}Helper.php -- Utilities

The voucher libraries follow a Strategy pattern via switch dispatch: each library receives a $provider object and delegates to a carrier-specific private method based on $provider->class_name. Carrier SDK classes encapsulate the actual HTTP API calls.

DHL Special Handling

DHL vouchers are stored as local PDF files in DHL_VOUCHER_SAVE_PATH and tracked in a separate shop_order_dhl_voucher table with fields: order_id, dispatch_confirmation_number, tracking_number, filename, shipping_date, product_code. Cancellation deletes both the DB record and the physical file.

Data Model

shop_order (voucher columns)

ColumnTypeDescription
gtcodevarchar(30)Carrier tracking/voucher number
gtjobcodevarchar(255)Carrier-side job ID (used for cancellation)
statusvarchar(...)Set to PENDING_ACCEPTED_VOUCHER on creation
transport_idint(11)FK to the carrier/transporter used
meta_datatextAudit trail: ordersAdmin:createVoucher{Carrier}

Order Status Transitions

PENDING_ACCEPTED (COD) ──┐
                         ├──> PENDING_ACCEPTED_VOUCHER ──> (tracking via AD-33)
PAID (prepaid)    ───────┘
                                    |
                                    | Cancel voucher
                                    v
                         Reverts to original status
                         (COD → PENDING_ACCEPTED, Prepaid → PAID)

Configuration

  • Provider credentials: Stored in transporters_settings key-value table, managed via AD-06 admin UI
  • Weight handling: Controlled by provider.send_weight flag; when enabled, order total_weight (in grams) is converted to kg
  • COD detection: isOrderPaidAtDeliveryByPayWay($order->payway) -- checks if payment method is cash-on-delivery
  • Smart point integration: ACS, Skroutz, and ASAP check order_smart_point table for pickup/locker destinations
  • DHL export handling: International shipments include basket items and invoice data for customs

Client Extension Points

  • Override voucher creation: In a client repo, create application/libraries/vouchers/AdvSetPendingWithVoucher.php with modified carrier logic
  • Custom carrier integration: Add SDK under src/Transporters/, add case to all four voucher libraries
  • Custom COD logic: Override canCreateVoucher() to change eligibility rules

Business Rules

  1. Eligibility guard: Only orders in PENDING_ACCEPTED (COD) or PAID (prepaid) can receive vouchers
  2. Address fallback: If shipping address is empty, billing address is used for the shipping label
  3. Weight normalization: Weights are converted from grams to kg; minimum weights vary by carrier (0.5kg for ACS, 1kg for Geniki)
  4. COD amount: For cash-on-delivery orders, total_vat (order total with tax) is sent as the COD collection amount
  5. Status reversion on cancel: orderGetRevertedStatusForPayWayVoucher($payway) determines the rollback status
  6. ELTA cancel is local-only: No carrier API call -- just resets the order status locally
  7. ASAP uses geocoding: Shipping address is geocoded via OpenStreetMaps before voucher creation; if geocoding fails, voucher creation is skipped
  8. Batch print limits: Center caps batch printing at 20 vouchers; ACS processes in chunks of 10 with FPDI PDF merging
  9. Courier comments: Sanitized with replaceAmpersand() before sending to carrier APIs

Wiki Guide: DHL Integration Guide -- specific DHL Express voucher and tracking setup