solidus_braintree 0.2.1 → 1.0.0

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/.travis.yml +18 -2
  3. data/CHANGELOG.md +2 -0
  4. data/Gemfile +6 -1
  5. data/README.md +7 -0
  6. data/app/controllers/spree/api/braintree_client_token_controller.rb +2 -0
  7. data/app/helpers/braintree_view_helpers.rb +20 -0
  8. data/app/models/concerns/solidus_braintree/add_name_validation_concern.rb +8 -0
  9. data/app/models/concerns/solidus_braintree/inject_device_data_concern.rb +18 -0
  10. data/app/models/concerns/solidus_braintree/payment_braintree_nonce_concern.rb +8 -0
  11. data/app/models/concerns/solidus_braintree/permitted_attributes_concern.rb +11 -0
  12. data/app/models/concerns/solidus_braintree/skip_require_card_numbers_concern.rb +14 -0
  13. data/app/models/concerns/solidus_braintree/use_data_field_concern.rb +23 -0
  14. data/app/models/payment_decorator.rb +1 -0
  15. data/app/models/solidus/gateway/braintree_gateway.rb +46 -5
  16. data/app/overrides/spree/checkout/_confirm/braintree_security.html.erb.deface +9 -0
  17. data/app/views/spree/admin/payments/source_forms/_braintree.html.erb +38 -0
  18. data/app/views/spree/admin/payments/source_views/_braintree.html.erb +30 -0
  19. data/app/views/spree/checkout/payment/_braintree.html.erb +55 -0
  20. data/app/views/spree/checkout/payment/_braintree_initialization.html.erb +12 -0
  21. data/config/initializers/braintree.rb +1 -0
  22. data/config/locales/en.yml +4 -0
  23. data/db/migrate/20150910170527_add_data_to_credit_card.rb +1 -1
  24. data/db/migrate/20160426221931_add_braintree_device_data_to_order.rb +5 -0
  25. data/lib/assets/javascripts/spree/backend/braintree/solidus_braintree.js +15 -12
  26. data/lib/assets/javascripts/spree/frontend/braintree/solidus_braintree.js +144 -0
  27. data/lib/assets/javascripts/vendor/braintree.js +8 -0
  28. data/lib/assets/stylesheets/spree/frontend/solidus_braintree.scss +26 -0
  29. data/lib/generators/solidus_braintree/install/install_generator.rb +9 -0
  30. data/lib/solidus_braintree/version.rb +1 -1
  31. data/solidus_braintree.gemspec +9 -6
  32. metadata +78 -22
  33. data/Guardfile +0 -70
  34. data/app/models/concerns/add_name_validation_concern.rb +0 -6
  35. data/app/models/concerns/payment_braintree_nonce_concern.rb +0 -6
  36. data/app/models/concerns/permitted_attributes_concern.rb +0 -5
  37. data/app/models/concerns/skip_require_card_numbers_concern.rb +0 -12
  38. data/app/models/concerns/use_data_field_concern.rb +0 -21
  39. data/app/overrides/admin_payment_add_braintree_dropin.rb +0 -10
  40. data/app/overrides/admin_payment_remove_credit_card_fields.rb +0 -5
  41. data/lib/assets/javascripts/vendor/braintree-2.14.0.js +0 -7
  42. data/out.json +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4a399277a93c8efbab357b8e7c04718847b14ce
4
- data.tar.gz: 4d338a2564ad5627d7471441c717a401079e6c9d
3
+ metadata.gz: 28f5f6d8e3a10296a22d9b5d9bf9f96f7146b5bb
4
+ data.tar.gz: f4718eedf1bcc717672fc6b97ff5cf9542f48566
5
5
  SHA512:
6
- metadata.gz: 9604aba646ce971377fe38ad54a76ee9d0186f4211903cecbf9af5b381b956f9b4348c4d54df501c7e2adb2121ede7ae2be8a36faf498628fadcbdd5936d15e9
7
- data.tar.gz: 406707b29a747f948fcbaf0f9b2897d03b11a0104f4bae9208b9ac1b35cad1c9ddceeb8a83c7973a39aff88ad3a2453c4cf95a216aa71cdd92cd83a12b9658d2
6
+ metadata.gz: 54b07c41d17e102b686ff466b4361b07bc6b0b051c53953ee229ea163e05b0dc19f8841e9f866c5ac9bc169b8098f816ae7e3e39fd0d6dff12fc773343c7e4fc
7
+ data.tar.gz: 7875589e8a6034511cbeed423e44f83cd9adb66de4bea5f94fe5b5d45bbd7ced33c7f58f1f4dce6a339e36979a11afd8224f6582f4c0d55adf8768ec0a119ffa
data/.travis.yml CHANGED
@@ -1,4 +1,20 @@
1
+ sudo: false
2
+ cache: bundler
1
3
  language: ruby
