solidus_paypal_braintree 0.2.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9b312aa57cfc45563e9144f43afd85c8f948de70
4
- data.tar.gz: 21c6f9335713d620391a3b88dfd0a547bcc3c972
3
+ metadata.gz: 76631d8678366736ecdab190121377841bafa0df
4
+ data.tar.gz: bcc44b77f8ad9172d850676dd1784c7e43bad1cc
5
5
  SHA512:
6
- metadata.gz: 2ece71d9f4adef4898506c4394ff4325a6718f26f0cb3bb9df8cd06922797a024f0fb522e31855586d6adcffcd4168fd94b7618dc4dd533a79dde7d4507fac1b
7
- data.tar.gz: 4dadbe474742f3250c38e4bd1be205601c3334d532a67a4b38011dd2be2c8d855e8b77840e83d4e694884874d9c8eb07dbd8e17cb1f83aeb4014467bf3269891
6
+ metadata.gz: 75c8efca91fcd54a20d88e3e8c09e9c9d45bc14fe2bd662a9cb01cc4b6ff6fcf10c406447ef833246d508850e4e8c1f34e39970d5057d6817f4477ffb0a22dde
7
+ data.tar.gz: ea6ffff49064914e97b69f08ceaaeadc809b3154cbaccea3f771000f225ac84fe6fd6b267bde462f63f3408bb6c23d6b32dc0fddfac75bb9fa3c07a8e99352f0
data/README.md CHANGED
@@ -37,25 +37,30 @@ Tokenization Keys, Encryption Keys** section.
37
37
  Payment methods can accept preferences either directly entered in admin, or from a static source in code. For most projects we recommend using a static source, so that sensitive account credentials are not stored in the database.
38
38
 
39
39
  1. Set static preferences in an initializer
40
- ```ruby
41
- # config/initializers/spree.rb
42
- Spree::Config.configure do |config|
43
- config.static_model_preferences.add(
44
- SolidusPaypalBraintree::Gateway,
45
- 'braintree_credentials', {
46
- environment: Rails.env.production? ? 'production' : 'sandbox',
47
- merchant_id: ENV['BRAINTREE_MERCHANT_ID'],
48
- public_key: ENV['BRAINTREE_PUBLIC_KEY'],
49
- private_key: ENV['BRAINTREE_PRIVATE_KEY']
50
- }
51
- )
52
- end
53
- ```
54
- Other optional preferences are discussed below.
40
+ ```ruby
41
+ # config/initializers/spree.rb
42
+ Spree::Config.configure do |config|
43
+ config.static_model_preferences.add(
44
+ SolidusPaypalBraintree::Gateway,
45
+ 'braintree_credentials', {
46
+ environment: Rails.env.production? ? 'production' : 'sandbox',
47
+ merchant_id: ENV['BRAINTREE_MERCHANT_ID'],
48
+ public_key: ENV['BRAINTREE_PUBLIC_KEY'],
49
+ private_key: ENV['BRAINTREE_PRIVATE_KEY']
50
+ }
51
+ )
52
+ end
53
+ ```
54
+ Other optional preferences are discussed below.
55
+
55
56
  2. Visit `/admin/payment_methods/new`
57
+
56
58
  3. Set `provider` to SolidusPaypalBraintree::Gateway
59
+
57
60
  4. Click "Save"
61
+
58
62
  5. Choose `braintree_credentials` from the `Preference Source` select
63
+
59
64
  6. Click `Update` to save
60
65
 
61
66
  Alternatively, create a payment method from the Rails console with:
