Appearance
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):
| Action | Trigger | Library |
|---|---|---|
| Generate voucher(s) | Admin bulk action on selected orders | AdvSetPendingWithVoucher::set() |
| Cancel voucher(s) | Admin bulk action on selected orders | AdvCancelVoucher::cancel() |
| Print single voucher | Admin order detail | AdvPrintVoucher::print() |
| Print batch vouchers | Admin bulk action on selected orders | AdvPrintVoucher::printBatch() |
REST Endpoints
No dedicated voucher REST endpoint. Order status reflecting voucher state is exposed via:
| Method | Path | Notes |
|---|---|---|
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 → PAIDVoucher 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 PDFsDomain Layer
Key Files
| File | Responsibility |
|---|---|
ecommercen/libraries/vouchers/AdvSetPendingWithVoucher.php | Voucher creation (16 carriers, ~1370 lines) |
ecommercen/libraries/vouchers/AdvCancelVoucher.php | Voucher cancellation (15 carriers) |
ecommercen/libraries/vouchers/AdvPrintVoucher.php | Single + batch voucher label printing (16 carriers) |
ecommercen/libraries/vouchers/AdvTrackAndTrace.php | On-demand tracking (see AD-33) |
ecommercen/eshop/controllers/Adv_orders_admin.php | Admin UI trigger point |
ecommercen/eshop/models/Adv_order_model.php | Order update + DHL voucher storage |
src/Transporters/{Carrier}/{Carrier}.php | Carrier SDK (API client) |
src/Transporters/{Carrier}/{Carrier}Config.php | Per-carrier config DTO |
Supported Carriers (16 for creation)
| class_name | Carrier | Create | Cancel | Batch Print | |
|---|---|---|---|---|---|
GT | Geniki v1 | Yes | Yes | No (legacy) | No |
GTV2 | Geniki v2 | Yes | Yes | Yes | Yes |
ACS | ACS (REST) | Yes | Yes | Yes | Yes (FPDI merge) |
ACSSoap | ACS (SOAP) | No | No | No | No |
ELTA | ELTA | Yes | Yes (local only) | Yes | No |
SPEEDEX | Speedex | Yes | Yes | Yes | Yes |
CENTER | Center | Yes | Yes | Yes | Yes (max 20) |
EASYMAIL | EasyMail | Yes | Yes | Yes | Yes |
FIS | FIS Courier | Yes | Yes | Redirect | Yes (redirect) |
BOXNOW | BoxNow | Yes | Yes | Yes | No |
DHL | DHL Express | Yes | Yes (+ file cleanup) | Yes (local file) | No |
TAXYDEMA | Taxydema v1 | Yes | Yes | Yes | No |
DAILYCOURIER | DailyCourier | Yes | No | Yes | Yes |
SKROUTZ | Skroutz Last Mile | Yes | Yes | Yes | No |
ASAP | ASAP | Yes (batch) | Yes (local) | Yes | No |
TAXYDEMAV2 | Taxydema v2 | Yes | Yes | Yes | No |
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 -- UtilitiesThe 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)
| Column | Type | Description |
|---|---|---|
gtcode | varchar(30) | Carrier tracking/voucher number |
gtjobcode | varchar(255) | Carrier-side job ID (used for cancellation) |
status | varchar(...) | Set to PENDING_ACCEPTED_VOUCHER on creation |
transport_id | int(11) | FK to the carrier/transporter used |
meta_data | text | Audit 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_settingskey-value table, managed via AD-06 admin UI - Weight handling: Controlled by
provider.send_weightflag; when enabled, ordertotal_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_pointtable 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.phpwith 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
- Eligibility guard: Only orders in
PENDING_ACCEPTED(COD) orPAID(prepaid) can receive vouchers - Address fallback: If shipping address is empty, billing address is used for the shipping label
- Weight normalization: Weights are converted from grams to kg; minimum weights vary by carrier (0.5kg for ACS, 1kg for Geniki)
- COD amount: For cash-on-delivery orders,
total_vat(order total with tax) is sent as the COD collection amount - Status reversion on cancel:
orderGetRevertedStatusForPayWayVoucher($payway)determines the rollback status - ELTA cancel is local-only: No carrier API call -- just resets the order status locally
- ASAP uses geocoding: Shipping address is geocoded via OpenStreetMaps before voucher creation; if geocoding fails, voucher creation is skipped
- Batch print limits: Center caps batch printing at 20 vouchers; ACS processes in chunks of 10 with FPDI PDF merging
- Courier comments: Sanitized with
replaceAmpersand()before sending to carrier APIs
Related Flows
- AD-03 Order Management -- Admin UI triggering voucher actions
- AD-06 Transporter Admin -- Carrier configuration and credentials
- AD-33 Multi-Carrier Tracking -- Post-voucher delivery tracking
- IN-09 Transporter Integrations -- Carrier API details
- SY-01 Cron Framework -- Job scheduling for automated tracking after voucher creation
Wiki Guide: DHL Integration Guide -- specific DHL Express voucher and tracking setup