Overview
Ship with GLS from OpenCart 4.1 in Hungary, the Czech Republic, Slovakia, Romania, Slovenia and Croatia. The extension integrates with the MyGLS API using your own MyGLS login and GLS client number: customers choose home delivery or a GLS ParcelShop / Parcel Locker at checkout, you create and print the label from the order page, and the parcel status comes back to the order.
What it does
- Two checkout methods. "GLS delivery to your address" and "GLS ParcelShop or Parcel Locker", each with your own price (flat or by weight, free from an order value, optional weight limit, tax class, geo zone).
- Point picker. Below the point method the customer searches the public GLS point list by postal code or town, nearest first, with ParcelShops / Parcel Lockers filters, opening hours and a cash-on-delivery tag. The browser only reports a point ID; the shop checks it on the server against the stored GLS list of the delivery country. At checkout, an order without a valid point is not created, and cash on delivery is only accepted with a point that takes it.
- Labels. From the GLS tab of the order or automatically at an order status: 1 to 99 parcels, cash on delivery, extra services (FDS, FSS, SM2, INS, 24H, T09, T10, T12, SAT, TGS), weight and box size. PDF (A4 2x2, A4 4x1, Connect, Thermo, ShipItThermoPdf) or ZPL (203 / 300 dpi, ShipItThermoZpl). Print again in another format.
- Never two parcels. The request is saved with its reference before it is sent to GLS. If GLS does not answer, nothing is sent again: "Check at GLS" looks the reference up first and adopts the parcel it finds (only when its postal code, country and client number match the order).
- Cancel and returns. Cancel a label before the hand-over (its return parcel is cancelled with it), create a return label from the customer to you (optional SRS or PRS return service).
- Tracking. The cron run reads the status of open parcels (GetParcelStatuses); the tracking link goes to the order history and a delivered parcel can move the order to a status of your choice.
- Safe by default. MyGLS passwords stored encrypted, automatic calls paused after MyGLS refuses the login, names, addresses, phones and e-mails masked in the log, data kept across updates. Events and cron only: no OCMOD, no core edits.
Requirements
- OpenCart 4.1.0.x, PHP 8.1 to 8.3 (as OpenCart 4.1 requires) with cURL, OpenSSL and SimpleXML.
- A GLS contract with MyGLS access in HU, CZ, SK, RO, SI or HR: the MyGLS user (e-mail), password and GLS client number. Test system credentials come from your local GLS IT contact. Not included.
- For cash on delivery: the COD currency (HUF, CZK, RON or EUR by default) in System > Localisation > Currencies, or another COD currency set on the Labels tab.
Not in 1.0
Serbia (MyGLS needs the sender ID card number / PIB there), the GLS countries outside MyGLS (Germany, Austria, Poland and others use another GLS API), several MyGLS accounts in one shop, labels of many orders in one file, a map at checkout (the list is searchable instead). OpenCart 3 is not supported (no OpenCart 3 package is shipped).
GLS, MyGLS and ParcelShop are trademarks of their owner. This extension is independent software by Kavio Labs that integrates with the MyGLS API; it is not affiliated with or endorsed by GLS.
Usage
GLS tab of an order. Shown for orders with a GLS method (or with a GLS label or point).
- Create label: choose the number of parcels (1 to 99) and press Create label. The label opens with Label; the parcel numbers link to the GLS tracking page.
- Print again as: pick another printer type (for example ThermoZPL) and press Print. Same parcel, new file.
- Set point: type a GLS point ID to change the point of the order; it is checked against the stored list of the delivery country.
- Refresh: reads the parcel status from GLS now.
- Cancel: only before the hand-over to GLS. The return parcel is cancelled with it.
- Create return label: a parcel from the customer to your sender address. Download it and send it to the customer.
Result unknown. If GLS does not answer a label request, the label is marked "Result unknown" and nothing is sent again. Press Check at GLS: the shop searches the parcel list by its reference (for example OC1024-7) and adopts the parcel. If nothing is found, check MyGLS yourself, then press Nothing was created to allow a new label. A return label request with an unknown result is never repeated either; there is no automatic lookup for it: look for its reference (ending in -R) in MyGLS, then press Nothing was created.
Automatic labels. With "Create the label at status" set, the label is created after the order reaches that status (after the page answered, so customers never wait for GLS). Failures are shown on the GLS tab; safe ones are retried by the cron run.
Shipments tab of the settings lists the latest labels with their state.
Developer notes
Extension code kv_gls (OpenCart 4.1), namespace KavioLabs\KvGls (shared core in system/library/), shipping codes kv_gls.home and kv_gls.point, licence id kv_oc_gls. Port of the CS-Cart add-on kv_gls 1.0.0 with all fixes of its release audit (research/quality/AUDIT-2026-11-07-gls.md) and of the OpenCart BOX NOW audit (research/quality/AUDIT-2026-11-08-oc-boxnow.md: M1 method name follows the point, M2 case-insensitive route guards). PHP 8.1 to 8.3 (OpenCart 4.1 requires 8.1); the code keeps PHP 7.4 syntax.
Which GLS API and why
MyGLS API ("MyGLS API for system integration" ver. 25.12.11, https://api.mygls.hu/docs/MyGLS_API.pdf, sample clients https://api.mygls.hu/docs/mygls_api_20260331.zip): the only GLS API whose full contract (endpoints, classes, error codes, statuses, label formats) is public and has a test system (api.test.mygls.<country>). Countries HR, CZ, HU, RO, SI, SK. Serbia is left out: its parcels need SenderIdentityCardNumber (error 44), Content (48) and parcel properties, and the public point list has no RS. The GLS Group developer portal (ShipIT) shows no documents without an account and is not used.
Endpoints
https://api.mygls.<cc>/ParcelService.svc/json/<Method> (test: api.test.mygls.<cc>), HTTP POST, Content-Type: application/json.
| Action | Method | Notes |
|---|
| Label step 1 | PrepareLabels | returns ParcelInfoList[{ClientReference, ParcelId}]; WebshopEngine = OpenCart (REQUIRED, change 17) |
| Label step 2, reprint | GetPrintedLabels | ParcelIdList (max 99), PrintPosition, ShowPrintDialog false, TypeOfPrinter; Labels byte[] = JSON array of integers |
| Cancel | DeleteLabels | ParcelIdList (max 50) incl. the return parcel |
| Check at GLS, Test connection | GetParcelList | by print date, then by pickup date |
| Tracking | GetParcelStatuses | ParcelNumber (long), ReturnPOD false, LanguageIsoCode left out (default EN) |
Authentication in every body (APIRequestBase): Username (MyGLS e-mail), Password = SHA-512 of the password as a byte array (unpack('C*', hash('sha512', $pwd, true)), as the official PHP sample). ClientNumberList is not sent ("do not use"). HTTP 401 and Appendix A 14 / 27 pause the automatic calls (Accounts::block) until the connection is saved or tested again.
Parcel object
ClientNumber (int), ClientReference = <prefix><order>-<record> (default prefix OC, -R for returns; written to the database before the call), Count 1-99 (1 with INS, error 28), Content, PickupDate = 23:59:59 of today in the shop time zone (config_timezone) plus the "pickup in days" setting, as the official sample (strtotime("... 23:59:59")), so the UTC instant stays on the same day (CS audit G-M4). PickupAddress = sender, DeliveryAddress = customer (swapped for returns), FinalDeliveryAddress = customer on PSD parcels (change 30). COD: CODAmount, CODCurrency, CODReference (order id). ParcelPropertyList with weight, content and the box size of the settings (change 19).
Address: HouseNumber "ONLY NUMBER" (change 6, error 23 "cannot be 0"), so "Váci út 12/b" becomes Street "Váci út", HouseNumber "12", HouseNumberInfo "/b" (Parcel::street). Field lengths are not published; house caps are cut by characters with mb_substr (Name / Street 80, City 60, info 40, contact 60, content 80, reference 40). OpenCart text is HTML-decoded (Env::dec) before it leaves the shop.
Services (Appendix B / E): PSD with PSDParameter.StringValue = point ID; FDS (e-mail), FSS (phone, only with FDS, error 30), SM2 (phone), INS (INSParameter.Value), SAT (top-level Value, change 28), value-less 24H, T09, T10, T12, TGS; returns SRS / PRS. PSD needs ContactName, ContactPhone, ContactEmail; FDS / FSS are left out on point deliveries (GLS PSD guide). XS is not offered (CS audit G-M5). Unverified (as in the CS edition): whether MyGLS accepts PSD with FDS / FSS, and whether DeliveryAddress on PSD should carry the point address (the official sample sends a street address).
TypeOfPrinter: A4_2x2, A4_4x1, Connect, Thermo, ThermoZPL (203 dpi), ShipItThermoPdf, ThermoZPL_300DPI, ShipItThermoZpl. The stored file is ZPL when it contains ^XA, PDF otherwise. Unverified: whether GetPrintedLabels reprints an already printed parcel on the live system; on error 18 for a prepared record the parcel number is taken from GetParcelList and the merchant is told to print from MyGLS.
Idempotency
- One per-order MySQL lock (
label-<order_id>) for create, print, cancel, return label, Check at GLS and "Nothing was created". - A
W (pending) row and its deterministic ClientReference are written before PrepareLabels; payload errors are found before the row exists. - The
ParcelId is saved (A, prepared) before GetPrintedLabels; a print failure can be printed again safely. - No answer, 408, 5xx or error 1001 = result unknown (
U): never retried automatically (the queue job fails permanently). "Check at GLS" adopts the parcel with the same ClientReference (U -> A claim, then print); "Nothing was created" closes it (U -> X claim). A W row found under the lock is a dead request and becomes U. - Return labels:
returnState pending before the call, pending found under the lock becomes unknown (CS audit G-M1), never repeated. - Every status transition is
UPDATE ... WHERE status = <read state> with its ROW_COUNT() checked (the "unknown" marks and the return markers are written under the order lock); tracking writes only when the state is unchanged and never leaves D, R or X (CS audit G-M3). - Cancel also deletes the return parcel (G-m3). A partial DeleteLabels answer is read as such: the outbound parcel deleted = cancelled, an error on the return parcel is kept as a note; error 4 (deleted in MyGLS already) or error 6 on a never printed record closes the row in the store (G-m4, OC audit M1).
- "Check at GLS" adopts a parcel only when the Parcel object GLS returns with it has the order's postal code, country and our client number (two shops on one MyGLS account never adopt each other's parcels). Error 18 on a return label takes the number from GetParcelList and asks for a print in MyGLS.
Checkout
Points come from the public GLS list (https://ps-maps.gls-czech.com/getDropoffPoints.php?ctrcode=XX, GLS Czech Republic "How to implement ShopDeliveryService", 20 countries), downloaded daily per point country into kv_gls_dropoff (an empty or much smaller download is not applied; XML parsed with LIBXML_NONET, no entity expansion). The picker searches near a postal code or town; the browser reports only the point ID, Points::resolve() checks it against the list of the delivery country, the session keeps the server record. Guards listen on catalog/controller/*/before|after and compare the lower-cased route (checkout/confirm, .confirm, checkout/shipping_method.save), so a re-cased route cannot skip them. The method name is rebuilt from the session point whenever it changes.
Tracking
Tracking page https://gls-group.com/HU/en/parcel-tracking/?match=<number> for Hungary and https://gls-group.com/GROUP/en/parcel-tracking/?match=<number> for the other countries; both pages read the match parameter (saved 2026-11-07). Appendix G codes mapped in Shipments::STATUS_MAP (delivered 5, 58, 59, 92, 93; at a point 54, 55, 56, 97; returned 23, 40; problems as in the CS edition).
Events, cron, data
Events: admin/view/sale/order_info/before (GLS tab), admin/controller/common/header/before (licence tick), catalog/model/checkout/order.addHistory/after (label at a status, run after the response), catalog/view/checkout/shipping_method/after (picker), catalog/controller/*/before|after (guards), catalog/view/checkout/confirm/after (message), catalog/model/checkout/order.addOrder|editOrder/after (point stored). Cron: OpenCart cron job (minute) or the secret URL; a web call without the key gets 403 (func_num_args() tells the cron runner apart, the route text is not used); one run at a time (GET_LOCK). Tables kv_gls_accounts (sealed passwords, AES-256-GCM, key in kv_seal), kv_gls_shipments, kv_gls_points, kv_gls_dropoff, kv_gls_jobs, kv_gls_log; uninstall keeps them, settings are restored by Keep. No flow leaves the shop, so the OpenCart 4.1 SameSite return rule does not apply.
Tests
php81 tests/unit.php / php83 tests/unit.php (throw-away MariaDB DB): request bodies against tests/fixtures/official/contract.json, payloads, services, COD, statuses, idempotency races, locks, return labels, log masking, house-rule checks.node tests/e2e.js on an oc41 pool store against tests/mock-server/server.js (MyGLS stand-in, unknown fields refused; it is NOT GLS). The script refuses to run unless the store points at 127.0.0.1.node tooling/release-templates/oc-upgrade/upgrade-test.js oc_gls <store> tests/upgrade-seed.sql.
GLS, MyGLS and ParcelShop are trademarks of their owner. This extension is independent software and is not affiliated with, endorsed by or certified by GLS.
FAQ
Are the checkout prices live GLS prices?
No. The MyGLS API has no rate operation; you enter your prices per method.
Which countries?
Labels for MyGLS accounts in Hungary, the Czech Republic, Slovakia, Romania, Slovenia and Croatia. Home delivery to the countries you list (your GLS contract decides where GLS delivers); pick-up points in the countries of the public GLS point list.
Is there a map at checkout?
No, a searchable list of GLS points (postal code or town, nearest first, ParcelShops and Parcel Lockers filters).
Can a parcel be created twice?
No. The reference is saved before the request; after a lost answer nothing is sent again until "Check at GLS" has looked the reference up.
Thermal printers?
Yes: ThermoZPL (203 dpi), ThermoZPL_300DPI and ShipItThermoZpl give ZPL files; the other formats give PDF.
Cash on delivery?
Yes, for the payment codes you list, in the currency of your MyGLS country (or another one you set). At checkout, a cash on delivery payment is not accepted with a point that does not take cash on delivery; the customer chooses another payment method or point.
Does any page leave the shop?
No. There is no payment or return redirect; only the tracking link opens the GLS tracking page.
OCMOD?
No. Events and cron only.
OpenCart 3?
No, OpenCart 4.1 only.
What did you test?
Tested on OpenCart 4.1.0.4 (PHP 8.3) against a stand-in built from the public MyGLS API document, not against a GLS account; unit tests on PHP 8.1 and 8.3.
Are you affiliated with GLS?
No. GLS, MyGLS and ParcelShop are trademarks of their owner.