Class: Portage::Ucp::ReferenceAdapter
- Includes:
- Support::CheckoutState, Support::Idempotency
- Defined in:
- lib/portage/ucp/reference_adapter.rb
Overview
In-memory Adapter implementing every capability in the contract,
including dev.ucp.shopping.discount/fulfillment/identity — roadmap §8
step 1 called for one of these ("no real backend required to prove the
protocol layer works") and it never shipped; spec/support/fake_adapter.rb
filled that gap for the core gem's own specs, but stayed test-only,
catalog/cart/checkout/order only, and undocumented outside this repo.
Two jobs: a copy-paste starting point for a third-party adapter author (see README "Writing your own adapter"), and the fixture the conformance kit (Portage::Ucp::RSpec, lib/portage/ucp/rspec.rb) runs its own shared examples against to prove the kit itself is correct.
Not a mock — every mutating action does the real bookkeeping (line-item
totals, checkout status transitions, order adjustments) an adapter over
a live platform would, just against an in-process Hash instead of an
HTTP API. seed_product is the one method with no Adapter contract
counterpart — there is no real backend to seed real data into.
Constant Summary collapse
- OUT_OF_STOCK_PREFIX =
Products whose id starts with this prefix are treated as sold out — #complete_checkout raises Portage::Ucp::OutOfStockError for them, the same way a real adapter's stock re-check (Adapter#complete_checkout's docs, design-log §16) would. Lets the conformance kit exercise that path without depending on adapter-specific seed data.
"oos_".freeze
Constants included from Support::Idempotency
Support::Idempotency::INIT_MUTEX
Instance Method Summary collapse
- #cancel_cart(cart_id:, idempotency_key:) ⇒ Object
- #cancel_checkout(checkout_id:, idempotency_key:) ⇒ Object
- #cancel_order(order_id:, idempotency_key:, reason: nil) ⇒ Object
-
#complete_checkout(checkout_id:, payment_token:, idempotency_key:) ⇒ Object
payment_token: is part of the Adapter contract's call signature (and already validated as non-PAN by PaymentTokenGuard before this ever runs, per §9) but this in-memory adapter has no payment processor to pass it on to — nothing here should hold onto it any longer than the single call needs to, so it's accepted and left unused rather than stored (see .rubocop.yml's Lint/UnusedMethodArgument exclude, same posture as the abstract Adapter#complete_checkout it overrides).
- #create_cart(line_items:, idempotency_key:, discount_codes: nil) ⇒ Object
- #create_checkout(line_items:, idempotency_key:, discount_codes: nil, fulfillment: nil) ⇒ Object
- #discount_codes_supported? ⇒ Boolean
- #fulfillment_supported? ⇒ Boolean
- #get_cart(cart_id:) ⇒ Object
- #get_checkout(checkout_id:) ⇒ Object
- #get_order(order_id:) ⇒ Object
- #get_product(product_id:) ⇒ Object
-
#initialize ⇒ ReferenceAdapter
constructor
A new instance of ReferenceAdapter.
-
#link_identity(oauth_token:) ⇒ Object
Accepts any non-blank token and mints a stable identity for it — real OAuth verification is a platform concern this in-memory adapter has no platform to defer to, same posture as PaymentTokenGuard drawing the line at "rejects the clearest misuse" rather than proving validity.
- #refund_order(order_id:, line_items:, idempotency_key:, reason: nil) ⇒ Object
- #request_return(order_id:, line_items:, idempotency_key:, reason: nil) ⇒ Object
- #search_catalog(query:, limit:) ⇒ Object
-
#seed_product(product) ⇒ Object
Not part of the Adapter contract — this adapter has no backend to seed real data into, so callers (a spec, a README example) hand it catalog fixtures directly.
- #update_cart(cart_id:, line_items:, idempotency_key:, discount_codes: nil) ⇒ Object
- #update_checkout(checkout_id:, line_items:, idempotency_key:, discount_codes: nil, fulfillment: nil) ⇒ Object
Constructor Details
#initialize ⇒ ReferenceAdapter
Returns a new instance of ReferenceAdapter.
33 34 35 36 37 38 39 40 41 |
# File 'lib/portage/ucp/reference_adapter.rb', line 33 def initialize super @products = {} @carts = {} @checkouts = {} @orders = {} @identities = {} @next_id = 0 end |
Instance Method Details
#cancel_cart(cart_id:, idempotency_key:) ⇒ Object
75 76 77 78 79 80 |
# File 'lib/portage/ucp/reference_adapter.rb', line 75 def cancel_cart(cart_id:, idempotency_key:) dedup(idempotency_key) do @carts.delete(cart_id) Portage::Ucp::Cart.new(id: cart_id, line_items: [], currency: "USD", totals: zero_totals) end end |
#cancel_checkout(checkout_id:, idempotency_key:) ⇒ Object
120 121 122 123 124 125 126 |
# File 'lib/portage/ucp/reference_adapter.rb', line 120 def cancel_checkout(checkout_id:, idempotency_key:) dedup(idempotency_key) do record_checkout_status(checkout_id, "canceled") checkout = @checkouts.fetch(checkout_id) @checkouts[checkout_id] = Portage::Ucp::Checkout.new(**checkout.to_h, status: "canceled") end end |
#cancel_order(order_id:, idempotency_key:, reason: nil) ⇒ Object
130 131 132 133 134 |
# File 'lib/portage/ucp/reference_adapter.rb', line 130 def cancel_order(order_id:, idempotency_key:, reason: nil) dedup(idempotency_key) do add_adjustment(order_id, type: "cancellation", status: "completed", description: reason) end end |
#complete_checkout(checkout_id:, payment_token:, idempotency_key:) ⇒ Object
payment_token: is part of the Adapter contract's call signature (and already validated as non-PAN by PaymentTokenGuard before this ever runs, per §9) but this in-memory adapter has no payment processor to pass it on to — nothing here should hold onto it any longer than the single call needs to, so it's accepted and left unused rather than stored (see .rubocop.yml's Lint/UnusedMethodArgument exclude, same posture as the abstract Adapter#complete_checkout it overrides).
107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/portage/ucp/reference_adapter.rb', line 107 def complete_checkout(checkout_id:, payment_token:, idempotency_key:) dedup(idempotency_key) do checkout = @checkouts.fetch(checkout_id) raise_if_any_line_out_of_stock!(checkout) order = store_order(checkout) confirmation = Portage::Ucp::OrderConfirmation.new(id: order.id, permalink_url: order.permalink_url) record_checkout_status(checkout_id, "completed") @checkouts[checkout_id] = Portage::Ucp::Checkout.new(**checkout.to_h, status: "completed", order: confirmation) end end |
#create_cart(line_items:, idempotency_key:, discount_codes: nil) ⇒ Object
62 63 64 65 66 67 |
# File 'lib/portage/ucp/reference_adapter.rb', line 62 def create_cart(line_items:, idempotency_key:, discount_codes: nil) dedup(idempotency_key) do id = next_id("cart") @carts[id] = build_cart(id, line_items, discount_codes) end end |
#create_checkout(line_items:, idempotency_key:, discount_codes: nil, fulfillment: nil) ⇒ Object
82 83 84 85 86 87 88 |
# File 'lib/portage/ucp/reference_adapter.rb', line 82 def create_checkout(line_items:, idempotency_key:, discount_codes: nil, fulfillment: nil) dedup(idempotency_key) do id = next_id("chk") record_checkout_status(id, "incomplete") @checkouts[id] = build_checkout(id, line_items, discount_codes, fulfillment, status: "incomplete") end end |
#discount_codes_supported? ⇒ Boolean
148 |
# File 'lib/portage/ucp/reference_adapter.rb', line 148 def discount_codes_supported? = true |
#fulfillment_supported? ⇒ Boolean
149 |
# File 'lib/portage/ucp/reference_adapter.rb', line 149 def fulfillment_supported? = true |
#get_cart(cart_id:) ⇒ Object
60 |
# File 'lib/portage/ucp/reference_adapter.rb', line 60 def get_cart(cart_id:) = @carts[cart_id] |
#get_checkout(checkout_id:) ⇒ Object
90 |
# File 'lib/portage/ucp/reference_adapter.rb', line 90 def get_checkout(checkout_id:) = @checkouts[checkout_id] |
#get_order(order_id:) ⇒ Object
128 |
# File 'lib/portage/ucp/reference_adapter.rb', line 128 def get_order(order_id:) = @orders[order_id] |
#get_product(product_id:) ⇒ Object
55 56 57 58 |
# File 'lib/portage/ucp/reference_adapter.rb', line 55 def get_product(product_id:) product = @products[product_id] product && Portage::Ucp::ProductDetail.new(product: product) end |
#link_identity(oauth_token:) ⇒ Object
Accepts any non-blank token and mints a stable identity for it — real OAuth verification is a platform concern this in-memory adapter has no platform to defer to, same posture as PaymentTokenGuard drawing the line at "rejects the clearest misuse" rather than proving validity.
156 157 158 159 160 161 162 163 |
# File 'lib/portage/ucp/reference_adapter.rb', line 156 def link_identity(oauth_token:) raise Portage::Ucp::AuthenticationError, "blank oauth_token" if oauth_token.to_s.empty? @identities[oauth_token] ||= Portage::Ucp::Identity.new( subject: "user_#{Digest::SHA256.hexdigest(oauth_token)[0, 12]}", email: nil, linked_at: Time.now.utc.iso8601 ) end |
#refund_order(order_id:, line_items:, idempotency_key:, reason: nil) ⇒ Object
142 143 144 145 146 |
# File 'lib/portage/ucp/reference_adapter.rb', line 142 def refund_order(order_id:, line_items:, idempotency_key:, reason: nil) dedup(idempotency_key) do add_adjustment(order_id, type: "refund", status: "completed", line_items: line_items, description: reason) end end |
#request_return(order_id:, line_items:, idempotency_key:, reason: nil) ⇒ Object
136 137 138 139 140 |
# File 'lib/portage/ucp/reference_adapter.rb', line 136 def request_return(order_id:, line_items:, idempotency_key:, reason: nil) dedup(idempotency_key) do add_adjustment(order_id, type: "return", status: "pending", line_items: line_items, description: reason) end end |
#search_catalog(query:, limit:) ⇒ Object
50 51 52 53 |
# File 'lib/portage/ucp/reference_adapter.rb', line 50 def search_catalog(query:, limit:) matches = @products.values.select { |p| p.title.downcase.include?(query.downcase) }.first(limit) Portage::Ucp::CatalogSearchResult.new(products: matches) end |
#seed_product(product) ⇒ Object
Not part of the Adapter contract — this adapter has no backend to seed real data into, so callers (a spec, a README example) hand it catalog fixtures directly.
46 47 48 |
# File 'lib/portage/ucp/reference_adapter.rb', line 46 def seed_product(product) @products[product.id] = product end |
#update_cart(cart_id:, line_items:, idempotency_key:, discount_codes: nil) ⇒ Object
69 70 71 72 73 |
# File 'lib/portage/ucp/reference_adapter.rb', line 69 def update_cart(cart_id:, line_items:, idempotency_key:, discount_codes: nil) dedup(idempotency_key) do @carts[cart_id] = build_cart(cart_id, line_items, discount_codes, previous: @carts[cart_id]) end end |
#update_checkout(checkout_id:, line_items:, idempotency_key:, discount_codes: nil, fulfillment: nil) ⇒ Object
92 93 94 95 96 97 98 |
# File 'lib/portage/ucp/reference_adapter.rb', line 92 def update_checkout(checkout_id:, line_items:, idempotency_key:, discount_codes: nil, fulfillment: nil) dedup(idempotency_key) do record_checkout_status(checkout_id, "incomplete") @checkouts[checkout_id] = build_checkout(checkout_id, line_items, discount_codes, fulfillment, status: "incomplete", previous: @checkouts[checkout_id]) end end |