flowcommerce_spree 0.0.3 → 0.0.8

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -6
  3. data/app/controllers/concerns/current_zone_loader_decorator.rb +11 -17
  4. data/app/controllers/flowcommerce_spree/inventory_controller.rb +23 -0
  5. data/app/controllers/flowcommerce_spree/orders_controller.rb +20 -0
  6. data/app/controllers/flowcommerce_spree/webhooks_controller.rb +16 -18
  7. data/app/controllers/users/sessions_controller_decorator.rb +19 -2
  8. data/app/helpers/spree/core/controller_helpers/flow_io_order_helper_decorator.rb +0 -16
  9. data/app/models/flowcommerce_spree/settings.rb +1 -0
  10. data/app/models/spree/address_decorator.rb +1 -1
  11. data/app/models/spree/calculator/flow_io.rb +24 -12
  12. data/app/models/spree/calculator/shipping/flow_io.rb +5 -2
  13. data/app/models/spree/flow_io_credit_card_decorator.rb +21 -0
  14. data/app/models/spree/flow_io_order_decorator.rb +181 -0
  15. data/app/models/spree/flow_io_product_decorator.rb +5 -0
  16. data/app/models/spree/flow_io_variant_decorator.rb +16 -6
  17. data/app/models/spree/gateway/flow_io.rb +61 -24
  18. data/app/models/spree/{credit_card_decorator.rb → payment_capture_event_decorator.rb} +1 -1
  19. data/app/models/spree/zones/flow_io_product_zone_decorator.rb +4 -0
  20. data/app/overrides/spree/admin/order_sidebar_summary_flow_link.rb +13 -0
  21. data/app/overrides/spree/admin/products/order_price_flow_message.rb +9 -0
  22. data/app/serializers/api/v2/order_serializer_decorator.rb +20 -0
  23. data/app/services/flowcommerce_spree/import_experience_items.rb +1 -21
  24. data/app/services/flowcommerce_spree/import_item.rb +45 -0
  25. data/app/services/flowcommerce_spree/order_sync.rb +50 -222
  26. data/app/services/flowcommerce_spree/order_updater.rb +78 -0
  27. data/app/services/flowcommerce_spree/webhooks/capture_upserted_v2.rb +76 -0
  28. data/app/services/flowcommerce_spree/webhooks/card_authorization_upserted_v2.rb +66 -0
  29. data/app/services/flowcommerce_spree/webhooks/experience_upserted_v2.rb +25 -0
  30. data/app/services/flowcommerce_spree/webhooks/fraud_status_changed.rb +35 -0
  31. data/app/services/flowcommerce_spree/webhooks/local_item_upserted.rb +40 -0
  32. data/app/views/spree/admin/payments/source_views/_flow_io_gateway.html.erb +21 -0
  33. data/app/workers/flowcommerce_spree/import_item_worker.rb +24 -0
  34. data/config/routes.rb +3 -1
  35. data/db/migrate/20201021755957_add_meta_to_spree_tables.rb +6 -4
  36. data/lib/flow/simple_gateway.rb +0 -36
  37. data/lib/flowcommerce_spree.rb +6 -2
  38. data/lib/flowcommerce_spree/engine.rb +6 -1
  39. data/lib/flowcommerce_spree/experience_service.rb +1 -27
  40. data/lib/flowcommerce_spree/logging_http_client.rb +29 -13
  41. data/lib/flowcommerce_spree/session.rb +5 -25
  42. data/lib/flowcommerce_spree/version.rb +1 -1
  43. data/lib/tasks/flowcommerce_spree.rake +4 -1
  44. metadata +77 -28
  45. data/app/mailers/spree/spree_order_mailer_decorator.rb +0 -24
  46. data/app/models/spree/line_item_decorator.rb +0 -15
  47. data/app/models/spree/order_decorator.rb +0 -244
  48. data/app/views/spree/order_mailer/confirm_email.html.erb +0 -86
  49. data/app/views/spree/order_mailer/confirm_email.text.erb +0 -38
  50. data/lib/flow/error.rb +0 -73
  51. data/lib/flow/pay_pal.rb +0 -25
  52. data/lib/flowcommerce_spree/webhook_service.rb +0 -184
  53. data/lib/simple_csv_writer.rb +0 -44
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Spree
4
- OrderMailer.class_eval do
5
- # default from: ApplicationMailer::DEFAULT_FROM
6
-
7
- def refund_complete_email(web_hook_event)
8
- auth_id = web_hook_event.dig('refund', 'authorization', 'key')
9
-
10
- raise Flow::Error, 'authorization key not found in WebHookEvent [refund_capture_upserted_v2]' unless auth_id
11
-
12
- authorization = FlowcommerceSpree.client.authorizations.get_by_key FlowcommerceSpree::ORGANIZATION, auth_id
13
-
14
- refund_requested = web_hook_event['refund']['requested']
15
- @mail_to = authorization.customer.email
16
- @full_name = "#{authorization.customer.name.first} #{authorization.customer.name.last}"
17
- @amount = "#{refund_requested['amount']} #{refund_requested['currency']}"
18
- @number = authorization.order.number
19
- @order = Spree::Order.find_by number: @number
20
-
21
- mail(to: @mail_to, subject: "We refunded your order for ammount #{@amount}")
22
- end
23
- end
24
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Flow (2017)
4
- # Enable this modifications if you want to display flow localized line item
5
- # Example: https://i.imgur.com/7v2ix2G.png
6
- module Spree
7
- LineItem.class_eval do
8
- # admin show line item price
9
- def single_money
10
- price = display_price.to_s
11
- price += " (#{order.flow_line_item_price(self)})" if order.flow_order
12
- price
13
- end
14
- end
15
- end
@@ -1,244 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # `:display_total` modifications to display total prices beside Spree default. Example: https://i.imgur.com/7v2ix2G.png
4
- module Spree # rubocop:disable Metrics/ModuleLength
5
- # Added flow specific methods to Spree::Order
6
- Order.class_eval do
7
- serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)
8
-
9
- store_accessor :meta, :flow_data
10
-
11
- before_save :sync_to_flow_io
12
- after_touch :sync_to_flow_io
13
-
14
- def flow_tax_cache_key
15
- [number, 'flowcommerce', 'allocation', line_items.sum(:quantity)].join('-')
16
- end
17
-
18
- def sync_to_flow_io
19
- return unless zone&.flow_io_active_experience? && state == 'cart' && line_items.size > 0
20
-
21
- flow_io_order = FlowcommerceSpree::OrderSync.new(order: self)
22
- flow_io_order.build_flow_request
23
- flow_io_order.synchronize! if flow_data['digest'] != flow_io_order.digest
24
- end
25
-
26
- def display_total
27
- price = FlowcommerceSpree::Api.format_default_price total
28
- price += " (#{flow_total})" if flow_order
29
- price.html_safe
30
- end
31
-
32
- def flow_order
33
- return unless flow_data&.[]('order')
34
-
35
- Hashie::Mash.new flow_data['order']
36
- end
37
-
38
- # accepts line item, usually called from views
39
- def flow_line_item_price(line_item, total = false)
40
- result = if flow_order
41
- item = flow_order.lines&.find { |el| el['item_number'] == line_item.variant.sku }
42
-
43
- return 'n/a' unless item
44
-
45
- total ? item['total']['label'] : item['price']['label']
46
- else
47
- FlowcommerceSpree::Api.format_default_price(line_item.price * (total ? line_item.quantity : 1))
48
- end
49
-
50
- # add line item promo
51
- # promo_total, adjustment_total
52
- result += " (#{FlowcommerceSpree::Api.format_default_price(line_item.promo_total)})" if line_item.promo_total > 0
53
-
54
- result
55
- end
56
-
57
- # prepares array of prices that can be easily renderd in templates
58
- def flow_cart_breakdown
59
- prices = []
60
-
61
- price_model = Struct.new(:name, :label)
62
-
63
- if flow_order
64
- # duty, vat, ...
65
- unless flow_order.prices
66
- message = Flow::Error.format_order_message flow_order
67
- raise Flow::Error, message
68
- end
69
-
70
- flow_order.prices.each do |price|
71
- prices.push price_model.new(price['name'], price['label'])
72
- end
73
- else
74
- price_elements =
75
- %i[item_total adjustment_total included_tax_total additional_tax_total tax_total shipment_total promo_total]
76
- price_elements.each do |el|
77
- price = send(el)
78
- if price > 0
79
- label = FlowcommerceSpree::Api.format_default_price price
80
- prices.push price_model.new(el.to_s.humanize.capitalize, label)
81
- end
82
- end
83
-
84
- # discount is applied and we allways show it in default currency
85
- if adjustment_total != 0
86
- formated_discounted_price = FlowcommerceSpree::Api.format_default_price adjustment_total
87
- prices.push price_model.new('Discount', formated_discounted_price)
88
- end
89
- end
90
-
91
- # total
92
- prices.push price_model.new(Spree.t(:total), flow_total)
93
-
94
- prices
95
- end
96
-
97
- # shows localized total, if possible. if not, fall back to Spree default
98
- def flow_total
99
- # r flow_order.total.label
100
- price = flow_order&.total&.label
101
- price || FlowcommerceSpree::Api.format_default_price(total)
102
- end
103
-
104
- def flow_experience
105
- model = Struct.new(:key)
106
- model.new flow_order.experience.key
107
- rescue StandardError => _e
108
- model.new ENV.fetch('FLOW_BASE_COUNTRY')
109
- end
110
-
111
- def flow_io_checkout_token
112
- flow_data&.[]('checkout_token')
113
- end
114
-
115
- def flow_io_experience_key
116
- flow_data&.[]('exp')
117
- end
118
-
119
- def flow_io_experience_from_zone
120
- self.flow_data = (flow_data || {}).merge!('exp' => zone.flow_io_experience)
121
- end
122
-
123
- def flow_io_order_id
124
- flow_data&.dig('order', 'id')
125
- end
126
-
127
- def flow_io_session_expires_at
128
- flow_data&.[]('session_expires_at')&.to_datetime
129
- end
130
-
131
- def flow_io_attributes
132
- flow_data&.dig('order', 'attributes') || {}
133
- end
134
-
135
- def add_flow_checkout_token(token)
136
- self.flow_data ||= {}
137
- self.flow_data['checkout_token'] = token
138
- end
139
-
140
- def flow_io_attribute_add(attr_key, value)
141
- self.flow_data['order'] ||= {}
142
- self.flow_data['order']['attributes'] ||= {}
143
- self.flow_data['order']['attributes'][attr_key] = value
144
- end
145
-
146
- def add_user_uuid_to_flow_data
147
- self.flow_data['order'] ||= {}
148
- self.flow_data['order']['attributes'] ||= {}
149
- self.flow_data['order']['attributes']['user_uuid'] = user&.uuid || ''
150
- end
151
-
152
- def flow_io_attr_user_uuid
153
- flow_data&.dig('order', 'attributes', 'user_uuid')
154
- end
155
-
156
- def checkout_url
157
- FlowcommerceSpree::OrderSync.new(order: self).synchronize!
158
-
159
- checkout_token = flow_io_checkout_token
160
- return "https://checkout.flow.io/tokens/#{checkout_token}" if checkout_token
161
- end
162
-
163
- # clear invalid zero amount payments. Solidus bug?
164
- def clear_zero_amount_payments!
165
- # class attribute that can be set to true
166
- return unless Flow::Order.clear_zero_amount_payments
167
-
168
- payments.where(amount: 0, state: %w[invalid processing pending]).map(&:destroy)
169
- end
170
-
171
- def flow_order_authorized?
172
- flow_data&.[]('authorization') ? true : false
173
- end
174
-
175
- def flow_order_captured?
176
- flow_data['capture'] ? true : false
177
- end
178
-
179
- # completes order and sets all states to finalized and complete
180
- # used when we have confirmed capture from Flow API or PayPal
181
- def flow_finalize!
182
- finalize! unless state == 'complete'
183
- update_column :payment_state, 'paid' if payment_state != 'paid'
184
- update_column :state, 'complete' if state != 'complete'
185
- end
186
-
187
- def flow_payment_method
188
- if flow_data['payment_type'] == 'paypal'
189
- 'paypal'
190
- else
191
- 'cc' # creait card is default
192
- end
193
- end
194
-
195
- def flow_customer_email
196
- flow_data.dig('order', 'customer', 'email')
197
- end
198
-
199
- def flow_ship_address
200
- flow_destination = flow_data.dig('order', 'destination')
201
- return unless flow_destination.present?
202
-
203
- flow_destination['first'] = flow_destination.dig('contact', 'name', 'first')
204
- flow_destination['last'] = flow_destination.dig('contact', 'name', 'last')
205
- flow_destination['phone'] = flow_destination.dig('contact', 'phone')
206
-
207
- s_address = ship_address || build_ship_address
208
- s_address.prepare_from_flow_attributes(flow_destination)
209
- s_address
210
- end
211
-
212
- def flow_bill_address
213
- flow_payment_address = flow_data.dig('order', 'payments')&.last&.[]('address')
214
- return unless flow_payment_address
215
-
216
- flow_payment_address['first'] = flow_payment_address.dig('name', 'first')
217
- flow_payment_address['last'] = flow_payment_address.dig('name', 'last')
218
- flow_payment_address['phone'] = ship_address['phone']
219
-
220
- b_address = bill_address || build_bill_address
221
- b_address.prepare_from_flow_attributes(flow_payment_address)
222
- b_address
223
- end
224
-
225
- def prepare_flow_addresses
226
- address_attributes = {}
227
-
228
- s_address = flow_ship_address
229
-
230
- if s_address&.changes&.any?
231
- s_address.save
232
- address_attributes[:ship_address_id] = s_address.id unless ship_address_id
233
- end
234
-
235
- b_address = flow_bill_address
236
- if b_address&.changes&.any?
237
- b_address.save
238
- address_attributes[:bill_address_id] = b_address.id unless bill_address_id
239
- end
240
-
241
- address_attributes
242
- end
243
- end
244
- end
@@ -1,86 +0,0 @@
1
- <%
2
- # render text email in console. parts[1] for html body
3
- # puts Spree::OrderMailer.confirm_email(Spree::Order.last).body.parts[0].body
4
-
5
- @prices = @order.flow_cart_breakdown
6
- @total_price = @prices.pop
7
- %>
8
-
9
- <style>
10
- table.order td { padding: 4px; border-top: 1px solid #bbb; }
11
- </style>
12
-
13
- <h6>Dear <%= @order.bill_address.firstname %></h6>
14
-
15
- <br>
16
- <br>
17
-
18
- <p><%= Spree.t('order_mailer.confirm_email.instructions') %></p>
19
- <p><%= Spree.t('order_mailer.confirm_email.order_summary') %></p>
20
-
21
- <table class="order">
22
- <tr>
23
- <th>Product</th>
24
- <th width="100" align="right">Price</th>
25
- <th width="80" align="center">Quantity</th>
26
- <th width="100" align="right">Total</th>
27
- </tr>
28
- <% @order.line_items.each do |line_item| %>
29
- <tr>
30
- <td><%= line_item.variant.product.name %></td>
31
- <td align="right"><%= @order.flow_line_item_price(line_item) %></td>
32
- <td align="center"><%= line_item.quantity %></td>
33
- <td align="right"><%= @order.flow_line_item_price(line_item, :with_quantity) %></td>
34
- </tr>
35
- <% end %>
36
- </table>
37
-
38
- <br>
39
-
40
- <p><b>Total</b></p>
41
-
42
- <table class="order">
43
-
44
- <% @prices.each do |price| %>
45
- <tr><td width="120"><%= price.name.capitalize %></td><td align="right"><%= price.label %></td></tr>
46
- <% end %>
47
-
48
- <tr>
49
- <td><%= Spree.t(:total) %></td>
50
- <td align="right"><b><%= @total_price.label %></b></td>
51
- </tr>
52
- <tr>
53
- <td>Payment method</td>
54
- <td align="right"><%= @order.flow_payment_method == 'paypal' ? 'PayPal' : 'Credit Card' %></td>
55
- </tr>
56
- </table>
57
-
58
- <br>
59
-
60
- <% ['ship', 'bill'].each do |name|
61
- address = @order.send('%s_address' % name)
62
- %>
63
- <p><b><%= name.capitalize %>ing address</b></p>
64
-
65
- <table class="order">
66
- <tr>
67
- <td>Full name</td>
68
- <td><%= address.firstname %> <%= address.lastname %></td>
69
- </tr>
70
- <tr>
71
- <td>Address</td>
72
- <td><%= address.address1 %></td>
73
- </tr>
74
- <tr>
75
- <td>City</td>
76
- <td><%= address.city %></td>
77
- </tr>
78
- <tr>
79
- <td>Country</td>
80
- <td><%= address.country.name rescue '-' %>, <%= address.state.name rescue '-' %></td>
81
- </tr>
82
- </table>
83
- <br />
84
- <% end %>
85
-
86
- <p><%= Spree.t('order_mailer.confirm_email.thanks') %></p>
@@ -1,38 +0,0 @@
1
- <%= Spree.t('order_mailer.confirm_email.dear_customer') %>
2
-
3
- <%= Spree.t('order_mailer.confirm_email.instructions') %>
4
-
5
- ============================================================
6
- <%= Spree.t('order_mailer.confirm_email.order_summary') %>
7
- ============================================================
8
- <% @order.line_items.each do |item| %>
9
- <%= item.variant.sku %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (<%=item.quantity%>) @ <%= item.single_money %> = <%= @order.flow_line_item_price(line_item, :with_quantity) %>
10
- <% end %>
11
- ============================================================
12
- <%= Spree.t('order_mailer.confirm_email.subtotal', :subtotal => @order.display_item_total) %>
13
- <% if @order.line_item_adjustments.exists? %>
14
- <% if @order.all_adjustments.promotion.eligible.exists? %>
15
- <% @order.all_adjustments.promotion.eligible.group_by(&:label).each do |label, adjustments| %>
16
- <%= Spree.t(:promotion) %>: <%= label %> <%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %>
17
- <% end %>
18
- <% end %>
19
- <% end %>
20
-
21
- <% @order.shipments.group_by { |s| s.selected_shipping_rate.try(:name) }.each do |name, shipments| %>
22
- <%= Spree.t(:shipping) %>: <%= name %> <%= Spree::Money.new(shipments.sum(&:discounted_cost), currency: @order.currency) %>
23
- <% end %>
24
-
25
- <% if @order.all_adjustments.eligible.tax.exists? %>
26
- <% @order.all_adjustments.eligible.tax.group_by(&:label).each do |label, adjustments| %>
27
- <%= Spree.t(:tax) %>: <%= label %> <%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %>
28
- <% end %>
29
- <% end %>
30
-
31
- <% @order.adjustments.eligible.each do |adjustment| %>
32
- <% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %>
33
- <%= adjustment.label %> <%= adjustment.display_amount %>
34
- <% end %>
35
- ============================================================
36
- <%= Spree.t('order_mailer.confirm_email.total', :total => @order.display_total) %>
37
-
38
- <%= Spree.t('order_mailer.confirm_email.thanks') %>
data/lib/flow/error.rb DELETED
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Flow (2017)
4
- # api error logger and formater
5
-
6
- require 'digest/sha1'
7
-
8
- class Flow::Error < StandardError
9
- # logs error to file for easy discovery and fix
10
- def self.log(exception, request)
11
- history = exception.backtrace.reject { |el| el.index('/gems/') }.map { |el| el.sub(Rails.root.to_s, '') }.join($/)
12
-
13
- msg = "#{exception.class} in #{request.url}"
14
- data = [msg, exception.message, history].join("\n\n")
15
- key = Digest::SHA1.hexdigest(exception.backtrace.first.split(' ').first)
16
-
17
- folder = Rails.root.join('log/exceptions').to_s
18
- Dir.mkdir(folder) unless Dir.exist?(folder)
19
-
20
- folder += "/#{exception.class.to_s.tableize.gsub('/', '-')}"
21
- Dir.mkdir(folder) unless Dir.exist?(folder)
22
-
23
- "#{folder}/#{key}.txt".tap do |path|
24
- File.write(path, data)
25
- end
26
- end
27
-
28
- def self.format_message(exception)
29
- # format Flow errors in a special way
30
- # Io::Flow::V0::HttpClient::ServerError - 422 Unprocessable Entity:
31
- # {"code":"invalid_number","messages":["Card number is not valid"]}
32
- # hash['code'] = 'invalid_number'
33
- # hash['message'] = 'Card number is not valid'
34
- # hash['title'] = '422 Unprocessable Entity'
35
- # hash['klass'] = 'Io::Flow::V0::HttpClient::ServerError'
36
- if exception.class == Io::Flow::V0::HttpClient::ServerError
37
- parts = exception.message.split(': ', 2)
38
- hash = Oj.load(parts[1])
39
-
40
- hash[:message] = hash['messages'].join(', ')
41
- hash[:title] = parts[0]
42
- hash[:klass] = exception.class
43
- hash[:code] = hash['code']
44
- else
45
- msg = exception.message.is_a?(Array) ? exception.message.join(' - ') : exception.message
46
-
47
- hash = {}
48
- hash[:message] = msg
49
- hash[:title] = '-'
50
- hash[:klass] = exception.class
51
- hash[:code] = '-'
52
- end
53
-
54
- hash
55
- end
56
-
57
- def self.format_order_message(order)
58
- message = if order['messages']
59
- msg = order['messages'].join(', ')
60
- msg += " (#{Spree::Variant.where(id: order['numbers']).map(&:name).join(', ')})" if order['numbers']
61
- msg
62
- else
63
- 'Order not properly localized (sync issue)'
64
- end
65
-
66
- # sub_info = 'Flow.io'
67
- # sub_info += ' - %s' % flow_experience.key[0, 15] if flow_experience
68
-
69
- # '%s (%s)' % [message, sub_info]
70
-
71
- message
72
- end
73
- end