4
+ addons:
5
+ postgresql: "9.3"
6
+ env:
7
+ matrix:
8
+ - SOLIDUS_BRANCH=v1.1 DB=postgres
9
+ - SOLIDUS_BRANCH=v1.2 DB=postgres
10
+ - SOLIDUS_BRANCH=v1.3 DB=postgres
11
+ - SOLIDUS_BRANCH=master DB=postgres
12
+ - SOLIDUS_BRANCH=v1.1 DB=mysql
13
+ - SOLIDUS_BRANCH=v1.2 DB=mysql
14
+ - SOLIDUS_BRANCH=v1.3 DB=mysql
15
+ - SOLIDUS_BRANCH=master DB=mysql
16
+ script:
17
+ - bundle exec rake test_app
18
+ - bundle exec rspec
2
19
  rvm:
3
- - 2.1.6
4
- before_install: gem install bundler -v 1.10.5
20
+ - 2.1.8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ## HEAD
2
2
 
3
+ * Upgrade braintree to 2.23.0 for bug fixes and fraud detection with deviceData.
4
+
3
5
  ## 0.2.0
4
6
 
5
7
  * Add functionality around PayPal by adding a json or text field on credit card for storing extra data coming back from Braintree.
data/Gemfile CHANGED
@@ -1,5 +1,10 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in solidus_braintree.gemspec
4
- gem "solidus", github: "solidusio/solidus", branch: "master"
4
+ branch = ENV.fetch('SOLIDUS_BRANCH', 'master')
5
+ gem "solidus", github: "solidusio/solidus", branch: branch
6
+
7
+ gem 'pg'
8
+ gem 'mysql2'
9
+
5
10
  gemspec
data/README.md CHANGED
@@ -19,6 +19,13 @@ And then execute:
19
19
  $ bundle
20
20
  $ bundle exec rails g solidus_braintree:install
21
21
 
22
+
23
+ ## Fraud detection
24
+
25
+ This gem has support for the advanced fraud tools flow from Braintree, to activate
26
+ fully the associated Braintree account must enable advanced fraud tools in the
27
+ Control Panel.
28
+
22
29
  ## Usage
23
30
 
24
31
  This gem extends your solidus application by adding a `POST /api/payment_client_token` endpoint to you application to generate Braintree payment client token. This endpoint requires an authentication token in your request header.
@@ -1,4 +1,6 @@
1
1
  class Spree::Api::BraintreeClientTokenController < Spree::Api::BaseController
2
+ skip_before_action :authenticate_user
3
+
2
4
  def create
3
5
  if params[:payment_method_id]
4
6
  gateway = Solidus::Gateway::BraintreeGateway.find_by!(id: params[:payment_method_id])
