flowcommerce-solidus 0.1.11

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 (33) hide show
  1. checksums.yaml +7 -0
  2. data/.version +1 -0
  3. data/bin/flowcommerce-solidus +121 -0
  4. data/lib/flowcommerce-solidus.rb +7 -0
  5. data/static/app/flow/README.md +77 -0
  6. data/static/app/flow/SOLIDUS_FLOW.md +127 -0
  7. data/static/app/flow/decorators/admin_decorators.rb +34 -0
  8. data/static/app/flow/decorators/localized_coupon_code_decorator.rb +49 -0
  9. data/static/app/flow/decorators/spree_credit_card_decorator.rb +35 -0
  10. data/static/app/flow/decorators/spree_order_decorator.rb +128 -0
  11. data/static/app/flow/decorators/spree_product_decorator.rb +16 -0
  12. data/static/app/flow/decorators/spree_user_decorator.rb +16 -0
  13. data/static/app/flow/decorators/spree_variant_decorator.rb +124 -0
  14. data/static/app/flow/flow.rb +67 -0
  15. data/static/app/flow/flow/error.rb +46 -0
  16. data/static/app/flow/flow/experience.rb +51 -0
  17. data/static/app/flow/flow/order.rb +267 -0
  18. data/static/app/flow/flow/pay_pal.rb +27 -0
  19. data/static/app/flow/flow/session.rb +77 -0
  20. data/static/app/flow/flow/simple_crypt.rb +30 -0
  21. data/static/app/flow/flow/simple_gateway.rb +123 -0
  22. data/static/app/flow/flow/webhook.rb +62 -0
  23. data/static/app/flow/lib/flow_api_refresh.rb +89 -0
  24. data/static/app/flow/lib/spree_flow_gateway.rb +86 -0
  25. data/static/app/flow/lib/spree_stripe_gateway.rb +142 -0
  26. data/static/app/views/spree/admin/payments/index.html.erb +37 -0
  27. data/static/app/views/spree/admin/promotions/edit.html.erb +59 -0
  28. data/static/app/views/spree/admin/shared/_order_summary.html.erb +58 -0
  29. data/static/app/views/spree/admin/shared/_order_summary_flow.html.erb +13 -0
  30. data/static/app/views/spree/order_mailer/confirm_email.html.erb +85 -0
  31. data/static/app/views/spree/order_mailer/confirm_email.text.erb +41 -0
  32. data/static/lib/tasks/flow.rake +248 -0
  33. metadata +160 -0
