Module: Portage::Ucp::Etsy::Mapper
- Defined in:
- lib/portage/ucp/etsy/mapper.rb
Overview
Converts Etsy Open API v3 response bodies into the protocol-layer value objects from Portage::Ucp::ValueObjects — nothing Etsy-shaped is allowed to leak past this file.
Etsy's Money type ({amount, divisor, currency_code}) is already
an integer, unlike every other adapter in this project — amount / divisor gives the decimal value, but amount itself is already in
the currency's minor units when divisor is 100 (the only value
Etsy documents ever returning), so no BigDecimal conversion is
needed here at all.
Class Method Summary collapse
-
.checkout(listings, id:, status:) ⇒ Object
id:is caller-supplied: there's no real Etsy checkout resource behind this at all — see Portage::Ucp::Etsy::Adapter's class-level comment. - .checkout_line_item(node) ⇒ Object
- .description(node) ⇒ Object
- .implicit_variant(node) ⇒ Object
- .line_total(node) ⇒ Object
- .money(node) ⇒ Object
- .order(node) ⇒ Object
- .order_line_item(node, status) ⇒ Object
- .price(node) ⇒ Object
- .price_range(node) ⇒ Object
-
.product(node) ⇒ Object
node["variants_detail"]is adapter-populated, not a real Etsy field: a listing's own resource has no variant/SKU breakdown at all — that's a second call to the separate Inventory endpoint (/listings/{id}/inventory), made only for #get_product's single-listing path, same reasoning as every other adapter's N+1 variant fetch. -
.receipt_status(node) ⇒ Object
Etsy core has no per-transaction fulfillment tracking on the Receipt resource beyond a single receipt-wide
is_shippedflag — every line gets the same coarse status, same simplification as Portage::Ucp::WooCommerce::Mapper/Portage::Ucp::Magento::Mapper. - .totals(listings) ⇒ Object
- .variant(node, parent_node) ⇒ Object
-
.variants(node) ⇒ Object
A listing with no inventory-level variation is its own single implicit variant, same as a single-variant Shopify product using that variant's own id.
Class Method Details
.checkout(listings, id:, status:) ⇒ Object
id: is caller-supplied: there's no real Etsy checkout resource
behind this at all — see Portage::Ucp::Etsy::Adapter's class-level
comment. links points at each listing's own public Etsy URL
rather than a cart, since Etsy's public API has no way to deep-
link a multi-item add-to-cart flow.
82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 82 def checkout(listings, id:, status:) line_items = listings.map { |l| checkout_line_item(l) } currency = listings.first&.dig("price", "currency_code") Portage::Ucp::Checkout.new( id: id, status: status, line_items: line_items, currency: currency, totals: totals(listings), links: listings.map { |l| Portage::Ucp::Link.new(type: "checkout", url: l["url"], title: l["title"]) } ) end |
.checkout_line_item(node) ⇒ Object
95 96 97 98 99 100 101 102 103 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 95 def checkout_line_item(node) Portage::Ucp::LineItem.new( id: node["listing_id"].to_s, item: Portage::Ucp::Item.new(id: node["listing_id"].to_s, title: node["title"], price: node.dig("price", "amount")), quantity: node["quantity"] || 1, totals: Portage::Ucp::Support::Totals.line(line_total(node)) ) end |
.description(node) ⇒ Object
21 22 23 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 21 def description(node) Portage::Ucp::Description.new(plain: node["description"]) end |
.implicit_variant(node) ⇒ Object
61 62 63 64 65 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 61 def implicit_variant(node) available = node["state"] == "active" && node["quantity"].to_i.positive? Portage::Ucp::Variant.new(id: node["listing_id"].to_s, title: node["title"], description: description(node), price: price(node["price"]), availability: { "available" => available }) end |
.line_total(node) ⇒ Object
105 106 107 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 105 def line_total(node) node.dig("price", "amount") * (node["quantity"] || 1) end |
.money(node) ⇒ Object
17 18 19 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 17 def money(node) Portage::Ucp::Money.new(amount_minor: node["amount"], currency: node["currency_code"]) end |
.order(node) ⇒ Object
134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 134 def order(node) status = receipt_status(node) Portage::Ucp::Order.new( id: node["receipt_id"].to_s, checkout_id: "", permalink_url: "", line_items: (node["transactions"] || []).map { |n| order_line_item(n, status) }, fulfillment: Portage::Ucp::Fulfillment.new, currency: node.dig("total_price", "currency_code"), totals: Portage::Ucp::Support::Totals.summary(subtotal: money(node["subtotal"]).amount_minor, total: money(node["total_price"]).amount_minor) ) end |
.order_line_item(node, status) ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 148 def order_line_item(node, status) quantity = node["quantity"] fulfilled = Portage::Ucp::Support::LineItemStatus.fulfilled_quantity(status, quantity) line_total = node.dig("price", "amount") * quantity Portage::Ucp::OrderLineItem.new( id: node["transaction_id"].to_s, item: Portage::Ucp::Item.new(id: node["listing_id"].to_s, title: node["title"], price: node.dig("price", "amount")), quantity: { original: quantity, total: quantity, fulfilled: fulfilled }, totals: Portage::Ucp::Support::Totals.line(line_total), status: status ) end |
.price(node) ⇒ Object
25 26 27 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 25 def price(node) Portage::Ucp::Price.new(amount: node["amount"], currency: node["currency_code"]) end |
.price_range(node) ⇒ Object
29 30 31 32 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 29 def price_range(node) p = price(node) Portage::Ucp::PriceRange.new(min: p, max: p) end |
.product(node) ⇒ Object
node["variants_detail"] is adapter-populated, not a real Etsy
field: a listing's own resource has no variant/SKU breakdown at
all — that's a second call to the separate Inventory endpoint
(/listings/{id}/inventory), made only for #get_product's
single-listing path, same reasoning as every other adapter's N+1
variant fetch.
40 41 42 43 44 45 46 47 48 49 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 40 def product(node) Portage::Ucp::Product.new( id: node["listing_id"].to_s, title: node["title"], description: description(node), price_range: price_range(node["price"]), variants: variants(node), url: node["url"] ) end |
.receipt_status(node) ⇒ Object
Etsy core has no per-transaction fulfillment tracking on the
Receipt resource beyond a single receipt-wide is_shipped flag —
every line gets the same coarse status, same simplification as
Portage::Ucp::WooCommerce::Mapper/Portage::Ucp::Magento::Mapper.
permalink_url is left blank — Etsy's API doesn't return a
public, shareable receipt URL (buyers see receipts inside their
own account, not via a link). checkout_id is always blank too,
for a more fundamental reason than the other adapters: this
gem's Checkout is never a real Etsy resource (see
Portage::Ucp::Etsy::Adapter), so there is no "checkout that led
to this receipt" for the adapter to have recorded in the first
place.
127 128 129 130 131 132 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 127 def receipt_status(node) return "fulfilled" if node["is_shipped"] return "removed" if node["status"] == "canceled" "processing" end |
.totals(listings) ⇒ Object
109 110 111 112 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 109 def totals(listings) subtotal = listings.sum { |l| line_total(l) } Portage::Ucp::Support::Totals.summary(subtotal: subtotal, total: subtotal) end |
.variant(node, parent_node) ⇒ Object
67 68 69 70 71 72 73 74 75 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 67 def variant(node, parent_node) title = (node["property_values"] || []).flat_map { |p| p["values"] }.join(" / ") offering = (node["offerings"] || []).find { |o| o["is_enabled"] } || {} Portage::Ucp::Variant.new( id: node["product_id"].to_s, title: title, description: description(parent_node), price: price(offering["price"] || {}), availability: { "available" => offering["quantity"].to_i.positive? } ) end |
.variants(node) ⇒ Object
A listing with no inventory-level variation is its own single implicit variant, same as a single-variant Shopify product using that variant's own id.
54 55 56 57 58 59 |
# File 'lib/portage/ucp/etsy/mapper.rb', line 54 def variants(node) detail = node["variants_detail"] return [implicit_variant(node)] unless detail (detail["products"] || []).reject { |p| p["is_deleted"] }.map { |p| variant(p, node) } end |