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
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
+