spree_affirm 0.2.19 → 0.2.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +15 -0
  3. data/Gemfile +19 -0
  4. data/LICENSE +26 -0
  5. data/README.md +47 -0
  6. data/Rakefile +15 -0
  7. data/Versionfile +5 -0
  8. data/app/assets/javascripts/spree/backend/spree_affirm.js +3 -0
  9. data/app/assets/javascripts/spree/frontend/spree_affirm.js +2 -0
  10. data/app/assets/stylesheets/spree/backend/spree_affirm.css +4 -0
  11. data/app/assets/stylesheets/spree/frontend/spree_affirm.css +4 -0
  12. data/app/controllers/spree/affirm_controller.rb +127 -0
  13. data/app/models/affirm/address_validator.rb +25 -0
  14. data/app/models/spree/affirm_checkout.rb +147 -0
  15. data/app/models/spree/gateway/affirm.rb +58 -0
  16. data/app/views/spree/admin/log_entries/_affirm.html.erb +4 -0
  17. data/app/views/spree/admin/log_entries/index.html.erb +28 -0
  18. data/app/views/spree/admin/payments/source_forms/_affirm.html.erb +19 -0
  19. data/app/views/spree/admin/payments/source_views/_affirm.html.erb +12 -0
  20. data/app/views/spree/checkout/affirm/_learn_more.html.erb +0 -0
  21. data/app/views/spree/checkout/payment/_affirm.html.erb +190 -0
  22. data/bin/rails +7 -0
  23. data/config/locales/en.yml +31 -0
  24. data/config/routes.rb +5 -0
  25. data/db/migrate/20140514194315_create_affirm_checkout.rb +10 -0
  26. data/lib/active_merchant/billing/affirm.rb +161 -0
  27. data/lib/generators/spree_affirm/install/install_generator.rb +21 -0
  28. data/lib/spree_affirm.rb +2 -0
  29. data/lib/spree_affirm/engine.rb +27 -0
  30. data/lib/spree_affirm/factories.rb +5 -0
  31. data/lib/spree_affirm/factories/affirm_checkout_factory.rb +219 -0
  32. data/lib/spree_affirm/factories/affirm_payment_method_factory.rb +8 -0
  33. data/lib/spree_affirm/factories/payment_factory.rb +10 -0
  34. data/lib/spree_affirm/version.rb +3 -0
  35. data/spec/controllers/affirm_controller_spec.rb +138 -0
  36. data/spec/lib/active_merchant/billing/affirm_spec.rb +294 -0
  37. data/spec/models/affirm_address_validator_spec.rb +13 -0
  38. data/spec/models/spree_affirm_checkout_spec.rb +349 -0
  39. data/spec/models/spree_gateway_affirm_spec.rb +137 -0
  40. data/spec/spec_helper.rb +98 -0
  41. data/spree_affirm.gemspec +30 -0
  42. metadata +53 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 38559017f05c14c9451465ff332deb9a3e237a5c
4
- data.tar.gz: 0a081b035ff825752b9d2d9747a1957c177b7fa3
3
+ metadata.gz: 45df2137b956abd8f68319faa39cae1459d4caf3
4
+ data.tar.gz: 297f6f51a606ff37ff125d3c23f13b75530bd819
5
5
  SHA512:
