workarea-flow_io 1.2.1

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.
Files changed (272) hide show
  1. checksums.yaml +7 -0
  2. data/.eslintignore +2 -0
  3. data/.eslintrc.json +37 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  5. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  6. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  7. data/.github/workflows/ci.yml +90 -0
  8. data/.gitignore +21 -0
  9. data/.rubocop.yml +2 -0
  10. data/.ruby-version +1 -0
  11. data/.stylelintrc.json +12 -0
  12. data/CHANGELOG.md +200 -0
  13. data/Gemfile +19 -0
  14. data/LICENSE +52 -0
  15. data/README.md +200 -0
  16. data/Rakefile +59 -0
  17. data/app/assets/javascripts/workarea/storefront/flow_io/adapters/flow_io_adapter.js +61 -0
  18. data/app/assets/javascripts/workarea/storefront/flow_io/configuration.js.erb +10 -0
  19. data/app/assets/javascripts/workarea/storefront/flow_io/flow.js +91 -0
  20. data/app/assets/stylesheets/flow_io/storefront/country_picker.scss +189 -0
  21. data/app/controllers/workarea/admin/application_controller.decorator +5 -0
  22. data/app/controllers/workarea/admin/catalog_categories_controller.decorator +9 -0
  23. data/app/controllers/workarea/admin/flow_imports_controller.rb +9 -0
  24. data/app/controllers/workarea/admin/orders_controller.decorator +6 -0
  25. data/app/controllers/workarea/admin/pricing_skus_controller.decorator +7 -0
  26. data/app/controllers/workarea/admin/search_settings_controller.decorator +27 -0
  27. data/app/controllers/workarea/storefront/application_controller.decorator +80 -0
  28. data/app/controllers/workarea/storefront/checkouts_controller.decorator +38 -0
  29. data/app/controllers/workarea/storefront/flow_io_webhook_controller.rb +49 -0
  30. data/app/controllers/workarea/storefront/recent_views_controller.decorator +15 -0
  31. data/app/controllers/workarea/storefront/searches_controller.decorator +11 -0
  32. data/app/helpers/workarea/admin/flow_helper.rb +13 -0
  33. data/app/helpers/workarea/storefront/flow_analytics_helper.rb +23 -0
  34. data/app/helpers/workarea/storefront/flow_content_helper.rb +21 -0
  35. data/app/middleware/workarea/flow_io/session_middleware.rb +68 -0
  36. data/app/models/workarea/address.decorator +12 -0
  37. data/app/models/workarea/catalog/category.decorator +8 -0
  38. data/app/models/workarea/checkout.decorator +26 -0
  39. data/app/models/workarea/flow_io/experience_geo.rb +18 -0
  40. data/app/models/workarea/flow_io/experience_summary.rb +15 -0
  41. data/app/models/workarea/flow_io/import.rb +31 -0
  42. data/app/models/workarea/flow_io/imported_item.rb +124 -0
  43. data/app/models/workarea/flow_io/local_item.rb +28 -0
  44. data/app/models/workarea/flow_io/localized_price.rb +30 -0
  45. data/app/models/workarea/flow_io/price.rb +12 -0
  46. data/app/models/workarea/flow_io/price_with_base.rb +7 -0
  47. data/app/models/workarea/flow_io/webhook/shared_secret.rb +12 -0
  48. data/app/models/workarea/flow_price_adjustment_set.rb +80 -0
  49. data/app/models/workarea/fulfillment.decorator +63 -0
  50. data/app/models/workarea/order.decorator +39 -0
  51. data/app/models/workarea/order/item.decorator +43 -0
  52. data/app/models/workarea/payment.decorator +11 -0
  53. data/app/models/workarea/payment/authorize/flow_payment.rb +48 -0
  54. data/app/models/workarea/payment/capture/flow_payment.rb +35 -0
  55. data/app/models/workarea/payment/flow_payment_data.rb +25 -0
  56. data/app/models/workarea/payment/flow_payment_operation.rb +20 -0
  57. data/app/models/workarea/payment/purchase/flow_payment.rb +44 -0
  58. data/app/models/workarea/payment/refund/flow_payment.rb +42 -0
  59. data/app/models/workarea/payment/store_flow_credit_card.rb +34 -0
  60. data/app/models/workarea/payment/tender.decorator +8 -0
  61. data/app/models/workarea/payment/tender/flow_payment.rb +15 -0
  62. data/app/models/workarea/payment/transaction.decorator +12 -0
  63. data/app/models/workarea/pricing/calculators/flow_localization_calculator.rb +30 -0
  64. data/app/models/workarea/pricing/calculators/item_calculator.decorator +39 -0
  65. data/app/models/workarea/pricing/collection.decorator +113 -0
  66. data/app/models/workarea/pricing/order_totals.decorator +53 -0
  67. data/app/models/workarea/pricing/price.decorator +17 -0
  68. data/app/models/workarea/pricing/price_distributor.decorator +51 -0
  69. data/app/models/workarea/pricing/request.decorator +17 -0
  70. data/app/models/workarea/pricing/shipping_totals.decorator +24 -0
  71. data/app/models/workarea/pricing/sku.decorator +46 -0
  72. data/app/models/workarea/search/settings.decorator +8 -0
  73. data/app/models/workarea/search/storefront/product.decorator +25 -0
  74. data/app/models/workarea/shipping.decorator +28 -0
  75. data/app/queries/workarea/order_metrics.decorator +23 -0
  76. data/app/queries/workarea/search/product_search.decorator +14 -0
  77. data/app/queries/workarea/search/storefront_search/response.decorator +40 -0
  78. data/app/services/workarea/flow_io/checkout.rb +242 -0
  79. data/app/services/workarea/flow_io/checkout_token_form.rb +36 -0
  80. data/app/services/workarea/flow_io/countries.rb +14 -0
  81. data/app/services/workarea/flow_io/detailed_shipping_notification_form.rb +84 -0
  82. data/app/services/workarea/flow_io/experiences.rb +24 -0
  83. data/app/services/workarea/flow_io/fulfillment_cancellation_form.rb +40 -0
  84. data/app/services/workarea/flow_io/item.rb +122 -0
  85. data/app/services/workarea/flow_io/item_importer.rb +108 -0
  86. data/app/services/workarea/flow_io/line_item_form.rb +72 -0
  87. data/app/services/workarea/flow_io/order_put_form.rb +62 -0
  88. data/app/services/workarea/flow_io/price_applier.rb +81 -0
  89. data/app/services/workarea/flow_io/price_applier/item_applier.rb +157 -0
  90. data/app/services/workarea/flow_io/routing_contraints.rb +10 -0
  91. data/app/services/workarea/flow_io/session.rb +90 -0
  92. data/app/services/workarea/flow_io/webhook.rb +28 -0
  93. data/app/services/workarea/flow_io/webhook/experience_deleted_v2.rb +9 -0
  94. data/app/services/workarea/flow_io/webhook/experience_upserted_v2.rb +9 -0
  95. data/app/services/workarea/flow_io/webhook/order_upserted_v2.rb +36 -0
  96. data/app/services/workarea/flow_io/webhook_request_signature.rb +19 -0
  97. data/app/view_models/workarea/admin/flow_imports_view_model.rb +36 -0
  98. data/app/view_models/workarea/storefront/cart_view_model.decorator +27 -0
  99. data/app/view_models/workarea/storefront/content_blocks/category_summary_view_model.decorator +11 -0
  100. data/app/view_models/workarea/storefront/content_blocks/product_insights_view_model.decorator +11 -0
  101. data/app/view_models/workarea/storefront/content_blocks/product_list_view_model.decorator +13 -0
  102. data/app/view_models/workarea/storefront/order_item_view_model.decorator +35 -0
  103. data/app/view_models/workarea/storefront/order_view_model.decorator +51 -0
  104. data/app/view_models/workarea/storefront/product_templates/option_set_view_model.decorator +20 -0
  105. data/app/view_models/workarea/storefront/product_templates/package_view_model.decorator +14 -0
  106. data/app/view_models/workarea/storefront/product_view_model.decorator +15 -0
  107. data/app/view_models/workarea/storefront/product_view_model/cache_key.decorator +7 -0
  108. data/app/view_models/workarea/storefront/recommendations_view_model.decorator +20 -0
  109. data/app/view_models/workarea/storefront/user_activity_view_model.decorator +18 -0
  110. data/app/views/workarea/admin/catalog_categories/edit.html.haml +85 -0
  111. data/app/views/workarea/admin/facets/_price_inputs.html.haml +50 -0
  112. data/app/views/workarea/admin/flow_imports/index.html.haml +44 -0
  113. data/app/views/workarea/admin/orders/_flow.html.haml +22 -0
  114. data/app/views/workarea/admin/orders/_flow_aux_navigation.html.haml +2 -0
  115. data/app/views/workarea/admin/orders/flow.html.haml +109 -0
  116. data/app/views/workarea/admin/orders/tenders/_flow_payment.html.haml +3 -0
  117. data/app/views/workarea/admin/pricing_skus/_cards.html.haml +83 -0
  118. data/app/views/workarea/admin/pricing_skus/flow.html.haml +60 -0
  119. data/app/views/workarea/admin/search_settings/show.html.haml +55 -0
  120. data/app/views/workarea/admin/shared/_flow_imports_link.html.haml +1 -0
  121. data/app/views/workarea/storefront/_flow_io_country_picker.html.haml +2 -0
  122. data/app/views/workarea/storefront/_flow_io_country_picker_javascript.html.haml +1 -0
  123. data/app/views/workarea/storefront/order_mailer/tenders/_flow_payment.html.haml +2 -0
  124. data/app/views/workarea/storefront/order_mailer/tenders/_flow_payment.text.erb +1 -0
  125. data/app/views/workarea/storefront/orders/tenders/_flow_payment.html.haml +3 -0
  126. data/app/views/workarea/storefront/products/_pricing.html.haml +41 -0
  127. data/app/workers/workarea/flow_io/delete_items.rb +30 -0
  128. data/app/workers/workarea/flow_io/fetch_import.rb +34 -0
  129. data/app/workers/workarea/flow_io/fulfillment_cancellation.rb +14 -0
  130. data/app/workers/workarea/flow_io/item_exporter.rb +47 -0
  131. data/app/workers/workarea/flow_io/process_import.rb +28 -0
  132. data/app/workers/workarea/flow_io/shipping_notifications.rb +13 -0
  133. data/bin/rails +25 -0
  134. data/config/initializers/appends.rb +39 -0
  135. data/config/initializers/freedom_patches.rb +23 -0
  136. data/config/initializers/middleware.rb +11 -0
  137. data/config/initializers/payment_proxy.rb +5 -0
  138. data/config/initializers/rack_attack.rb +15 -0
  139. data/config/initializers/scheduled_jobs.rb +9 -0
  140. data/config/initializers/workarea.rb +101 -0
  141. data/config/initializers/workarea_i18n.rb +11 -0
  142. data/config/locales/en.yml +72 -0
  143. data/config/routes.rb +21 -0
  144. data/lib/active_merchant/billing/bogus_flow_gateway.rb +16 -0
  145. data/lib/active_merchant/billing/flow_gateway.rb +48 -0
  146. data/lib/tasks/flow_io.rake +117 -0
  147. data/lib/workarea/flow_io.rb +73 -0
  148. data/lib/workarea/flow_io/bogus_client.rb +57 -0
  149. data/lib/workarea/flow_io/bogus_client/checkout_tokens.rb +25 -0
  150. data/lib/workarea/flow_io/bogus_client/experiences.rb +152 -0
  151. data/lib/workarea/flow_io/bogus_client/fulfillments.rb +9 -0
  152. data/lib/workarea/flow_io/bogus_client/items.rb +11 -0
  153. data/lib/workarea/flow_io/bogus_client/orders.rb +499 -0
  154. data/lib/workarea/flow_io/bogus_client/organizations.rb +42 -0
  155. data/lib/workarea/flow_io/bogus_client/proxy_client.rb +29 -0
  156. data/lib/workarea/flow_io/bogus_client/sessions.rb +115 -0
  157. data/lib/workarea/flow_io/bogus_client/shipping_notifications.rb +10 -0
  158. data/lib/workarea/flow_io/bogus_client/webhook_settings.rb +11 -0
  159. data/lib/workarea/flow_io/bogus_client/webhooks.rb +11 -0
  160. data/lib/workarea/flow_io/controller_log_subscriber.rb +21 -0
  161. data/lib/workarea/flow_io/engine.rb +8 -0
  162. data/lib/workarea/flow_io/http_handler.rb +27 -0
  163. data/lib/workarea/flow_io/version.rb +5 -0
  164. data/lib/workarea/freedom_patches/flow_io.rb +53 -0
  165. data/package.json +9 -0
  166. data/script/admin_ci +9 -0
  167. data/script/ci +11 -0
  168. data/script/core_ci +9 -0
  169. data/script/plugins_ci +9 -0
  170. data/script/storefront_ci +9 -0
  171. data/test/dummy/.ruby-version +1 -0
  172. data/test/dummy/Rakefile +6 -0
  173. data/test/dummy/bin/bundle +3 -0
  174. data/test/dummy/bin/rails +4 -0
  175. data/test/dummy/bin/rake +4 -0
  176. data/test/dummy/bin/setup +28 -0
  177. data/test/dummy/bin/update +28 -0
  178. data/test/dummy/bin/yarn +11 -0
  179. data/test/dummy/config.ru +5 -0
  180. data/test/dummy/config/application.rb +32 -0
  181. data/test/dummy/config/boot.rb +5 -0
  182. data/test/dummy/config/cable.yml +10 -0
  183. data/test/dummy/config/environment.rb +5 -0
  184. data/test/dummy/config/environments/development.rb +52 -0
  185. data/test/dummy/config/environments/production.rb +88 -0
  186. data/test/dummy/config/environments/test.rb +45 -0
  187. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  188. data/test/dummy/config/initializers/assets.rb +14 -0
  189. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  190. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  191. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  192. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  193. data/test/dummy/config/initializers/inflections.rb +16 -0
  194. data/test/dummy/config/initializers/mime_types.rb +4 -0
  195. data/test/dummy/config/initializers/workarea.rb +5 -0
  196. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  197. data/test/dummy/config/locales/en.yml +33 -0
  198. data/test/dummy/config/puma.rb +34 -0
  199. data/test/dummy/config/routes.rb +5 -0
  200. data/test/dummy/config/secrets.yml +22 -0
  201. data/test/dummy/config/spring.rb +6 -0
  202. data/test/dummy/db/seeds.rb +2 -0
  203. data/test/dummy/log/.keep +0 -0
  204. data/test/dummy/package.json +5 -0
  205. data/test/factories/workarea/flow_io.rb +136 -0
  206. data/test/factories/workarea/flow_io/euro_order_upserted_payload.rb +581 -0
  207. data/test/fixtures/files/example.csv +14 -0
  208. data/test/integration/workarea/admin/flow_imports_integration_test.rb +20 -0
  209. data/test/integration/workarea/admin/flow_order_integration_test.rb +37 -0
  210. data/test/integration/workarea/admin/search_settings_integration_test.decorator +44 -0
  211. data/test/integration/workarea/flow_locale_integration_test.rb +34 -0
  212. data/test/integration/workarea/storefront/copying_guest_order_integration_test.decorator +113 -0
  213. data/test/integration/workarea/storefront/flow_io_order_redirect_test.rb +55 -0
  214. data/test/integration/workarea/storefront/flow_io_session_integration_test.rb +122 -0
  215. data/test/integration/workarea/storefront/flow_io_webhooks/order_integration_test.rb +750 -0
  216. data/test/integration/workarea/storefront/flow_webhook_rack_attack_integration_test.rb +54 -0
  217. data/test/integration/workarea/storefront/search_integration_test.decorator +51 -0
  218. data/test/javascripts/flow_spec.js +39 -0
  219. data/test/javascripts/spec_helper.js +3 -0
  220. data/test/models/workarea/address_test.decorator +20 -0
  221. data/test/models/workarea/checkout_test.decorator +20 -0
  222. data/test/models/workarea/flow_io/imported_item_test.rb +50 -0
  223. data/test/models/workarea/fulfillment_test.decorator +12 -0
  224. data/test/models/workarea/oms_fulfillment_test.decorator +14 -0
  225. data/test/models/workarea/order/item_test.decorator +46 -0
  226. data/test/models/workarea/order_test.decorator +16 -0
  227. data/test/models/workarea/payment/flow_payment_integration_test.rb +163 -0
  228. data/test/models/workarea/price_adjustment_set_flow_test.rb +8 -0
  229. data/test/models/workarea/pricing/calculators/flow_localization_calculator_test.rb +152 -0
  230. data/test/models/workarea/pricing/calculators/item_calculator_test.decorator +59 -0
  231. data/test/models/workarea/pricing/collection_test.decorator +140 -0
  232. data/test/models/workarea/pricing/order_totals_test.rb +247 -0
  233. data/test/models/workarea/pricing/price_distributor_test.decorator +21 -0
  234. data/test/models/workarea/pricing/sku_test.decorator +113 -0
  235. data/test/models/workarea/shipping_test.decorator +70 -0
  236. data/test/queries/workarea/flow_order_metrics_test.rb +104 -0
  237. data/test/services/workarea/commit_shipments_test.decorator +14 -0
  238. data/test/services/workarea/flow_io/checkout_test.rb +43 -0
  239. data/test/services/workarea/flow_io/detailed_shipping_notification_form_test.rb +145 -0
  240. data/test/services/workarea/flow_io/experiences_test.rb +14 -0
  241. data/test/services/workarea/flow_io/fulfillment_cancellation_form_test.rb +46 -0
  242. data/test/services/workarea/flow_io/item_importer_test.rb +357 -0
  243. data/test/services/workarea/flow_io/item_test.rb +72 -0
  244. data/test/services/workarea/flow_io/line_item_form_test.rb +34 -0
  245. data/test/services/workarea/flow_io/order_put_form_test.rb +48 -0
  246. data/test/services/workarea/flow_io/price_applier_test.rb +916 -0
  247. data/test/support/flow_fixtures.rb +1362 -0
  248. data/test/support/webhook_integration_test.rb +43 -0
  249. data/test/support/workarea/flow_bogus_client_support.rb +18 -0
  250. data/test/support/workarea/flow_io_vcr_config.rb +22 -0
  251. data/test/system/workarea/admin/search_fulfillment_system_test.decorator +25 -0
  252. data/test/system/workarea/storefront/analytics_system_test.decorator +28 -0
  253. data/test/system/workarea/storefront/flow_cart_recommendations_system_test.rb +70 -0
  254. data/test/system/workarea/storefront/users_flow_orders_system_test.rb +25 -0
  255. data/test/teaspoon_env.rb +6 -0
  256. data/test/test_helper.rb +27 -0
  257. data/test/vcr_cassettes/payment/flow/auth_capture.yml +188 -0
  258. data/test/vcr_cassettes/payment/flow/auth_capture_refund.yml +249 -0
  259. data/test/vcr_cassettes/payment/flow/auth_void.yml +188 -0
  260. data/test/vcr_cassettes/payment/flow/purchase_refund.yml +249 -0
  261. data/test/vcr_cassettes/payment/flow/store_auth.yml +127 -0
  262. data/test/vcr_cassettes/payment/flow/store_purchase.yml +188 -0
  263. data/test/view_models/workarea/storefront/cart_view_model_test.decorator +19 -0
  264. data/test/view_models/workarea/storefront/order_item_view_model_test.decorator +24 -0
  265. data/test/view_models/workarea/storefront/order_view_model_test.decorator +41 -0
  266. data/test/view_models/workarea/storefront/package_view_model_test.decorator +11 -0
  267. data/test/workers/workarea/flow_io/delete_items_test.rb +37 -0
  268. data/test/workers/workarea/flow_io/fetch_import_test.rb +41 -0
  269. data/test/workers/workarea/flow_io/item_exporter_test.rb +47 -0
  270. data/test/workers/workarea/flow_io/process_import_test.rb +25 -0
  271. data/workarea-flow_io.gemspec +20 -0
  272. metadata +375 -0
