SubmitOrder (Web → NAV)¶
Legacy system
This page documents the current (legacy) SubmitOrder integration in Zwerfkei Old (CodeIgniter), reverse-engineered directly from that codebase. It is reference material for building the equivalent flow on the new platform — see ZWE-392.
SubmitOrder is the SOAP operation on the NAV WSDL (config key wdslNav, called via Connection::requestCallWdslNav()) that pushes an order (or a return) from the webshop to NAV.
Not the LS Retail WSDL
This is not the LS Retail WSDL (wsdlLs) — that's a separate endpoint with its own operation list, and SubmitOrder does not appear if you query the LS WSDL. Order.php (requestCallWdsl()) explicitly returns 'requestCallWdslNav', which in Connection.php selects the wdslNav config, not wsdlLs.
Call flow¶
Order::sendOrderToNav() / Order::createNegativeOrderToNav()
→ creates a K3_synchronization record (model='Order', action=positive|negative, status=false)
K3_synchronization::sendToNav() (cronjob)
→ GET_LOCK('k3_sync') + loops over all open syncs (status=false)
→ _makeK3retailCall()
action=positive → new K3retail\Call\Order_positive($this)
action=negative → new K3retail\Call\Order_negative($this)
Order_positive / Order_negative (extends K3retail\Call\Order)
→ _checkOrder() validation (customer/country/navCustomerId/orderproducts)
→ _createOrderRequestObject() builds K3retail\Classes\Order (NavOrder + NavCustomer + NavAddress)
→ wraps in K3retail\Classes\SubmitOrder->order
→ callToNav() → Connection::requestCallWdslNav('SubmitOrder', $request)
→ response() stores sync->status=true, sync->date_send=time()
Files:
| File | Role |
|---|---|
application/models/order/Order.php |
sendOrderToNav() / createNegativeOrderToNav() — creates the K3_synchronization job |
application/models/K3_synchronization.php |
Queue/dispatcher (sendToNav(), _makeK3retailCall()) |
application/components/k3retail/Order.php |
Base class: validation + shared line/customer/address helpers |
application/components/k3retail/Order_positive.php |
Builds a normal order (SubmitOrder, action=positive) |
application/components/k3retail/Order_negative.php |
Builds a return order (SubmitOrder, action=negative) |
application/components/k3retail/classes/SubmitOrder.php |
SOAP request wrapper ({ order: NavOrder-graph }) |
application/components/k3retail/classes/Order.php |
Container: NavOrder + NavCustomer + NavAddress |
application/components/k3retail/classes/NavOrder.php |
Order header/totals + NavOrderLine[] |
application/components/k3retail/classes/NavOrderLine.php |
Order line |
application/components/k3retail/classes/NavCustomer.php |
Customer |
application/components/k3retail/classes/NavAddress.php |
Shipping address |
application/components/k3retail/classes/NavOrderStatus.php |
Status constants (OrderStatus/ShippingStatus/PaymentStatus) |
Request envelope¶
K3retail\Classes\SubmitOrder {
order: K3retail\Classes\Order {
NavOrder: NavOrder // header + lines
NavCustomer: NavCustomer
NavAddress: NavAddress // = shipping address (always the fixed Zwerfkei warehouse address)
}
}
_createOrderRequestObject() (Order.php:111) throws an error if NavOrder, NavCustomer, NavAddress or NavOrder->Id are empty — the call is not sent in that case.
NavOrder — fields¶
| Field | Type | Origin / notes |
|---|---|---|
Id |
int | K3_synchronization->nav_id — own incrementing counter per model (K3_synchronization.php:182), not the website order number |
PurchaseOrderNumber |
string | positive: Ymd of sync->date_created + ordernr. Return: same + "R" + ordernr + "_" + serial_number |
StoreId |
int | constant Order::StoreId = 1 |
CustomerId |
int | country->navCustomerId (set per country on Customer_country) |
BillingAddressId |
int | constant Order::BillingAddressId = 1 |
ShippingAddressId |
int | constant Order::ShippingAddressId = 1 |
CustomerTaxDisplayType |
int | always 1 (incl. VAT) |
OrderStatusId |
int | always NavOrderStatus::orderStatus_complete (30) |
ShippingStatusId |
int | always NavOrderStatus::shipmentStatus_shipped (40) |
PaymentStatusId |
int | always NavOrderStatus::paymentStatus_paid (30) |
PaymentMethodName |
string | order->payment_provider (code comment: "nothing happens when you change this value") |
ShippingMethod |
string | always 'POSTNL' (hardcoded) |
OrderSubtotalInclTax / ExclTax |
decimal | sum of order lines (_countOrderSubInclFromOrderline / _countOrderSubExFromOrderline) |
OrderSubtotalDiscInclTax / ExclTax |
decimal | subtotal minus line discount, or 0 if no discount |
OrderShippingInclTax / ExclTax |
decimal | always 0 (shipping cost goes as a separate order line, see below) |
PaymentFeeInclTax / ExclTax |
decimal | always 0 (same, as an order line) |
OrderTax |
decimal | difference between discount incl/excl, or subtotal incl−excl if no discount |
OrderDiscount |
decimal | always 0 ("We don't need these") |
OrderTotal |
decimal | positive: order->totaal minus paid giftcard/voucher amounts. Return: subtotal incl − discount incl |
RefundedAmount |
decimal | always 0 |
RewardPointsWereAdded |
bool | always false |
CustomerLanguageId |
int | always 0 |
CreatedOnUtc |
datetime | positive: order->datum as ISO-8601 (DateTime::ATOM). Return: sync->date_created as Y-m-d\TH:i:s |
NavOrderLine |
NavOrderLine[] |
see below |
NavOrderAttribute |
— | defined on the class, not populated in the current flow |
Status constants (NavOrderStatus.php)¶
| Group | Value | Meaning |
|---|---|---|
| OrderStatus | 10/20/30/40 | Pending / Processing / Complete / Cancelled |
| ShippingStatus | 10/20/30/40/50 | Not Required / Not Yet Shipped / Partially Shipped / Shipped / Delivered |
| PaymentStatus | 10/20/30/40/50/60 | Pending / Authorized / Paid / Partially Refunded / Refunded / Voided |
The current implementation always sends Complete/Shipped/Paid, regardless of the actual order status in the webshop.
NavOrderLine — fields¶
| Field | Type | Origin |
|---|---|---|
Id |
int/string | orderproduct->id (return: + sync->serial_number appended) |
OrderId |
int | sync->nav_id (same as NavOrder->Id) |
ProductId |
int | defined on the class, not set by the builders |
ItemNo |
string | K3 item number (orderproduct->Article) |
VariantCode |
string | k3_article_variant->Code |
Quantity |
decimal | orderproduct->aantal (return/negative lines with - prefix) |
UnitPriceInclTax |
decimal | orderproduct->prijs |
UnitPriceExclTax |
decimal | prijs / VATCounted, 2 decimals |
PriceInclTax |
decimal | aantal * prijs (always returned positive; return with - prefix) |
PriceExclTax |
decimal | aantal * (prijs / VATCounted) (same) |
DiscountAmountInclTax |
decimal | bundle discount, or aantal*prijs − aantal*aanbieding, else 0 |
DiscountAmountExclTax |
decimal | same, excl. VAT |
ItemWeight |
decimal | always 0 |
Special order lines¶
Beyond regular product lines, extra NavOrderLine entries are conditionally added to NavOrder->NavOrderLine:
| Line | Trigger | Item |
|---|---|---|
| Shipping/payment cost | order->verzendkosten and/or order->betaalkosten > 0 |
Order::shippingArticleNo (223000001) / GTIN 2000000443508 |
| Giftcard discount (return) | Payment::getTotalDiscountGiftByReference() |
variant on onlinekadobon2_gtin (config) |
| Voucher (return) | Payment::getTotalDiscountByReference() |
variant on goedmaakbon (config) |
Negative line (aantal_gepakt < 0) |
in _createOrderLinesObject() |
own negative-line calculation |
For Order_negative (return flow), return costs, giftcard refunds and voucher refunds also come in as separate lines via Order_service records (type = shipment/payment/retour/giftcard/discount, refund=1).
NavCustomer — fields¶
| Field | Origin |
|---|---|
Id |
country->navCustomerId |
CustomerNo |
country->navCustomerNo |
ContactNo |
country->navContactNo |
Password / PasswordFormat / PasswordSalt |
defined on the class, not populated for SubmitOrder |
Source: Customer_country of the customer on the order (order->customer->country). Without navCustomerId, _checkOrder() fails.
NavAddress — fields (= shipping address)¶
Hardcoded to the Zwerfkei warehouse, not the customer's address:
Id = settings.shippingAddressId // constant 1
Code = 'VERZ-0001'
Company = 'Zwerfkei Buitensport b.v.'
FirstName = 'Webshop'
LastName = 'orders'
Address = 'Jaap Bijzerweg 10'
ZipPostalCode = '3446CR'
City = 'WOERDEN'
CountryCode = 'NL'
CountryId = 0
StateProvinceId = 0
(Email, Address2, PhoneNumber, FaxNumber exist on the class but are not populated.)
Precondition checks (_checkOrder())¶
The call is not sent (and the sync gets status K3_synchronization::error + an admin email) if:
sync->RelatedModelis notOrder, the sync already has a status, or POS is not "started" (POSStartStatus::getStatus())- the order has no
customer - the customer has no
country country->navCustomerIdis emptysettings(derived from the country) could not be built- the order has no
orderproducts
Response¶
SubmitOrder_Result is a genuinely empty struct — confirmed against the live WSDL (see zwerfkei-api PR #14, ZWE-425). NAV returns no confirmation id, order number, or any field at all on success. This is why the new backend generates and stores its own order id as the reference rather than relying on anything NAV sends back — see ZWE-503 and Order Push Flow Demo.
Error scenarios¶
A rejected/invalid submission (e.g. a VariantCode exceeding NAV's 10-character limit) returns a SOAP fault, not a populated SubmitOrder_Result — there is no NAV-side "rejected" response shape to document beyond the fault itself. Confirmed live in ZWE-425, which catches this fault and logs it against the correlating backend order.
What you need to build a SubmitOrder call yourself¶
- Connection:
wsdlLsendpoint, basic auth (K3retail_gateway.username/password), viaConnection::requestCallWdslNav('SubmitOrder', $payload). nav_id: own incrementing counter (per model) fromk3_synchronization.nav_id— not the website order number. Must be unique towards NAV.- Customer mapping:
navCustomerId/navCustomerNo/navContactNoper country (Customer_country) — without this mapping nothing can be sent. - Lines: each
Orderproduct→ItemNo(K3 item number) +VariantCode(fromK3_article_variant) + incl./excl. VAT prices + incl./excl. discount. Note:PriceInclTax/PriceExclTaxare always sent positive for a regular line and with a"-"string prefix for return/negative lines (not a real negative number, but a minus-prefixed string — that's apparently how NAV expects it). - Fixed values not coming from the order itself:
StoreId=1,BillingAddressId=1,ShippingAddressId=1,CustomerTaxDisplayType=1,ShippingMethod='POSTNL', and the fixed shipping address above. - Status fields are always sent as "complete" (Complete/Shipped/Paid) — there is no mapping to intermediate webshop order statuses.
- Shipping/payment cost and giftcards/vouchers are not fields on the order, but separate synthetic order lines with a fixed
ItemNo(223000001) or the giftcard/voucher config items.