@@ -0,0 +1,20 @@
1
+ module BraintreeViewHelpers
2
+ # Returns a link to the Braintree web UI for the given Braintree +payment_id+
3
+ # (e.g. from Spree::Payment#response_code), or just the +payment_id+ if
4
+ # Braintree's merchant ID is not configured or +payment_id+ is blank
5
+ def braintree_transaction_link(payment_id)
6
+ env = ENV['BRAINTREE_ENV'] == 'production' ? 'www' : 'sandbox'
7
+ merchant = ENV['BRAINTREE_MERCHANT_ID']
8
+
9
+ if payment_id.present? && merchant.present?
10
+ link_to(
11
+ payment_id,
12
+ "https://#{env}.braintreegateway.com/merchants/#{merchant}/transactions/#{payment_id}",
13
+ title: 'Show payment on Braintree',
14
+ target: '_blank'
15
+ )
16
+ else
17
+ payment_id
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ module SolidusBraintree
2
+ module AddNameValidationConcern
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ validates :name, presence: true, on: :create
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module SolidusBraintree
2
+ module InjectDeviceDataConcern
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ prepend(InstanceMethods)
6
+ end
7
+
8
+ module InstanceMethods
9
+ def gateway_options
10
+ options = super
11
+
12
+ options[:device_data] = order.braintree_device_data if order.braintree_device_data
13
+
14
+ options
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ module SolidusBraintree
2
+ module PaymentBraintreeNonceConcern
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ attr_accessor :payment_method_nonce
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ module SolidusBraintree
2
+ module PermittedAttributesConcern
3
+ def payment_attributes
4
+ super | [:payment_method_nonce]
5
+ end
6
+
7
+ def checkout_attributes
8
+ super | [:braintree_device_data]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module SolidusBraintree
2
+ module SkipRequireCardNumbersConcern
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ prepend(InstanceMethods)
6
+ end
7
+
8
+ module InstanceMethods
9
+ def require_card_numbers?
10
+ super && !self.payment_method.kind_of?(Solidus::Gateway::BraintreeGateway)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ module SolidusBraintree
2
+ module UseDataFieldConcern
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ prepend(InstanceMethods)
6
+ end
7
+
8
+ module InstanceMethods
9
+
10
+ def email
11
+ data["email"]
12
+ end
13
+
14
+ def display_number
15
+ cc_type == 'paypal' ? email : super
16
+ end
17
+
18
+ def data
19
+ super.is_a?(String) ? JSON.parse(super) : super
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1 +1,2 @@
1
1
  Spree::Payment.include SolidusBraintree::PaymentBraintreeNonceConcern
2
+ Spree::Payment.include SolidusBraintree::InjectDeviceDataConcern
@@ -21,6 +21,10 @@ module Solidus
21
21
  'Visa' => 'visa',
22
22
  }
23
23
 
24
+ def method_type
25
+ 'braintree'
26
+ end
27
+
24
28
  def gateway_options
25
29
  {
26
30
  environment: preferred_environment.to_sym,
@@ -49,12 +53,13 @@ module Solidus
49
53
  return if source.gateway_customer_profile_id.present? || payment.payment_method_nonce.nil?
50
54
 
51
55
  user = payment.order.user
56
+ email = user ? user.email : payment.order.email
52
57
  address = (payment.source.address || payment.order.bill_address).try(:active_merchant_hash)
53
58
 
54
59
  params = {
55
60
  first_name: source.first_name,
56
61
  last_name: source.last_name,
57
- email: user.email,
62
+ email: email,
58
63
  credit_card: {
59
64
  cardholder_name: source.name,
60
65
  billing_address: map_address(address),
@@ -63,6 +68,7 @@ module Solidus
63
68
  verify_card: true,
64
69
  },
65
70
  },
71
+ device_data: payment.order.braintree_device_data
66
72
  }
67
73
 
68
74
  result = braintree_gateway.customer.create(params)
@@ -119,9 +125,19 @@ module Solidus
119
125
  end
120
126
 
121
127
  def void(authorization_code, source = {}, options = {})
122
- result = braintree_gateway.transaction.void(authorization_code)
123
-
124
- handle_result(result)
128
+ # Allows voiding payments that are in a checkout state
129
+ if authorization_code.nil?
130
+ # Fake response since we don't need to void anything with Braintree
131
+ ActiveMerchant::Billing::Response.new(
132
+ true,
133
+ "OK",
134
+ {},
135
+ {}
136
+ )
137
+ else
138
+ result = braintree_gateway.transaction.void(authorization_code)
139
+ handle_result(result)
140
+ end
125
141
  end
126
142
 
127
143
  def credit(cents, source, authorization_code, options = {})
@@ -137,6 +153,18 @@ module Solidus
137
153
  ].include?(transaction.status)
138
154
  end
139
155
 
156
+ def card_number_placeholder
157
+ '4141 4141 4141 4141'
158
+ end
159
+
160
+ def expiration_date_placeholder
161
+ '01/2020'
162
+ end
163
+
164
+ def card_code_placeholder
165
+ '123'
166
+ end
167
+
140
168
  private
141
169
  def message_from_result(result)
142
170
  if result.success?
@@ -150,12 +178,25 @@ module Solidus
150
178
  end
151
179
  end
152
180
 