@@ -0,0 +1,62 @@
1
+ module Workarea
2
+ module FlowIo
3
+ class OrderPutForm
4
+ # @param order [Workarea::Order]
5
+ # @param shippings [Array<Workarea::Shipping>, nil]
6
+ # @param discounts [Array<Workarea::Pricing::Discount>, nil]
7
+ #
8
+ def self.from(order:, shippings: nil, discounts: nil)
9
+ new(order: order, shippings: shippings).to_flow_model
10
+ end
11
+
12
+ attr_reader :order, :shippings, :discounts
13
+
14
+ # @param order [Workarea::Order]
15
+ # @param shippings [Array<Workarea::Shipping>, nil]
16
+ # @param discounts [Array<Workarea::Pricing::Discount>, nil]
17
+ #
18
+ def initialize(order:, shippings: nil, discounts: nil)
19
+ @order = order
20
+ @shippings = shippings || []
21
+ @discounts = discounts || []
22
+ end
23
+
24
+ # @return [::Io::Flow::V0::Models::OrderPutForm]
25
+ #
26
+ def to_flow_model
27
+ ::Io::Flow::V0::Models::OrderPutForm.new(
28
+ {
29
+ attributes: { number: order.id },
30
+ customer: customer,
31
+ items: items
32
+ }
33
+ )
34
+ end
35
+
36
+ private
37
+
38
+ def customer
39
+ return nil unless user.present?
40
+
41
+ {
42
+ email: user.email,
43
+ name: {
44
+ first: user.first_name,
45
+ last: user.last_name
46
+ },
47
+ phone: user.default_shipping_address&.phone_number
48
+ }
49
+ end
50
+
51
+ def user
52
+ return unless order.user_id.present?
53
+
54
+ @user ||= Workarea::User.find(order.user_id) rescue nil
55
+ end
56
+
57
+ def items
58
+ order.items.map { |item| FlowIo::LineItemForm.from(order_item: item, discounts: discounts) }
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,81 @@
1
+ module Workarea
2
+ module FlowIo
3
+ class PriceApplier
4
+ def self.perform(order:, flow_order:, shipping: nil)
5
+ new(order: order, flow_order: flow_order, shipping: shipping).perform!
6
+ end
7
+
8
+ attr_reader :order, :flow_order, :shipping
9
+
10
+ # @param order [Workarea::Order]
11
+ # @param flow_order [::Io::Flow::V0::Models::Order]
12
+ # @param shipping [Array<Workarea::Shipping>]
13
+ #
14
+ def initialize(order:, flow_order:, shipping: nil)
15
+ @order = order
16
+ @flow_order = flow_order
17
+ @shipping = shipping
18
+ end
19
+
20
+ def perform!
21
+ adjust_items
22
+ adjust_shipping
23
+ end
24
+
25
+ private
26
+
27
+ # @return [Array<Workarea::Pricing::Discount>]
28
+ #
29
+ def discounts
30
+ @discounts ||= Workarea::Pricing::Discount
31
+ .where(:_id.in => order.discount_ids)
32
+ .to_a
33
+ end
34
+
35
+ def adjust_shipping
36
+ return unless shipping.present? && order.requires_shipping?
37
+
38
+ # Prices are of types: adjustment, subtotal, vat, duty, shipping,
39
+ # insurance, discount. Filter out subtotal and discount prices
40
+ flow_prices = flow_order.prices.reject do |order_price_detail|
41
+ ["subtotal", "discount"].include? order_price_detail.key.value
42
+ end
43
+
44
+ flow_prices.each do |price|
45
+ price_slug = price.name == "Shipping" ? "shipping" : "tax"
46
+ shipping.adjust_pricing(
47
+ price: price_slug,
48
+ description: price.name,
49
+ calculator: self.class.name,
50
+ amount: price.base.to_m,
51
+ data: { "order_price_detail" => price.to_hash }
52
+ )
53
+
54
+ # build price adjustments in the currency the user checked out in.
55
+ shipping.adjust_flow_pricing(
56
+ price: price_slug,
57
+ description: price.name,
58
+ calculator: self.class.name,
59
+ amount: price.to_m
60
+ )
61
+ end
62
+ end
63
+
64
+ # update each Order::Item pricing with new prices from Flow.io
65
+ #
66
+ def adjust_items
67
+ order.items.each do |order_item|
68
+ flow_item = flow_order.items.detect do |localized_line_item|
69
+ localized_line_item.number == order_item.sku
70
+ end
71
+
72
+ ItemApplier.perform(
73
+ order_item: order_item,
74
+ localized_line_item: flow_item,
75
+ discounts: discounts
76
+ )
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,157 @@
1
+ module Workarea
2
+ module FlowIo
3
+ # Updates a `Wokrarea::Order::Item` pricing with information from a
4
+ # `::Io::Flow::V0::Models::LocalizedLineItem`.
5
+ #
6
+ class PriceApplier::ItemApplier
7
+ def self.perform(order_item:, localized_line_item:, discounts:)
8
+ new(
9
+ order_item: order_item,
10
+ localized_line_item: localized_line_item,
11
+ discounts: discounts
12
+ ).perform!
13
+ end
14
+
15
+ attr_reader :order_item,
16
+ :localized_line_item,
17
+ :discounts,
18
+ :item_calculator_data,
19
+ :flow_item_calculator_data
20
+
21
+ # @param order_item [::Worakrea::Order::Item]
22
+ # @param localized_line_item [::Io::Flow::V0::Models::LocalizedLineItem]
23
+ #
24
+ def initialize(order_item:, localized_line_item:, discounts:)
25
+ @order_item = order_item
26
+ @localized_line_item = localized_line_item
27
+ @discounts = discounts
28
+ @item_calculator_data = order_item.price_adjustments&.first&.data || {}
29
+ @flow_item_calculator_data = order_item.flow_price_adjustments&.first&.data || {}
30
+ end
31
+
32
+ def perform!
33
+ order_item.reset_price_adjustments
34
+ order_item.reset_flow_price_adjustments
35
+
36
+ add_base_adjustments
37
+ add_line_item_discounts
38
+ add_unit_price_rounding
39
+ end
40
+
41
+ private
42
+
43
+ def add_base_adjustments
44
+ order_item.adjust_pricing(
45
+ item_adjustment_data.merge(
46
+ quantity: order_item.quantity,
47
+ amount: localized_item_price.base.to_m * order_item.quantity,
48
+ description: 'Item Subtotal',
49
+ data: item_calculator_data.merge(
50
+ "localized_item_price" => localized_item_price.to_hash,
51
+ "description" => 'Flow Localized Item Price Base'
52
+ ).deep_stringify_keys
53
+ )
54
+ )
55
+
56
+ order_item.adjust_flow_pricing(
57
+ item_adjustment_data.merge(
58
+ quantity: order_item.quantity,
59
+ amount: localized_item_price.to_m * order_item.quantity,
60
+ description: 'Item Subtotal',
61
+ data: flow_item_calculator_data.merge(
62
+ "description" => 'Flow Localized Item Price'
63
+ ).deep_stringify_keys
64
+ )
65
+ )
66
+ end
67
+
68
+ def add_line_item_discounts
69
+ return unless localized_line_item.discounts.present?
70
+
71
+ localized_line_item.discounts.each do |localized_line_item_discount|
72
+ discount = discounts.detect do |pricing_discount|
73
+ pricing_discount.name == localized_line_item_discount.discount_label
74
+ end
75
+
76
+ price_level = discount&.class&.price_level || "item"
77
+
78
+ order_item.adjust_pricing(
79
+ item_adjustment_data.merge(
80
+ quantity: order_item.quantity,
81
+ price: price_level,
82
+ amount: localized_line_item_discount.base.to_m,
83
+ description: localized_line_item_discount.discount_label,
84
+ data: {
85
+ "localized_line_item_discount" => localized_line_item_discount.to_hash,
86
+ "discount_id" => discount&.id&.to_s,
87
+ "description" => localized_line_item_discount.discount_label,
88
+ "discount_amount" => localized_line_item_discount.base.to_m.abs
89
+ }.deep_stringify_keys.compact
90
+ )
91
+ )
92
+
93
+ order_item.adjust_flow_pricing(
94
+ item_adjustment_data.merge(
95
+ quantity: order_item.quantity,
96
+ price: price_level,
97
+ amount: localized_line_item_discount.to_m,
98
+ description: localized_line_item_discount.discount_label,
99
+ data: {
100
+ "description" => localized_line_item_discount.discount_label,
101
+ "discount_amount" => localized_line_item_discount.to_m.abs
102
+ }
103
+ )
104
+ )
105
+ end
106
+ end
107
+
108
+ # flow will add or subtract money to make the line item price
109
+ # have an even unit price we do the same thing and roll it into the discount
110
+ # adjustment
111
+ def add_unit_price_rounding
112
+ return unless needs_unti_price_rounding?
113
+
114
+ value = unit_quantity_price - order_item_total_value
115
+
116
+ item_adjustment_data.merge(
117
+ quantity: order_item.quantity,
118
+ amount: value,
119
+ description: "Discount",
120
+ data: {
121
+ description: 'Flow Localized Line Item Discount',
122
+ discount_amount: value.abs
123
+ }
124
+ )
125
+ end
126
+
127
+ def needs_unti_price_rounding?
128
+ unit_quantity_price != order_item_total_value
129
+ end
130
+
131
+ def unit_quantity_price
132
+ order_item.current_flow_unit_price * order_item.quantity
133
+ end
134
+
135
+ def order_item_total_value
136
+ order_item
137
+ .flow_price_adjustments
138
+ .adjusting("item")
139
+ .sum
140
+ end
141
+
142
+ def localized_item_price
143
+ @localized_item_price ||= localized_line_item
144
+ .local
145
+ .prices
146
+ .detect { |localized_price| localized_price.key == "localized_item_price" }
147
+ end
148
+
149
+ def item_adjustment_data
150
+ {
151
+ price: 'item',
152
+ calculator: self.class.name
153
+ }
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,10 @@
1
+ module Workarea
2
+ module FlowIo
3
+ class RoutingContraints
4
+ def matches?(request)
5
+ request.params[:locale].blank? ||
6
+ FlowIo::Countries.all.any? { |c| c.iso_3166_3.downcase == request.params[:locale] }
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,90 @@
1
+ module Workarea
2
+ module FlowIo
3
+ class Session
4
+ attr_reader :request
5
+
6
+ def initialize(env)
7
+ @request = ActionDispatch::Request.new(env)
8
+ @set_f60_session_cookie = false
9
+ end
10
+
11
+ def experience
12
+ if cookies.key?("flow_experience")
13
+ experience_from_cookies
14
+ else
15
+ session.experience
16
+ end
17
+ end
18
+
19
+ def flow_country
20
+ if cookies.key?("flow_country")
21
+ cookies["flow_country"]
22
+ else
23
+ session&.geo&.country&.iso_3166_3&.downcase
24
+ end
25
+ end
26
+
27
+ def id
28
+ session.id
29
+ end
30
+
31
+ def session
32
+ @session ||= session_from_cookies || create_session!
33
+ end
34
+
35
+ def set_f60_session_cookie?
36
+ @set_f60_session_cookie || !cookies.key?('_f60_session')
37
+ end
38
+
39
+ def set_flow_experience_cookie?
40
+ !cookies.key?('flow_experience')
41
+ end
42
+
43
+ def set_flow_country_cookie?
44
+ !cookies.key?('flow_country')
45
+ end
46
+
47
+ private
48
+
49
+ delegate :params, to: :request
50
+
51
+ # gets session id from cookies and pulls it from Flow API
52
+ # if the request 404s creates a new session
53
+ def session_from_cookies
54
+ return unless cookies['_f60_session'].present?
55
+
56
+ FlowIo.client.sessions.get_by_session(cookies['_f60_session'])
57
+ rescue ::Io::Flow::V0::HttpClient::ServerError => error
58
+ if error.code == 404
59
+ @set_f60_session_cookie = true
60
+ return create_session!
61
+ else
62
+ raise error
63
+ end
64
+ end
65
+
66
+ def create_session!
67
+ Workarea::FlowIo.client.sessions.post_organizations_by_organization(
68
+ Workarea::FlowIo.organization_id,
69
+ { ip: request.remote_ip, country: country }.compact
70
+ )
71
+ end
72
+
73
+ def experience_from_cookies
74
+ return unless cookies["flow_experience"].present?
75
+
76
+ ::Io::Flow::V0::Models::ExperienceGeo.new(JSON.parse(cookies[:flow_experience]))
77
+ rescue
78
+ nil
79
+ end
80
+
81
+ def country
82
+ request.env['HTTP_GEOIP_CITY_COUNTRY_CODE3']
83
+ end
84
+
85
+ def cookies
86
+ request.cookie_jar
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,28 @@
1
+ module Workarea
2
+ module FlowIo
3
+ class Webhook
4
+ class Error < RuntimeError; end
5
+ class Error::NotFound < RuntimeError; end
6
+ class Error::UnhandledWebhook < RuntimeError; end
7
+
8
+ # @param ::Io::Flow::V0::Models::Event
9
+ def self.process(event)
10
+ begin
11
+ klass = "Workarea::FlowIo::Webhook::#{event.discriminator.classify}".constantize
12
+ rescue NameError => _error
13
+ raise Error::UnhandledWebhook, "no class defined to handle #{event.discriminator.classify}"
14
+ end
15
+
16
+ klass.new(event).process
17
+ end
18
+
19
+ attr_reader :event
20
+
21
+ def initialize(event)
22
+ @event = event
23
+ end
24
+
25
+ def process; end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ module Workarea
2
+ module FlowIo
3
+ class Webhook::ExperienceDeletedV2 < Webhook
4
+ def process
5
+ FlowIo::Experiences.refresh_cache
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Workarea
2
+ module FlowIo
3
+ class Webhook::ExperienceUpsertedV2 < Webhook
4
+ def process
5
+ FlowIo::Experiences.refresh_cache
6
+ end
7
+ end
8
+ end
9
+ end