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,62 @@
1
+ # Flow.io (2017)
2
+ # communicates with flow api, responds to webhook events
3
+
4
+ class Flow::Webhook
5
+ attr_accessor :product
6
+ attr_accessor :variant
7
+
8
+ class << self
9
+ def process data, opts={}
10
+ web_hook = new data, opts
11
+ web_hook.process
12
+ end
13
+ end
14
+
15
+ ###
16
+
17
+ def initialize(data, opts={})
18
+ @data = data
19
+ @opts = opts
20
+ end
21
+
22
+ def process
23
+ @discriminator = @data['discriminator']
24
+
25
+ m = 'hook_%s' % @discriminator
26
+
27
+ return 'Error: No hook for %s' % @discriminator unless respond_to?(m)
28
+ raise ArgumentError, 'Organization name mismatch for %s' % @data['organization'] if @data['organization'] != Flow.organization
29
+
30
+ send(m)
31
+ end
32
+
33
+ # hooks
34
+
35
+ def hook_localized_item_upserted
36
+ raise ArgumentError, 'number not found' unless @data['number']
37
+ raise ArgumentError, 'local not found' unless @data['local']
38
+
39
+ number = @data['number']
40
+ exp_key = @data['local']['experience']['key']
41
+
42
+ # for testing we need ability to inject dependency for variant class
43
+ variant_class = @opts[:variant_class] || Spree::Variant
44
+
45
+ @variant = variant_class.find number
46
+ @product = @variant.product
47
+ is_included = @data['local']['status'] == 'included'
48
+
49
+ @product.flow_data['%s.excluded' % exp_key] = is_included ? 0 : 1
50
+
51
+ @product.save!
52
+
53
+ message = is_included ? 'included in' : 'excluded from'
54
+
55
+ 'Product id:%s - "%s" (from variant %s) %s experience "%s"' % [@product.id, @product.name, @variant.id, message, exp_key]
56
+ end
57
+
58
+ # we should consume only localized_item_upserted
59
+ def hook_subcatalog_item_upserted
60
+ 'not used'
61
+ end
62
+ end
@@ -0,0 +1,89 @@
1
+ # Flow.io (2017)
2
+ # helper class to manage product sync scheduling
3
+
4
+ require 'json'
5
+ require 'logger'
6
+
7
+ module FolwApiRefresh
8
+ extend self
9
+
10
+ SYNC_INTERVAL_IN_MINUTES = 60 unless defined?(SYNC_INTERVAL_IN_MINUTES)
11
+ CHECK_FILE = Pathname.new './tmp/last-flow-refresh.txt' unless defined?(CHECK_FILE)
12
+ LOGGER = Logger.new('./log/sync.log', 3, 1024000) unless defined?(LOGGER)
13
+
14
+ ###
15
+
16
+ def get_data
17
+ CHECK_FILE.exist? ? JSON.parse(CHECK_FILE.read) : {}
18
+ end
19
+
20
+ def write
21
+ data = get_data
22
+ yield data
23
+ CHECK_FILE.write data.to_json
24
+ data
25
+ end
26
+
27
+ def log message
28
+ $stdout.puts message
29
+ LOGGER.info '%s (pid/ppid: %d/%d)' % [message, Process.pid, Process.ppid]
30
+ end
31
+
32
+ def schedule_refresh!
33
+ write do |data|
34
+ data['force_refresh'] = true
35
+ end
36
+ end
37
+
38
+ def log_refresh! start_time=nil
39
+ write do |data|
40
+ if start_time
41
+ data['force_refresh'] = false
42
+ data['duration_in_seconds'] = Time.now.to_i - start_time.to_i if start_time
43
+ data['start'] = start_time.to_i
44
+ data['end'] = Time.now.to_i
45
+ data.delete('started')
46
+ else
47
+ data['started'] = true
48
+ data['start'] = Time.now.to_i
49
+ end
50
+ end
51
+ end
52
+
53
+ def last_refresh
54
+ json = get_data
55
+
56
+ return 'No last sync data' unless json['end']
57
+
58
+ info = []
59
+
60
+ info.push 'Sync started %d seconds ago (it is in progress).' % (Time.now.to_i - json['start'].to_i) if json['started']
61
+
62
+ info.push 'Last sync finished %{finished} minutes ago and lasted for %{duration} sec. We sync every %{every} minutes.' %
63
+ {
64
+ finished: (Time.now.to_i - json['end'].to_i)/60,
65
+ duration: json['duration_in_seconds'] || '?',
66
+ every: SYNC_INTERVAL_IN_MINUTES
67
+ }
68
+
69
+ info.join(' ')
70
+ end
71
+
72
+ def sync_products_if_needed!
73
+ json = get_data
74
+
75
+ sync_needed = false
76
+
77
+ unless json['started']
78
+ sync_needed ||= true if json['force_refresh']
79
+ sync_needed ||= true if json['start'].to_i < (Time.now.to_i - SYNC_INTERVAL_IN_MINUTES * 60)
80
+ end
81
+
82
+ if sync_needed
83
+ log 'Sync needed, running ...'
84
+ system 'bundle exec rake flow:sync_localized_items'
85
+ else
86
+ log last_refresh
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,86 @@
1
+ # Flow.io (2017)
2
+ # adapter for Solidus/Spree that talks to activemerchant_flow
3
+
4
+ # load '/Users/dux/dev/org/flow.io/activemerchant_flow/lib/active_merchant/billing/gateways/flow.rb'
5
+
6
+ module Spree
7
+ class Gateway::Flow < Gateway
8
+ def provider_class
9
+ self.class
10
+ end
11
+
12
+ def actions
13
+ %w(capture authorize purchase refund void)
14
+ end
15
+
16
+ # if user wants to force auto capture
17
+ def auto_capture?
18
+ false
19
+ end
20
+
21
+ def payment_profiles_supported?
22
+ false
23
+ end
24
+
25
+ def method_type
26
+ 'gateway'
27
+ end
28
+
29
+ def preferences
30
+ {}
31
+ end
32
+
33
+ # def create_profile(payment)
34
+ # # binding.pry
35
+ # # ActiveMerchant::Billing::FlowGateway.new(token: Flow.api_key, organization: Flow.organization)
36
+
37
+ # case payment.order.state
38
+ # when 'payment'
39
+ # when 'confirm'
40
+ # end
41
+ # end
42
+
43
+ def supports?(source)
44
+ # flow supports credit cards
45
+ source.class == Spree::CreditCard
46
+ end
47
+
48
+ def authorize(amount, payment_method, options={})
49
+ order = load_order options
50
+ order.cc_authorization
51
+ end
52
+
53
+ def capture(amount, payment_method, options={})
54
+ order = load_order options
55
+ order.cc_capture
56
+ end
57
+
58
+ def purchase(amount, payment_method, options={})
59
+ order = load_order options
60
+ flow_auth = order.cc_authorization
61
+
62
+ if flow_auth.success?
63
+ order.cc_capture
64
+ else
65
+ flow_auth
66
+ end
67
+ end
68
+
69
+ def refund(money, authorization_key, options={})
70
+ order = load_order options
71
+ order.cc_refund
72
+ end
73
+
74
+ def void(money, authorization_key, options={})
75
+ # binding.pry
76
+ end
77
+
78
+ private
79
+
80
+ def load_order(options)
81
+ order_number = options[:order_id].split('-').first
82
+ spree_order = Spree::Order.find_by number: order_number
83
+ ::Flow::SimpleGateway.new spree_order
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,142 @@
1
+ # require 'flowcommerce-activemerchant'
2
+
3
+ # module Spree
4
+ # class Gateway::StripeGateway < Gateway
5
+ # # https://dashboard.stripe.com/account/apikeys
6
+
7
+ # preference :secret_key, :string
8
+ # preference :publishable_key, :string
9
+
10
+ # CARD_TYPE_MAPPING = {
11
+ # 'American Express' => 'american_express',
12
+ # 'Diners Club' => 'diners_club',
13
+ # 'Visa' => 'visa'
14
+ # }
15
+
16
+ # def method_type
17
+ # 'gateway'
18
+ # end
19
+
20
+ # def provider_class
21
+ # ActiveMerchant::Billing::FlowGatewaya
22
+ # end
23
+
24
+ # def payment_profiles_supported?
25
+ # true
26
+ # end
27
+
28
+ # def purchase(money, creditcard, gateway_options)
29
+ # ap [:purchase, money, creditcard, gateway_options]
30
+ # binding.pry
31
+ # provider.purchase(*options_for_purchase_or_auth(money, creditcard, gateway_options))
32
+ # end
33
+
34
+ # def authorize(money, creditcard, gateway_options)
35
+ # ap [:authorize, money, creditcard, gateway_options]
36
+ # binding.pry
37
+ # provider.authorize(*options_for_purchase_or_auth(money, creditcard, gateway_options))
38
+ # end
39
+
40
+ # def capture(money, response_code, gateway_options)
41
+ # ap [:capture, money, response_code, gateway_options]
42
+ # binding.pry
43
+ # provider.capture(money, response_code, gateway_options)
44
+ # end
45
+
46
+ # def credit(money, creditcard, response_code, gateway_options)
47
+ # ap [:credit, money, creditcard, response_code, gateway_options]
48
+ # binding.pry
49
+ # provider.refund(money, response_code, {})
50
+ # end
51
+
52
+ # def void(response_code, creditcard, gateway_options)
53
+ # ap [:void, response_code, creditcard, gateway_options]
54
+ # binding.pry
55
+ # provider.void(response_code, {})
56
+ # end
57
+
58
+ # def cancel(response_code)
59
+ # ap [:cancel, response_code]
60
+ # provider.void(response_code, {})
61
+ # end
62
+
63
+ # def create_profile(payment)
64
+ # ap [:create_profile, payment]
65
+ # return unless payment.source.gateway_customer_profile_id.nil?
66
+ # options = {
67
+ # email: payment.order.email,
68
+ # login: preferred_secret_key,
69
+ # }.merge! address_for(payment)
70
+
71
+ # source = update_source!(payment.source)
72
+ # if source.number.blank? && source.gateway_payment_profile_id.present?
73
+ # creditcard = source.gateway_payment_profile_id
74
+ # else
75
+ # creditcard = source
76
+ # end
77
+
78
+ # response = provider.store(creditcard, options)
79
+ # if response.success?
80
+ # payment.source.update_attributes!({
81
+ # cc_type: payment.source.cc_type, # side-effect of update_source!
82
+ # gateway_customer_profile_id: response.params['id'],
83
+ # gateway_payment_profile_id: response.params['default_source'] || response.params['default_card']
84
+ # })
85
+
86
+ # else
87
+ # payment.send(:gateway_error, response.message)
88
+ # end
89
+ # end
90
+
91
+ # private
92
+
93
+ # # In this gateway, what we call 'secret_key' is the 'login'
94
+ # def options
95
+ # options = super
96
+ # options.merge(:login => preferred_secret_key)
97
+ # end
98
+
99
+ # def options_for_purchase_or_auth(money, creditcard, gateway_options)
100
+ # options = {}
101
+ # options[:description] = "Spree Order ID: #{gateway_options[:order_id]}"
102
+ # options[:currency] = gateway_options[:currency]
103
+
104
+ # if customer = creditcard.gateway_customer_profile_id
105
+ # options[:customer] = customer
106
+ # end
107
+ # if token_or_card_id = creditcard.gateway_payment_profile_id
108
+ # # The Stripe ActiveMerchant gateway supports passing the token directly as the creditcard parameter
109
+ # # The Stripe ActiveMerchant gateway supports passing the customer_id and credit_card id
110
+ # # https://github.com/Shopify/active_merchant/issues/770
111
+ # creditcard = token_or_card_id
112
+ # end
113
+ # return money, creditcard, options
114
+ # end
115
+
116
+ # def address_for(payment)
117
+ # {}.tap do |options|
118
+ # if address = payment.order.bill_address
119
+ # options.merge!(address: {
120
+ # address1: address.address1,
121
+ # address2: address.address2,
122
+ # city: address.city,
123
+ # zip: address.zipcode
124
+ # })
125
+
126
+ # if country = address.country
127
+ # options[:address].merge!(country: country.name)
128
+ # end
129
+
130
+ # if state = address.state
131
+ # options[:address].merge!(state: state.name)
132
+ # end
133
+ # end
134
+ # end
135
+ # end
136
+
137
+ # def update_source!(source)
138
+ # source.cc_type = CARD_TYPE_MAPPING[source.cc_type] if CARD_TYPE_MAPPING.include?(source.cc_type)
139
+ # source
140
+ # end
141
+ # end
142
+ # end
@@ -0,0 +1,37 @@
1
+ <%= render partial: 'spree/admin/shared/order_tabs', locals: { current: "Payments" } %>
2
+
3
+ <% content_for :page_actions do %>
4
+ <% if @order.outstanding_balance? %>
5
+ <li id="new_payment_section">
6
+ <%= button_link_to Spree.t(:new_payment), new_admin_order_payment_url(@order) %>
7
+ </li>
8
+ <% end %>
9
+ <% end %>
10
+
11
+ <% admin_breadcrumb(plural_resource_name(Spree::Payment)) %>
12
+
13
+ <% if @order.outstanding_balance? %>
14
+ <h5 class="outstanding-balance"><%= @order.outstanding_balance < 0 ? Spree.t(:credit_owed) : Spree.t(:balance_due) %>: <strong><%= @order.display_outstanding_balance %></strong></h5>
15
+ <% end %>
16
+
17
+ <% if @payments.any? %>
18
+
19
+ <fieldset data-hook="payment_list" class="no-border-bottom">
20
+ <legend align="center"><%= plural_resource_name(Spree::Payment) %></legend>
21
+ <%= render :partial => 'list', :locals => { :payments => @payments } %>
22
+ </fieldset>
23
+
24
+ <% if @refunds.any? %>
25
+ <fieldset data-hook="payment_list" class="no-border-bottom">
26
+ <legend align="center"><%= plural_resource_name(Spree::Refund) %></legend>
27
+ <%= render :partial => 'spree/admin/shared/refunds', :locals => { :refunds => @refunds, show_actions: true } %>
28
+ </fieldset>
29
+ <% end %>
30
+
31
+ <% else %>
32
+ <div class="col-xs-9 no-objects-found"><%= Spree.t(:order_has_no_payments) %></div>
33
+ <% end %>
34
+
35
+ <!-- SHOW REFUNDS START -->
36
+ <%= render '/flow/show_refunds' %>
37
+ <!-- SHOW REFUNDS END -->
@@ -0,0 +1,59 @@
1
+ <% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), spree.admin_promotions_path) %>
2
+ <% admin_breadcrumb(@promotion.name) %>
3
+
4
+
5
+ <% content_for :page_actions do %>
6
+ <li>
7
+ <% if can?(:display, Spree::PromotionCode) %>
8
+ <%= button_link_to Spree.t(:download_promotion_code_list), admin_promotion_promotion_codes_path(promotion_id: @promotion.id, format: :csv) %>
9
+ <% end %>
10
+ </li>
11
+ <% end %>
12
+
13
+ <%= form_for @promotion, :url => object_url, :method => :put do |f| %>
14
+ <fieldset class="no-border-top">
15
+ <%= render :partial => 'form', :locals => { :f => f } %>
16
+ <% if can?(:update, @promotion) %>
17
+ <%= render :partial => 'spree/admin/shared/edit_resource_links' %>
18
+ <% end %>
19
+ </fieldset>
20
+ <% end %>
21
+
22
+ <div id="promotion-filters" class="row">
23
+ <div id="rules_container" class="col-xs-6">
24
+ <%= render :partial => 'rules' %>
25
+ </div>
26
+
27
+ <div id="actions_container" class="col-xs-6">
28
+ <%= render :partial => 'actions' %>
29
+ </div>
30
+ </div>
31
+
32
+ <!-- Flow filters experience -->
33
+
34
+ <%
35
+ @promotion_keys = @promotion.flow_data.dig('filter', 'experience') || []
36
+ %>
37
+
38
+ <script>
39
+ window.promotion_set_option = function(key_name, value) {
40
+ var opts = {
41
+ id: <%= @promotion.id %>,
42
+ type: 'experience',
43
+ name: key_name,
44
+ value: value ? 1 : 0
45
+ };
46
+
47
+ $.post('/flow/promotion_set_option', opts, function(r) { console.log(r); });
48
+ }
49
+ </script>
50
+
51
+ <fieldset>
52
+ <legend align="center">Enable for Flow experiences</legend>
53
+
54
+ <p>If you do not select single experience, promotion will be enabled for all experiences.</p>
55
+
56
+ <% for experience in Flow::Experience.all %>
57
+ <p><label><input type="checkbox" onclick="promotion_set_option('<%= experience.key %>', this.checked);" <%= @promotion_keys.include?(experience.key) ? 'checked="1"' : '' %> /> <%= experience.key %></label></p>
58
+ <% end %>
59
+ </fieldset>