6
- metadata.gz: 02bcd7fcedcabdf7c640460cc1843d0e61b7274e5a821e8f27733c83f3fc700aff11f5daeff92fce17d6e3145dfc79a8b6ab254363a232f6d39c0ddbc296b274
7
- data.tar.gz: 1a5e7405b1a56f03653a44f7104108f6a475dbcb9657c1c3cc60dab60c408c6d7abf5ee295e00813e5855131e0bc434f6227e17eba738d25eb772112fec7e301
6
+ metadata.gz: c5c4f528a7067637547b78c95759ba6d93c21716e4b3bbc171ed7bbef59bc5658d4a791eacecf0ac067ae19c3c373e6e020be33ad8f6388809a630f56ef009af
7
+ data.tar.gz: 472759dd5657dbffb708b654012798a8adf382336fa26d1530a746a1473f77980ab752fdb622cf7526bd28e64e41b6d9d3e41d1cca839fd8beebf72f45eda56a
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ \#*
2
+ *~
3
+ .#*
4
+ .DS_Store
5
+ .idea
6
+ .project
7
+ .sass-cache
8
+ coverage
9
+ Gemfile.lock
10
+ tmp
11
+ nbproject
12
+ pkg
13
+ *.swp
14
+ spec/dummy
15
+ .rspec
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'spree', github: 'spree/spree', branch: '2-3-stable'
4
+ gem 'spree_gateway', github: 'spree/spree_gateway', branch: '2-3-stable'
5
+ gem 'spree_auth_devise', github: 'spree/spree_auth_devise', branch: '2-3-stable'
6
+
7
+ group :test do
8
+ gem 'coffee-rails'
9
+ gem 'sass-rails', '~> 4.0.3'
10
+ gem 'money', '6.0.1'
11
+ gem 'rspec-rails', '~> 2.10'
12
+ gem 'shoulda-matchers', '2.2.0'
13
+ gem 'simplecov', :require => false
14
+ gem 'simplecov-rcov'
15
+ gem 'database_cleaner'
16
+ gem 'minitest'
17
+ end
18
+
19
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2014 [name of plugin creator]
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name Spree nor the names of its contributors may be used to
13
+ endorse or promote products derived from this software without specific
14
+ prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ SpreeAffirm
2
+ ===========
3
+
4
+ Installation
5
+ ------------
6
+
7
+ 1. Add this extension to your Gemfile with this line:
8
+
9
+ gem 'spree_affirm', :github => "affirm/spree_affirm"
10
+
11
+ 2. Install the gem using Bundler:
12
+
13
+ bundle install
14
+
15
+ 3. Copy & run migrations
16
+
17
+ bundle exec rails g spree_affirm:install
18
+
19
+ 4. Restart your server
20
+
21
+ ## Contributing
22
+
23
+ 1. Fork it
24
+ 2. Create your feature branch (```git checkout -b my-new-feature```).
25
+ 3. Commit your changes (```git commit -am 'Added some feature'```)
26
+ 4. Push to the branch (```git push origin my-new-feature```)
27
+ 5. Create new Pull Request
28
+
29
+ Testing
30
+ -------
31
+
32
+ Be sure to bundle your dependencies and then create a dummy test app for the specs to run against.
33
+
34
+ ```shell
35
+ bundle
36
+ bundle exec rake test_app
37
+ bundle exec rspec spec
38
+ ```
39
+
40
+ When testing your applications integration with this extension you may use it's factories.
41
+ Simply add this require statement to your spec_helper:
42
+
43
+ ```ruby
44
+ require 'spree_affirm/factories'
45
+ ```
46
+
47
+ Copyright (c) 2014 [name of extension creator], released under the New BSD License
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'spree/testing_support/extension_rake'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task :default => [:spec]
10
+
11
+ desc 'Generates a dummy app for testing'
12
+ task :test_app do
13
+ ENV['LIB_NAME'] = 'spree_affirm'
14
+ Rake::Task['extension:test_app'].invoke
15
+ end
data/Versionfile ADDED
@@ -0,0 +1,5 @@
1
+ # This file is used to designate compatibilty with different versions of Spree
2
+ # Please see http://spreecommerce.com/documentation/extensions.html#versionfile for details
3
+
4
+ "2.2.x" => { :branch => "master" }
5
+ "2.0.x" => { :branch => "2-0-stable" }
@@ -0,0 +1,3 @@
1
+ // Placeholder manifest file.
2
+ // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js'
3
+ //
@@ -0,0 +1,2 @@
1
+ // Placeholder manifest file.
2
+ // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js'
@@ -0,0 +1,4 @@
1
+ /*
2
+ Placeholder manifest file.
3
+ the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/backend/all.css'
4
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Placeholder manifest file.
3
+ the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css'
4
+ */
@@ -0,0 +1,127 @@
1
+ module Spree
2
+ class AffirmController < Spree::StoreController
3
+ helper 'spree/orders'
4
+
5
+ #the confirm will do it's own protection by making calls to affirm
6
+ protect_from_forgery :except => [:confirm]
7
+
8
+ def confirm
9
+ order = current_order || raise(ActiveRecord::RecordNotFound)
10
+
11
+ if !params[:checkout_token]
12
+ flash[:notice] = "Invalid order confirmation data."
13
+ return redirect_to checkout_state_path(current_order.state)
14
+ end
15
+
16
+ if order.complete?
17
+ flash[:notice] = "Order already completed."
18
+ return redirect_to completion_route order
19
+ end
20
+
21
+ _affirm_checkout = Spree::AffirmCheckout.new(
22
+ order: order,
23
+ token: params[:checkout_token],
24
+ payment_method: payment_method
25
+ )
26
+
27
+ # check if data needs to be updated
28
+ unless _affirm_checkout.valid?
29
+
30
+ _affirm_checkout.errors.each do |field, error|
31
+ case field
32
+ when :billing_address
33
+ # FIXME(brian): pass the phone number to the |order| in a better place
34
+ phone = order.bill_address.phone
35
+ order.bill_address = generate_spree_address(_affirm_checkout.details['billing'])
36
+ order.bill_address.phone = phone
37
+
38
+ when :shipping_address
39
+ # FIXME(brian): pass the phone number to the |order| in a better place
40
+ phone = order.shipping_address.phone
41
+ order.ship_address = generate_spree_address(_affirm_checkout.details['shipping'])
42
+ order.ship_address.phone = phone
43
+
44
+ when :billing_email
45
+ order.email = _affirm_checkout.details["billing"]["email"]
46
+
47
+ end
48
+ end
49
+
50
+ order.save
51
+ end
52
+
53
+ _affirm_checkout.save
54
+
55
+ _affirm_payment = order.payments.create!({
56
+ payment_method: payment_method,
57
+ amount: order.total,
58
+ source: _affirm_checkout
59
+ })
60
+
61
+ # transition to confirm or complete
62
+ while order.next; end
63
+
64
+ if order.completed?
65
+ session[:order_id] = nil
66
+ flash.notice = Spree.t(:order_processed_successfully)
67
+ flash[:commerce_tracking] = "nothing special"
68
+ redirect_to completion_route order
69
+ else
70
+ redirect_to checkout_state_path(order.state)
71
+ end
72
+ end
73
+
74
+ def cancel
75
+ redirect_to checkout_state_path(current_order.state)
76
+ end
77
+
78
+ private
79
+
80
+ def payment_method
81
+ Spree::PaymentMethod.find(params[:payment_method_id])
82
+ end
83
+
84
+ def provider
85
+ payment_method.provider
86
+ end
87
+
88
+ def completion_route(order)
89
+ spree.order_path(order)
90
+ end
91
+
92
+ def generate_spree_address(affirm_address)
93
+ # find the state and country in spree
94
+ _state = Spree::State.find_by_abbr(affirm_address["address"]["region1_code"]) or
95
+ Spree::State.find_by_name(affirm_address["address"]["region1_code"])
96
+ _country = Spree::Country.find_by_iso3(affirm_address["address"]["country_code"]) or
97
+ Spree::Country.find_by_iso(affirm_address["address"]["country_code"])
98
+
99
+ # try to get the name from first and last
100
+ _firstname = affirm_address["name"]["first"] if affirm_address["name"]["first"].present?
101
+ _lastname = affirm_address["name"]["last"] if affirm_address["name"]["last"].present?
102
+
103
+ # fall back to using the full name if available
104
+ if _firstname.nil? and _lastname.nil? and affirm_address["name"]["full"].present?
105
+ _name_parts = affirm_address["name"]["full"].split " "
106
+ _lastname = _name_parts.pop
107
+ _firstname = _name_parts.join " "
108
+ end
109
+
110
+ # create new address
111
+ _spree_address = Spree::Address.new(
112
+ city: affirm_address["address"]["city"],
113
+ phone: affirm_address["phone_number"],
114
+ zipcode: affirm_address["address"]["postal_code"],
115
+ address1: affirm_address["address"]["street1"],
116
+ address2: affirm_address["address"]["street2"],
117
+ state: _state,
118
+ country: _country,
119
+ lastname: _lastname,
120
+ firstname: _firstname
121
+ )
122
+
123
+ _spree_address.save
124
+ _spree_address
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,25 @@
1
+ module Affirm
2
+ class AddressValidator
3
+ def self.normalize_affirm_address(affirm_address_details)
4
+ _address_mapping = {
5
+ "city" => 'city',
6
+ "street1" => 'line1',
7
+ "street2" => 'line2',
8
+ "postal_code" => 'zipcode',
9
+ "region1_code" => 'state',
10
+ "country_code" => 'country'
11
+ }
12
+
13
+ _address_mapping.each do |key, mapped_key|
14
+
15
+ unless affirm_address_details[key].present?
16
+ affirm_address_details[key] = affirm_address_details[mapped_key]
17
+ end
18
+
19
+ end
20
+
21
+ affirm_address_details
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,147 @@
1
+ module Spree
2
+ class AffirmCheckout < ActiveRecord::Base
3
+ belongs_to :payment_method
4
+ belongs_to :order
5
+
6
+ validate :validate_checkout_matches_order
7
+
8
+ scope :with_payment_profile, -> { all }
9
+
10
+ def name
11
+ "Affirm Checkout"
12
+ end
13
+
14
+ def details
15
+ @details ||= payment_method.provider.get_checkout token
16
+ end
17
+
18
+ def validate_checkout_matches_order
19
+ return if self.id
20
+
21
+ check_valid_products
22
+ check_matching_shipping_address
23
+ check_matching_billing_address
24
+ check_matching_billing_email
25
+ check_matching_product_key
26
+ end
27
+
28
+ def check_valid_products
29
+ # ensure the number of line items matches
30
+ if details["items"].size != order.line_items.size
31
+ errors.add :line_items, "Order size mismatch"
32
+ end
33
+
34
+ # iterate through the line items of the checkout
35
+ order.line_items.each do |line_item|
36
+
37
+ # check that the line item sku exists in the affirm checkout
38
+ if !(_item = details["items"][line_item.variant.sku])
39
+ errors.add :line_items, "Line Item not in checkout details"
40
+
41
+ # check quantity & price
42
+ elsif _item["qty"].to_i != line_item.quantity.to_i
43
+ errors.add :line_items, "Quantity mismatch"
44
+
45
+ elsif _item["unit_price"].to_i != (line_item.price*100).to_i
46
+ errors.add :line_items, "Price mismatch"
47
+
48
+ end
49
+ end
50
+
51
+ # all products match
52
+ true
53
+ end
54
+
55
+ def check_matching_billing_address
56
+ # ensure we have a standardized address format
57
+ details['billing']['address'] = normalize_affirm_address details['billing']['address']
58
+
59
+ # validate address
60
+ check_address_match details["billing"], order.bill_address, :billing_address
61
+ end
62
+
63
+ def check_matching_shipping_address
64
+ # ensure we have a standardized address format
65
+ details['shipping']['address'] = normalize_affirm_address details['shipping']['address']
66
+
67
+ # validate address
68
+ check_address_match details["shipping"], order.ship_address, :shipping_address
69
+ end
70
+
71
+ def check_matching_billing_email
72
+ if details["billing"]["email"].present? and details["billing"]["email"].casecmp(order.email) != 0
73
+ errors.add :billing_email, "Billing email mismatch"
74
+ end
75
+ end
76
+
77
+ def check_matching_product_key
78
+ if details["config"]["financial_product_key"] != payment_method.preferred_product_key
79
+ errors.add :financial_product_key, "Product key mismatch"
80
+ end
81
+ end
82
+
83
+ def actions
84
+ %w{capture void credit}
85
+ end
86
+
87
+ # Indicates whether its possible to capture the payment
88
+ def can_capture?(payment)
89
+ (payment.pending? || payment.checkout?) && !payment.response_code.blank?
90
+ end
91
+
92
+ # Indicates whether its possible to void the payment.
93
+ def can_void?(payment)
94
+ !payment.void? && payment.pending? && !payment.response_code.blank?
95
+ end
96
+
97
+ # Indicates whether its possible to credit the payment. Note that most gateways require that the
98
+ # payment be settled first which generally happens within 12-24 hours of the transaction.
99
+ def can_credit?(payment)
100
+ return false unless payment.completed?
101
+ return false unless payment.order.payment_state == 'credit_owed'
102
+ payment.credit_allowed > 0
103
+ end
104
+
105
+ private
106
+
107
+
108
+ def normalize_affirm_address(affirm_address_details)
109
+ Affirm::AddressValidator.normalize_affirm_address(affirm_address_details)
110
+ end
111
+
112
+ def check_address_match(affirm_address, spree_address, field)
113
+ # mapping from affirm address keys to spree address values
114
+ _key_mapping = {
115
+ city: spree_address.city,
116
+ street1: spree_address.address1,
117
+ street2: spree_address.address2,
118
+ postal_code: spree_address.zipcode,
119
+ region1_code: spree_address.state.abbr
120
+
121
+ # check that each value from affirm matches the spree address
122
+ }.each do |affirm_key, spree_val|
123
+ if affirm_address["address"][affirm_key.to_s].present? and
124
+ affirm_address["address"][affirm_key.to_s] != spree_val
125
+
126
+ errors.add field, "#{affirm_key.to_s} mismatch"
127
+ end
128
+ end
129
+
130
+ # test affirm names with first and last
131
+ if affirm_address["name"]["first"].present? and affirm_address["name"]["last"].present? and
132
+ (affirm_address["name"]["first"].casecmp(spree_address.firstname) != 0 or
133
+ affirm_address["name"]["last"].casecmp(spree_address.lastname) != 0)
134
+
135
+ errors.add field, "First/Last name mismatch"
136
+
137
+ # test affirm names with full name
138
+ elsif affirm_address["name"]["full"].present? and
139
+ !(affirm_address["name"]["full"].downcase.include?(spree_address.firstname.downcase) and
140
+ affirm_address["name"]["full"].downcase.include?(spree_address.lastname.downcase))
141
+
142
+ errors.add field, "Full name mismatch"
143
+ end
144
+ end
145
+
146
+ end
147
+ end