181
+ def build_results_hash(result)
182
+ if result.success?
183
+ {
184
+ authorization: result.transaction.id,
185
+ avs_result: {
186
+ code: result.transaction.avs_street_address_response_code
187
+ }
188
+ }
189
+ else
190
+ {}
191
+ end
192
+ end
193
+
153
194
  def handle_result(result)
154
195
  ActiveMerchant::Billing::Response.new(
155
196
  result.success?,
156
197
  message_from_result(result),
157
198
  {},
158
- { authorization: (result.transaction.id if result.success?) }
199
+ build_results_hash(result)
159
200
  )
160
201
  end
161
202
 
@@ -0,0 +1,9 @@
1
+ <!-- insert_before "erb[loud]:contains('place_order')" -->
2
+
3
+ <%
4
+ # Currently we assume we only have one braintree
5
+ # payment method. In practice this should be true and it simplifies the code.
6
+ braintree_payment = @order.unprocessed_payments.find {|p| p.payment_method.class == Solidus::Gateway::BraintreeGateway }
7
+ if braintree_payment %>
8
+ <%= render 'spree/checkout/payment/braintree_initialization', payment_method: braintree_payment.payment_method %>
9
+ <% end %>
@@ -0,0 +1,38 @@
1
+ <fieldset data-id='credit-card' class="no-border-bottom">
2
+ <div class="field" data-hook="previous_cards">
3
+ <% if previous_cards.any? %>
4
+ <% previous_cards.each do |card| %>
5
+ <label><%= radio_button_tag :card, card.id, card == previous_cards.first %> <%= card.display_number %><br /></label>
6
+ <% end %>
7
+ <label><%= radio_button_tag :card, 'new', false, { id: "card_new#{payment_method.id}" } %> <%= Spree.t(:use_new_cc) %></label>
8
+ <% end %>
9
+ </div>
10
+
11
+ <div id="card_form<%= payment_method.id %>" data-hook>
12
+ <% param_prefix = "payment_source[#{payment_method.id}]" %>
13
+
14
+ <div class="clear"></div>
15
+
16
+ <div class="alpha four columns">
17
+ <div data-hook="card_name">
18
+ <div class="field">
19
+ <%= label_tag "card_name#{payment_method.id}", Spree::CreditCard.human_attribute_name(:name), class: 'required' %>
20
+ <%= text_field_tag "#{param_prefix}[name]", '', id: "card_name#{payment_method.id}", class: 'required fullwidth', maxlength: 19 %>
21
+ </div>
22
+ </div>
23
+ </div>
24
+
25
+ <div id="braintree-dropin"></div>
26
+ <input type="hidden" id="payment_method_nonce" name="payment[payment_method_nonce]">
27
+
28
+ <div class="clear"></div>
29
+
30
+ <%= label_tag "card_address#{payment_method.id}", Spree.t(:billing_address) %>
31
+ <% address = @order.bill_address || @order.ship_address || Spree::Address.build_default %>
32
+ <%= fields_for "#{param_prefix}[address_attributes]", address do |f| %>
33
+ <%= render :partial => 'spree/admin/shared/address_form', :locals => { :f => f, :type => "billing" } %>
34
+ <% end %>
35
+
36
+ <div class="clear"></div>
37
+ </div>
38
+ </fieldset>
@@ -0,0 +1,30 @@
1
+ <fieldset data-hook="credit_card">
2
+ <legend align="center"><%= Spree::CreditCard.model_name.human %></legend>
3
+
4
+ <div class="row">
5
+ <div class="alpha six columns">
6
+ <dl>
7
+ <dt><%= Spree.t(:identifier) %>:</dt>
8
+ <dd><%= payment.number %></dd>
9
+
10
+ <dt><%= Spree.t(:response_code) %>:</dt>
11
+ <dd><%= braintree_transaction_link(payment.response_code) %></dd>
12
+
13
+ <dt><%= Spree.t(:name_on_card) %>:</dt>
14
+ <dd><%= payment.source.name %></dd>
15
+
16
+ <dt><%= Spree::CreditCard.human_attribute_name(:cc_type) %>:</dt>
17
+ <dd><%= payment.source.cc_type %></dd>
18
+
19
+ <dt><%= Spree::CreditCard.human_attribute_name(:number) %>:</dt>
20
+ <dd><%= payment.source.display_number %></dd>
21
+
22
+ <dt><%= Spree::CreditCard.human_attribute_name(:expiration) %>:</dt>
23
+ <dd><%= payment.source.month %>/<%= payment.source.year %></dd>
24
+ </dl>
25
+ <% if payment.source.address %>
26
+ <%= render partial: 'spree/admin/shared/address', locals: {address: payment.source.address} %>
27
+ <% end %>
28
+ </div>
29
+ </div>
30
+ </fieldset>
@@ -0,0 +1,55 @@
1
+ <div class="braintree-payment">
2
+ <%= render 'spree/checkout/payment/braintree_initialization', payment_method: payment_method %>
3
+
4
+ <div class="braintree-paypal-input">
5
+ <div class="braintree-paypal-header">
6
+ <%= t('solidus_braintree.paypal_header_html') %>
7
+ </div>
8
+ <div id="#braintree_paypal_container"></div>
9
+ </div>
10
+
11
+ <div class="braintree-cc-input">
12
+ <div class="braintree-cc-header">
13
+ <%= t('solidus_braintree.creditcard_header_html') %>
14
+ <%= image_tag 'credit_cards/credit_card.gif', :id => 'credit-card-image' %>
15
+ </div>
16
+ <% param_prefix = "payment_source[#{payment_method.id}]" %>
17
+
18
+ <p class="field">
19
+ <%= label_tag "name_on_card_#{payment_method.id}", Spree.t(:name_on_card) %><span class="required">*</span><br />
20
+ <%= text_field_tag "#{param_prefix}[name]", "#{@order.billing_firstname} #{@order.billing_lastname}", { id: "name_on_card_#{payment_method.id}", :autocomplete => "cc-name" } %>
21
+ </p>
22
+
23
+ <p class="field" data-hook="card_number">
24
+ <%= label_tag "braintree_card_number", Spree.t(:card_number) %><span class="required">*</span><br />
25
+ <label for="braintree_card_number" id="braintree_card_number" class="braintree-hosted-field"></label>
26
+ &nbsp;
27
+ <span id="card_type" style="display:none;">
28
+ ( <span id="looks_like" ><%= Spree.t(:card_type_is) %> <span id="type"></span></span>
29
+ <span id="unrecognized"><%= Spree.t(:unrecognized_card_type) %></span>
30
+ )
31
+ </span>
32
+ </p>
33
+
34
+ <p class="field" data-hook="card_expiration">
35
+ <%= label_tag "braintree_card_expiry", Spree.t(:expiration) %><span class="required">*</span><br />
36
+ <label for="braintree_card_expiry" id="braintree_card_expiry" class="braintree-hosted-field"></label>
37
+ </p>
38
+
39
+ <p class="field" data-hook="card_code">
40
+ <%= label_tag "braintree_card_code", Spree.t(:card_code) %><span class="required">*</span><br />
41
+
42
+ <label for="braintree_card_code" id="braintree_card_code" class="braintree-hosted-field card-code"></label>
43
+ <%= link_to "(#{Spree.t(:what_is_this)})", spree.cvv_path, :target => '_blank', "data-hook" => "cvv_link", :id => "cvv_link" %>
44
+ </p>
45
+ </div>
46
+
47
+ <% if @order.bill_address %>
48
+ <%= fields_for "#{param_prefix}[address_attributes]", @order.bill_address do |f| %>
49
+ <%= render :partial => 'spree/address/form_hidden', :locals => { :form => f } %>
50
+ <% end %>
51
+ <% end %>
52
+
53
+ <%= hidden_field_tag "#{param_prefix}[cc_type]", '', :id => "cc_type", :class => 'ccType' %>
54
+ <%= hidden_field_tag "order[payments_attributes][][payment_method_nonce]", '', :id => "payment_method_nonce" %>
55
+ </div>
@@ -0,0 +1,12 @@
1
+ <% content_for :head do %>
2
+ <%= javascript_tag do %>
3
+ braintree.environment = "<%= payment_method.preferred_environment %>"
4
+ braintree.placeholders = {
5
+ "number": "<%= payment_method.card_number_placeholder %>",
6
+ "expirationDate": "<%= payment_method.expiration_date_placeholder %>",
7
+ "cvv": "<%= payment_method.card_code_placeholder %>"
8
+ }
9
+ <% end %>
10
+ <% end %>
11
+
12
+ <%= hidden_field_tag "order[braintree_device_data]", '', :id => "device_data" %>