workarea-global_e 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (232) hide show
  1. checksums.yaml +7 -0
  2. data/.eslintrc.json +37 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  4. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.github/workflows/ci.yml +77 -0
  7. data/.gitignore +20 -0
  8. data/.rubocop.yml +2 -0
  9. data/.ruby-version +1 -0
  10. data/CHANGELOG.md +261 -0
  11. data/Gemfile +16 -0
  12. data/README.md +39 -0
  13. data/Rakefile +59 -0
  14. data/app/assets/javascripts/workarea/storefront/global_e/modules/checkout_info_validator.js +122 -0
  15. data/app/assets/javascripts/workarea/storefront/global_e/modules/suppress_price_ranges.js +19 -0
  16. data/app/controllers/workarea/admin/country_exceptions_controller.rb +67 -0
  17. data/app/controllers/workarea/admin/fixed_prices_controller.rb +89 -0
  18. data/app/controllers/workarea/admin/orders_controller.decorator +6 -0
  19. data/app/controllers/workarea/storefront/application_controller.decorator +30 -0
  20. data/app/controllers/workarea/storefront/checkouts_controller.decorator +22 -0
  21. data/app/controllers/workarea/storefront/ge_checkouts_controller.rb +10 -0
  22. data/app/controllers/workarea/storefront/global_e_controller.rb +42 -0
  23. data/app/controllers/workarea/storefront/globale/api_controller.rb +108 -0
  24. data/app/controllers/workarea/storefront/globale/refund_controller.rb +65 -0
  25. data/app/controllers/workarea/storefront/users/logins_controller.decorator +13 -0
  26. data/app/helpers/workarea/admin/global_e_helpers.rb +28 -0
  27. data/app/models/workarea/address.decorator +11 -0
  28. data/app/models/workarea/catalog/product.decorator +14 -0
  29. data/app/models/workarea/fulfillment/status/partially_refunded.rb +13 -0
  30. data/app/models/workarea/fulfillment/status/refunded.rb +15 -0
  31. data/app/models/workarea/fulfillment.decorator +55 -0
  32. data/app/models/workarea/global_e/country_exception.rb +37 -0
  33. data/app/models/workarea/global_e/fixed_price.rb +93 -0
  34. data/app/models/workarea/global_e/order_api_events.rb +29 -0
  35. data/app/models/workarea/order/item.decorator +46 -0
  36. data/app/models/workarea/order/status/pending_global_e_fraud_check.rb +12 -0
  37. data/app/models/workarea/order.decorator +64 -0
  38. data/app/models/workarea/payment/status/global_e_appoved.rb +9 -0
  39. data/app/models/workarea/payment/status/pending_global_e_fraud_check.rb +9 -0
  40. data/app/models/workarea/payment/tender/global_e_payment.rb +16 -0
  41. data/app/models/workarea/payment.decorator +10 -0
  42. data/app/models/workarea/price_adjustment.decorator +13 -0
  43. data/app/models/workarea/price_adjustment_set.decorator +25 -0
  44. data/app/models/workarea/pricing/calculators/item_calculator.decorator +48 -0
  45. data/app/models/workarea/pricing/collection.decorator +90 -0
  46. data/app/models/workarea/pricing/discount/appliation_group.decorator +16 -0
  47. data/app/models/workarea/pricing/discount/international_item_proxy.rb +27 -0
  48. data/app/models/workarea/pricing/discount/international_order.rb +29 -0
  49. data/app/models/workarea/pricing/discount.decorator +65 -0
  50. data/app/models/workarea/pricing/price_distributor.decorator +37 -0
  51. data/app/models/workarea/pricing/request.decorator +17 -0
  52. data/app/models/workarea/pricing/sku.decorator +17 -0
  53. data/app/models/workarea/search/admin/order.decorator +11 -0
  54. data/app/models/workarea/shipping/sku.decorator +23 -0
  55. data/app/models/workarea/shipping.decorator +11 -0
  56. data/app/models/workarea/user.decorator +10 -0
  57. data/app/seeds/workarea/global_e_seeds.rb +66 -0
  58. data/app/services/workarea/create_fulfillment.decorator +12 -0
  59. data/app/services/workarea/global_e/address_details.rb +218 -0
  60. data/app/services/workarea/global_e/api/notify_order_refunded.rb +45 -0
  61. data/app/services/workarea/global_e/api/perform_order_payment.rb +46 -0
  62. data/app/services/workarea/global_e/api/remove_restricted_products.rb +28 -0
  63. data/app/services/workarea/global_e/api/send_order_to_merchant/item_pricer.rb +147 -0
  64. data/app/services/workarea/global_e/api/send_order_to_merchant/save_user.rb +59 -0
  65. data/app/services/workarea/global_e/api/send_order_to_merchant.rb +359 -0
  66. data/app/services/workarea/global_e/api/update_order_shipping_info.rb +38 -0
  67. data/app/services/workarea/global_e/api/update_order_status/canceled.rb +42 -0
  68. data/app/services/workarea/global_e/api/update_order_status.rb +28 -0
  69. data/app/services/workarea/global_e/attribute.rb +44 -0
  70. data/app/services/workarea/global_e/brand.rb +19 -0
  71. data/app/services/workarea/global_e/cart_user_details.rb +34 -0
  72. data/app/services/workarea/global_e/category.rb +19 -0
  73. data/app/services/workarea/global_e/checkout_cart_info.rb +358 -0
  74. data/app/services/workarea/global_e/custom_product_attribute.rb +19 -0
  75. data/app/services/workarea/global_e/discount.rb +215 -0
  76. data/app/services/workarea/global_e/merchant/brand.rb +21 -0
  77. data/app/services/workarea/global_e/merchant/category.rb +21 -0
  78. data/app/services/workarea/global_e/merchant/customer.rb +36 -0
  79. data/app/services/workarea/global_e/merchant/customer_details.rb +201 -0
  80. data/app/services/workarea/global_e/merchant/discount.rb +127 -0
  81. data/app/services/workarea/global_e/merchant/international_details.rb +296 -0
  82. data/app/services/workarea/global_e/merchant/order.rb +425 -0
  83. data/app/services/workarea/global_e/merchant/order_refund.rb +103 -0
  84. data/app/services/workarea/global_e/merchant/original_order.rb +28 -0
  85. data/app/services/workarea/global_e/merchant/parcel_tracking.rb +31 -0
  86. data/app/services/workarea/global_e/merchant/payment_details.rb +161 -0
  87. data/app/services/workarea/global_e/merchant/product.rb +222 -0
  88. data/app/services/workarea/global_e/merchant/refund_product.rb +69 -0
  89. data/app/services/workarea/global_e/merchant/response_info.rb +115 -0
  90. data/app/services/workarea/global_e/merchant_cart_product_attribute.rb +21 -0
  91. data/app/services/workarea/global_e/order_status.rb +20 -0
  92. data/app/services/workarea/global_e/order_status_details.rb +79 -0
  93. data/app/services/workarea/global_e/order_status_reason.rb +20 -0
  94. data/app/services/workarea/global_e/parcel.rb +53 -0
  95. data/app/services/workarea/global_e/product.rb +612 -0
  96. data/app/services/workarea/global_e/product_meta_data.rb +13 -0
  97. data/app/services/workarea/global_e/tracking_details.rb +23 -0
  98. data/app/services/workarea/global_e/update_order_dispatch_exception.rb +46 -0
  99. data/app/services/workarea/global_e/update_order_dispatch_request.rb +108 -0
  100. data/app/services/workarea/global_e/user_id_number_type.rb +21 -0
  101. data/app/services/workarea/global_e/users_details.rb +151 -0
  102. data/app/services/workarea/global_e/vat_category.rb +20 -0
  103. data/app/services/workarea/global_e/vat_rate_type.rb +27 -0
  104. data/app/services/workarea/save_order_analytics.decorator +18 -0
  105. data/app/view_models/workarea/admin/order_view_model.decorator +13 -0
  106. data/app/view_models/workarea/storefront/cart_view_model.decorator +7 -0
  107. data/app/view_models/workarea/storefront/order_item_view_model.decorator +16 -0
  108. data/app/view_models/workarea/storefront/order_view_model.decorator +45 -0
  109. data/app/view_models/workarea/storefront/product_view_model.decorator +35 -0
  110. data/app/views/workarea/admin/catalog_products/_country_exceptions_card.html.haml +31 -0
  111. data/app/views/workarea/admin/catalog_products/_global_e_attributes.html.haml +3 -0
  112. data/app/views/workarea/admin/catalog_products/_global_e_fields.html.haml +4 -0
  113. data/app/views/workarea/admin/country_exceptions/edit.html.haml +48 -0
  114. data/app/views/workarea/admin/country_exceptions/index.html.haml +47 -0
  115. data/app/views/workarea/admin/country_exceptions/new.html.haml +44 -0
  116. data/app/views/workarea/admin/fixed_prices/edit.html.haml +73 -0
  117. data/app/views/workarea/admin/fixed_prices/index.html.haml +55 -0
  118. data/app/views/workarea/admin/fixed_prices/new.html.haml +70 -0
  119. data/app/views/workarea/admin/orders/_global_e.html.haml +27 -0
  120. data/app/views/workarea/admin/orders/attributes.html.haml +131 -0
  121. data/app/views/workarea/admin/orders/global_e.html.haml +110 -0
  122. data/app/views/workarea/admin/orders/show.html.haml +27 -0
  123. data/app/views/workarea/admin/orders/tenders/_global_e_payment.html.haml +3 -0
  124. data/app/views/workarea/admin/pricing_skus/_cards.html.haml +98 -0
  125. data/app/views/workarea/storefront/cart_items/create.html.haml +71 -0
  126. data/app/views/workarea/storefront/carts/_pricing.html.haml +14 -0
  127. data/app/views/workarea/storefront/carts/show.html.haml +187 -0
  128. data/app/views/workarea/storefront/ge_checkouts/show.html.haml +1 -0
  129. data/app/views/workarea/storefront/global_e/_country_picker.html.haml +1 -0
  130. data/app/views/workarea/storefront/global_e/_head.html.haml +10 -0
  131. data/app/views/workarea/storefront/orders/_summary.html.haml +275 -0
  132. data/app/views/workarea/storefront/orders/tenders/_global_e_payment.html.haml +3 -0
  133. data/app/views/workarea/storefront/products/_pricing.html.haml +49 -0
  134. data/app/views/workarea/storefront/products/_restricted_item_text.html.haml +1 -0
  135. data/app/views/workarea/storefront/users/orders/_summary.html.haml +22 -0
  136. data/app/workers/workarea/global_e/update_order_dispatch.rb +28 -0
  137. data/app/workers/workarea/save_user_order_details.decorator +9 -0
  138. data/bin/rails +25 -0
  139. data/config/initializers/appends.rb +46 -0
  140. data/config/initializers/seeds.rb +1 -0
  141. data/config/initializers/workarea.rb +64 -0
  142. data/config/locales/en.yml +105 -0
  143. data/config/routes.rb +31 -0
  144. data/lib/workarea/global_e/engine.rb +10 -0
  145. data/lib/workarea/global_e/error.rb +8 -0
  146. data/lib/workarea/global_e/version.rb +5 -0
  147. data/lib/workarea/global_e.rb +58 -0
  148. data/script/admin_ci +5 -0
  149. data/script/ci +8 -0
  150. data/script/core_ci +5 -0
  151. data/script/plugins_ci +5 -0
  152. data/script/storefront_ci +5 -0
  153. data/test/dummy/.ruby-version +1 -0
  154. data/test/dummy/Rakefile +6 -0
  155. data/test/dummy/app/assets/config/manifest.js +3 -0
  156. data/test/dummy/app/assets/images/.keep +0 -0
  157. data/test/dummy/app/assets/javascripts/application.js +14 -0
  158. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  159. data/test/dummy/app/controllers/application_controller.rb +2 -0
  160. data/test/dummy/app/controllers/concerns/.keep +0 -0
  161. data/test/dummy/app/helpers/application_helper.rb +2 -0
  162. data/test/dummy/app/jobs/application_job.rb +2 -0
  163. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  164. data/test/dummy/app/models/concerns/.keep +0 -0
  165. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  166. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  167. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  168. data/test/dummy/bin/bundle +3 -0
  169. data/test/dummy/bin/rails +4 -0
  170. data/test/dummy/bin/rake +4 -0
  171. data/test/dummy/bin/setup +28 -0
  172. data/test/dummy/bin/update +28 -0
  173. data/test/dummy/bin/yarn +11 -0
  174. data/test/dummy/config/application.rb +23 -0
  175. data/test/dummy/config/boot.rb +5 -0
  176. data/test/dummy/config/environment.rb +5 -0
  177. data/test/dummy/config/environments/development.rb +52 -0
  178. data/test/dummy/config/environments/production.rb +83 -0
  179. data/test/dummy/config/environments/test.rb +45 -0
  180. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  181. data/test/dummy/config/initializers/assets.rb +14 -0
  182. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  183. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  184. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  185. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  186. data/test/dummy/config/initializers/inflections.rb +16 -0
  187. data/test/dummy/config/initializers/mime_types.rb +4 -0
  188. data/test/dummy/config/initializers/workarea.rb +5 -0
  189. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  190. data/test/dummy/config/locales/en.yml +33 -0
  191. data/test/dummy/config/puma.rb +34 -0
  192. data/test/dummy/config/routes.rb +5 -0
  193. data/test/dummy/config/secrets.yml +18 -0
  194. data/test/dummy/config/spring.rb +6 -0
  195. data/test/dummy/config.ru +5 -0
  196. data/test/dummy/db/seeds.rb +2 -0
  197. data/test/dummy/lib/assets/.keep +0 -0
  198. data/test/dummy/log/.keep +0 -0
  199. data/test/factories/workarea/global_e_factories.rb +836 -0
  200. data/test/integration/workarea/admin/catalog_product_country_exceptions_integration_test.rb +81 -0
  201. data/test/integration/workarea/admin/global_e_jump_to_integration_test.rb +20 -0
  202. data/test/integration/workarea/admin/pricing_sku_fixed_prices_integration_test.rb +83 -0
  203. data/test/integration/workarea/storefront/global_e_api/receive_order_integration_test.rb +278 -0
  204. data/test/integration/workarea/storefront/global_e_api/receive_order_refund_integration_test.rb +25 -0
  205. data/test/integration/workarea/storefront/global_e_api/receive_payment_integration_test.rb +34 -0
  206. data/test/integration/workarea/storefront/global_e_api/receive_shipping_info_integration_test.rb +29 -0
  207. data/test/integration/workarea/storefront/global_e_api/remove_restricted_products_integration_test.rb +27 -0
  208. data/test/integration/workarea/storefront/global_e_api/update_order_status_integration_test.rb +29 -0
  209. data/test/integration/workarea/storefront/global_e_checkout_cart_info/fixed_pricing_test.rb +438 -0
  210. data/test/integration/workarea/storefront/global_e_checkout_cart_info/gift_card_test.rb +63 -0
  211. data/test/integration/workarea/storefront/global_e_checkout_cart_info_integration_test.rb +660 -0
  212. data/test/integration/workarea/storefront/global_e_checkouts_integration_test.rb +71 -0
  213. data/test/integration/workarea/storefront/users/global_e_logins_integration_test.rb +20 -0
  214. data/test/models/workarea/catalog/product_global_e_test.rb +13 -0
  215. data/test/models/workarea/fixed_pricing_test.rb +117 -0
  216. data/test/models/workarea/global_e/country_exception_test.rb +36 -0
  217. data/test/models/workarea/payment_test.decorator +31 -0
  218. data/test/models/workarea/pricing/calculators/item_calculator_fixed_prices_test.rb +83 -0
  219. data/test/models/workarea/pricing/collection_fixed_prices_test.rb +83 -0
  220. data/test/models/workarea/pricing/price_distributor_test.decorator +21 -0
  221. data/test/models/workarea/pricing/sku_fixed_price_test.rb +133 -0
  222. data/test/support/workarea/global_e_support.rb +18 -0
  223. data/test/system/workarea/admin/global_e_system_test.rb +21 -0
  224. data/test/system/workarea/storefront/global_e_cart_system_test.rb +48 -0
  225. data/test/system/workarea/storefront/global_e_order_history_system_test.rb +43 -0
  226. data/test/teaspoon_env.rb +6 -0
  227. data/test/test_helper.rb +9 -0
  228. data/test/view_models/workarea/admin/global_e_order_view_model_test.rb +22 -0
  229. data/test/workers/workarea/global_e/update_order_dispatch_test.rb +64 -0
  230. data/test/workers/workarea/global_e_save_user_order_details_test.rb +41 -0
  231. data/workarea-global_e.gemspec +16 -0
  232. metadata +293 -0