@@ -0,0 +1,58 @@
1
+ <header id="order_tab_summary" data-hook>
2
+ <dl class="additional-info">
3
+ <dt id="order_status" data-hook><%= Spree.t(:status) %>:</dt>
4
+ <dd><span class="state <%= @order.state %>"><%= Spree.t(@order.state, :scope => :order_state) %></span></dd>
5
+
6
+ <% if @order.user.present? %>
7
+ <dt data-hook='admin_order_user_link'><%= Spree.t(:user) %>:</dt>
8
+ <dd id='order_user_link'><%= link_to @order.user.email, spree.edit_admin_user_path(@order.user) %></dd>
9
+ <% end %>
10
+
11
+ <dt data-hook='admin_order_tab_subtotal_title'><%= Spree::Order.human_attribute_name(:item_total) %>:</dt>
12
+ <dd id='item_total'><%= @order.display_item_total.to_html %></dd>
13
+ <% if checkout_steps.include?("delivery") && @order.ship_total > 0 %>
14
+ <dt data-hook='admin_order_tab_ship_total_title'><%= Spree::Order.human_attribute_name(:shipment_total) %>:</dt>
15
+ <dd id='ship_total'><%= @order.display_ship_total.to_html %></dd>
16
+ <% end %>
17
+
18
+ <% if @order.included_tax_total != 0 %>
19
+ <dt data-hook='admin_order_tab_included_tax_title'><%= Spree::Order.human_attribute_name(:included_tax_total) %>:</dt>
20
+ <dd id='included_tax_total'><%= @order.display_included_tax_total.to_html %></dd>
21
+ <% end %>
22
+
23
+ <% if @order.additional_tax_total != 0 %>
24
+ <dt data-hook='admin_order_tab_additional_tax_title'><%= Spree::Order.human_attribute_name(:additional_tax_total) %>:</dt>
25
+ <dd id='additional_tax_total'><%= @order.display_additional_tax_total.to_html %></dd>
26
+ <% end %>
27
+
28
+ <dt data-hook='admin_order_tab_total_title'><%= Spree::Order.human_attribute_name(:total) %>:</dt>
29
+ <dd id='order_total'><%= @order.display_total.to_html %></dd>
30
+
31
+ <% if @order.completed? %>
32
+ <dt><%= Spree::Shipment.model_name.human %>: </dt>
33
+ <dd id='shipment_status'><span class="state <%= @order.shipment_state %>"><%= Spree.t(@order.shipment_state, :scope => :shipment_states, :default => [:missing, "none"]) %></span></dd>
34
+ <dt><%= Spree::Payment.model_name.human %>: </dt>
35
+ <dd id='payment_status'><span class="state <%= @order.payment_state %>"><%= Spree.t(@order.payment_state, :scope => :payment_states, :default => [:missing, "none"]) %></span></dd>
36
+ <dt data-hook='admin_order_tab_date_completed_title'><%= Spree::Order.human_attribute_name(:completed_at) %>:</dt>
37
+ <dd id='date_complete'><%= pretty_time(@order.completed_at) %></dd>
38
+ <% end %>
39
+
40
+ <% if @order.approved? %>
41
+ <dt><%= Spree::Order.human_attribute_name(:approver) %></dt>
42
+ <dd><%= @order.approver.try(:email) || @order.approver_name %></dd>
43
+ <dt><%= Spree::Order.human_attribute_name(:approved_at) %></dt>
44
+ <dd><%= pretty_time(@order.approved_at) %></dd>
45
+ <% end %>
46
+
47
+ <% if @order.canceled? && @order.canceler && @order.canceled_at %>
48
+ <dt><%= Spree::Order.human_attribute_name(:canceler) %></dt>
49
+ <dd><%= @order.canceler.email %></td>
50
+ <dt><%= Spree::Order.human_attribute_name(:canceled_at) %></dt>
51
+ <dd><%= pretty_time(@order.canceled_at) %></dd>
52
+ <% end %>
53
+
54
+ </dl>
55
+ </header>
56
+
57
+ <!-- Add Flow order summary, if available -->
58
+ <%= render partial: 'spree/admin/shared/order_summary_flow' %>
@@ -0,0 +1,13 @@
1
+ <hr />
2
+
3
+ <h5 class="sidebar-title">Flow details</h5>
4
+
5
+ <%= total_cart_breakdown '' %>
6
+
7
+ <% if @order.flow_data['order'] %>
8
+ <p>Experience: <b><%= @order.flow_data.dig('order', 'experience', 'key') %></b></p>
9
+ <a target="_fc_<%= @order.id %>" href="https://console.flow.io/<%= Flow.organization %>/orders/<%= @order.number %>">Open in Flow console</a>
10
+ <% else %>
11
+ <p>Order is not localized with Flow.</p>
12
+ <% end %>
13
+
@@ -0,0 +1,85 @@
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
+ <h4>Dear <%= @order.bill_address.firstname %></h4>
14
+
15
+ <br>
16
+
17
+ <p><%= Spree.t('order_mailer.confirm_email.instructions') %></p>
18
+ <p><%= Spree.t('order_mailer.confirm_email.order_summary') %></p>
19
+
20
+ <table class="order">
21
+ <tr>
22
+ <th>Product</th>
23
+ <th width="100" align="right">Price</th>
24
+ <th width="80" align="center">Quantity</th>
25
+ <th width="100" align="right">Total</th>
26
+ </tr>
27
+ <% @order.line_items.each do |line_item| %>
28
+ <tr>
29
+ <td><%= line_item.variant.product.name %></td>
30
+ <td align="right"><%= @order.flow_line_item_price(line_item) %></td>
31
+ <td align="center"><%= line_item.quantity %></td>
32
+ <td align="right"><%= @order.flow_line_item_price(line_item, :with_quantity) %></td>
33
+ </tr>
34
+ <% end %>
35
+ </table>
36
+
37
+ <br>
38
+
39
+ <p><b>Total</b></p>
40
+
41
+ <table class="order">
42
+
43
+ <% @prices.each do |price| %>
44
+ <tr><td width="120"><%= price.name.capitalize %></td><td align="right"><%= price.label %></td></tr>
45
+ <% end %>
46
+
47
+ <tr>
48
+ <td><%= Spree.t(:total) %></td>
49
+ <td align="right"><b><%= @total_price.label %></b></td>
50
+ </tr>
51
+ <tr>
52
+ <td>Payment method</td>
53
+ <td align="right"><%= @order.flow_paymeny_method == 'paypal' ? 'PayPal' : 'Credit Card' %></td>
54
+ </tr>
55
+ </table>
56
+
57
+ <br>
58
+
59
+ <% ['ship', 'bill'].each do |name|
60
+ address = @order.send('%s_address' % name)
61
+ %>
62
+ <p><b><%= name.capitalize %>ing address</b></p>
63
+
64
+ <table class="order">
65
+ <tr>
66
+ <td>Full name</td>
67
+ <td><%= address.firstname %> <%= address.lastname %></td>
68
+ </tr>
69
+ <tr>
70
+ <td>Address</td>
71
+ <td><%= address.address1 %></td>
72
+ </tr>
73
+ <tr>
74
+ <td>City</td>
75
+ <td><%= address.city %></td>
76
+ </tr>
77
+ <tr>
78
+ <td>Country</td>
79
+ <td><%= address.country.name rescue '-' %>, <%= address.state.name rescue '-' %></td>
80
+ </tr>
81
+ </table>
82
+ <br />
83
+ <% end %>
84
+
85
+ <p><%= Spree.t('order_mailer.confirm_email.thanks') %></p>
@@ -0,0 +1,41 @@
1
+ <%
2
+ @prices = @order.flow_cart_breakdown
3
+ @total_price = @prices.pop
4
+ %>
5
+
6
+ Dear <%= @order.bill_address.firstname %>
7
+ ---
8
+
9
+ <%= Spree.t('order_mailer.confirm_email.instructions') %>
10
+
11
+ <%= Spree.t('order_mailer.confirm_email.order_summary') %>
12
+ ---
13
+ Product | Price | Quantity |Total
14
+ <% @order.line_items.each do |line_item| %>
15
+ <%= line_item.variant.product.name %> | <%= @order.flow_line_item_price(line_item) %> | <%= line_item.quantity %> | <%= @order.flow_line_item_price(line_item, :with_quantity) %>
16
+ <% end %>
17
+
18
+ Total
19
+ ---
20
+ <% @prices.each do |price| %>
21
+ <%= price.name.ljust(15) %>: <%= price.label %>
22
+ <% end %>
23
+
24
+ <%= Spree.t(:total).ljust(15) %>: <%= @total_price[1] %>
25
+ Payment method : <%= @order.flow_paymeny_method == 'paypal' ? 'PayPal' : 'Credit Card' %>
26
+
27
+ <% ['ship', 'bill'].each do |name|
28
+ address = @order.send('%s_address' % name)
29
+ %>
30
+ <%= name.capitalize %>ing address
31
+ ---
32
+ Full name : <%= address.firstname %> <%= address.lastname %>
33
+ Address : <%= address.address1 %>
34
+ City : <%= address.city %>
35
+ Country : <%= address.country.name rescue '-' %>, <%= address.state.name rescue '-' %>
36
+
37
+
38
+ <% end %>
39
+
40
+ <%= Spree.t('order_mailer.confirm_email.thanks') %>
41
+
@@ -0,0 +1,248 @@
1
+ require 'flowcommerce'
2
+ require 'thread/pool'
3
+ require 'digest/sha1'
4
+ require 'colorize'
5
+
6
+ desc 'lists all flow tasks'
7
+ task :flow do |t|
8
+ command = 'rake -T | grep flow'
9
+ puts ' %s' % command
10
+ puts ' -'
11
+
12
+ tasks = `#{command}`.split($/)
13
+
14
+ tasks.shift # first task is this on, rake flow
15
+
16
+ tasks.each_with_index do |task, index|
17
+ puts ' %d. %s' % [index + 1, task]
18
+ end
19
+
20
+ puts ' -'
21
+ print 'Task number: '
22
+ task = $stdin.gets.to_i
23
+ task = tasks[task - 1].to_s.split(/\s+/)[1]
24
+
25
+ if task.present?
26
+ puts 'Executing: %s' % task
27
+ Rake::Task[task].invoke
28
+ else
29
+ puts 'task not found'.red
30
+ end
31
+ end
32
+
33
+ namespace :flow do
34
+ # uploads catalog to flow api
35
+ # using local solidus database
36
+ # run like 'rake flow:upload_catalog[:force]'
37
+ # if you want to force update all products
38
+ desc 'Upload catalog'
39
+ task :upload_catalog => :environment do |t|
40
+ # do reqests in paralel
41
+ # thread_pool = Thread.pool(5)
42
+ update_sum = 0
43
+ total_sum = 0
44
+ current_page = 0
45
+ variants = []
46
+
47
+ while current_page == 0 || variants.length > 0
48
+ current_page += 1
49
+ variants = Spree::Variant.order('updated_at desc').page(current_page).per(100).all
50
+
51
+ variants.each_with_index do |variant, i|
52
+ total_sum += 1
53
+
54
+ # multiprocess upload
55
+ # thread_pool.process do
56
+ # skip if sync not needed
57
+ if variant.flow_sync_product
58
+ update_sum += 1
59
+ $stdout.print "\n%s: %s (%s %s)" % [variant.id.to_s, variant.product.name, variant.price, variant.cost_currency]
60
+ else
61
+ $stdout.print '.'
62
+ end
63
+ # end
64
+ end
65
+ end
66
+
67
+ # thread_pool.shutdown
68
+
69
+ puts "\nFor total of %s products, %s needed update" % [total_sum.to_s.blue, (update_sum == 0 ? 'none' : update_sum).to_s.green]
70
+ end
71
+
72
+ desc 'Check if ENV vars, center and tier per experience is set'
73
+ task check: :environment do
74
+ puts 'Environment check'
75
+ required_env_variables = ['FLOW_API_KEY', 'FLOW_ORGANIZATION', 'FLOW_BASE_COUNTRY']
76
+ required_env_variables.each { |el| puts ' ENV: %s - %s ' % [el, ENV[el].present? ? 'present'.green : 'MISSING'.red] }
77
+
78
+ # experiences
79
+ puts 'Experiences:'
80
+ puts ' Getting experiences for flow org: %s' % Flow.organization
81
+ client = FlowCommerce.instance
82
+ experiences = client.experiences.get(Flow.organization)
83
+ puts ' Got %d experinences - %s'.green % [experiences.length, experiences.map(&:country).join(', ')]
84
+
85
+ # create detault experience unless one exists
86
+ puts 'Centers:'
87
+ center_name = 'default'
88
+ current_cetners = client.centers.get(Flow.organization).map(&:key)
89
+ if current_cetners.include?(center_name)
90
+ puts ' Default center: %s' % 'present'.green
91
+ else
92
+ Flow.api :put, '/:organization/centers/%s' % center_name, {}, {'key':center_name,'address':{'contact':{'name':{'first':'Kinto','last':'Doe'},'company':'XYZ Corporation, Inc','email':'dcone@test.flow.io','phone':'1-555-444-0001'},'location':{'streets':['88 East Broad Street'],'city':'Columbus','province':'OH','postal':'43215','country':'USA'}},'packaging':[{'dimensions':{'packaging':{'depth':{'value':'9','units':'inch'},'length':{'value':'13','units':'inch'},'weight':{'value':'1','units':'pound'},'width':{'value':'3','units':'inch'}}},'name':'Big Box','number':'box1'}],'name':'Solidus Test','services':[{'service':'dhl-express-worldwide'},{'service':'landmark-global'}],'schedule':{'holiday':'us_bank_holidays','exception':[{'type':'closed','datetime_range':{'from':'2016-05-05T18:30:00.000Z','to':'2016-05-06T18:30:00.000Z'}}],'calendar':'weekdays','cutoff':'16:30'},'timezone':'US/Eastern'}
93
+ puts ' Default center: %s (run again)' % 'created'.blue
94
+ end
95
+
96
+ # tiers
97
+ puts 'Tiers:'
98
+ tiers = FlowCommerce.instance.tiers.get(Flow.organization)
99
+ experiences.each do |exp|
100
+ exp_tiers = tiers.select{ |tier| tier.experience.id == exp.key }
101
+ count = exp_tiers.length
102
+ count_desc = count == 0 ? '0 (error!)'.red : count.to_s.green
103
+ print ' Experience %s has %s devivery tiers defined, ' % [exp.key.yellow, count_desc]
104
+
105
+ exp_services = exp_tiers.inject([]) { |total, tier| total.push(*tier.services.map(&:id)) }
106
+ if exp_services.length == 0
107
+ puts 'and no delivery services defined!'.red
108
+ else
109
+ puts 'with %s devlivery services defined (%s)' % [exp_services.length.to_s.green, exp_services.join(', ')]
110
+ end
111
+ end
112
+
113
+ # database fields
114
+ puts 'Database field (flow_data)'
115
+ for klass in [Spree::CreditCard, Spree::Product, Spree::Variant, Spree::Order, Spree::Promotion]
116
+ state = klass.new.respond_to?(:flow_data) ? 'exists'.green : 'not present (run rake flow:migrate)'.red
117
+ puts ' %s - %s' % [klass.to_s.ljust(18), state]
118
+ end
119
+
120
+ # default URL
121
+ puts 'Default store URL:'
122
+ url = Spree::Store.find_by(default:true).url
123
+ puts ' Spree::Store.find_by(default:true).url == "%s" (ensure this is valid and right URL)' % url.blue
124
+
125
+ # rate cards
126
+ puts 'Rate cards (checking shipping from USA to China): '
127
+ data = Flow.api :post, '/:organization/ratecard_estimates', {}, { origin:'USA', destination: 'China' }
128
+ if data.is_a?(Array) && data.length > 0
129
+ puts ' Rate cards set, OK'.green
130
+ else
131
+ puts ' error!'.red
132
+ ap data
133
+ end
134
+ end
135
+
136
+ desc 'Sync localized catalog items'
137
+ task :sync_localized_items => :environment do |t|
138
+ # https://api.flow.io/reference/countries
139
+ # https://docs.flow.io/#/module/localization/resource/experiences
140
+
141
+ # we have to log start, so that another process does not start while this one is running
142
+ FolwApiRefresh.log_refresh!
143
+
144
+ start = Time.now
145
+
146
+ total = 0
147
+
148
+ experiences = FlowCommerce.instance.experiences.get(Flow.organization)
149
+
150
+ experiences.each do |experience|
151
+ page_size = 100
152
+ offset = 0
153
+ items = []
154
+
155
+ while offset == 0 || items.length == 100
156
+ # show current list size
157
+ puts "\nGetting items: %s, rows %s - %s" % [experience.key.green, offset, offset + page_size]
158
+
159
+ items = FlowCommerce.instance.experiences.get_items Flow.organization, experience: experience.key, limit: page_size, offset: offset
160
+
161
+ offset += page_size
162
+
163
+ items.each do |item|
164
+ total += 1
165
+ sku = item.number.downcase
166
+ variant = Spree::Variant.find_by id: sku.split('-').last.to_i
167
+ next unless variant
168
+
169
+ # if item is not included, mark it in product as excluded
170
+ # regardles if excluded or restricted
171
+ unless item.local.status.value == 'included'
172
+ print '[%s]:' % item.local.status.value.red
173
+ if product = variant.product
174
+ product.flow_data['%s.excluded' % experience.key] = 1
175
+ product.update_column :flow_data, product.flow_data.dup
176
+ end
177
+ end
178
+
179
+ variant.flow_import_item item
180
+
181
+ print '%s, ' % sku
182
+ end
183
+ end
184
+ end
185
+
186
+ # low log duration
187
+ FolwApiRefresh.log_refresh! start
188
+
189
+ puts 'Finished with total of %s rows.' % total.to_s.green
190
+ end
191
+
192
+ # checks existance of every item in local produt catalog
193
+ # remove product from flow unless exists localy
194
+ desc 'Remove unused items from flow catalog'
195
+ task clean_catalog: :environment do
196
+
197
+ page_size = 100
198
+ offset = 0
199
+ items = []
200
+
201
+ thread_pool = Thread.pool(5)
202
+
203
+ while offset == 0 || items.length == 100
204
+ items = Flow.api :get, '/:organization/catalog/items', limit: page_size, offset: offset
205
+ offset += page_size
206
+
207
+ items.each do |item|
208
+ sku = item['number']
209
+
210
+ do_remove = false
211
+ do_remove = true if sku.to_i == 0 || sku.to_i.to_s != sku
212
+ do_remove = true if !do_remove && !Spree::Variant.find(sku.to_i)
213
+
214
+ next unless do_remove
215
+
216
+ thread_pool.process do
217
+ Flow.api :delete, '/:organization/catalog/items/%s' % sku
218
+ $stdout.puts 'Removed item: %s' % sku.red
219
+ end
220
+ end
221
+ end
222
+
223
+ thread_pool.shutdown
224
+ end
225
+
226
+ # creates needed fields in DB for Flow to work
227
+ desc 'Ensure we have DB prepared for flow'
228
+ task :migrate => :environment do |t|
229
+ migrate = []
230
+ migrate.push [:spree_credit_cards, :flow_data, :jsonb, default: {}]
231
+ migrate.push [:spree_products, :flow_data, :jsonb, default: {}]
232
+ migrate.push [:spree_variants, :flow_data, :jsonb, default: {}]
233
+ migrate.push [:spree_orders, :flow_data, :jsonb, default: {}]
234
+ migrate.push [:spree_promotions, :flow_data, :jsonb, default: {}]
235
+
236
+ migrate.each do |table, field, type, opts={}|
237
+ klass = table.to_s.sub('spree_','spree/').classify.constantize
238
+
239
+ if klass.new.respond_to?(field)
240
+ puts 'Field %s in table %s exists'.green % [field, table]
241
+ else
242
+ ActiveRecord::Migration.add_column table, field, type, opts
243
+ puts 'Field %s in table %s added'.blue % [field, table]
244
+ end
245
+ end
246
+ end
247
+ end
248
+