spree_doordash 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a028415e34fd065f59a11e00034d88ef6ea2dfa8155538bf61f63ba26cf16ed
|
|
4
|
+
data.tar.gz: f0425700d9f63218ee14eae64ce30d6e444c46b2eefae2fe4d3854f38426e13a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25f7dc0b927d237844b908072f4b3f9fa738fe3db1c6b4036280e51d739c6b5eea92c2578dc730dbd5c89b33de5a10318f9898ee91e295a55deafcdf3426ae5b
|
|
7
|
+
data.tar.gz: b32fb99c09cb7344185f06ca15c72c60253669b0d5ea5374de62490c4a62e007ce39589b6df4a491bcfd8a54e71c4caf266ea348c0b8d9abf1355b01c03dcd23
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.1.3
|
|
6
|
+
|
|
7
|
+
A real bug found live: the `doordash_quote_unavailable` warning added in 0.1.2 never actually
|
|
8
|
+
worked in production. `Spree::Calculator::Shipping::DoordashQuote#compute_package` pushed it onto
|
|
9
|
+
`package.order.warnings` — but `package.order` is **not** the same in-memory object as the order
|
|
10
|
+
the caller (`Spree::Order#create_proposed_shipments`) holds. `spree_core`'s own
|
|
11
|
+
`Spree::Stock::InventoryUnitBuilder#units` builds each inventory unit with `order_id: @order.id`
|
|
12
|
+
but deliberately *not* `order: @order` ("avoid loading the association to order until needed," per
|
|
13
|
+
its own comment) — the first thing that touches `.order` on one of those units triggers a fresh
|
|
14
|
+
`Spree::Order.find`, a brand-new Ruby object. So the warning was mutating a throwaway copy,
|
|
15
|
+
discarded the instant `compute_package` returned, and never reached the order this request
|
|
16
|
+
actually serializes back to the storefront as `cart.warnings`. Confirmed via `object_id` tracing
|
|
17
|
+
against production, not assumed — and confirmed the existing spec suite couldn't have caught it:
|
|
18
|
+
`doordash_quote_spec.rb`'s own `instance_double(Spree::Stock::Package, order: order)` makes
|
|
19
|
+
`package.order` the *same* object as the test's `order` by construction, sidestepping the exact
|
|
20
|
+
boundary that breaks in real Spree core.
|
|
21
|
+
|
|
22
|
+
Fixed by bridging across that boundary with the order's stable id instead of Ruby object identity
|
|
23
|
+
— `Spree::Calculator::Shipping::DoordashQuote.mark_unavailable(order_id)` sets a thread-local flag
|
|
24
|
+
(chosen over `Rails.cache`: this gem installs into arbitrary host apps, some of which legitimately
|
|
25
|
+
run `config.cache_store = :null_store`, which would silently degrade this back to the original bug
|
|
26
|
+
with zero indication anything was wrong — `compute_package` and `create_proposed_shipments` always
|
|
27
|
+
run synchronously on the same thread within one request, so a thread-local needs no external store
|
|
28
|
+
and no expiry logic). A new `Spree::OrderDecorator#create_proposed_shipments` reads the flag back
|
|
29
|
+
and merges the warning onto `self.warnings` immediately after `super` returns — `self` there *is*
|
|
30
|
+
the real order, since `create_proposed_shipments` is called directly on it by the checkout flow.
|
|
31
|
+
The flag is cleared unconditionally at the *start* of every call (not just after a successful
|
|
32
|
+
merge), so a stale flag left behind by an earlier request that raised before reaching the merge
|
|
33
|
+
step can never leak into a later, unrelated call that happens to reuse the same thread.
|
|
34
|
+
|
|
35
|
+
New `spec/models/spree/order_decorator_spec.rb` exercises the real
|
|
36
|
+
`order_routing_strategy -> Estimator -> Packer -> InventoryUnitBuilder` path end to end (only
|
|
37
|
+
`SpreeDoordash::Quote.call` is stubbed) — the only way to actually exercise the object-identity
|
|
38
|
+
boundary the fix bridges across, rather than doubling it away. Full suite: 95 examples, 0 failures
|
|
39
|
+
(5 new). Coverage: 94.14%. Brakeman clean.
|
|
40
|
+
|
|
41
|
+
## 0.1.2
|
|
42
|
+
|
|
43
|
+
A real bug found live during checkout-validation work: a blank phone on the dropoff address used
|
|
44
|
+
to fall back to a fake placeholder number (`+10000000000`) that DoorDash's API rejected anyway —
|
|
45
|
+
the request was doomed before it was even built, and (same as the E.164 issue fixed in 0.1.1) the
|
|
46
|
+
DoorDash Delivery rate just silently vanished with no error anywhere in the UI.
|
|
47
|
+
|
|
48
|
+
- `SpreeDoordash::Quote#call` now returns early (no API call at all) when the ship address has no
|
|
49
|
+
phone, instead of building a request known to fail. `format_phone`'s placeholder fallback is
|
|
50
|
+
removed entirely.
|
|
51
|
+
- `Spree::Calculator::Shipping::DoordashQuote#compute_package` now pushes a
|
|
52
|
+
`doordash_quote_unavailable` warning onto `Spree::Order#warnings` whenever `SpreeDoordash::Quote`
|
|
53
|
+
returns `nil` — generic to *why* the quote failed (bad/missing phone, an address DoorDash
|
|
54
|
+
genuinely can't serve, DoorDash API down), confirmed live for two different real causes.
|
|
55
|
+
`Spree::Order#warnings` is the same transient mechanism core's own
|
|
56
|
+
`ensure_available_shipping_rates` already uses for the identical class of problem, and flows
|
|
57
|
+
through to the Store API's `cart.warnings` with no new API surface. A storefront reference
|
|
58
|
+
implementation now renders this as a real, non-blocking banner instead of silence — see
|
|
59
|
+
`spree_storefront_web`'s `DeliveryMethodSection.tsx`.
|
|
60
|
+
|
|
61
|
+
Full suite: 90 examples, 0 failures (2 new). Coverage: 94.14% (241/256 lines). Brakeman clean.
|
|
62
|
+
|
|
5
63
|
## 0.1.1
|
|
6
64
|
|
|
7
65
|
Two real production bugs, both invisible to the full spec suite and to every prior "live
|
|
@@ -20,11 +20,62 @@ module Spree
|
|
|
20
20
|
|
|
21
21
|
def compute_package(package)
|
|
22
22
|
result = SpreeDoordash::Quote.call(package.order)
|
|
23
|
-
|
|
23
|
+
if result.nil?
|
|
24
|
+
# A real bug found live: a nil Result here (bad/missing phone, an
|
|
25
|
+
# address DoorDash genuinely can't serve, DoorDash API down —
|
|
26
|
+
# SpreeDoordash::Quote#call collapses all of these to the same
|
|
27
|
+
# "unavailable" nil, deliberately, so this stays generic to *why*)
|
|
28
|
+
# used to just make the rate silently vanish with zero signal
|
|
29
|
+
# anywhere in the UI. Spree::Order#warnings is the same
|
|
30
|
+
# transient, request-scoped mechanism core's own
|
|
31
|
+
# `ensure_available_shipping_rates` already uses for the
|
|
32
|
+
# identical class of problem (a line item that can't ship at
|
|
33
|
+
# all) — it flows through to the Store API's `cart.warnings`
|
|
34
|
+
# with no new API surface needed. The storefront renders its own
|
|
35
|
+
# localized copy for this code; `message` here is English-only,
|
|
36
|
+
# a fallback for any other API consumer, not the UI text itself.
|
|
37
|
+
#
|
|
38
|
+
# A SECOND real bug found live (v0.1.2, this fix is v0.1.3):
|
|
39
|
+
# package.order is not the same object as the order the caller
|
|
40
|
+
# holds (spree_core's InventoryUnitBuilder deliberately defers
|
|
41
|
+
# loading that association — see the Spree::OrderDecorator this
|
|
42
|
+
# gem ships for the full story), so pushing the warning directly
|
|
43
|
+
# onto package.order.warnings here mutated a throwaway copy that
|
|
44
|
+
# never reached the real order. Thread.current bridges across
|
|
45
|
+
# that boundary via the order's id instead of Ruby object
|
|
46
|
+
# identity — chosen over Rails.cache deliberately: this gem
|
|
47
|
+
# installs into arbitrary host apps, some of which run
|
|
48
|
+
# `config.cache_store = :null_store` (the Rails test-env
|
|
49
|
+
# default, and a legitimate production choice too), which would
|
|
50
|
+
# silently degrade this back to the original bug with no
|
|
51
|
+
# indication anything was wrong. compute_package and
|
|
52
|
+
# create_proposed_shipments always run synchronously on the same
|
|
53
|
+
# thread within one request (confirmed: this is the exact call
|
|
54
|
+
# chain reproduced live to root-cause the original bug), so a
|
|
55
|
+
# thread-local flag needs no external store and no expiry logic
|
|
56
|
+
# — Spree::OrderDecorator clears it unconditionally at the start
|
|
57
|
+
# of every create_proposed_shipments call, so a stale flag from
|
|
58
|
+
# an earlier request that errored out before consuming it can
|
|
59
|
+
# never leak into a later, unrelated one.
|
|
60
|
+
self.class.mark_unavailable(package.order.id)
|
|
61
|
+
return nil
|
|
62
|
+
end
|
|
24
63
|
|
|
25
64
|
result.fee_cents / 100.0
|
|
26
65
|
end
|
|
27
66
|
|
|
67
|
+
def self.mark_unavailable(order_id)
|
|
68
|
+
(Thread.current[:spree_doordash_quote_unavailable_order_ids] ||= []) << order_id
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.unavailable?(order_id)
|
|
72
|
+
Thread.current[:spree_doordash_quote_unavailable_order_ids]&.include?(order_id) || false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.clear_unavailable(order_id)
|
|
76
|
+
Thread.current[:spree_doordash_quote_unavailable_order_ids]&.delete(order_id)
|
|
77
|
+
end
|
|
78
|
+
|
|
28
79
|
# Called by Estimator to filter which shipping methods even attempt
|
|
29
80
|
# a compute_package call. Skips the DoorDash API round-trip entirely
|
|
30
81
|
# for an order with no ship address yet (early checkout, before
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
# Bridges Spree::Calculator::Shipping::DoordashQuote#compute_package's
|
|
3
|
+
# doordash_quote_unavailable warning back onto the real order object.
|
|
4
|
+
#
|
|
5
|
+
# A real bug found live (confirmed via object_id tracing against
|
|
6
|
+
# production, not assumed): `compute_package` receives a
|
|
7
|
+
# `Spree::Stock::Package`, and `package.order` is NOT the same in-memory
|
|
8
|
+
# object as `self` in `create_proposed_shipments` below. spree_core's own
|
|
9
|
+
# `Spree::Stock::InventoryUnitBuilder#units` builds each inventory unit
|
|
10
|
+
# with `order_id: @order.id` but deliberately *not* `order: @order` — its
|
|
11
|
+
# own comment says why: "avoid loading the association to order until
|
|
12
|
+
# needed." The first thing that touches `.order` on one of those units (or
|
|
13
|
+
# a Package built from them) triggers a fresh `Spree::Order.find`, a
|
|
14
|
+
# brand-new Ruby object. So `package.order.warnings |= [...]` inside the
|
|
15
|
+
# calculator was mutating a throwaway copy that's discarded the instant
|
|
16
|
+
# `compute_package` returns — the warning never reached the order object
|
|
17
|
+
# this request actually serializes back to the storefront as
|
|
18
|
+
# `cart.warnings`. `spree_doordash` v0.1.2's warning never worked in
|
|
19
|
+
# production despite passing its own specs (the dummy app's specs call
|
|
20
|
+
# the calculator directly against a single order instance, which can't
|
|
21
|
+
# exhibit this — it only shows up through the real
|
|
22
|
+
# create_proposed_shipments -> order_routing_strategy -> Estimator path).
|
|
23
|
+
#
|
|
24
|
+
# Fix: since Ruby object identity doesn't survive that boundary, bridge
|
|
25
|
+
# across it with the order's stable id instead, via a thread-local flag
|
|
26
|
+
# (see DoordashQuote.mark_unavailable/unavailable?/clear_unavailable for
|
|
27
|
+
# why Thread.current was chosen over Rails.cache). Cleared unconditionally
|
|
28
|
+
# at the *start* of every call — not just after a successful merge — so a
|
|
29
|
+
# stale flag left behind by an earlier request that raised before
|
|
30
|
+
# reaching the merge step can never leak into a later, unrelated
|
|
31
|
+
# create_proposed_shipments call that happens to reuse the same thread.
|
|
32
|
+
module OrderDecorator
|
|
33
|
+
def create_proposed_shipments
|
|
34
|
+
Spree::Calculator::Shipping::DoordashQuote.clear_unavailable(id)
|
|
35
|
+
result = super
|
|
36
|
+
merge_doordash_quote_warning!
|
|
37
|
+
result
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def merge_doordash_quote_warning!
|
|
43
|
+
return unless Spree::Calculator::Shipping::DoordashQuote.unavailable?(id)
|
|
44
|
+
|
|
45
|
+
Spree::Calculator::Shipping::DoordashQuote.clear_unavailable(id)
|
|
46
|
+
self.warnings |= [{
|
|
47
|
+
code: 'doordash_quote_unavailable',
|
|
48
|
+
message: 'We could not get a DoorDash delivery quote for this address.'
|
|
49
|
+
}]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Order.prepend OrderDecorator
|
|
54
|
+
end
|
|
@@ -17,6 +17,16 @@ module SpreeDoordash
|
|
|
17
17
|
def call(order)
|
|
18
18
|
location_mapping = location_mapping_for(order)
|
|
19
19
|
return nil unless location_mapping && order.ship_address
|
|
20
|
+
# A real bug found live: with no phone on the ship address,
|
|
21
|
+
# `format_phone` used to fall back to a fake placeholder number that
|
|
22
|
+
# DoorDash's API correctly rejects with a 400 — the request was
|
|
23
|
+
# doomed before it was even built. `Spree::Config[:address_requires_phone]`
|
|
24
|
+
# (spree_host) should make this unreachable in the normal storefront
|
|
25
|
+
# checkout flow, but this guard stays as defense in depth for any
|
|
26
|
+
# other path that can create an order (admin, API, migrated data)
|
|
27
|
+
# — same "unavailable, not exceptional" nil-return shape as every
|
|
28
|
+
# other unserviceable case below.
|
|
29
|
+
return nil if order.ship_address.phone.blank?
|
|
20
30
|
|
|
21
31
|
client = SpreeDoordash::Client.for_store(order.store || Spree::Store.default)
|
|
22
32
|
# A random suffix per attempt, not just order.number — confirmed live
|
|
@@ -93,11 +103,17 @@ module SpreeDoordash
|
|
|
93
103
|
# US-only, matching the rest of this demo (every address here is a US
|
|
94
104
|
# address) — strips everything but digits, assumes a bare 10-digit
|
|
95
105
|
# number is domestic and prepends the country code.
|
|
106
|
+
#
|
|
107
|
+
# No blank-phone fallback here (a prior version faked one as
|
|
108
|
+
# "+10000000000" — DoorDash rejected it too, just as uninformatively as
|
|
109
|
+
# a missing one; better to let a genuinely blank pickup phone fail
|
|
110
|
+
# DoorDash's own validation loudly than paper over it with fake data).
|
|
111
|
+
# `call` above already guards the dropoff phone specifically; this
|
|
112
|
+
# method is also reused for the pickup (StockLocation) phone, which is
|
|
113
|
+
# real business data expected to always be set.
|
|
96
114
|
def format_phone(raw)
|
|
97
115
|
digits = raw.to_s.gsub(/\D/, '')
|
|
98
116
|
digits = "1#{digits}" if digits.length == 10
|
|
99
|
-
return '+10000000000' if digits.blank?
|
|
100
|
-
|
|
101
117
|
"+#{digits}"
|
|
102
118
|
end
|
|
103
119
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_doordash
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amit Solanki
|
|
@@ -120,6 +120,7 @@ files:
|
|
|
120
120
|
- app/jobs/spree_doordash/delivery_dispatch_job.rb
|
|
121
121
|
- app/jobs/spree_doordash/delivery_webhook_job.rb
|
|
122
122
|
- app/models/spree/calculator/shipping/doordash_quote.rb
|
|
123
|
+
- app/models/spree/order_decorator.rb
|
|
123
124
|
- app/models/spree_doordash/credential.rb
|
|
124
125
|
- app/models/spree_doordash/delivery_mapping.rb
|
|
125
126
|
- app/models/spree_doordash/location_mapping.rb
|