spree_affirm 0.2.19 → 0.2.20

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