solidus_paypal_braintree 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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