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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c0101a0d46954d016cd3dac32a96708081c02d77
4
+ data.tar.gz: 7311c515495ab8d4f004c641619e590c17c70a2d
5
+ SHA512:
6
+ metadata.gz: e6a81592b490a7ea0b8efe4f1a9673eb1e3a606798d81c862a7cf8eb66595bf03be586504dc2986bce2cc184fe9aeaacb0016a3b02306d52be5ae72d67d34747
7
+ data.tar.gz: 7c2ba0914d7c8d5e39435e9711b1daf15dec210eca659d244caae85fa0719b1a9968c61064107281f694d90a83373833b979120b27c51af94fb980e45fe8173d
data/.version ADDED
@@ -0,0 +1 @@
1
+ 0.1.11
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'thor'
4
+ require 'fileutils'
5
+ require 'colorize'
6
+
7
+ module Helper
8
+ extend self
9
+
10
+ def copy_with_path src, destionation
11
+ target_dir = File.dirname(destionation)
12
+
13
+ FileUtils.mkdir_p(target_dir) unless Dir.exists?(target_dir)
14
+ FileUtils.cp src, destionation
15
+ end
16
+
17
+ def die text
18
+ puts text.red
19
+ exit
20
+ end
21
+
22
+ def glob_files root_folders
23
+ Cli.die './app not found, not in Rails root' unless Dir.exists?('%s/app' % Dir.pwd)
24
+
25
+ root_folder = `gem which flowcommerce-solidus`.chomp.sub('/lib/flowcommerce-solidus.rb', '/static')
26
+
27
+ files = root_folders.inject([]) do |total, folder|
28
+ total += Dir['%s/%s/**/*' % [root_folder, folder]]
29
+ end
30
+
31
+ files = files
32
+ .map { |f| f.sub('%s/' % root_folder, '') }
33
+ .select{ |f| f =~ /\.\w+$/ }
34
+
35
+ has_different_size_file = false
36
+
37
+ files.each do |file|
38
+ source = [root_folder, file].join('/')
39
+ target = [Dir.pwd, file].join('/')
40
+
41
+ do_copy = if File.exists?(target)
42
+ if File.size(target) == File.size(source)
43
+ print 'same size'.rjust(15).green
44
+ else
45
+ print 'different size'.rjust(15).blue
46
+ has_different_size_file = true
47
+ end
48
+
49
+ false
50
+ else
51
+ print 'copied'.rjust(15).green
52
+ true
53
+ end
54
+
55
+ Helper.copy_with_path source, target if do_copy
56
+
57
+ puts ' %s' % file
58
+ end
59
+
60
+ puts
61
+ puts ' '*14+'* files with different sizes you have to first deleted or renamed'.yellow if has_different_size_file
62
+ puts ' '*14+'* checked %d files'.yellow % files.length
63
+ end
64
+ end
65
+
66
+ ###
67
+
68
+ class FlowcommerceSolidus < Thor
69
+ desc 'install', 'Installs Flow Solidus libs in current installation, if missing.'
70
+ def install
71
+ Helper.glob_files ['app/flow', 'app/views/spree/shared', 'lib']
72
+ end
73
+
74
+ desc 'install_admin_views', 'Installs views (erb templates) in ./app/views/admin'
75
+ def install_admin_views
76
+ Helper.glob_files ['app/views/spree/admin']
77
+ end
78
+
79
+ desc 'install_email_views', 'Installs custom Flow views (erb templates) for email localization'
80
+ def install_email_views
81
+ Helper.glob_files ['app/views/spree/order_mailer']
82
+ end
83
+
84
+ desc 'dev_copy [folder]','Copy files from local solids to gem', :hide => true
85
+ def copy_to_gem solidus_folder=nil
86
+ Cli.die 'Solidus target folder not defined' unless solidus_folder
87
+ Cli.die './app not found' unless Dir.exists?('%s/app' % solidus_folder)
88
+ Cli.die 'Not executed in gem root folder' unless $0.include('/flowcommerce-solidus/bin')
89
+
90
+ `rm -rf ./static`
91
+ `mkdir ./static`
92
+
93
+ # add flow folder
94
+ files = Dir['%s/app/flow/**/*' % solidus_folder]
95
+
96
+ # admin view changes
97
+ files += Dir['%s/app/views/admin/*' % solidus_folder]
98
+
99
+ # add rake tasks
100
+ files += ['%s/lib/tasks/flow.rake' % solidus_folder]
101
+
102
+ # relativise paths and remove folders
103
+ files = files
104
+ .map { |f| f.sub(solidus_folder+'/', '') }
105
+ .select{ |f| f =~ /\.\w+$/ }
106
+
107
+ # copy file to ./static dir in gem home
108
+ files.each do |file|
109
+ source = [solidus_folder, file].join('/')
110
+ target = './static/%s' % file
111
+
112
+ Helper.copy_with_path source, target
113
+ end
114
+
115
+ puts files
116
+
117
+ puts 'Copied %d files to ./static'.green % files.length
118
+ end
119
+ end
120
+
121
+ FlowcommerceSolidus.start ARGV
@@ -0,0 +1,7 @@
1
+ require 'flowcommerce'
2
+
3
+ module Flowcommerce
4
+ module Solidus
5
+
6
+ end
7
+ end
@@ -0,0 +1,77 @@
1
+ <img align="right" src="http://i.imgur.com/tov8bTw.png">
2
+
3
+ # Flow.io adapter Solidus/Spree
4
+
5
+ Work in progress. This will be converted to gem.
6
+
7
+ All flow libs are located in ./app/flow folder with exception of two controllers
8
+ ApplicationController and FlowController that are present in ./app/controllers folder.
9
+
10
+
11
+ ## Instalation
12
+
13
+ Define this additional ENV variables. You will find them in [Flow console](https://console.flow.io)
14
+
15
+ ```
16
+ FLOW_API_KEY='SUPERsecretTOKEN'
17
+ FLOW_ORGANIZATION='solidus-app-sandbox'
18
+ FLOW_BASE_COUNTRY='usa'
19
+ ```
20
+
21
+ In ```./config/application.rb``` this is the only peace of code that is needed to
22
+ init complete flow app
23
+
24
+ ```
25
+ config.to_prepare do
26
+ # add all flow libs
27
+ overload = Dir.glob('./app/flow/**/*.rb')
28
+ overload.reverse.each { |c| require(c) }
29
+ end
30
+
31
+ config.after_initialize do |app|
32
+ # init Flow payments as an option
33
+ app.config.spree.payment_methods << Spree::Gateway::Flow
34
+
35
+ # define defaults
36
+ Flow.organization = ENV.fetch('FLOW_ORGANIZATION')
37
+ Flow.base_country = ENV.fetch('FLOW_BASE_COUNTRY')
38
+ Flow.api_key = ENV.fetch('FLOW_API_KEY')
39
+ end
40
+ ```
41
+
42
+ in ./config/application.rb to enable payments with Flow.
43
+
44
+ ## Flow API specific
45
+
46
+ Classes that begin with Flow are responsible for comunicating with flow API.
47
+
48
+ ### Flow
49
+
50
+ Helper class that offeres low level flow api access and few helper methods.
51
+
52
+ ### Flow::Experience
53
+
54
+ Responsible for selecting current experience. You have to define available experiences in flow console.
55
+
56
+ ### Flow::Order
57
+
58
+ Maintain and synchronizes Spree::Order with Flow API.
59
+
60
+ ### Flow::Session
61
+
62
+ Every shop user has a session. This class helps in creating and maintaining session with Flow.
63
+
64
+ ## Decorators
65
+
66
+ Decorators are found in ./app/flow/decorators folders and they decorate Solidus/Spree models with Flow specific methods.
67
+
68
+ All methods are prefixed with ```flow_```.
69
+
70
+ ## Helper lib
71
+
72
+ ### Spree::Flow::Gateway
73
+
74
+ Adapter for Solidus/Spree, that allows using [Flow.io](https://www.flow.io) as payment gateway. Flow is PCI compliant payment processor.
75
+
76
+
77
+
@@ -0,0 +1,127 @@
1
+ # Flow, ActiveMerchant and Solidus integration
2
+
3
+ Integration of Solidus with Flow, how it is done.
4
+
5
+ I plan to be concise as possible, but cover all important topics.
6
+
7
+ ## Instalation
8
+
9
+ In ```./config/application.rb``` this is the only peace of code that is needed to
10
+ init complete flow app.
11
+
12
+ ```
13
+ config.to_prepare do
14
+ # add all flow libs
15
+ overload = Dir.glob('./app/flow/**/*.rb')
16
+ overload.reverse.each { |c| require(c) }
17
+ end
18
+
19
+ config.after_initialize do |app|
20
+ # init Flow payments as an option
21
+ app.config.spree.payment_methods << Spree::Gateway::Flow
22
+
23
+ # define defaults
24
+ Flow.organization = ENV.fetch('FLOW_ORGANIZATION')
25
+ Flow.base_country = ENV.fetch('FLOW_BASE_COUNTRY')
26
+ Flow.api_key = ENV.fetch('FLOW_API_KEY')
27
+ end
28
+ ```
29
+
30
+ ## Things to take into account
31
+
32
+ ActiveMerchent is not supporting sessions and orders, natively. If one wants
33
+ to maintain sessions and orders in Flow, you have to do it outside the ActiveMerchant
34
+ terminology which focuses around purchases, voids and refunds.
35
+
36
+ Another thing to have in mind is that Solidus/Spree can't work with ActiveMerchent directly, it has to have
37
+ an adapter. Adapter can be "stupid" and light, and can forward all the "heavy lifting" to ActiveMerchant gem
38
+ but it can also have all the logic localy.
39
+
40
+ In http://guides.spreecommerce.org/developer/payments.html at the bottom of the page Spree authors say
41
+
42
+ "better_spree_paypal_express and spree-adyen are good examples of standalone
43
+ custom gateways. No dependency on spree_gateway or activemerchant required."
44
+
45
+ Reading that we can see this is even considered good approach. For us, this is a possibility
46
+ but we consume ActiveMerchatFlow gem.
47
+
48
+ ## ActiveMerchant gem into more detail
49
+
50
+ https://github.com/flowcommerce/active_merchant
51
+
52
+ Sopporst stanard public ActiveMerchant actions which are
53
+ purchase, authorize, capture, void, store and refund.
54
+
55
+ It depends on following gems
56
+
57
+ * flowcommerce - api calls
58
+ * flow-reference - we use currency validations
59
+
60
+ It is not aware of Solidus or any other shopping lib or framework.
61
+
62
+ ### ActiveMerchant::Flow supported actions in detail
63
+
64
+ * purchase - shortcut for authorize and then capture
65
+ * authorize - authorize the cc and funds.
66
+ * capture - capture the funds
67
+ * void - cancel the transaction
68
+ * store - store credit card (gets credit card flow token)
69
+ * refund - refund the funds
70
+
71
+ ## Solidus/Spree Implementation in more detail
72
+
73
+ Not present as standalone gem, yet. I will do that once we agree on implementation details.
74
+
75
+ From product list to purchase, complete chain v1
76
+
77
+ 1. customer has to prepare data, migrate db and connect to Flow. In general
78
+ * create experiences in Flow console, add tiers, shipping methods, etc.
79
+ * add flow_data (jsonb) fields to this models
80
+ * Spree::Variant - we cache localized product prices
81
+ * Spree::Order - we cache flow order state details, shipping method
82
+ * create and sync product catalog via rake tasks
83
+ 1. now site users can browse prooducts and add them to cart.
84
+ 1. when user comes to shop, FlowSession is created
85
+ 1. once product is in cart
86
+ * spree order is created and linked to Experience that we get from FlowSession
87
+ * it is captured and synced with flow, realtime
88
+ * we do this all the time because we want to have 100% accurate prices.
89
+ Product prices that are shown in cart come directly from Flow API.
90
+ * in checkout, when customer address is added or shipping method defined,
91
+ all is synced with flow order.
92
+ * when order is complete, we trigger flow-cc-authorize or flow-cc-capture directly
93
+ on Spree::Order object instance. This is good because all gateway actions
94
+ are functions of the order object anyway.
95
+ * flow-cc-authorize or flow-cc-capture use ActiveMerchantFlow to execute this actions
96
+ * ActiveMerchantFlow included flow-reference gem
97
+
98
+ ## What can be better
99
+
100
+ We need a way to access the order in Rails. Access it after it is created in
101
+ controller but before it hits the render.
102
+ Current implementation is -> "overload" ApplicationController render
103
+ If we detect @spree_order object or debug flags, we react.
104
+
105
+ * good - elegant solution, all need code is in one file in few lines of code
106
+ * bad - somehow intrusive, we overload render, somw people will not like that.
107
+ * alternatives: gem that allows before_render bethod, call explicitly when needed
108
+
109
+ ## Aditional notes - view and frontend
110
+
111
+ I see many Solidus/Spree merchant gems carry frontend code, js, coffe, views etc.
112
+ I thing that this is bad practise and that shop frontend has to be 100% customer code.
113
+
114
+ What I did not see but thing is great idea is to have custom light Flow admin present at
115
+
116
+ /admin/flow
117
+
118
+ that will ease the way of working with Flow. Code can be made to be Rails 4 and Rails 5 compatibile.
119
+ Part of that is allready done as can be seen here [Flow admin screenshot](https://i.imgur.com/FXbPrwK.png)
120
+
121
+ By default Flow Admin (on /admin/flow) is anybody that is Solidus admin.
122
+
123
+ This way we provide good frontend info, some integration notes in realtime as opposed to running
124
+ rake tests to check for integrity of Flow integration.
125
+
126
+ I suggest we make it part of flow-solidus gem.
127
+
@@ -0,0 +1,34 @@
1
+ # Flow (2017)
2
+ # Enable this modifications if you want to display flow localized line item
3
+ # and total prices beside Solidus/Spree default
4
+ # Example: https://i.imgur.com/7v2ix2G.png
5
+
6
+ Spree::LineItem.class_eval do
7
+ # admin show line item price
8
+ def single_money
9
+ price = display_price.to_s
10
+ price += ' (%s)' % order.flow_line_item_price(self) if order.flow_order
11
+ price
12
+ end
13
+ end
14
+
15
+ ###
16
+
17
+ Spree::Order.class_eval do
18
+ def display_total
19
+ price = Flow.format_default_price total
20
+ price += ' (%s)' % flow_total if flow_order
21
+ price.html_safe
22
+ end
23
+ end
24
+
25
+ ###
26
+
27
+ module Spree::Admin::OrdersHelper
28
+ # admin show line item total price
29
+ def line_item_shipment_price(line_item, quantity)
30
+ price = Spree::Money.new(line_item.price * quantity, { currency: line_item.currency }).to_s
31
+ price += ' (%s)' % @order.flow_line_item_price(line_item, quantity) if @order.flow_order
32
+ price.html_safe
33
+ end
34
+ end
@@ -0,0 +1,49 @@
1
+ # Flow (2017)
2
+
3
+ Spree::Api::OrdersController.class_eval do
4
+
5
+ # /flow/promotion_set_option?id=3&type=experience&name=canada&value=1
6
+ # alias :apply_coupon_code_pointer :apply_coupon_code
7
+ def apply_coupon_code
8
+ # find promotion code
9
+ coupon_code = params[:coupon_code]
10
+ promotion_code = Spree::PromotionCode.find_by value: coupon_code
11
+
12
+ if promotion_code
13
+ # promotion code found
14
+ promotion = Spree::Promotion.find promotion_code.promotion_id
15
+
16
+ # get experience key from session
17
+ experience_data = JSON.load(session[ApplicationController::FLOW_SESSION_KEY] || '{}')
18
+
19
+ experience_key = experience_data.dig 'local', 'experience', 'key'
20
+ forbiden_keys = promotion.flow_data.dig 'filter', 'experience'
21
+
22
+ # Rails.logger.error 'experience_key: %s' % experience_key
23
+ # Rails.logger.error 'forbiden_keys: %s' % forbiden_keys.join(', ')
24
+
25
+ allowed = true
26
+ allowed = false if experience_key && forbiden_keys.is_a?(Array) && !forbiden_keys.include?(experience_key)
27
+
28
+ return render(status: 400, json: {
29
+ successful: false,
30
+ success: nil,
31
+ status_code: 'coupon_code_not_found',
32
+ error: 'Promotion is not available in current country'
33
+ }) unless allowed
34
+ end
35
+
36
+ # call original coupon handler
37
+ # apply_coupon_code_pointer
38
+
39
+ authorize! :update, @order, order_token
40
+ @order.coupon_code = params[:coupon_code]
41
+ @handler = Spree::PromotionHandler::Coupon.new(@order).apply
42
+ if @handler.successful?
43
+ render "spree/api/promotions/handler", status: 200
44
+ else
45
+ logger.error("apply_coupon_code_error=#{@handler.error.inspect}")
46
+ render "spree/api/promotions/handler", status: 422
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,35 @@
1
+ # added flow specific methods to Spree::CreditCard
2
+
3
+ Spree::CreditCard.class_eval do
4
+
5
+ validate :flow_fetch_cc_token
6
+
7
+ def flow_fetch_cc_token
8
+ return false unless respond_to?(:flow_data) # Flow not installed
9
+ return false if flow_data['cc_token']
10
+ return false unless number
11
+ return errors.add(:verification_value, 'CVV verification value is required') unless verification_value.present?
12
+
13
+ # build cc hash
14
+ data = {}
15
+ data[:number] = number
16
+ data[:name] = name
17
+ data[:cvv] = verification_value
18
+ data[:expiration_year] = year.to_i
19
+ data[:expiration_month] = month.to_i
20
+
21
+ card_form = ::Io::Flow::V0::Models::CardForm.new(data)
22
+ result = FlowCommerce.instance.cards.post(Flow.organization, card_form)
23
+
24
+ # cache the result
25
+ flow_data['cc_token'] = result.token
26
+
27
+ true
28
+ rescue Io::Flow::V0::HttpClient::ServerError
29
+ puts $!.message
30
+ flow_error = $!.message.split(':', 2).first
31
+ errors.add(:base, flow_error)
32
+ end
33
+
34
+ end
35
+