Repository navigation
fix(orders,quotes): idempotent order creation, UTC scheduling, quote currency, expiry and preview - #412
Merged
roncodes merged 2 commits intoOct 23, 2026
Conversation
…currency, expiry and preview - POST /v1/orders honours an Idempotency-Key header: repeats within a day return the first order (Idempotent-Replayed: true); a concurrent duplicate gets 409. - scheduled_at and time windows sent with a UTC offset are stored as the same instant in the app timezone. The datetime cast used to drop the offset. The schedule endpoint's company-timezone input is now stored in UTC as well. - Service quotes: the preliminary path read currency with has(), filtering by a boolean; it is now the currency code. Stored quotes get expired_at (default 48h, fleetops.service_quotes.ttl_minutes). preview=1 prices without storing quote rows, for display-only callers such as storefront checkouts.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## release/v0.6.75 #412 +/- ##
===================================================
Coverage 100.00% 100.00%
- Complexity 13596 13625 +29
===================================================
Files 634 634
Lines 49342 49393 +51
===================================================
+ Hits 49342 49393 +51
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…he locked replay toStorageDateTime() parsed every value with Carbon::parse(), which throws on a numeric string such as "1792605600". The datetime cast it replaces accepted Unix timestamps, so form-encoded API clients that send one would have started getting a 500. Numeric values now go through Carbon::createFromTimestamp(). Also covers the replay found after the lock is taken (a duplicate that finished first), the one line codecov/patch flagged.
Member
Author
|
Review: the idempotency, currency, expiry and preview changes look right. I pushed one fix and one test (6845d8f):
Notes, not blocking:
Retargeted to |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Four API problems found while building the WooCommerce plugin and the Shopify app, which create and quote orders on merchants' behalf:
POST /v1/orderscannot know whether the order was created, so a retry can duplicate deliveries.scheduled_atloses its UTC offset. Thedatetimecast formats the parsed value asY-m-d H:i:s, so2026-10-21T11:00:00-07:00was stored as11:00UTC instead of18:00UTC. The schedule endpoint had the same problem: it read the time in the company timezone, then stored that wall time as UTC. Dispatch comparesscheduled_atwithnow()in UTC, so orders for companies outside UTC were dispatched at the wrong time.currencyfilter returns nothing.queryFromPreliminary()readcurrencywith$request->has('currency'), so it filtered rates by a boolean.expired_at, which is noisy for storefront integrations that price each address a customer types.Changes
OrderController::create()(API v1) checks anIdempotency-Keyheader (max 255 characters), scoped to the company and API credential.Idempotent-Replayed: true.409.Order::setScheduledAtAttribute()mutator, plus the time-window normaliser, converts values that carry an offset to the app timezone before formatting.currencyis read as a code everywhere.expired_at, defaulting to 48 hours to matchfleetops:purge-service-quotes; setfleetops.service_quotes.ttl_minutesto change it.preview=1calculates quotes without storing quote or item rows. Preview quotes carry no id, so callers request a stored quote when they need to attach one to an order.Behaviour change to note
PUT /v1/orders/{id}/schedulewith adate,timeand company timezone now stores the UTC instant (14:30 Singapore is stored as 06:30), and API inputs with offsets are stored as their real instant. Rows written before this keep their old values. Two existing tests that expected the old values (currency => trueand the schedule wall time) are updated.Verification
OrderControllerIdempotencyTest: replay, scoping, concurrent duplicate, failed creation, missing key.OrderSchedulingTimezoneTest: offsets,Z, no offset, DateTime objects, time windows.ServiceQuoteControllerQueryTest: expiry, TTL fallback, preview with no rows, currency parsing, the preview flag.