From fb4adc6ec2554cb2fb5ded25901e647c88594275 Mon Sep 17 00:00:00 2001 From: Ross Chapman Date: Mon, 13 Nov 2023 13:50:44 -0800 Subject: [PATCH] Reset --- Gemfile | 2 +- Gemfile.lock | 41 +++-- app/components/select_component.html.erb | 7 + app/components/select_component.rb | 12 ++ app/furniture/livestreams/_form.html.erb | 13 +- .../cart/delivery_area_component.html.erb | 18 +- .../cart/delivery_area_component.rb | 8 + .../cart/delivery_areas/_form.html.erb | 6 +- app/furniture/marketplace/marketplace.rb | 13 +- .../marketplace/marketplace_component.rb | 3 +- app/furniture/marketplace/order.rb | 130 -------------- app/furniture/marketplace/square_order.rb | 165 ++++++++++++++++++ .../marketplace/stripe_events_controller.rb | 4 +- app/views/application/_select.html.erb | 6 - app/views/furnitures/_new.html.erb | 10 +- app/views/spaces/_sections.html.erb | 9 +- app/views/utilities/new.html.erb | 11 +- package.json | 12 +- spec/factories/furniture/marketplace.rb | 8 + .../buying_products_system_spec.rb | 4 +- .../cart/delivery_area_component_spec.rb | 35 ++++ .../collecting_payments_system_spec.rb | 2 +- .../marketplace/square_order_spec.rb | 55 ++++++ .../stripe_events_controller_request_spec.rb | 42 ++--- spec/system/furniture_spec.rb | 2 +- yarn.lock | 140 +++++++-------- 26 files changed, 468 insertions(+), 290 deletions(-) create mode 100644 app/components/select_component.html.erb create mode 100644 app/components/select_component.rb create mode 100644 app/furniture/marketplace/square_order.rb delete mode 100644 app/views/application/_select.html.erb create mode 100644 spec/furniture/marketplace/cart/delivery_area_component_spec.rb create mode 100644 spec/furniture/marketplace/square_order_spec.rb diff --git a/Gemfile b/Gemfile index 2924460e2..b0eac84d1 100644 --- a/Gemfile +++ b/Gemfile @@ -125,7 +125,7 @@ group :development, :test do gem "rubocop-rails" gem "rubocop-rspec" - gem "standard", "~> 1.31" + gem "standard", "~> 1.32" end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index d12a6f84c..3f9279e41 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -124,7 +124,7 @@ GEM aws-sigv4 (~> 1.6) aws-sigv4 (1.6.0) aws-eventstream (~> 1, >= 1.0.2) - base64 (0.1.1) + base64 (0.2.0) bcrypt (3.1.19) better_errors (2.10.1) erubi (>= 1.0.0) @@ -169,7 +169,7 @@ GEM dotenv-rails (2.8.1) dotenv (= 2.8.1) railties (>= 3.2) - drb (2.1.1) + drb (2.2.0) ruby2_keywords erubi (1.12.0) factory_bot (6.2.0) @@ -177,7 +177,7 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (3.2.1) + faker (3.2.2) i18n (>= 1.8.11, < 2) faraday (2.7.11) base64 @@ -259,7 +259,7 @@ GEM method_source (1.0.0) mini_magick (4.12.0) mini_mime (1.1.5) - mini_portile2 (2.8.4) + mini_portile2 (2.8.5) minitest (5.20.0) monetize (1.12.0) money (~> 6.12) @@ -272,7 +272,7 @@ GEM railties (>= 3.0) msgpack (1.7.2) multipart-post (2.3.0) - mutex_m (0.1.2) + mutex_m (0.2.0) net-http-persistent (4.0.2) connection_pool (~> 2.2) net-imap (0.4.1) @@ -307,7 +307,7 @@ GEM nio4r (~> 2.0) pundit (2.3.1) activesupport (>= 3.0.0) - racc (1.7.1) + racc (1.7.3) rack (2.2.8) rack-session (1.0.1) rack (< 3) @@ -355,19 +355,19 @@ GEM thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.0.6) + rake (13.1.0) ranked-model (0.4.9) activerecord (>= 5.2) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rdoc (6.5.0) + rdoc (6.6.0) psych (>= 4.0.0) redcarpet (3.6.0) redis-client (0.18.0) connection_pool regexp_parser (2.8.2) - reline (0.3.9) + reline (0.4.0) io-console (~> 0.5) rexml (3.2.6) rotp (6.3.0) @@ -399,12 +399,11 @@ GEM rswag-ui (2.11.0) actionpack (>= 3.1, < 7.2) railties (>= 3.1, < 7.2) - rubocop (1.56.4) - base64 (~> 0.1.1) + rubocop (1.57.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.3) + parser (>= 3.2.2.4) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) @@ -435,14 +434,14 @@ GEM ffi (~> 1.12) ruby2_keywords (0.0.5) rubyzip (2.3.2) - selenium-webdriver (4.14.0) + selenium-webdriver (4.15.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - sentry-rails (5.12.0) + sentry-rails (5.13.0) railties (>= 5.0) - sentry-ruby (~> 5.12.0) - sentry-ruby (5.12.0) + sentry-ruby (~> 5.13.0) + sentry-ruby (5.13.0) concurrent-ruby (~> 1.0, >= 1.0.2) shoulda-matchers (5.3.0) activesupport (>= 5.2.0) @@ -469,10 +468,10 @@ GEM apimatic_core (~> 0.3.0) apimatic_core_interfaces (~> 0.2.0) apimatic_faraday_client_adapter (~> 0.1.0) - standard (1.31.2) + standard (1.32.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.56.4) + rubocop (~> 1.57.2) standard-custom (~> 1.0.0) standard-performance (~> 1.2) standard-custom (1.0.2) @@ -483,10 +482,10 @@ GEM rubocop-performance (~> 1.19.1) stimulus-rails (1.3.0) railties (>= 6.0.0) - stringio (3.0.8) + stringio (3.0.9) strip_attributes (1.13.0) activemodel (>= 3.0, < 8.0) - stripe (10.0.0) + stripe (10.1.0) strong_migrations (1.6.4) activerecord (>= 5.2) thor (1.3.0) @@ -575,7 +574,7 @@ DEPENDENCIES spring-watcher-listen! sprockets-rails square.rb - standard (~> 1.31) + standard (~> 1.32) stimulus-rails strip_attributes (~> 1.13) stripe diff --git a/app/components/select_component.html.erb b/app/components/select_component.html.erb new file mode 100644 index 000000000..f41be8a4d --- /dev/null +++ b/app/components/select_component.html.erb @@ -0,0 +1,7 @@ +
+ <% unless @skip_label %> + <%= @form.label @attribute %> + <% end %> + <%= @form.select @attribute, @choices, @options, @html_options %> + <%= render partial: "error", locals: { model: @form.object, attribute: @attribute } %> +
diff --git a/app/components/select_component.rb b/app/components/select_component.rb new file mode 100644 index 000000000..73f88d346 --- /dev/null +++ b/app/components/select_component.rb @@ -0,0 +1,12 @@ +class SelectComponent < ApplicationComponent + def initialize(config, **kwargs) + @form = config.fetch(:form) + @attribute = config.fetch(:attribute) + @choices = config.fetch(:choices, []) + @options = config.fetch(:options, {}) + @html_options = config.fetch(:html_options, {}) + @skip_label = config.fetch(:skip_label, true) + + super(**kwargs) + end +end diff --git a/app/furniture/livestreams/_form.html.erb b/app/furniture/livestreams/_form.html.erb index 33f284758..d3f8ee1d2 100644 --- a/app/furniture/livestreams/_form.html.erb +++ b/app/furniture/livestreams/_form.html.erb @@ -1,6 +1,13 @@ <%= form.fields_for(:gizmo) do |furniture_fields| %> <%= render "text_field", form: furniture_fields, attribute: :channel %> - <%= render "select", form: furniture_fields, attribute: :layout, options: ['video', 'video_with_chat'], include_blank: false %> - - <%= render "select", form: furniture_fields, attribute: :provider, options: ['twitch'], include_blank: false %> + <%= render SelectComponent.new({ + choices: ['video', 'video_with_chat'], + attribute: :layout, + form: furniture_fields, + }) %> + <%= render SelectComponent.new({ + choices: ['twitch'], + attribute: :provider, + form: furniture_fields, + }) %> <%- end %> diff --git a/app/furniture/marketplace/cart/delivery_area_component.html.erb b/app/furniture/marketplace/cart/delivery_area_component.html.erb index 9bf3723bd..0a5d18a59 100644 --- a/app/furniture/marketplace/cart/delivery_area_component.html.erb +++ b/app/furniture/marketplace/cart/delivery_area_component.html.erb @@ -4,18 +4,26 @@ <%- if delivery_area.present? %>
<%= delivery_area.label %> -
<%= render(Marketplace::Cart::DeliveryExpectationsComponent.new(cart: cart)) %> + <%- if single_delivery_area? %> +
Orders outside of this location will be subject to cancellation.
+ <% end %>
-
-
- <%= link_to(t('marketplace.cart.delivery_areas.edit.link_to'), cart.location(:edit, child: :delivery_area)) %> + <%- unless single_delivery_area? %> + <%= link_to(t('marketplace.cart.delivery_areas.edit.link_to'), cart.location(:edit, child: :delivery_area)) %> + <%- end %>
<%- else %> - <%= render "marketplace/cart/delivery_areas/form", cart: cart %> + <%- if single_delivery_area? %> +
<%= single_delivery_area_label %>
+ Orders outside of this location will be subject to cancellation. + + <% else %> + <%= render "marketplace/cart/delivery_areas/form", cart: cart %> + <%- end %> <%- end %> <%- end %> <%- end %> diff --git a/app/furniture/marketplace/cart/delivery_area_component.rb b/app/furniture/marketplace/cart/delivery_area_component.rb index 276da8011..24b301c53 100644 --- a/app/furniture/marketplace/cart/delivery_area_component.rb +++ b/app/furniture/marketplace/cart/delivery_area_component.rb @@ -11,5 +11,13 @@ def initialize(*args, cart:, **kwargs) def dom_id super(cart, :delivery_area) end + + def single_delivery_area? + cart.marketplace.delivery_areas.size == 1 + end + + def single_delivery_area_label + cart.marketplace.delivery_areas.first.label + end end end diff --git a/app/furniture/marketplace/cart/delivery_areas/_form.html.erb b/app/furniture/marketplace/cart/delivery_areas/_form.html.erb index 6ac7afcba..3a32352b5 100644 --- a/app/furniture/marketplace/cart/delivery_areas/_form.html.erb +++ b/app/furniture/marketplace/cart/delivery_areas/_form.html.erb @@ -1,10 +1,12 @@ <%= render AlertComponent.new(title: "Where are you Ordering from?") do %>

Delivery prices and times vary based upon your location

<%- end %> -
<%= form_with model: cart.location, url: cart.location(child: :delivery_area) do |form| %> - <%= render "select", options: cart.marketplace.delivery_areas.pluck(:label, :id), include_blank: true, attribute: :delivery_area_id, form: form, skip_label: true %> + <%= render SelectComponent.new({ + choices: cart.marketplace.delivery_areas.pluck(:label, :id), attribute: :delivery_area_id, + form: form, + }) %> <%= form.submit %> <%- end %>
diff --git a/app/furniture/marketplace/marketplace.rb b/app/furniture/marketplace/marketplace.rb index 1005c53ec..7a21960a2 100644 --- a/app/furniture/marketplace/marketplace.rb +++ b/app/furniture/marketplace/marketplace.rb @@ -100,13 +100,16 @@ def self.model_name @_model_name ||= ActiveModel::Name.new(self, ::Marketplace) end - def square_order_notifications_enabled? - square_location_id.present? + def square_connection + if square_order_notifications_enabled? + @square_client ||= Square::Client.new(access_token: square_access_token, environment: square_environment) + end end - # TODO: Add `square_environment` attribute to database/model - def square_client - @square_client ||= Square::Client.new(access_token: square_access_token, environment: square_environment) + private + + def square_order_notifications_enabled? + square_location_id.present? end end end diff --git a/app/furniture/marketplace/marketplace_component.rb b/app/furniture/marketplace/marketplace_component.rb index ccc43dc17..a30e5b546 100644 --- a/app/furniture/marketplace/marketplace_component.rb +++ b/app/furniture/marketplace/marketplace_component.rb @@ -13,7 +13,8 @@ def render? end def cart - @cart ||= carts.create_with(contact_email: shopper.person&.email).find_or_create_by(shopper: shopper, status: :pre_checkout) + delivery_area = marketplace.delivery_areas.first if marketplace.delivery_areas.size == 1 + @cart ||= carts.create_with(contact_email: shopper.person&.email).find_or_create_by(delivery_area: delivery_area, shopper: shopper, status: :pre_checkout) end def delivery_area_component diff --git a/app/furniture/marketplace/order.rb b/app/furniture/marketplace/order.rb index 479e49ad3..e94db7dac 100644 --- a/app/furniture/marketplace/order.rb +++ b/app/furniture/marketplace/order.rb @@ -52,135 +52,5 @@ def marketplace_name def price_total [product_total, delivery_fee, tax_total].compact.sum(0) end - - def send_to_square_seller_dashboard - square_create_order_response = create_square_order - - if square_create_order_response - square_create_payment_response = create_square_order_payment(square_create_order_response.body.order[:id]) - - # This data is intended for use in debugging, etc... until we further - # the Square integration productize - { - order_id: square_create_order_response.body.order[:id], - payment_id: square_create_payment_response.body.payment[:id] - } - else - # TODO: Noop for now - end - end - - # Square sets max of 43 chars - private def square_idemp_key - "#{id}_#{8.times.map { rand(10) }.join}" - end - - private def create_square_order - square_create_order_body = build_square_create_order_body(marketplace) - marketplace.square_client.orders.create_order(body: square_create_order_body) - end - - # NOTE: Square requires that orders are paid in order to show up in the Seller - # Dashboard - private def create_square_order_payment(square_order_id) - square_location_id = marketplace.settings["square_location_id"] - space_id = marketplace.space.id - square_create_payment_body = build_square_create_order_payment_body( - square_order_id, - square_location_id, - space_id - ) - - marketplace.square_client.payments.create_payment(body: square_create_payment_body) - end - - # NOTE: Square requires that orders include fulfillments in order to show up - # in the Seller Dashboard - # See: https://developer.squareup.com/docs/orders-api/create-orders - private def build_square_create_order_body(marketplace) - location_id = marketplace.settings["square_location_id"] - customer_id = shopper.id - - line_items = ordered_products.map { |ordered_product| - { - name: ordered_product.name, - quantity: ordered_product.quantity.to_s, - item_type: "ITEM", # ITEM|CUSTOM_AMOUNT|CGI - base_price_money: { - amount: ordered_product.product.price.cents, - currency: "USD" - } - } - } - - taxes = marketplace.tax_rates.map { |tax_rate| - { - uid: tax_rate.id, - name: tax_rate.label, - percentage: tax_rate.tax_rate.to_s, - scope: "LINE_ITEM" - } - } - - { - order: { - location_id:, - line_items:, - taxes:, - fulfillments: [ - { - type: "DELIVERY", # DELIVERY|SHIPMENT|PICKUP - state: "PROPOSED", # PROPOSED|RESERVED|PREPARED|COMPLETED|CANCELED|FAILED - delivery_details: { - recipient: { - display_name: shopper.person.display_name, - phone_number: contact_phone_number, - address: { - address_line_1: delivery_address - } - }, - schedule_type: "SCHEDULED", # SCHEDULED|ASAP - # Square requires this field. Until we have a delivery - # calculation, let's put `now`? - deliver_at: Time.now.to_datetime.rfc3339 - } - } - ], - customer_id: - }, - idempotency_key: square_idemp_key - } - end - - # NOTE: For payments that are received outside of Square - such as Convene - # orders paid with Stripe -- Square recommends putting "EXTERNAL" for the - # `source_id` and "OTHER" for the `external_details.type`. - # See: https://developer.squareup.com/docs/payments-api/take-payments/external-payments - # - # NOTE: `amount_money` much match the price total of line items in the - # square order (`line_items.sum(&base_price_money[:amount])`) to be valid - # - # TODO: Consider adding a price check? - private def build_square_create_order_payment_body(square_order_id, square_location_id, space_id) - { - source_id: "EXTERNAL", - idempotency_key: square_idemp_key, - amount_money: { - amount: product_total.cents, - currency: "USD" - }, - order_id: square_order_id, - location_id: square_location_id, - external_details: { - type: "OTHER", - source: "CONVENE_SYSTEM_PAYMENT for Space #{space_id}" - # TODO: Need this later? - # source_fee_money: { - # amount: "test", - # currency: "test" - # } - } - } - end end end diff --git a/app/furniture/marketplace/square_order.rb b/app/furniture/marketplace/square_order.rb new file mode 100644 index 000000000..2d1cff07d --- /dev/null +++ b/app/furniture/marketplace/square_order.rb @@ -0,0 +1,165 @@ +# SquareOrder encapsulates logic for coordinating and executing interactions +# between a Convene Marketplace and the seller's Square account. It could be +# described as a (budding) service or process class, but current behavior is +# limited to only registering orders in a seller's account upon sale. +# +# Registering orders serves to both synchronize record keeping between +# Marketplace managers owners (eg. Pikkkup) and sellers (eg. Crumble & Wisk) +# and automatically notify sellers of new orders. +# +# NOTE: the simple `Markeplace > SquareOrder` namespacing was chosen +# to avoid collision with the `Square` namespace already included by Square's gem. +# +# TODOS/considerations: +# 1. Implement validation of `order` argument on initialization +# Eg: if order.is_not_valid?; exit; end +# 2. Implement state tracking +# Eg: READY | ORDER_SENT | etc... +class Marketplace + class SquareOrder + attr_accessor :order, :square_order_id, :square_payment_id + delegate :shopper, :marketplace, :ordered_products, to: :order + delegate :space, :square_location_id, to: :marketplace + + def initialize(order) + @order = order + end + + # Sends an order to Square. Will create both an order and payment in the + # receiving account which are requirements for the order to show up in the + # Seller Dashboard. + def send_order + Rails.logger.info("Start creating Square order") + square_create_order_response = create_square_order + order.events.create(description: "Square Order Created") + Rails.logger.info("Finished creating Square order") + + if square_create_order_response + @square_order_id = square_create_order_response.body.order[:id] + Rails.logger.info("Start creating Square order payment") + square_create_payment_response = create_square_order_payment + Rails.logger.info("Finished creating Square order payment") + order.events.create(description: "Square Order Created") + @square_payment_id = square_create_payment_response.body.payment[:id] + # @state = "ORDER_SENT" + + # This data is intended for use in debugging, etc... until we further + # the Square integration productize + { + order_id: @square_order_id, + payment_id: @square_payment_id + } + else + # TODO: Noop for now + end + end + + # Square sets max of 43 chars so this assumes an order id remains a 36-char + # uuid + private def square_idemp_key + "#{order[:id]}_#{8.times.map { rand(10) }.join}" + end + + private def create_square_order + square_create_order_body = prepare_square_create_order_body(marketplace) + @square_client.orders.create_order(body: square_create_order_body) + end + + # NOTE: Square requires that orders must have be in a state of complete + # payment to display in the Seller Dashboard + private def create_square_order_payment + square_create_payment_body = prepare_square_create_order_payment_body + + @square_client.payments.create_payment(body: square_create_payment_body) + end + + # NOTE: Square requires that orders must include fulfillments to display + # in the Seller Dashboard + # See: https://developer.squareup.com/docs/orders-api/create-orders + private def prepare_square_create_order_body + location_id = marketplace.square_location_id + customer_id = shopper.id + + line_items = ordered_products.map { |ordered_product| + { + name: ordered_product.name, + quantity: ordered_product.quantity.to_s, + item_type: "ITEM", # ITEM|CUSTOM_AMOUNT|CGI + base_price_money: { + amount: ordered_product.product.price.cents, + currency: "USD" + } + } + } + + taxes = @marketplace.tax_rates.map { |tax_rate| + { + uid: tax_rate.id, + name: tax_rate.label, + percentage: tax_rate.tax_rate.to_s, + scope: "LINE_ITEM" + } + } + + { + order: { + location_id:, + line_items:, + taxes:, + fulfillments: [ + { + type: "DELIVERY", # DELIVERY|SHIPMENT|PICKUP + state: "PROPOSED", # PROPOSED|RESERVED|PREPARED|COMPLETED|CANCELED|FAILED + delivery_details: { + recipient: { + display_name: shopper.person.display_name, + phone_number: order.contact_phone_number, + address: { + address_line_1: order.delivery_address + } + }, + schedule_type: "SCHEDULED", # SCHEDULED|ASAP + # Square requires this field. Until we have a delivery + # calculation, let's put `now`? + deliver_at: Time.now.to_datetime.rfc3339 + } + } + ], + customer_id: + }, + idempotency_key: square_idemp_key + } + end + + # NOTE: For payments that are received outside of Square - such as Convene + # orders paid with Stripe -- Square recommends putting "EXTERNAL" for the + # `source_id` and "OTHER" for the `external_details.type`. + # See: https://developer.squareup.com/docs/payments-api/take-payments/external-payments + # + # NOTE: `amount_money` much match the price total of line items in the + # square order (`line_items.sum(&base_price_money[:amount])`) to be valid + # + # TODO: Consider adding a price check? + private def prepare_square_create_order_payment_body + { + source_id: "EXTERNAL", + idempotency_key: square_idemp_key, + amount_money: { + amount: order.product_total.cents, + currency: "USD" + }, + order_id: @square_order_id, + location_id: @square_location_id, + external_details: { + type: "OTHER", + source: "Paid by Stripe (Charge #{order.stripe_payment_id}) via #{space.name} (#{space.id})" + # TODO: Need this later? + # source_fee_money: { + # amount: "test", + # currency: "test" + # } + } + } + end + end +end diff --git a/app/furniture/marketplace/stripe_events_controller.rb b/app/furniture/marketplace/stripe_events_controller.rb index def94256b..b55fe5061 100644 --- a/app/furniture/marketplace/stripe_events_controller.rb +++ b/app/furniture/marketplace/stripe_events_controller.rb @@ -23,9 +23,7 @@ def create order.update!(status: :paid, placed_at: DateTime.now, payment_processor_fee_cents: balance_transaction.fee) order.events.create(description: "Payment Received") - if marketplace.square_order_notifications_enabled? - order.send_to_square_seller_dashboard - end + marketplace.square_connection&.send_order Order::ReceivedMailer.notification(order).deliver_later order.events.create(description: "Notifications to Vendor and Distributor Sent") diff --git a/app/views/application/_select.html.erb b/app/views/application/_select.html.erb deleted file mode 100644 index 1dc4d8dc2..000000000 --- a/app/views/application/_select.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -
- <%= form.label attribute unless local_assigns[:skip_label] %> - <%= form.select attribute, options, include_blank: local_assigns.fetch(:include_blank, true), - prompt: local_assigns.fetch(:prompt, false) %> - <%= render partial: "error", locals: { model: form.object, attribute: attribute } %> -
diff --git a/app/views/furnitures/_new.html.erb b/app/views/furnitures/_new.html.erb index 5ecbb3efe..8b9aa4e96 100644 --- a/app/views/furnitures/_new.html.erb +++ b/app/views/furnitures/_new.html.erb @@ -2,8 +2,14 @@

<%= t('rooms.place_furniture_heading') %>

<%= form_with model: furniture.location do | form | %> <%= form.hidden_field :slot, value: furniture.room.gizmos.count %> - <%= render "select", attribute: :furniture_kind, options: Furniture.registry.keys.map { |k| [k.to_s.titleize, k] }, - form: form, prompt: "-- Pick a type of gizmo --", include_blank: false %> + <%= render SelectComponent.new({ + choices: Furniture.registry.keys.map { |k| [k.to_s.titleize, k] }, + attribute: :furniture_kind, + form: form, + options: { + prompt: "-- Pick a type of gizmo --", + } + }) %> diff --git a/app/views/spaces/_sections.html.erb b/app/views/spaces/_sections.html.erb index 3a4d5543f..9ee532ffe 100644 --- a/app/views/spaces/_sections.html.erb +++ b/app/views/spaces/_sections.html.erb @@ -2,7 +2,6 @@

Sections

-
<%- space.rooms.each do |room| %> <%- if policy(room).edit? %> @@ -14,15 +13,17 @@ <%- end %> <% end %>
- - <%= form_with(model: space.location) do |space_form| %> - <%= render "select", attribute: :entrance_id, include_blank: true, options: space.rooms.map { |room| [room.name, room.id] }, form: space_form %> + <%= render SelectComponent.new({ + choices: space.rooms.map { |room| [room.name, room.id] }, + attribute: :entrance_id, + form: space_form, + }) %> <%= space_form.submit %> <%- end %> <% end %> diff --git a/app/views/utilities/new.html.erb b/app/views/utilities/new.html.erb index 7b4bff89e..f3933d7e0 100644 --- a/app/views/utilities/new.html.erb +++ b/app/views/utilities/new.html.erb @@ -1,9 +1,14 @@ <%- breadcrumb :new_utility, utility %> - <%= render CardComponent.new(classes: "mt-3") do %> <%= form_with(model: utility.location) do |form| %> - <%= render "select", attribute: :utility_slug, options: Utility.registry.keys, form: form, - prompt: "-- Pick a utility type --", include_blank: false %> + <%= render SelectComponent.new({ + choices: Utility.registry.keys, + attribute: :utility_slug, + form: form, + options: { + prompt: "-- Pick a utility type --" + } + }) %> <%= render "text_field", attribute: :name, form: form %> <%= form.submit %> <%- end %> diff --git a/package.json b/package.json index b3b79b0d3..add99a705 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "@cucumber/cucumber": "^10.0.1", "@cucumber/pretty-formatter": "^1.0.0", - "axios": "^1.6.0", + "axios": "^1.6.1", "axios-case-converter": "^1.1.0", "change-case": "^5.1.2", "dotenv": "^16.3.1", @@ -42,17 +42,17 @@ "lodash": "^4.17.21", "prettier": "^3.0.3", "promise-retry": "^2.0.1", - "selenium-webdriver": "^4.14.0", + "selenium-webdriver": "^4.15.0", "webpack-dev-server": "^4.15.1" }, "dependencies": { "@hotwired/stimulus": "^3.2.2", "@hotwired/stimulus-webpack-helpers": "^1.0.1", "@hotwired/turbo-rails": "^7.3.0", - "@rails/actioncable": "^7.1.1", - "@rails/activestorage": "^7.1.1", - "@sentry/browser": "^7.77.0", - "@tailwindcss/forms": "^0.5.6", + "@rails/actioncable": "^7.1.2", + "@rails/activestorage": "^7.1.2", + "@sentry/browser": "^7.79.0", + "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.10", "@webpack-cli/serve": "^2.0.5", "autoprefixer": "^10.4.16", diff --git a/spec/factories/furniture/marketplace.rb b/spec/factories/furniture/marketplace.rb index 78a1ebf18..0a12c5d81 100644 --- a/spec/factories/furniture/marketplace.rb +++ b/spec/factories/furniture/marketplace.rb @@ -65,6 +65,14 @@ with_delivery_areas with_notification_methods end + + trait :with_square do + square_location_id { ENV.fetch("MARKETPLACE_VENDOR_SQUARE_LOCATION") } + square_environment { ENV.fetch("SQUARE_ENV", "sandbox") } + secrets { + square_access_token { ENV.fetch("MARKETPLACE_VENDOR_SQUARE_ACCESS_TOKEN") } + } + end end factory :marketplace_bazaar, class: "Marketplace::Bazaar" do diff --git a/spec/furniture/marketplace/buying_products_system_spec.rb b/spec/furniture/marketplace/buying_products_system_spec.rb index 0f55f9e96..89540df26 100644 --- a/spec/furniture/marketplace/buying_products_system_spec.rb +++ b/spec/furniture/marketplace/buying_products_system_spec.rb @@ -26,8 +26,6 @@ def url_options it "Works for Guests" do # rubocop:disable RSpec/ExampleLength visit(polymorphic_path(marketplace.room.location)) - select(marketplace.delivery_areas.first.label, from: "cart[delivery_area_id]") - click_button("Save changes") add_product_to_cart(marketplace.products.first) @@ -58,6 +56,8 @@ def url_options expect(order_placed_notifications).to be_present expect(order_placed_notifications.length).to eq 1 + expect(Rails.logger).to have_received(:info).with("Finished creating Square order") + expect(Rails.logger).to have_received(:info).with("Finished creating Square order payment") end def add_product_to_cart(product) diff --git a/spec/furniture/marketplace/cart/delivery_area_component_spec.rb b/spec/furniture/marketplace/cart/delivery_area_component_spec.rb new file mode 100644 index 000000000..3574ad665 --- /dev/null +++ b/spec/furniture/marketplace/cart/delivery_area_component_spec.rb @@ -0,0 +1,35 @@ +require "rails_helper" + +RSpec.describe Marketplace::Cart::DeliveryAreaComponent, type: :component do + subject(:output) { render_inline(component) } + + let(:component) { described_class.new(cart: cart) } + + let(:cart) { create(:marketplace_cart, marketplace: marketplace) } + + context "when a delivery area is present" do + context "with a single delivery area" do + let(:marketplace) { create(:marketplace, :with_delivery_areas) } + + it { is_expected.to have_content "Orders outside of this location will be subject to cancellation." } + end + + context "with multiple delivery areas" do + let(:marketplace) { + create(:marketplace, :with_delivery_areas, delivery_area_quantity: 2) + } + + it { is_expected.to have_content "Where are you Ordering from?" } + it { is_expected.to have_content "Delivery prices and times vary based upon your location" } + it { is_expected.to have_css("option", count: 2) } + end + end + + context "when a delivery area is not present" do + let(:marketplace) { create(:marketplace) } + + it { is_expected.to have_content("Where are you Ordering from?") } + it { is_expected.to have_content "Delivery prices and times vary based upon your location" } + it { is_expected.not_to have_css("option") } + end +end diff --git a/spec/furniture/marketplace/collecting_payments_system_spec.rb b/spec/furniture/marketplace/collecting_payments_system_spec.rb index 67b096489..56e98f17f 100644 --- a/spec/furniture/marketplace/collecting_payments_system_spec.rb +++ b/spec/furniture/marketplace/collecting_payments_system_spec.rb @@ -24,7 +24,7 @@ end click_link("Add a Stripe API key to #{space.name}") click_link("Add Utility") - select("stripe", from: "Type") + select("stripe", from: "utility_utility_slug") fill_in("Name", with: "Test Stripe Account") click_button("Create") click_link("Edit stripe 'Test Stripe Account'") diff --git a/spec/furniture/marketplace/square_order_spec.rb b/spec/furniture/marketplace/square_order_spec.rb new file mode 100644 index 000000000..7ef8b34dc --- /dev/null +++ b/spec/furniture/marketplace/square_order_spec.rb @@ -0,0 +1,55 @@ +require "rails_helper" + +# test + +RSpec.describe Marketplace::SquareOrder do + subject(:square_order) { described_class.new(order: order) } + + let(:marketplace) { create(:marketplace) } + let(:order) { create(:marketplace_order, marketplace: marketplace) } + + let(:product_a) { create(:marketplace_product, marketplace: order.marketplace) } + let(:product_b) { create(:marketplace_product, marketplace: order.marketplace, tax_rate_ids: [sales_tax.id]) } + let(:sales_tax) { create(:marketplace_tax_rate, marketplace: order.marketplace, tax_rate: 5.0) } + + before do + order.ordered_products.create!(product: product_a, quantity: 1) + order.ordered_products.create!(product: product_b, quantity: 2) + end + + # describe "send_order" do + # it "will return the correct success data" do + # # TODO + # end + + # it "will noop when an error occurs" do + # # TODO + # end + # end + + # describe "create_square_order" do + # # TODO + # end + + describe "#prepare_square_create_order_payment_body" do + subject(:result) { square_order.send(:prepare_square_create_order_payment_body) } + + it { is_expected.to have_attributes(foo: "bar") } + end + + # describe "#prepare_square_create_order_body" do + # it "returns the correct object shape and data for a logged-in shopper" do + # # TODO + # end + + # it "returns the correct object shape and data for a guest shopper" do + # # TODO + # end + # end + + # describe "#square_idemp_key" do + # it "returns a unique string made up of order id and 8 random numbers between 1-10" do + # # TODO + # end + # end +end diff --git a/spec/furniture/marketplace/stripe_events_controller_request_spec.rb b/spec/furniture/marketplace/stripe_events_controller_request_spec.rb index ca9107390..8074821b6 100644 --- a/spec/furniture/marketplace/stripe_events_controller_request_spec.rb +++ b/spec/furniture/marketplace/stripe_events_controller_request_spec.rb @@ -3,13 +3,9 @@ # rubocop:disable RSpec/VerifiedDoubles RSpec.describe Marketplace::StripeEventsController, type: :request do let(:marketplace) { create(:marketplace, :with_stripe_utility, stripe_account: "sa_1234", stripe_webhook_endpoint_secret: "whsec_1234") } - let(:marketplace_with_square) { create(:marketplace, :with_stripe_utility, stripe_account: "sa_1234", stripe_webhook_endpoint_secret: "whsec_1234", square_location_id: "123456789", square_environment: "sandbox", secrets: {square_access_token: "fake_square_access_token"}) } let(:space) { marketplace.space } let(:member) { create(:membership, space: space).member } let(:order) { create(:marketplace_order, :with_products, status: :pre_checkout, marketplace: marketplace) } - let(:person) { create(:person) } - let(:shopper) { create(:marketplace_shopper, person: person) } - let(:order_for_square) { create(:marketplace_order, :with_products, status: :pre_checkout, marketplace: marketplace_with_square, shopper: shopper) } let(:stripe_event) do double(Stripe::Event, type: "checkout.session.completed", @@ -26,13 +22,7 @@ double(Stripe::Charge, balance_transaction: "btx_2234") } - let(:square_client) { - double(Square::Client) - } - - let(:square_orders_api) { - double(Square::OrdersApi) - } + let(:square_order) { instance_double(Marketplace::Marketplace::SquareOrder, send_order: nil) } before do allow(Stripe::Webhook).to receive(:construct_event).with(anything, "sig_1234", marketplace.stripe_webhook_endpoint_secret).and_return(stripe_event) @@ -41,9 +31,7 @@ allow(Stripe::Transfer).to receive(:create).and_return(double(Stripe::Transfer, id: "st_fake_1234")) allow(Stripe::BalanceTransaction).to receive(:retrieve).with("btx_2234", anything).and_return(balance_transaction) allow(Stripe::Charge).to receive(:retrieve).with("ch_1234", anything).and_return(charge) - allow(Square::Client).to receive(:new).with(access_token: anything, environment: anything).and_return(square_client) - allow(square_client).to receive(:orders).and_return(square_orders_api) - allow(square_orders_api).to receive(:create_order) + allow(Marketplace::Marketplace::SquareOrder).to receive(:new).and_return(square_order) end describe "#create" do @@ -98,19 +86,25 @@ end context "when Square notifications are not enabled" do - it "does not attempt to transfer the order to seller's Square dashboard" do - call - expect(square_client).not_to(have_received(:orders)) - end + # TODO + # it "does not attempt to transfer the order to seller's Square dashboard" do + # call + # expect(square_order).not_to(have_received(:send_order)) + # end end context "when Square notifications are enabled" do - let(:order) { order_for_square } - - it "attempts to transfer the order to seller's Square dashboard" do - call - expect(square_client).to have_received(:orders) - end + # TODO + # let(:marketplace) { create_marketplace_with_square } + # let(:person) { create(:person) } + # let(:shopper) { create(:marketplace_shopper, person: person) } + # let(:order) { create(:marketplace_order, :with_products, status: :pre_checkout, marketplace: marketplace, shopper: shopper) } + + # it "attempts to transfer the order to seller's Square dashboard" do + # call + # expect(Marketplace::Marketplace::SquareOrder).to have_received(:new).with(order) + # expect(square_order).to have_received(:send_order) + # end end end end diff --git a/spec/system/furniture_spec.rb b/spec/system/furniture_spec.rb index 4e9a05483..b19547961 100644 --- a/spec/system/furniture_spec.rb +++ b/spec/system/furniture_spec.rb @@ -15,7 +15,7 @@ def add_gizmo(type, room:) visit(polymorphic_path(room.location(:edit))) - select(type, from: "Type of gizmo") + select(type, from: "furniture_furniture_kind") click_button("Add Gizmo") end diff --git a/yarn.lock b/yarn.lock index f1498c934..6672a0722 100644 --- a/yarn.lock +++ b/yarn.lock @@ -554,72 +554,72 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@rails/actioncable@^7.0", "@rails/actioncable@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.1.1.tgz#e8c49769d41f35a4473133c259cc98adc04dddf8" - integrity sha512-ZRJ9rdwFQQjRbtgJnweY0/4UQyxN6ojEGRdib0JkjnuIciv+4ok/aAeZmBJqNreTMaBqS0eHyA9hCArwN58opg== +"@rails/actioncable@^7.0", "@rails/actioncable@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.1.2.tgz#d261ff4b72844f5af496671346ec478798f4ac2c" + integrity sha512-KGziTZfbmGm8/fHOpj515xupbYU+49hsp4etfdpoDJ/CEY2bRZR0cyFcJkpK6n0t/sxOHNWY6bo9vSgXZvT7Mg== -"@rails/activestorage@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@rails/activestorage/-/activestorage-7.1.1.tgz#3f12e8ac784f460f6a9d205744354abd79a525f2" - integrity sha512-QGBj+y4fbZt/QMMpjqnpKlzCKpDGTYrvJ+qc0QLis34AfbBLVgRo7kPzmdmeOTtwvWqpcivB9CrjTcV/C/7ruA== +"@rails/activestorage@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@rails/activestorage/-/activestorage-7.1.2.tgz#088dce680fa1e0a4f8e0c5ac91073f729204ed06" + integrity sha512-evC/xGlpq5XGpcNJina3oNVVB8pUp1GpnN3a84SVA+UNuB6O91OdNRl9BGHNAOo6/jxmFtLb73PIjWqQyVE14w== dependencies: spark-md5 "^3.0.1" -"@sentry-internal/tracing@7.77.0": - version "7.77.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.77.0.tgz#f3d82486f8934a955b3dd2aa54c8d29586e42a37" - integrity sha512-8HRF1rdqWwtINqGEdx8Iqs9UOP/n8E0vXUu3Nmbqj4p5sQPA7vvCfq+4Y4rTqZFc7sNdFpDsRION5iQEh8zfZw== - dependencies: - "@sentry/core" "7.77.0" - "@sentry/types" "7.77.0" - "@sentry/utils" "7.77.0" - -"@sentry/browser@^7.77.0": - version "7.77.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.77.0.tgz#155440f1a0d3a1bbd5d564c28d6b0c9853a51d72" - integrity sha512-nJ2KDZD90H8jcPx9BysQLiQW+w7k7kISCWeRjrEMJzjtge32dmHA8G4stlUTRIQugy5F+73cOayWShceFP7QJQ== - dependencies: - "@sentry-internal/tracing" "7.77.0" - "@sentry/core" "7.77.0" - "@sentry/replay" "7.77.0" - "@sentry/types" "7.77.0" - "@sentry/utils" "7.77.0" - -"@sentry/core@7.77.0": - version "7.77.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.77.0.tgz#21100843132beeeff42296c8370cdcc7aa1d8510" - integrity sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg== - dependencies: - "@sentry/types" "7.77.0" - "@sentry/utils" "7.77.0" - -"@sentry/replay@7.77.0": - version "7.77.0" - resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.77.0.tgz#21d242c9cd70a7235237216174873fd140b6eb80" - integrity sha512-M9Ik2J5ekl+C1Och3wzLRZVaRGK33BlnBwfwf3qKjgLDwfKW+1YkwDfTHbc2b74RowkJbOVNcp4m8ptlehlSaQ== - dependencies: - "@sentry-internal/tracing" "7.77.0" - "@sentry/core" "7.77.0" - "@sentry/types" "7.77.0" - "@sentry/utils" "7.77.0" - -"@sentry/types@7.77.0": - version "7.77.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.77.0.tgz#c5d00fe547b89ccde59cdea59143bf145cee3144" - integrity sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA== - -"@sentry/utils@7.77.0": - version "7.77.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.77.0.tgz#1f88501f0b8777de31b371cf859d13c82ebe1379" - integrity sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g== - dependencies: - "@sentry/types" "7.77.0" - -"@tailwindcss/forms@^0.5.6": - version "0.5.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.6.tgz#29c6c2b032b363e0c5110efed1499867f6d7e868" - integrity sha512-Fw+2BJ0tmAwK/w01tEFL5TiaJBX1NLT1/YbWgvm7ws3Qcn11kiXxzNTEQDMs5V3mQemhB56l3u0i9dwdzSQldA== +"@sentry-internal/tracing@7.79.0": + version "7.79.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.79.0.tgz#db99820e93e15bf4d990f1b270a1d1c2a69fd564" + integrity sha512-Mf9Bd0OrZ24h1qZpvmz9IRnfORMGYNYC1xWBBFpIR1AauEDX89x+mJwIOrUc4KKAAAwt73shrJv1QA8QOm4E3g== + dependencies: + "@sentry/core" "7.79.0" + "@sentry/types" "7.79.0" + "@sentry/utils" "7.79.0" + +"@sentry/browser@^7.79.0": + version "7.79.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.79.0.tgz#d05460161774642f37e4f53ee6006551aae49fed" + integrity sha512-gWbWEElF61uZeTFLIZz3NMyCkAzBDOpMAogEbVu2GX91SHKB7GXlE//INnS/R5wfE5j/CFaZc53mzzoIuMy1sA== + dependencies: + "@sentry-internal/tracing" "7.79.0" + "@sentry/core" "7.79.0" + "@sentry/replay" "7.79.0" + "@sentry/types" "7.79.0" + "@sentry/utils" "7.79.0" + +"@sentry/core@7.79.0": + version "7.79.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.79.0.tgz#08871bd686afd58125f43421d3dcb65a3b9208b0" + integrity sha512-9vG7SfOcJNJNiqlqg4MuHDUCaSf2ZXpv3eZYRPbBkgPGr8X1ekrSABpOK+6kBNvbtKxfWVTWbLpAA6xU+cwnVw== + dependencies: + "@sentry/types" "7.79.0" + "@sentry/utils" "7.79.0" + +"@sentry/replay@7.79.0": + version "7.79.0" + resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.79.0.tgz#53c658e5a51698bc32019be167427b8692e2a2b7" + integrity sha512-vF79NxWGYfoD0hnIkdgUQqedoMcRHHp5UAfZlxhpQzJf4TnbOjollp63AvOrfd38osSG2d3E5kTUU9xs/zKhBQ== + dependencies: + "@sentry-internal/tracing" "7.79.0" + "@sentry/core" "7.79.0" + "@sentry/types" "7.79.0" + "@sentry/utils" "7.79.0" + +"@sentry/types@7.79.0": + version "7.79.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.79.0.tgz#b47c53a3f8b9057aac820fe99e1154949aac934d" + integrity sha512-3tV32+v/DF8w7kD0p3kLWtgVTVdFL39oGY02+vz//rjWg/vzeqSE95mCYKm5pUfd6cPETX/8dunCiuTBQIkTHQ== + +"@sentry/utils@7.79.0": + version "7.79.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.79.0.tgz#c410b6c0e3032dbc9e708177555c70bdb8d1f63b" + integrity sha512-tUTlb6PvfZawqBmBK9CPXflqrZDXHKWoX3fve2zLK6W0FSpIMOO4TH8PBqkHBFs0ZgF/bnv/bsM4z7uEAlAtzg== + dependencies: + "@sentry/types" "7.79.0" + +"@tailwindcss/forms@^0.5.7": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.7.tgz#db5421f062a757b5f828bc9286ba626c6685e821" + integrity sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw== dependencies: mini-svg-data-uri "^1.2.3" @@ -1140,10 +1140,10 @@ axios-case-converter@^1.1.0: snake-case "^3.0.3" tslib "^2.3.0" -axios@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#f1e5292f26b2fd5c2e66876adc5b06cdbd7d2102" - integrity sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg== +axios@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.1.tgz#76550d644bf0a2d469a01f9244db6753208397d7" + integrity sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" @@ -3757,10 +3757,10 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selenium-webdriver@^4.14.0: - version "4.14.0" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.14.0.tgz#d39917cd7c1bb30f753c1f668158f37d1905fafc" - integrity sha512-637rs8anqMKHbWxcBZpyG3Gcs+rBUtAUiqk0O/knUqH4Paj3MFUZrz88/pVGOLNryEVy2z92fZomT8p1ENl1gA== +selenium-webdriver@^4.15.0: + version "4.15.0" + resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.15.0.tgz#ee827f8993864dc0821df0d3b46d4f312d6f1aa3" + integrity sha512-BNG1bq+KWiBGHcJ/wULi0eKY0yaDqFIbEmtbsYJmfaEghdCkXBsx1akgOorhNwjBipOr0uwpvNXqT6/nzl+zjg== dependencies: jszip "^3.10.1" tmp "^0.2.1"