flowcommerce-solidus 0.1.11

Sign up to get free protection for your applications and to get access to all the features.
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
+