portage-ucp 0.12.0 → 0.13.0

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: 4f3dd667eb94ccfb68762797d5020e233f282d941b4790503a812b01067be09f
4
- data.tar.gz: bb3cb10f93333cb720172aa427f2b09b03d186c03b98485b63a8dca9dae8a302
3
+ metadata.gz: 1e47e8018ba5966b67205cb9046efdf0e8f0edbe9ab58eef1e7613232a5b6b27
4
+ data.tar.gz: c5eaa266f8d88a7fda16a822e0eb0b6aad82465b1e6adbf36abe08e7d2c859c4
5
5
  SHA512:
6
- metadata.gz: 58b3c2ab0aad754862745bf0381e8e37987d2fa8c4da6f80c6460284cb4242d7f9c45e74b83918f6ab032770f38467f6bb62f08cefa06555e2de2dedead2e588
7
- data.tar.gz: e9dd08412d7fb321da9118ceb01804a71894da1bca6a7263ffab5ed3efda335657e5c1c2d3bf52d027056bd4a88ab0e6889eacdb9c454103e595ed961a0b885a
6
+ metadata.gz: '053845855d29f395fb001b78d13622798aadf54c8dc087c88bdf32a943e65e992e7d6d79a3f9a5c86232ebfb8f65f36eb1137888bd9a806e2ceeeef045a57789'
7
+ data.tar.gz: 6e1cec328b2d07567d4441340fe177367db3bbe7e094aa93388446cb062ea948e600abeb353dcf31b3833f7f76d2ccc189c275a44ab4c5ea546b94e4f3c152d7
data/CHANGELOG.md CHANGED
@@ -6,6 +6,10 @@ pre-1.0, so APIs may still shift between minor versions.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.13.0] - 2026-10-07
10
+
11
+ - **Added: `Support::CheckoutState` can ask the platform about a checkout this process doesn't track.** An adapter may define a private `platform_checkout_order(checkout_id)` that returns the `Order` its platform placed from that checkout, or nil. `#checkout_status` then reports `completed` for an id with no in-process record, the new `#checkout_order` returns its `OrderConfirmation`, and the new `#checkout_from_platform_order` rebuilds a completed `Checkout` from the order when the platform has already dropped the cart. A found order is cached through `#record_checkout_status`/`#record_order_checkout`; no order, no hook, or a lookup error keeps today's `incomplete`. This lets `portage orders reconcile`, which runs in a new process, see a checkout the shopper paid for in the browser (design-log §55). The `Adapter` contract is unchanged. Minor-level.
12
+
9
13
  ## [0.12.0] - 2026-10-05
10
14
 
11
15
  - **Removed the unused `services/shopping/embedded.openrpc.json` schema.** Nothing loaded or listed it (`SchemaValidator` only reads `mcp.openrpc.json`); it just shipped in the gem. Patch-level.
@@ -14,7 +14,9 @@ module Portage
14
14
  #
15
15
  # Both default rather than raise on an unknown id ("incomplete" and "",
16
16
  # respectively): a checkout this process didn't create is still
17
- # schema-valid to report, just not information-complete.
17
+ # schema-valid to report, just not information-complete. An adapter
18
+ # can add a platform lookup for the unknown-id case (see
19
+ # #platform_order).
18
20
  module CheckoutState
19
21
  # Dispatcher wraps each adapter call in .with_observability rather
20
22
  # than writing [logger, correlation_id] onto an instance variable on
@@ -49,8 +51,77 @@ module Portage
49
51
 
50
52
  private
51
53
 
54
+ # A checkout this process has no record of falls back to the
55
+ # platform: `portage orders reconcile` runs in a new process, after
56
+ # the shopper paid in their browser, and the in-process hash alone
57
+ # would always say "incomplete" there (design-log §55). See
58
+ # #platform_order.
52
59
  def checkout_status(checkout_id)
53
- (@checkout_status ||= {}).fetch(checkout_id, "incomplete")
60
+ recorded = (@checkout_status ||= {})[checkout_id]
61
+ return recorded if recorded
62
+
63
+ platform_order(checkout_id) ? "completed" : "incomplete"
64
+ end
65
+
66
+ # The order_confirmation for a checkout #platform_order found an order
67
+ # for, nil otherwise (an in-process #complete_checkout hands its own
68
+ # back directly).
69
+ def checkout_order(checkout_id)
70
+ order = (@platform_orders ||= {})[checkout_id]
71
+ order && Portage::Ucp::OrderConfirmation.new(id: order.id, permalink_url: order.permalink_url)
72
+ end
73
+
74
+ # For a #get_checkout whose cart read came back not-found: most
75
+ # platforms drop the cart once it becomes an order (BigCommerce
76
+ # deletes it on payment, Magento deactivates the quote), so a paid
77
+ # checkout would otherwise only ever read as not-found. This builds
78
+ # the completed Checkout from the order the platform reports for it.
79
+ # No order means nil: a vanished cart on its own is never read as a
80
+ # completion (docs/plans/handoff-reconcile.md, "Settle only on a
81
+ # `completed` status the store actually reports").
82
+ def checkout_from_platform_order(checkout_id)
83
+ order = platform_order(checkout_id)
84
+ return nil unless order
85
+
86
+ line_items = order.line_items.map do |li|
87
+ Portage::Ucp::LineItem.new(id: li.id, item: li.item, quantity: li.quantity[:total], totals: li.totals)
88
+ end
89
+ Portage::Ucp::Checkout.new(id: checkout_id, status: "completed", line_items: line_items,
90
+ currency: order.currency, totals: order.totals, links: [],
91
+ order: checkout_order(checkout_id))
92
+ end
93
+
94
+ # The optional per-adapter hook: an adapter that can ask its platform
95
+ # "did this checkout become a placed order?" defines a private
96
+ # `platform_checkout_order(checkout_id)` returning that Portage::Ucp::
97
+ # Order, or nil. Without the hook (or when this process already
98
+ # tracks the checkout) nothing changes. A found order is cached
99
+ # through #record_checkout_status/#record_order_checkout so the rest
100
+ # of this process agrees with it. A lookup error is swallowed: the
101
+ # caller gets today's in-process answer, which reconcile reads as
102
+ # "still pending", never as a settle. The hook is a private method
103
+ # rather than a new Adapter method or kwarg for the same §9/§23
104
+ # reason as .with_observability above.
105
+ def platform_order(checkout_id)
106
+ orders = (@platform_orders ||= {})
107
+ return orders[checkout_id] if orders.key?(checkout_id)
108
+
109
+ order = ask_platform(checkout_id)
110
+ return nil unless order
111
+
112
+ orders[checkout_id] = order
113
+ record_order_checkout(order.id, checkout_id)
114
+ record_checkout_status(checkout_id, "completed")
115
+ order
116
+ end
117
+
118
+ def ask_platform(checkout_id)
119
+ return nil if (@checkout_status ||= {}).key?(checkout_id)
120
+ return nil unless respond_to?(:platform_checkout_order, true)
121
+
122
+ platform_checkout_order(checkout_id)
123
+ rescue StandardError
124
+ nil
54
125
  end
55
126
 
56
127
  def record_checkout_status(checkout_id, status)
@@ -1,5 +1,5 @@
1
1
  module Portage
2
2
  module Ucp
3
- VERSION = "0.12.0".freeze
3
+ VERSION = "0.13.0".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: portage-ucp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Whitbread
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-05 00:00:00.000000000 Z
11
+ date: 2026-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64