@@ -68,24 +73,27 @@ SolidusPaypalBraintree::Gateway.new(
68
73
 
69
74
  ### Configure payment types
70
75
  Your payment method can accept payments in three ways: through Paypal, through ApplePay, or with credit card details entered directly by the customer. By default all are disabled for all your site's stores.
76
+
71
77
  1. Visit /solidus_paypal_braintree/configurations/list
78
+
72
79
  2. Check the payment types you'd like to accept. If your site has multiple stores, there'll be a set of checkboxes for each.
80
+
73
81
  3. Click `Save changes` to save
74
82
 
75
- Or from the console:
76
- ```ruby
77
- Spree::Store.all.each do |store|
78
- store.create_braintree_configuration(
79
- credit_card: true,
80
- paypal: true,
81
- apple_pay: true
82
- )
83
- end
84
- ```
83
+ Or from the console:
84
+ ```ruby
85
+ Spree::Store.all.each do |store|
86
+ store.create_braintree_configuration(
87
+ credit_card: true,
88
+ paypal: true,
89
+ apple_pay: true
90
+ )
91
+ end
92
+ ```
85
93
 
86
- 3. If your site uses an unmodified `solidus_frontend`, it should now be ready to take payments. See below for more information on configuring Paypal and ApplePay.
94
+ 4. If your site uses an unmodified `solidus_frontend`, it should now be ready to take payments. See below for more information on configuring Paypal and ApplePay.
87
95
 
88
- 4. Typical Solidus sites will have customized frontend code, and may require some additional work. Use `lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb` and `app/assets/javascripts/solidus_paypal_braintree/checkout.js` as models.
96
+ 5. Typical Solidus sites will have customized frontend code, and may require some additional work. Use `lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb` and `app/assets/javascripts/solidus_paypal_braintree/checkout.js` as models.
89
97
 
90
98
  ## Apple Pay
91
99
  ### Developing with Apple Pay
@@ -101,7 +109,7 @@ The following is a relatively bare-bones implementation to enable Apple Pay on t
101
109
 
102
110
  ```html
103
111
  <% if current_store.braintree_configuration.apple_pay? %>
104
- <script src="https://js.braintreegateway.com/web/3.14.0/js/apple-pay.min.js"></script>
112
+ <script src="https://js.braintreegateway.com/web/3.22.1/js/apple-pay.min.js"></script>
105
113
 
106
114
  <button id="apple-pay-button" class="apple-pay-button"></button>
107
115
 
@@ -131,7 +139,7 @@ The following is a relatively bare-bones implementation to enable Apple Pay on t
131
139
  <% end %>
132
140
  ```
133
141
 
134
- ### Further information
142
+ ### Further Apple Pay information
135
143
  Braintree has some [excellent documentation](https://developers.braintreepayments.com/guides/apple-pay/configuration/javascript/v3) on what you'll need to do to get Apple Pay up and running.
136
144
 
137
145
  For additional information check out [Apple's documentation](https://developer.apple.com/reference/applepayjs/) and [Braintree's documentation](https://developers.braintreepayments.com/guides/apple-pay/client-side/javascript/v3).
@@ -9,11 +9,8 @@ SolidusPaypalBraintree = {
9
9
 
10
10
  // Override to provide your own error messages.
11
11
  braintreeErrorHandle: function(braintreeError) {
12
- var $contentContainer = $("#content");
13
- var $flash = $("<div class='flash error'>" + braintreeError.name + ": " + braintreeError.message + "</div>");
14
- $contentContainer.prepend($flash);
15
-
16
- $flash.show().delay(5000).fadeOut(500);
12
+ BraintreeError.getErrorFromSlug(braintreeError.code);
13
+ SolidusPaypalBraintree.showError(error);
17
14
  },
18
15
 
19
16
  classes: {
@@ -35,6 +32,13 @@ SolidusPaypalBraintree = {
35
32
  }
36
33
  },
37
34
 
35
+ showError: function(error) {
36
+ var $contentContainer = $("#content");
37
+ var $flash = $("<div class='flash error'>" + error + "</div>");
38
+ $contentContainer.prepend($flash);
39
+ $flash.show().delay(5000).fadeOut(500);
40
+ },
41
+
38
42
  createHostedForm: function() {
39
43
  return SolidusPaypalBraintree._factory(SolidusPaypalBraintree.config.classes.hostedForm(), arguments);
40
44
  },
@@ -91,24 +91,27 @@ SolidusPaypalBraintree.PaypalButton.prototype._transactionParams = function(payl
91
91
  * @param {object} payload - The payload returned by Braintree after tokenization
92
92
  */
93
93
  SolidusPaypalBraintree.PaypalButton.prototype._addressParams = function(payload) {
94
- if (payload.details.shippingAddress.recipientName) {
95
- var first_name = payload.details.shippingAddress.recipientName.split(" ")[0];
96
- var last_name = payload.details.shippingAddress.recipientName.split(" ")[1];
94
+ var first_name, last_name;
95
+ var payload_address = payload.details.shippingAddress || payload.details.billingAddress;
96
+
97
+ if (payload_address.recipientName) {
98
+ first_name = payload_address.recipientName.split(" ")[0];
99
+ last_name = payload_address.recipientName.split(" ")[1];
97
100
  }
98
- if (first_name == null || last_name == null) {
99
- var first_name = payload.details.firstName;
100
- var last_name = payload.details.lastName;
101
+
102
+ if (!first_name || !last_name) {
103
+ first_name = payload.details.firstName;
104
+ last_name = payload.details.lastName;
101
105
  }
102
106
 
103
107
  return {
104
108
  "first_name" : first_name,
105
109
  "last_name" : last_name,
106
- "address_line_1" : payload.details.shippingAddress.line1,
107
- "address_line_2" : payload.details.shippingAddress.line2,
108
- "city" : payload.details.shippingAddress.city,
109
- "state_code" : payload.details.shippingAddress.state,
110
- "zip" : payload.details.shippingAddress.postalCode,
111
- "country_code" : payload.details.shippingAddress.countryCode
112
- }
110
+ "address_line_1" : payload_address.line1,
111
+ "address_line_2" : payload_address.line2,
112
+ "city" : payload_address.city,
113
+ "state_code" : payload_address.state,
114
+ "zip" : payload_address.postalCode,
115
+ "country_code" : payload_address.countryCode
116
+ };
113
117
  };
114
-
@@ -68,8 +68,8 @@ $(function() {
68
68
  if (!$paymentForm.length || !$hostedFields.length) { return; }
69
69
 
70
70
  $.when(
71
- $.getScript("https://js.braintreegateway.com/web/3.9.0/js/client.min.js"),
72
- $.getScript("https://js.braintreegateway.com/web/3.9.0/js/hosted-fields.min.js")
71
+ $.getScript("https://js.braintreegateway.com/web/3.22.1/js/client.min.js"),
72
+ $.getScript("https://js.braintreegateway.com/web/3.22.1/js/hosted-fields.min.js")
73
73
  ).done(function() {
74
74
  $hostedFields.each(function() {
75
75
  var $this = $(this),
@@ -3,9 +3,9 @@
3
3
  $(document).ready(function() {
4
4
  if (document.getElementById("empty-cart")) {
5
5
  $.when(
6
- $.getScript("https://js.braintreegateway.com/web/3.14.0/js/client.min.js"),
7
- $.getScript("https://js.braintreegateway.com/web/3.14.0/js/paypal.min.js"),
8
- $.getScript("https://js.braintreegateway.com/web/3.14.0/js/data-collector.min.js")
6
+ $.getScript("https://js.braintreegateway.com/web/3.22.1/js/client.min.js"),
7
+ $.getScript("https://js.braintreegateway.com/web/3.22.1/js/paypal.min.js"),
8
+ $.getScript("https://js.braintreegateway.com/web/3.22.1/js/data-collector.min.js")
9
9
  ).done(function() {
10
10
  $('<script/>').attr({
11
11
  'data-merchant' : "braintree",
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_merchant/billing/avs_result'
4
+
5
+ module SolidusPaypalBraintree
6
+ class AVSResult < ActiveMerchant::Billing::AVSResult
7
+ # Mapping took from ActiveMerchant::Billing::BraintreeBlueGateway
8
+ AVS_MAPPING = {
9
+ 'M' => {
10
+ 'M' => 'M',
11
+ 'N' => 'A',
12
+ 'U' => 'B',
13
+ 'I' => 'B',
14
+ 'A' => 'B'
15
+ },
16
+ 'N' => {
17
+ 'M' => 'Z',
18
+ 'N' => 'C',
19
+ 'U' => 'C',
20
+ 'I' => 'C',
21
+ 'A' => 'C'
22
+ },
23
+ 'U' => {
24
+ 'M' => 'P',
25
+ 'N' => 'N',
26
+ 'U' => 'I',
27
+ 'I' => 'I',
28
+ 'A' => 'I'
29
+ },
30
+ 'I' => {
31
+ 'M' => 'P',
32
+ 'N' => 'C',
33
+ 'U' => 'I',
34
+ 'I' => 'I',
35
+ 'A' => 'I'
36
+ },
37
+ 'A' => {
38
+ 'M' => 'P',
39
+ 'N' => 'C',
40
+ 'U' => 'I',
41
+ 'I' => 'I',
42
+ 'A' => 'I'
43
+ },
44
+ nil => { nil => nil }
45
+ }.freeze
46
+
47
+ class << self
48
+ private :new
49
+
50
+ def build(transaction)
51
+ new(
52
+ code: avs_code_from(transaction),
53
+ street_match: transaction.avs_street_address_response_code,
54
+ postal_match: transaction.avs_postal_code_response_code
55
+ )
56
+ end
57
+
58
+ private
59
+
60
+ def avs_code_from(transaction)
61
+ transaction.avs_error_response_code ||
62
+ AVS_MAPPING[transaction.avs_street_address_response_code][transaction.avs_postal_code_response_code]
63
+ end
64
+ end
65
+ end
66
+ end
@@ -2,6 +2,11 @@ require 'braintree'
2
2
 
3
3
  module SolidusPaypalBraintree
4
4
  class Gateway < ::Spree::PaymentMethod
5
+ include RequestProtection
6
+
7
+ # Error message from Braintree that gets returned by a non voidable transaction
8
+ NON_VOIDABLE_STATUS_ERROR_REGEXP = /can only be voided if status is authorized/
9
+
5
10
  TOKEN_GENERATION_DISABLED_MESSAGE = 'Token generation is disabled.' \
6
11
  ' To re-enable set the `token_generation_enabled` preference on the' \
7
12
  ' gateway to `true`.'.freeze
@@ -15,6 +20,7 @@ module SolidusPaypalBraintree
15
20
 
16
21
  VOIDABLE_STATUSES = [
17
22
  Braintree::Transaction::Status::SubmittedForSettlement,
23
+ Braintree::Transaction::Status::SettlementPending,
18
24
  Braintree::Transaction::Status::Authorized
19
25
  ].freeze
20
26
 
@@ -64,12 +70,14 @@ module SolidusPaypalBraintree
64
70
  # extra options to send along. e.g.: device data for fraud prevention
65
71
  # @return [Response]
66
72
  def purchase(money_cents, source, gateway_options)
67
- result = braintree.transaction.sale(
68
- amount: dollars(money_cents),
69
- **transaction_options(source, gateway_options, true)
70
- )
73
+ protected_request do
74
+ result = braintree.transaction.sale(
75
+ amount: dollars(money_cents),
76
+ **transaction_options(source, gateway_options, true)
77
+ )
71
78
 
72
- Response.build(result)
79
+ Response.build(result)
80
+ end
73
81
  end
74
82
 
75
83
  # Authorize a payment to be captured later.
@@ -81,12 +89,14 @@ module SolidusPaypalBraintree
81
89
  # extra options to send along. e.g.: device data for fraud prevention
82
90
  # @return [Response]
83
91
  def authorize(money_cents, source, gateway_options)
84
- result = braintree.transaction.sale(
85
- amount: dollars(money_cents),
86
- **transaction_options(source, gateway_options)
87
- )
92
+ protected_request do
93
+ result = braintree.transaction.sale(
94
+ amount: dollars(money_cents),
95
+ **transaction_options(source, gateway_options)
96
+ )
88
97
 
89
- Response.build(result)
98
+ Response.build(result)
99
+ end
90
100
  end
91
101
 
92
102
  # Collect funds from an authorized payment.
@@ -97,11 +107,13 @@ module SolidusPaypalBraintree
97
107
  # @param response_code [String] the transaction id of the payment to capture
98
108
  # @return [Response]
99
109
  def capture(money_cents, response_code, _gateway_options)
100
- result = braintree.transaction.submit_for_settlement(
101
- response_code,
102
- dollars(money_cents)
103
- )
104
- Response.build(result)
110
+ protected_request do
111
+ result = braintree.transaction.submit_for_settlement(
112
+ response_code,
113
+ dollars(money_cents)
114
+ )
115
+ Response.build(result)
116
+ end
105
117
  end
106
118
 
107
119
  # Used to refeund a customer for an already settled transaction.
@@ -111,11 +123,13 @@ module SolidusPaypalBraintree
111
123
  # @param response_code [String] the transaction id of the payment to refund
112
124
  # @return [Response]
113
125
  def credit(money_cents, _source, response_code, _gateway_options)
114
- result = braintree.transaction.refund(
115
- response_code,
116
- dollars(money_cents)
117
- )
118
- Response.build(result)
126
+ protected_request do
127
+ result = braintree.transaction.refund(
128
+ response_code,
129
+ dollars(money_cents)
130
+ )
131
+ Response.build(result)
132
+ end
119
133
  end
120
134
 
121
135
  # Used to cancel a transaction before it is settled.
@@ -124,8 +138,10 @@ module SolidusPaypalBraintree
124
138
  # @param response_code [String] the transaction id of the payment to void
125
139
  # @return [Response]
126
140
  def void(response_code, _source, _gateway_options)
127
- result = braintree.transaction.void(response_code)
128
- Response.build(result)
141
+ protected_request do
142
+ result = braintree.transaction.void(response_code)
143
+ Response.build(result)
144
+ end
129
145
  end
130
146
 
131
147
  # Will either refund or void the payment depending on its state.
@@ -137,7 +153,9 @@ module SolidusPaypalBraintree
137
153
  # @param response_code [String] the transaction id of the payment to void
138
154
  # @return [Response]
139
155
  def cancel(response_code)
140
- transaction = braintree.transaction.find(response_code)
156
+ transaction = protected_request do
157
+ braintree.transaction.find(response_code)
158
+ end
141
159
  if VOIDABLE_STATUSES.include?(transaction.status)
142
160
  void(response_code, nil, {})
143
161
  else
@@ -145,6 +163,32 @@ module SolidusPaypalBraintree
145
163
  end
146
164
  end
147
165
 
166
+ # Will void the payment depending on its state or return false
167
+ #
168
+ # Used by Solidus >= 2.4 instead of +cancel+
169
+ #
170
+ # If the transaction has not yet been settled, we can void the transaction.
171
+ # Otherwise, we return false so Solidus creates a refund instead.
172
+ #
173
+ # @api public
174
+ # @param response_code [String] the transaction id of the payment to void
175
+ # @return [Response|FalseClass]
176
+ def try_void(response_code)
177
+ transaction = braintree.transaction.find(response_code)
178
+ if transaction.status.in? SolidusPaypalBraintree::Gateway::VOIDABLE_STATUSES
179
+ # Sometimes Braintree returns a voidable status although it is not voidable anymore.
180
+ # When we try to void that transaction we receive an error and need to return false
181
+ # so Solidus can create a refund instead.
182
+ begin
183
+ void(response_code, nil, {})
184
+ rescue ActiveMerchant::ConnectionError => e
185
+ e.message.match(NON_VOIDABLE_STATUS_ERROR_REGEXP) ? false : raise(e)
186
+ end
187
+ else
188
+ false
189
+ end
190
+ end
191
+
148
192
  # Creates a new customer profile in Braintree
149
193
  #
150
194
  # @api public
@@ -1,3 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_merchant/billing/response'
4
+ require_relative 'avs_result'
5
+
1
6
  # Response object that all actions on the gateway should return
2
7
  module SolidusPaypalBraintree
3
8
  class Response < ActiveMerchant::Billing::Response
@@ -15,36 +20,58 @@ module SolidusPaypalBraintree
15
20
 
16
21
  def build_success(result)
17
22
  transaction = result.transaction
18
-
19
- test = true
20
- authorization = transaction.id
21
- fraud_review = nil
22
- avs_result = nil
23
- cvv_result = nil
24
-
25
- options = {
26
- test: test,
27
- authorization: authorization,
28
- fraud_review: fraud_review,
29
- avs_result: avs_result,
30
- cvv_result: cvv_result
31
- }
32
-
33
- new(true, transaction.status, {}, options)
23
+ new(true, transaction.status, {}, response_options(transaction))
34
24
  end
35
25
 
36
26
  def build_failure(result)
37
- new(false, error_message(result))
27
+ transaction = result.transaction
28
+ options = response_options(transaction).update(
29
+ # For error responses we want to have the CVV code
30
+ cvv_result: transaction.try!(:cvv_response_code)
31
+ )
32
+ new(false, error_message(result), result.params, options)
33
+ end
34
+
35
+ def response_options(transaction)
36
+ # Some error responses do not have a transaction
37
+ return {} if transaction.nil?
38
+ {
39
+ authorization: transaction.id,
40
+ avs_result: SolidusPaypalBraintree::AVSResult.build(transaction),
41
+ # As we do not provide the CVV while submitting the transaction (for PCI compliance reasons),
42
+ # we need to ignore the only response we get back (I = not provided).
43
+ # Otherwise Solidus thinks this payment is risky.
44
+ cvv_result: nil
45
+ }
38
46
  end
39
47
 
40
48
  def error_message(result)
41
49
  if result.errors.any?
42
50
  result.errors.map { |e| "#{e.message} (#{e.code})" }.join(" ")
43
51
  else
44
- [result.transaction.status,
45
- result.transaction.gateway_rejection_reason,
46
- result.transaction.processor_settlement_response_code,
47
- result.transaction.processor_settlement_response_text].compact.join(" ")
52
+ transaction_error_message(result.transaction)
53
+ end
54
+ end
55
+
56
+ # Human readable error message for transaction responses
57
+ def transaction_error_message(transaction)
58
+ case transaction.status
59
+ when 'gateway_rejected'
60
+ I18n.t(transaction.gateway_rejection_reason,
61
+ scope: 'solidus_paypal_braintree.gateway_rejection_reasons',
62
+ default: "#{transaction.status.humanize} #{transaction.gateway_rejection_reason.humanize}")
63
+ when 'processor_declined'
64
+ I18n.t(transaction.processor_response_code,
65
+ scope: 'solidus_paypal_braintree.processor_response_codes',
66
+ default: "#{transaction.processor_response_text} (#{transaction.processor_response_code})")
67
+ when 'settlement_declined'
68
+ I18n.t(transaction.processor_settlement_response_code,
69
+ scope: 'solidus_paypal_braintree.processor_settlement_response_codes',
70
+ default: "#{transaction.processor_settlement_response_text} (#{transaction.processor_settlement_response_code})")
71
+ else
72
+ I18n.t(transaction.status,
73
+ scope: 'solidus_paypal_braintree.transaction_statuses',
74
+ default: transaction.status.humanize)
48
75
  end
49
76
  end
50
77
  end
@@ -1,5 +1,7 @@
1
1
  module SolidusPaypalBraintree
2
- class Source < ApplicationRecord
2
+ class Source < SolidusSupport.payment_source_parent_class
3
+ include RequestProtection
4
+
3
5
  PAYPAL = "PayPalAccount"
4
6
  APPLE_PAY = "ApplePayCard"
5
7
  CREDIT_CARD = "CreditCard"
@@ -32,7 +34,13 @@ module SolidusPaypalBraintree
32
34
  end
33
35
 
34
36
  def can_void?(payment)
35
- !payment.failed? && !payment.void?
37
+ return false unless payment.response_code
38
+ transaction = protected_request do
39
+ braintree_client.transaction.find(payment.response_code)
40
+ end
41
+ Gateway::VOIDABLE_STATUSES.include?(transaction.status)
42
+ rescue ActiveMerchant::ConnectionError
43
+ false
36
44
  end
37
45
 
38
46
  def can_credit?(payment)
@@ -51,19 +59,28 @@ module SolidusPaypalBraintree
51
59
  payment_type == PAYPAL
52
60
  end
53
61
 
62
+ def reusable?
63
+ true
64
+ end
65
+
54
66
  def credit_card?
55
67
  payment_type == CREDIT_CARD
56
68
  end
57
69
 
58
70
  def display_number
59
- "XXXX-XXXX-XXXX-#{last_digits}"
71
+ "XXXX-XXXX-XXXX-#{last_digits.to_s.rjust(4, 'X')}"
60
72
  end
61
73
 
62
74
  private
63
75
 
64
76
  def braintree_payment_method
65
77
  return unless braintree_client && credit_card?
66
- @braintree_payment_method ||= braintree_client.payment_method.find(token)
78
+ @braintree_payment_method ||= protected_request do
79
+ braintree_client.payment_method.find(token)
80
+ end
81
+ rescue ActiveMerchant::ConnectionError, ArgumentError => e
82
+ Rails.logger.warn("#{e}: token unknown or missing for #{inspect}")
83
+ nil
67
84
  end
68
85
 
69
86
  def braintree_client
@@ -9,7 +9,6 @@ module SolidusPaypalBraintree
9
9
  validates :nonce, presence: true
10
10
  validates :payment_method, presence: true
11
11
  validates :payment_type, presence: true
12
- validates :phone, presence: true
13
12
  validates :email, presence: true
14
13
 
15
14
  validate do
@@ -10,14 +10,14 @@ module SolidusPaypalBraintree
10
10
  :city, :zip, :state_code, :address_line_1, :address_line_2
11
11
 
12
12
  validates :first_name, :last_name, :address_line_1, :city, :zip,
13
- :state_code, :country_code, presence: true
13
+ :country_code, presence: true
14
14
 
15
15
  before_validation do
16
16
  self.country_code = country_code.presence || "us"
17
17
  end
18
18
 
19
19
  validates :spree_country, presence: true
20
- validates :spree_state, presence: true, if: :should_match_state_model?
20
+ validates :state_code, :spree_state, presence: true, if: :should_match_state_model?
21
21
 
22
22
  def initialize(attributes = {})
23
23
  country_name = attributes.delete(:country_name) || ""
@@ -57,10 +57,9 @@ module SolidusPaypalBraintree
57
57
  address
58
58
  end
59
59
 
60
- # Check to see if this address should match to a state
61
- # model in the database.
60
+ # Check to see if this address should match to a state model in the database
62
61
  def should_match_state_model?
63
- spree_country.present? && spree_country.states.any?
62
+ spree_country.try!(:states_required?)
64
63
  end
65
64
  end
66
65
  end
@@ -82,10 +82,10 @@ module SolidusPaypalBraintree
82
82
 
83
83
  def update_payment_total(payment)
84
84
  payment_total = order.payments.where(state: %w[checkout pending]).sum(:amount)
85
- remaining_total = order.outstanding_balance - payment_total
85
+ payment_difference = order.outstanding_balance - payment_total
86
86
 
87
- if remaining_total > 0
88
- payment.update!(amount: payment.amount + remaining_total)
87
+ if payment_difference != 0
88
+ payment.update!(amount: payment.amount + payment_difference)
89
89
  end
90
90
  end
91
91
  end
@@ -0,0 +1,19 @@
1
+ <script type="text/javascript">
2
+ BraintreeError = {
3
+ HOSTED_FIELDS_FIELDS_EMPTY: "<%= I18n.t('solidus_paypal_braintree.errors.empty_fields')%>",
4
+ HOSTED_FIELDS_FIELDS_INVALID: "<%= I18n.t('solidus_paypal_braintree.errors.invalid_fields')%>",
5
+ HOSTED_FIELDS_FAILED_TOKENIZATION: "<%= I18n.t('solidus_paypal_braintree.errors.invalid_card')%>",
6
+ HOSTED_FIELDS_TOKENIZATION_NETWORK_ERROR: "<%= I18n.t('solidus_paypal_braintree.errors.network_error')%>",
7
+ HOSTED_FIELDS_FIELD_DUPLICATE_IFRAME: "<%= I18n.t('solidus_paypal_braintree.errors.duplicate_iframe')%>",
8
+ HOSTED_FIELDS_TOKENIZATION_FAIL_ON_DUPLICATE: "<%= I18n.t('solidus_paypal_braintree.errors.fail_on_duplicate')%>",
9
+ HOSTED_FIELDS_TOKENIZATION_CVV_VERIFICATION_FAILED: "<%= I18n.t('solidus_paypal_braintree.errors.cvv_verification_failed')%>",
10
+
11
+ getErrorFromSlug: function(slug) {
12
+ // Default error message
13
+ error = "<%= I18n.t('solidus_paypal_braintree.errors.default_error')%>"
14
+ if (slug in BraintreeError)
15
+ error = BraintreeError[slug]
16
+ return error
17
+ }
18
+ }
19
+ </script>
@@ -1,5 +1,7 @@
1
1
  <% prefix = "payment_source[#{id}]" %>
2
2
 
3
+ <%= render partial: "spree/shared/braintree_errors" %>
4
+
3
5
  <div class="hosted-fields">
4
6
  <div class="field" data-hook="card_number">
5
7
  <%= label_tag "card_number#{id}", Spree::CreditCard.human_attribute_name(:number), class: "required" %>
@@ -18,6 +18,15 @@ en:
18
18
  braintree: Braintree
19
19
  nonce: Nonce
20
20
  token: Token
21
+ errors:
22
+ default_error: "Something bad happened!"
23
+ empty_fields: "All fields are empty! Please fill out the form."
24
+ invalid_fields: "Some payment input fields are invalid."
25
+ invalid_card: "Credit card data is incorrect."
26
+ network_error: "Network error occurred."
27
+ duplicate_iframe: "Duplicate Braintree iframe."
28
+ fail_on_duplicate: "This payment method already exists in your vault."
29
+ cvv_verification_failed: "CVV did not pass verification."
21
30
  payment_type:
22
31
  label: Payment Type
23
32
  apple_pay_card: Apple Pay
@@ -28,3 +37,16 @@ en:
28
37
  tab: Braintree
29
38
  update_success: Successfully updated Braintree configurations.
30
39
  update_error: An error occurred while updating Braintree configurations.
40
+ gateway_rejection_reasons:
41
+ avs: AVS check failed.
42
+ avs_and_cvv: AVS and CVV check failed.
43
+ cvv: CVV check failed.
44
+ duplicate: Duplicate transaction.
45
+ fraud: Credit card was rejected because of potential fraud.
46
+ three_d_secure: 3D Secure check failed.
47
+ transaction_statuses:
48
+ authorization_expired: Payment authorization has expired.
49
+ failed: An error occurred when sending the transaction to the processor.
50
+ gateway_rejected: Gateway rejected this transaction.
51
+ processor_declined: Processor declined this transaction.
52
+ settlement_declined: Settlement was declined by processor.
@@ -0,0 +1,11 @@
1
+ it:
2
+ solidus_paypal_braintree:
3
+ errors:
4
+ default_error: "Qualcosa è andato storto."
5
+ empty_fields: "I campi non possono essere vuoti."
6
+ invalid_fields: "Alcuni campi non sono corretti."
7
+ invalid_card: "I dati della carta di credito non sono corretti."
8
+ network_error: "Si è verificato un errore di rete."
9
+ duplicate_iframe: "Ci sono due form Braintree."
10
+ fail_on_duplicate: "Questo metodo di pagamento già esiste."
11
+ cvv_verification_failed: "Il codice CVV non è corretto."
@@ -1,7 +1,7 @@
1
1
  require 'solidus_core'
2
- require 'solidus_support'
3
2
  require 'solidus_paypal_braintree/engine'
4
3
  require 'solidus_paypal_braintree/country_mapper'
4
+ require 'solidus_paypal_braintree/request_protection'
5
5
 
6
6
  module SolidusPaypalBraintree
7
7
  def self.table_name_prefix
@@ -1,3 +1,5 @@
1
+ require 'solidus_support'
2
+
1
3
  module SolidusPaypalBraintree
2
4
  class Engine < Rails::Engine
3
5
  isolate_namespace SolidusPaypalBraintree
@@ -21,15 +23,7 @@ module SolidusPaypalBraintree
21
23
 
22
24
  config.to_prepare(&method(:activate).to_proc)
23
25
 
24
- def self.frontend_available?
25
- defined?(Spree::Frontend::Engine) == "constant"
26
- end
27
-
28
- def self.backend_available?
29
- defined?(Spree::Backend::Engine) == "constant"
30
- end
31
-
32
- if frontend_available?
26
+ if SolidusSupport.frontend_available?
33
27
  config.assets.precompile += [
34
28
  'solidus_paypal_braintree/checkout.js',
35
29
  'solidus_paypal_braintree/frontend.js',
@@ -40,13 +34,19 @@ module SolidusPaypalBraintree
40
34
  paths["app/views"] << "lib/views/frontend"
41
35
  end
42
36
 
43
- if backend_available?
37
+ if SolidusSupport.backend_available?
44
38
  config.assets.precompile += ["spree/backend/solidus_paypal_braintree.js"]
45
39
  paths["app/controllers"] << "lib/controllers/backend"
46
40
 
47
41
  # We support Solidus v1.2, which requires some different markup in the
48
42
  # source form partial. This will take precedence over lib/views/backend.
49
- paths["app/views"] << "lib/views/backend_v1.2" if Gem::Version.new(Spree.solidus_version) < Gem::Version.new('1.3')
43
+ paths["app/views"] << "lib/views/backend_v1.2" if SolidusSupport.solidus_gem_version < Gem::Version.new('1.3')
44
+
45
+ # Solidus v2.4 introduced preference field partials but does not ship a hash field type.
46
+ # This is solved in Solidus v2.5.
47
+ if SolidusSupport.solidus_gem_version <= Gem::Version.new('2.5.0')
48
+ paths["app/views"] << "lib/views/backend_v2.4"
49
+ end
50
50
 
51
51
  paths["app/views"] << "lib/views/backend"
52
52
  end
@@ -1,11 +1,11 @@
1
- FactoryGirl.define do
1
+ FactoryBot.define do
2
2
  # Define your Spree extensions Factories within this file to enable applications, and other extensions to use and override them.
3
3
  #
4
4
  # Example adding this to your spec_helper will load these Factories for use:
5
5
  # require 'solidus_paypal_braintree/factories'
6
6
  end
7
7
 
8
- FactoryGirl.modify do
8
+ FactoryBot.modify do
9
9
  # The Solidus address factory randomizes the zipcode.
10
10
  # The OrderWalkThrough we use in the credit card checkout spec uses this factory for the user addresses.
11
11
  # For credit card payments we transmit the billing address to braintree, for paypal payments the shipping address.
@@ -0,0 +1,18 @@
1
+ require 'active_merchant/network_connection_retries'
2
+
3
+ module SolidusPaypalBraintree
4
+ module RequestProtection
5
+ include ActiveMerchant::NetworkConnectionRetries
6
+
7
+ def protected_request
8
+ raise ArgumentError unless block_given?
9
+ options = {
10
+ connection_exceptions: {
11
+ Braintree::BraintreeError => 'Error while connecting to Braintree gateway'
12
+ },
13
+ logger: Rails.logger
14
+ }
15
+ retry_exceptions(options) { yield }
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module SolidusPaypalBraintree
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -0,0 +1,12 @@
1
+ <% label = local_assigns[:label].presence %>
2
+ <% html_options = {class: 'input_hash fullwidth'}.update(local_assigns[:html_options] || {}) %>
3
+
4
+ <div class="field">
5
+ <% if local_assigns[:form] %>
6
+ <%= form.label attribute, label %>
7
+ <%= form.text_area attribute, html_options %>
8
+ <% else %>
9
+ <%= label_tag name, label %>
10
+ <%= text_area_tag name, value, html_options %>
11
+ <% end %>
12
+ </div>
@@ -2,19 +2,19 @@
2
2
  <% id = payment_method.id %>
3
3
 
4
4
  <% content_for :head do %>
5
- <script src="https://js.braintreegateway.com/web/3.14.0/js/client.min.js"></script>
6
- <script src="https://js.braintreegateway.com/web/3.14.0/js/data-collector.min.js"></script>
5
+ <script src="https://js.braintreegateway.com/web/3.22.1/js/client.min.js"></script>
6
+ <script src="https://js.braintreegateway.com/web/3.22.1/js/data-collector.min.js"></script>
7
7
 
8
8
  <% if current_store.braintree_configuration.paypal? %>
9
- <script src="https://js.braintreegateway.com/web/3.14.0/js/paypal.min.js"></script>
9
+ <script src="https://js.braintreegateway.com/web/3.22.1/js/paypal.min.js"></script>
10
10
  <% end %>
11
11
 
12
12
  <% if current_store.braintree_configuration.credit_card? %>
13
- <script src="https://js.braintreegateway.com/web/3.14.0/js/hosted-fields.min.js"></script>
13
+ <script src="https://js.braintreegateway.com/web/3.22.1/js/hosted-fields.min.js"></script>
14
14
  <% end %>
15
15
 
16
16
  <% if current_store.braintree_configuration.apple_pay? %>
17
- <script src="https://js.braintreegateway.com/web/3.14.0/js/apple-pay.min.js"></script>
17
+ <script src="https://js.braintreegateway.com/web/3.22.1/js/apple-pay.min.js"></script>
18
18
  <% end %>
19
19
 
20
20
  <%= javascript_include_tag "solidus_paypal_braintree/checkout" %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidus_paypal_braintree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stembolt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-20 00:00:00.000000000 Z
11
+ date: 2018-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: solidus
@@ -36,14 +36,14 @@ dependencies:
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '0'
39
+ version: 0.1.3
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: '0'
46
+ version: 0.1.3
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: braintree
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -58,20 +58,34 @@ dependencies:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
60
  version: '2.65'
61
+ - !ruby/object:Gem::Dependency
62
+ name: activemerchant
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.48'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.48'
61
75
  - !ruby/object:Gem::Dependency
62
76
  name: capybara
63
77
  requirement: !ruby/object:Gem::Requirement
64
78
  requirements:
65
- - - ">="
79
+ - - "~>"
66
80
  - !ruby/object:Gem::Version
67
- version: '0'
81
+ version: '2.18'
68
82
  type: :development
69
83
  prerelease: false
70
84
  version_requirements: !ruby/object:Gem::Requirement
71
85
  requirements:
72
- - - ">="
86
+ - - "~>"
73
87
  - !ruby/object:Gem::Version
74
- version: '0'
88
+ version: '2.18'
75
89
  - !ruby/object:Gem::Dependency
76
90
  name: capybara-screenshot
77
91
  requirement: !ruby/object:Gem::Requirement
@@ -157,7 +171,7 @@ dependencies:
157
171
  - !ruby/object:Gem::Version
158
172
  version: '0'
159
173
  - !ruby/object:Gem::Dependency
160
- name: factory_girl
174
+ name: factory_bot
161
175
  requirement: !ruby/object:Gem::Requirement
162
176
  requirements:
163
177
  - - ">="
@@ -188,16 +202,16 @@ dependencies:
188
202
  name: rubocop
189
203
  requirement: !ruby/object:Gem::Requirement
190
204
  requirements:
191
- - - ">="
205
+ - - "~>"
192
206
  - !ruby/object:Gem::Version
193
- version: '0.47'
207
+ version: 0.53.0
194
208
  type: :development
195
209
  prerelease: false
196
210
  version_requirements: !ruby/object:Gem::Requirement
197
211
  requirements:
198
- - - ">="
212
+ - - "~>"
199
213
  - !ruby/object:Gem::Version
200
- version: '0.47'
214
+ version: 0.53.0
201
215
  - !ruby/object:Gem::Dependency
202
216
  name: rubocop-rspec
203
217
  requirement: !ruby/object:Gem::Requirement
@@ -293,6 +307,7 @@ files:
293
307
  - app/controllers/solidus_paypal_braintree/client_tokens_controller.rb
294
308
  - app/helpers/braintree_admin_helper.rb
295
309
  - app/models/application_record.rb
310
+ - app/models/solidus_paypal_braintree/avs_result.rb
296
311
  - app/models/solidus_paypal_braintree/configuration.rb
297
312
  - app/models/solidus_paypal_braintree/customer.rb
298
313
  - app/models/solidus_paypal_braintree/gateway.rb
@@ -303,9 +318,11 @@ files:
303
318
  - app/models/solidus_paypal_braintree/transaction_import.rb
304
319
  - app/models/spree/store_decorator.rb
305
320
  - app/overrides/admin_navigation_menu.rb
321
+ - app/views/spree/shared/_braintree_errors.html.erb
306
322
  - app/views/spree/shared/_braintree_hosted_fields.html.erb
307
323
  - config/initializers/braintree.rb
308
324
  - config/locales/en.yml
325
+ - config/locales/it.yml
309
326
  - config/routes.rb
310
327
  - db/migrate/20160830061749_create_solidus_paypal_braintree_sources.rb
311
328
  - db/migrate/20160906201711_create_solidus_paypal_braintree_customers.rb
@@ -322,12 +339,14 @@ files:
322
339
  - lib/solidus_paypal_braintree/country_mapper.rb
323
340
  - lib/solidus_paypal_braintree/engine.rb
324
341
  - lib/solidus_paypal_braintree/factories.rb
342
+ - lib/solidus_paypal_braintree/request_protection.rb
325
343
  - lib/solidus_paypal_braintree/version.rb
326
344
  - lib/views/backend/solidus_paypal_braintree/configurations/_admin_tab.html.erb
327
345
  - lib/views/backend/solidus_paypal_braintree/configurations/list.html.erb
328
346
  - lib/views/backend/spree/admin/payments/source_forms/_paypal_braintree.html.erb
329
347
  - lib/views/backend/spree/admin/payments/source_views/_paypal_braintree.html.erb
330
348
  - lib/views/backend_v1.2/spree/admin/payments/source_forms/_paypal_braintree.html.erb
349
+ - lib/views/backend_v2.4/spree/admin/shared/preference_fields/_hash.html.erb
331
350
  - lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb
332
351
  homepage: https://stembolt.com
333
352
  licenses:
@@ -349,7 +368,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
349
368
  version: '0'
350
369
  requirements: []
351
370
  rubyforge_project:
352
- rubygems_version: 2.6.11
371
+ rubygems_version: 2.2.2
353
372
  signing_key:
354
373
  specification_version: 4
355
374
  summary: Officially supported Paypal/Braintree extension