@@ -0,0 +1,15 @@
1
+ module Workarea
2
+ class Fulfillment
3
+ module Status
4
+ class Refunded
5
+ include StatusCalculator::Status
6
+
7
+ def in_status?
8
+ # Need some items in the fulfillment
9
+ order.items.any? &&
10
+ order.items.all? { |i| i.quantity_refunded >= i.quantity }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ module Workarea
2
+ decorate Fulfillment, with: :global_e do
3
+ decorated do
4
+ field :global_e_tracking_url, type: String
5
+ field :global_e, type: Boolean, default: false
6
+ end
7
+
8
+ def ship_items(tracking_number, shipped_items, send_to_global_e: true)
9
+ result = super(tracking_number, shipped_items)
10
+ if result && send_to_global_e && global_e?
11
+ GlobalE::UpdateOrderDispatch.perform_async(id, tracking_number)
12
+ end
13
+ result
14
+ end
15
+
16
+ # Refund items in the fulfillment
17
+ #
18
+ # The hashes should contain 'id', the order_item_id and 'quantity' the amount
19
+ # to be canceled, any other key/values will be stored on the Fulfillment::Event#data
20
+ #
21
+ # refund_items([
22
+ # { 'id' => '1234', 'quantity' => 4, 'replacement_sku' => '9999' },
23
+ # { 'id' => '4321', 'quantity' => 1 }
24
+ # ])
25
+ #
26
+ # @param [Array<Hash>] refunded_items
27
+ # @return [Boolean]
28
+ #
29
+ def refund_items(refunded_items)
30
+ return false unless refunded_items.present?
31
+
32
+ occured_at = Time.current
33
+
34
+ refunded_items = refunded_items.map do |refunded_item|
35
+ refunded_item = refunded_item.with_indifferent_access
36
+ next unless refunded_item['quantity'].to_i > 0
37
+
38
+ item = items.detect { |i| i.order_item_id == refunded_item['id'].to_s }
39
+ next unless item.present?
40
+
41
+ item.events.build(
42
+ status: 'refunded',
43
+ quantity: refunded_item['quantity'],
44
+ created_at: occured_at,
45
+ updated_at: occured_at,
46
+ data: refunded_item.except('id', 'quantity')
47
+ )
48
+ end.compact
49
+
50
+ return false unless refunded_items.present?
51
+
52
+ save
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ module Workarea
2
+ module GlobalE
3
+ class CountryException
4
+ include ApplicationDocument
5
+
6
+ field :country, type: Country
7
+ field :restricted, type: Boolean, default: false
8
+ field :vat_rate, type: Float
9
+
10
+ embedded_in :product, class_name: "Workarea::Catalog::Product"
11
+
12
+ validates_presence_of :country
13
+ validate :unique_country
14
+ validate :restricted_or_vat_rate_presence
15
+
16
+ private
17
+
18
+ def unique_country
19
+ return unless product.present? && country.present?
20
+
21
+ country_exists = product.country_exceptions.any? do |country_exception|
22
+ country_exception != self && country_exception.country == country
23
+ end
24
+
25
+ if country_exists
26
+ errors.add(:base, I18n.t('workarea.country_exception.errors.country_must_be_unique'))
27
+ end
28
+ end
29
+
30
+ def restricted_or_vat_rate_presence
31
+ return if restricted.present? || vat_rate.present?
32
+
33
+ errors.add(:base, I18n.t('workarea.country_exception.errors.restricted_or_vat_rate_required'))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,93 @@
1
+ module Workarea
2
+ module GlobalE
3
+ class FixedPrice
4
+ include ApplicationDocument
5
+ include Releasable
6
+
7
+ field :country, type: Country
8
+ field :currency_code, type: String
9
+ field :regular, type: Money
10
+ field :sale, type: Money
11
+ field :msrp, type: Money
12
+
13
+ embedded_in :pricing_sku, inverse_of: :fixed_prices
14
+
15
+ validates_presence_of :regular, :currency
16
+ validate :validate_foreign_currency
17
+ validate :valid_currency
18
+ validate :currencies_match
19
+ validate :unique_currency
20
+ validate :unique_country
21
+
22
+ delegate :on_sale?, to: :pricing_sku
23
+
24
+ def currency
25
+ Money::Currency.find currency_code
26
+ end
27
+
28
+ def sell
29
+ if on_sale? && sale.present?
30
+ sale
31
+ else
32
+ regular
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def validate_foreign_currency
39
+ if regular.present? && regular.currency == Money.default_currency
40
+ errors.add(:regular, I18n.t('workarea.fixed_price.errors.invalid_fixed_price_currency'))
41
+ end
42
+
43
+ if sale.present? && sale.currency == Money.default_currency
44
+ errors.add(:sale, I18n.t('workarea.fixed_price.errors.invalid_fixed_price_currency'))
45
+ end
46
+ end
47
+
48
+ def valid_currency
49
+ return unless currency_code.present?
50
+
51
+ if Money::Currency.find(currency_code).blank?
52
+ errors.add(:currency_code, I18n.t('workarea.fixed_price.errors.invalid_currency'))
53
+ end
54
+ end
55
+
56
+ def currencies_match
57
+ if currency.present? && regular.present? && regular.currency != currency
58
+ errors.add(:base, I18n.t('workarea.fixed_price.errors.currency_and_regular_mistmatch'))
59
+ end
60
+
61
+ if sale.present? && regular.present? && regular.currency != sale.currency
62
+ errors.add(:base, I18n.t('workarea.fixed_price.errors.sale_and_regular_currency_mismatch'))
63
+ end
64
+ end
65
+
66
+ def unique_currency
67
+ return unless pricing_sku.present? && currency_code.present?
68
+
69
+ currency_exists = pricing_sku.fixed_prices.any? do |fixed_price|
70
+ fixed_price != self &&
71
+ fixed_price.currency_code == currency_code &&
72
+ fixed_price.country == country
73
+ end
74
+
75
+ if currency_exists
76
+ errors.add(:base, I18n.t('workarea.fixed_price.errors.currency_must_be_unique'))
77
+ end
78
+ end
79
+
80
+ def unique_country
81
+ return unless pricing_sku.present? && country.present?
82
+
83
+ country_exists = pricing_sku.fixed_prices.any? do |fixed_price|
84
+ fixed_price != self && fixed_price.country == country
85
+ end
86
+
87
+ if country_exists
88
+ errors.add(:base, I18n.t('workarea.fixed_price.errors.country_must_be_unique'))
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,29 @@
1
+ module Workarea
2
+ module GlobalE
3
+ class OrderApiEvents
4
+ include ApplicationDocument
5
+
6
+ field :receive_order, type: Hash
7
+ field :receive_order_response, type: Hash
8
+ field :receive_payment, type: Hash
9
+ field :receive_payment_response, type: Hash
10
+ field :update_order_status, type: Hash
11
+ field :update_order_status_response, type: Hash
12
+ field :receive_shipping_info, type: Hash
13
+ field :receive_shipping_info_response, type: Hash
14
+
15
+ def self.upsert_one(id, set: {})
16
+ timestamp = Time.current
17
+
18
+ collection.update_one(
19
+ { _id: id.to_s },
20
+ {
21
+ '$setOnInsert' => { created_at: timestamp },
22
+ '$set' => { updated_at: timestamp }.merge(set),
23
+ },
24
+ upsert: true
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ module Workarea
2
+ decorate Order::Item, with: :global_e do
3
+ decorated do
4
+ field :international_total_value, type: Money
5
+ field :international_total_price, type: Money
6
+ field :discounted_price_for_customs, type: Money
7
+ field :generic_hs_code, type: String
8
+
9
+ embeds_many :international_price_adjustments,
10
+ class_name: 'Workarea::PriceAdjustment',
11
+ extend: PriceAdjustmentExtension
12
+ end
13
+
14
+ def international_discounted_price
15
+ international_total_value
16
+ end
17
+
18
+ # The original item calculator price adjustment amount before GlobalE
19
+ # adjusting
20
+ #
21
+ # @return [nil, Money]
22
+ #
23
+ def nonadjusted_amount
24
+ return if price_adjustments.first.data["nonadjusted_amount"].nil?
25
+
26
+ Money.demongoize(price_adjustments.first.data["nonadjusted_amount"])
27
+ end
28
+
29
+ # The original item calculator price adjustment price before GlobalE
30
+ # adjusting
31
+ #
32
+ # @return [nil, Float]
33
+ #
34
+ def nonadjusted_price
35
+ price_adjustments.first.data["nonadjusted_price"]
36
+ end
37
+
38
+ # Adds an international price adjustment to the item. Does not persist.
39
+ #
40
+ # @return [self]
41
+ #
42
+ def adjust_international_pricing(options = {})
43
+ international_price_adjustments.build(options)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,12 @@
1
+ module Workarea
2
+ class Order::Status::PendingGlobalEFraudCheck
3
+ include StatusCalculator::Status
4
+
5
+ def in_status?
6
+ order.global_e? &&
7
+ order.placed? &&
8
+ order.global_e_approved_at.blank? &&
9
+ !order.canceled?
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,64 @@
1
+ module Workarea
2
+ decorate Order, with: :global_e do
3
+ decorated do
4
+ field :currency, type: String, default: -> { Money.default_currency.iso_code }
5
+ field :shipping_country, type: Country
6
+ field :fixed_pricing, type: Boolean, default: false
7
+
8
+ field :global_e, type: Boolean, default: false
9
+ field :global_e_id, type: String
10
+ field :global_e_token, type: String, default: -> { SecureRandom.hex(5).upcase }
11
+ field :global_e_approved_at, type: Time
12
+
13
+ field :duties_guaranteed, type: Boolean, default: false
14
+
15
+ field :international_subtotal_price, type: Money
16
+ field :international_shipping_total, type: Money
17
+ field :discounted_international_shipping_total, type: Money
18
+ field :total_duties_price, type: Money
19
+ field :international_total_price, type: Money
20
+ field :contains_clearance_fees_price, type: Money
21
+
22
+ embeds_many :discount_adjustments,
23
+ class_name: 'Workarea::PriceAdjustment',
24
+ extend: PriceAdjustmentExtension
25
+
26
+ embeds_many :international_discount_adjustments,
27
+ class_name: 'Workarea::PriceAdjustment',
28
+ extend: PriceAdjustmentExtension
29
+
30
+ index({ global_e_token: 1 })
31
+ index({ global_e_id: 1 })
32
+ end
33
+
34
+ # All international price adjustments on this order.
35
+ #
36
+ # @return [PriceAdjustmentSet]
37
+ #
38
+ def international_price_adjustments
39
+ PriceAdjustmentSet.new(items.flat_map(&:international_price_adjustments))
40
+ end
41
+
42
+ # Do not run callbacks if global_e order, they will be run after
43
+ # PerformOrderPayment
44
+ def place
45
+ return super unless purchasable? && global_e?
46
+
47
+ self.placed_at = Time.current
48
+ with(write: { w: "majority", j: true }) { save }
49
+ end
50
+
51
+ def global_e_approve!
52
+ run_callbacks :place do
53
+ update_attributes(global_e_approved_at: Time.current)
54
+ end
55
+ end
56
+
57
+ def discount_ids
58
+ super + discount_adjustments
59
+ .map { |pa| pa.data['discount_id'] }
60
+ .compact
61
+ .uniq
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,9 @@
1
+ module Workarea
2
+ class Payment::Status::GlobalEApproved
3
+ include StatusCalculator::Status
4
+
5
+ def in_status?
6
+ model.global_e_payment.present? && model.global_e_approved_at.present?
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Workarea
2
+ class Payment::Status::PendingGlobalEFraudCheck
3
+ include StatusCalculator::Status
4
+
5
+ def in_status?
6
+ model.global_e_payment.present? && model.global_e_approved_at.blank?
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module Workarea
2
+ class Payment
3
+ class Tender
4
+ class GlobalEPayment < Tender
5
+ field :name, type: String
6
+ field :payment_method_code, type: String
7
+ field :last_four, type: String
8
+ field :expiration_date, type: String
9
+
10
+ def slug
11
+ :global_e_payment
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ module Workarea
2
+ decorate Payment, with: :global_e do
3
+ decorated do
4
+ field :global_e_approved_at, type: Time
5
+
6
+ embeds_one :global_e_payment,
7
+ class_name: "Workarea::Payment::Tender::GlobalEPayment"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Workarea
2
+ decorate PriceAdjustment, with: :global_e do
3
+ def global_e_discount_code
4
+ return unless discount?
5
+
6
+ if price == "item"
7
+ "#{data['discount_id']}-#{_parent.id.to_s}"
8
+ else
9
+ data['discount_id']
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ module Workarea
2
+ decorate PriceAdjustmentSet, with: :global_e do
3
+ def group_discounts_by_id
4
+ reduce({}) do |memo, adjustment|
5
+ if adjustment.discount?
6
+ memo[adjustment.data["discount_id"]] ||= PriceAdjustment.new(
7
+ quantity: adjustment.quantity,
8
+ data: adjustment.data.merge("discount_value" => 0.0),
9
+ price: adjustment.price,
10
+ description: adjustment.description,
11
+ calculator: adjustment.calculator,
12
+ amount: 0.to_m(adjustment.amount.currency)
13
+ )
14
+
15
+ memo[adjustment.data["discount_id"]].amount += adjustment.amount
16
+ memo[adjustment.data["discount_id"]].data["discount_value"] += adjustment.data["discount_value"]
17
+ else
18
+ memo[adjustment.id] = adjustment
19
+ end
20
+
21
+ memo
22
+ end.values
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ module Workarea
2
+ decorate Pricing::Calculators::ItemCalculator, with: :global_e do
3
+ def adjust
4
+ order.fixed_pricing = is_fixed_price?
5
+
6
+ return super unless is_fixed_price?
7
+
8
+ order.items.each do |item|
9
+ price = pricing.for_sku(item.sku, quantity: item.quantity)
10
+
11
+ item.adjust_pricing(
12
+ price: 'item',
13
+ quantity: item.quantity,
14
+ calculator: self.class.name,
15
+ amount: price.sell * item.quantity,
16
+ description: 'Item Subtotal',
17
+ data: {
18
+ 'on_sale' => price.on_sale?,
19
+ 'original_price' => price.regular.to_f,
20
+ 'tax_code' => price.tax_code
21
+ }
22
+ )
23
+
24
+ fixed_price = pricing.fixed_price_for_sku(item.sku, currency_code: order.currency, country: order.shipping_country)
25
+
26
+ item.adjust_international_pricing(
27
+ price: 'item',
28
+ quantity: item.quantity,
29
+ calculator: self.class.name,
30
+ amount: fixed_price.sell * item.quantity,
31
+ description: 'Item Subtotal',
32
+ data: {
33
+ 'on_sale' => fixed_price.on_sale?,
34
+ 'original_price' => fixed_price.regular.to_f,
35
+ 'tax_code' => price.tax_code
36
+ }
37
+ )
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def is_fixed_price?
44
+ pricing.records.present? &&
45
+ pricing.records.all? { |sku| sku.fixed_price_for(currency_code: order.currency, country: order.shipping_country) }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,90 @@
1
+ module Workarea
2
+ decorate Pricing::Collection, with: :global_e do
3
+ # GlobalE sell min fixed prices
4
+ # Returns a hash of:
5
+ # { defualts: { Currency: Price }, Country: Price }
6
+ #
7
+ # @return [::Hash]
8
+ #
9
+ def sell_min_fixed_prices
10
+ sell_min_defaults = default_fixed_prices.map do |currency, prices|
11
+ [currency, prices.map { |p| p.sell.to_f }.sort.first]
12
+ end.to_h.presence
13
+
14
+ sell_min_country_prices = country_fixed_prices.map do |country, prices|
15
+ [country.alpha2, prices.map { |p| p.sell.to_f }.sort.first]
16
+ end.to_h
17
+
18
+
19
+ { defaults: sell_min_defaults }.merge(sell_min_country_prices).compact
20
+ end
21
+
22
+ # GlobalE sell max fixed prices
23
+ # Returns a hash of:
24
+ # { defualts: { Currency: Price }, Country: Price }
25
+ #
26
+ # @return [::Hash]
27
+ #
28
+ def sell_max_fixed_prices
29
+ sell_min_defaults = default_fixed_prices.map do |currency, prices|
30
+ [currency, prices.map { |p| p.sell.to_f }.sort.last]
31
+ end.to_h.presence
32
+
33
+ sell_min_country_prices = country_fixed_prices.map do |country, prices|
34
+ [country.alpha2, prices.map { |p| p.sell.to_f }.sort.last]
35
+ end.to_h
36
+
37
+
38
+ { defaults: sell_min_defaults }.merge(sell_min_country_prices).compact
39
+ end
40
+
41
+ def original_min_fixed_prices
42
+ original_min_defaults = default_fixed_prices.map do |currency, prices|
43
+ [currency, prices.map { |p| [p.msrp&.to_f, p.regular.to_f].compact.max }.sort.first]
44
+ end.to_h.presence
45
+
46
+ original_min_country_prices = country_fixed_prices.map do |country, prices|
47
+ [country.alpha2, prices.map { |p| [p.msrp&.to_f, p.regular.to_f].compact.max }.sort.first]
48
+ end.to_h
49
+
50
+ { defaults: original_min_defaults }.merge(original_min_country_prices).compact
51
+ end
52
+
53
+ def original_max_fixed_prices
54
+ original_max_defaults = default_fixed_prices.map do |currency, prices|
55
+ [currency, prices.map { |p| [p.msrp&.to_f, p.regular.to_f].compact.max }.sort.last]
56
+ end.to_h.presence
57
+
58
+ original_max_country_prices = country_fixed_prices.map do |country, prices|
59
+ [country.alpha2, prices.map { |p| [p.msrp&.to_f, p.regular.to_f].compact.max }.sort.last]
60
+ end.to_h
61
+
62
+ { defaults: original_max_defaults }.merge(original_max_country_prices).compact
63
+ end
64
+
65
+ # @ return [Workarea::GlobalE::FixedPrice, nil]
66
+ #
67
+ def fixed_price_for_sku(sku, currency_code:, country:)
68
+ return unless sku = records.detect { |r| r.id == sku }
69
+ sku.fixed_price_for(currency_code: currency_code, country: country)
70
+ end
71
+
72
+ private
73
+
74
+ def fixed_prices
75
+ @fixed_prices ||= records.flat_map(&:fixed_prices)
76
+ end
77
+
78
+ def default_fixed_prices
79
+ @default_fixed_prices ||= fixed_prices.select do |fixed_price|
80
+ fixed_price.country.blank?
81
+ end.group_by(&:currency_code)
82
+ end
83
+
84
+ def country_fixed_prices
85
+ @country_fixed_prices ||= fixed_prices.select do |fixed_price|
86
+ fixed_price.country.present?
87
+ end.group_by(&:country)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,16 @@
1
+ module Workarea
2
+ decorate Pricing::Discount::ApplicationGroup, with: :global_e do
3
+ def apply
4
+ return super unless order.fixed_pricing?
5
+
6
+ discounts.each do |discount|
7
+ discount_order = Pricing::Discount::Order.new(order, shippings, discount)
8
+ next unless discount.qualifies?(discount_order)
9
+
10
+ discount.apply(discount_order)
11
+ international_discount_order = Pricing::Discount::InternationalOrder.new(order, shippings, discount)
12
+ discount.apply(international_discount_order)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ module Workarea
2
+ module Pricing
3
+ class Discount
4
+ class InternationalItemProxy
5
+ attr_reader :order_item
6
+ delegate_missing_to :order_item
7
+
8
+ def initialize(order_item)
9
+ @order_item = order_item
10
+ end
11
+
12
+ def current_unit_price
13
+ return 0.to_m(order.currency) if international_price_adjustments.blank?
14
+ international_price_adjustments.adjusting('item').map(&:unit).sum.to_m
15
+ end
16
+
17
+ # Adds an international price adjustment to the item. Does not persist.
18
+ #
19
+ # @return [self]
20
+ #
21
+ def adjust_pricing(options = {})
22
+ international_price_adjustments.build(options)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ module Workarea
2
+ module Pricing
3
+ class Discount
4
+ class InternationalOrder < Discount::Order
5
+ # Only return items that are discountable.
6
+ #
7
+ # TODO this needs update in 3.4
8
+ #
9
+ # @return [Array<Workarea::Order::Item>]
10
+ #
11
+ def items
12
+ @order.items.select(&:discountable?).select do |item|
13
+ allow_sale_items? || !item.on_sale?
14
+ end.map { |item| InternationalItemProxy.new(item) }
15
+ end
16
+
17
+ # The subtotal, not including items that cannot be discounted.
18
+ #
19
+ # @return [Money]
20
+ #
21
+ def subtotal_price
22
+ items.reduce(0.to_m(@order.currency)) do |memo, item|
23
+ memo + item.international_price_adjustments.sum
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end