Skip to content

Laravel in Jeddah

Laravel development for Jeddah retail and distribution

A Laravel development company for Jeddah retailers and distributors. Cash on delivery as a real order state, promotions that run on a lunar calendar, and a queue that has to survive one month a year.

Jeddah is where Saudi commerce is bought and sold, and its systems have a property that surprises anybody arriving from European e-commerce: the order is frequently not paid for.

Cash on delivery is normal here rather than marginal. That single fact reshapes the order model, the stock model and the reconciliation process, and it is the thing most e-commerce codebases get wrong on arrival because they were written on the assumption that money and commitment happen at the same moment.

Payment after delivery in Jeddah is a lifecycle, not a flag

In a prepaid system, the payment event and the order event are nearly simultaneous, so a boolean is almost enough.

With cash on delivery they are days apart and the sequence can fail at any point. The order is confirmed, stock is committed, a courier collects it, and then the customer is not home, or refuses it, or pays a different amount because something arrived damaged. Each of those is an ordinary Tuesday, not an exception.

So the order needs explicit states with recorded transitions - who moved it, when, and why - and the money needs to be a separate record that points at the order rather than a column on it. The practical test is whether the system can answer why a particular order is in its current state three weeks later. If the answer involves looking at courier emails, the model is wrong.

Laravel stock has to be released, not just reserved

The second consequence follows directly and causes more damage.

When payment confirms an order, stock movement has an obvious trigger. Without a payment event, stock is committed on confirmation - which means something has to give it back when an order is refused, abandoned or left undelivered. That release is the step that gets written last and tested least, and its absence is invisible until a sale exposes it as negative availability.

Reservations therefore need expiry built in from the start, and the expiry needs to be a scheduled job that can be reasoned about. Being safe to run twice matters particularly here, because a release job that runs twice must not credit stock twice.

Jeddah promotions run on a lunar calendar

This is the local detail that quietly produces wrong answers, and no library solves it on its own.

Promotional seasons, some accounting periods and a large share of the year's revenue sit on Hijri dates. Lunar months are 29 or 30 days, and the start of a month is not purely arithmetic, so a Hijri period converted to Gregorian once and stored cannot be projected into next year.

The reliable arrangement is that the business supplies the dates for the period ahead, the system stores both representations of them, and no code path converts between the two calendars at runtime. The same reasoning as money and dates generally: the conversion is the bug, and the way to avoid it is to record what was meant rather than recompute it.

Reconciling what a Jeddah courier collected against what was owed

Cash on delivery means a third party holds the money for several days, and the reconciliation of that is a real piece of accounting rather than an import script.

The courier sends a remittance - a file, a statement, sometimes a spreadsheet with a different layout each month - listing what they collected and what they are paying over, net of their fees. The application has its own view of what each shipment should have collected. Those two lists disagree, every cycle, and the differences are the interesting output.

A shipment collected for less than the order total because the customer refused one item at the door. A shipment marked delivered and collected that the application still shows as in transit, because the status update was missed. A shipment in the remittance that the application has no record of. Fees charged at a rate other than the contracted one. Each of those is a different problem with a different owner, and a reconciliation that produces one number - we are out by this much - has done almost none of the work.

So the reconciliation is per shipment, with a status per line, and the unmatched lines are a queue somebody works through rather than a total somebody accepts. Ageing matters too, because money held past the agreed remittance period is a receivable and should appear as one. The courier is a debtor, and treating them as a logistics integration rather than as an account is how a business discovers at year end that it is owed a material amount by a company it never invoiced.

Laravel stock across several Jeddah locations

Once there is more than one place stock can be, availability stops being a number and becomes a question about which place.

A store with shelf stock, a warehouse with bulk, stock in transit between them, and stock reserved against orders that have not yet been picked. A single quantity column cannot hold that, and the sum of all locations is the wrong answer to almost every question anybody asks - because stock in the warehouse does not help a customer standing in the store, and stock in transit is not available anywhere until it arrives.

So quantity is per location and per state, and availability is computed for a purpose. Available to promise for an online order depends on which locations can fulfil it; available for a walk-in depends on one. Transfers are documents with their own lifecycle, because stock that has left one location and not arrived at the other is somewhere, and the somewhere has to be accountable or the discrepancy appears as shrinkage.

Which location fulfils an order is a rule rather than a default, and the rule is commercial. Nearest to the customer, or the location with the oldest stock, or the one that minimises splitting the order, or whichever has capacity to pick today. Expressing that as a configurable rule with the decision recorded against the order is what lets the business change it without a release - and lets somebody answer why a Jeddah order shipped from somewhere else.

Commercially

Scope fixed in writing before work starts, priced per project. The country-level ground - data residency, e-invoicing, who contracts - is on the Saudi Arabia market page.

Invoiced by Codefacture Yazılım A.Ş. in Türkiye, in US dollars, by bank transfer, with no Turkish VAT under the export-of-services exemption.

The regulatory and commercial ground for this city is on the Saudi Arabia market page.

Frequently asked questions

How should cash on delivery be modelled in a Jeddah commerce system?
As its own lifecycle, not as a boolean on the order. The order is placed, confirmed, dispatched, delivered, and only then paid - and at any point after dispatch the customer can refuse it, which means stock returns, the courier is owed a fee and nothing was ever collected. A `paid` column cannot express that. What works is an explicit state machine where every transition is recorded with its time and its cause, so the question "why is this order in this state" has an answer.
Our stock goes negative during a sale. Why?
Because reservation and payment were coupled. In a prepaid model, stock leaves when payment succeeds. With cash on delivery there is no payment event at order time, so stock has to be reserved on confirmation and released on refusal or expiry - and the release is the part that gets forgotten. A reservation without an expiry is a slow leak that only shows up when demand is high enough to notice.
Our promotion ended three days late. It was scheduled correctly.
Almost certainly a Hijri date converted once and stored as a Gregorian timestamp. Lunar months are 29 or 30 days and the start depends on observation, so a Hijri period cannot be calculated forward from a conversion made last year. The business supplies the dates for the year, the system stores both representations, and nothing in the code converts between them on the fly.
Everything slows down during Ramadan. Is it the database?
Usually the queue before the database. Order volume multiplies and concentrates into the evening, so jobs that were comfortable at a hundred an hour queue up at a thousand, and the symptom users see is a confirmation email arriving forty minutes late. The work is to find which jobs are on the critical path for a customer and which are not, split them onto separate queues, and make sure the slow ones cannot starve the fast ones.
Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page