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 +4 -4
- data/README.md +37 -29
- data/app/assets/javascripts/solidus_paypal_braintree/constants.js +9 -5
- data/app/assets/javascripts/solidus_paypal_braintree/paypal_button.js +17 -14
- data/app/assets/javascripts/spree/backend/solidus_paypal_braintree.js +2 -2
- data/app/assets/javascripts/spree/frontend/paypal_button.js +3 -3
- data/app/models/solidus_paypal_braintree/avs_result.rb +66 -0
- data/app/models/solidus_paypal_braintree/gateway.rb +67 -23
- data/app/models/solidus_paypal_braintree/response.rb +48 -21
- data/app/models/solidus_paypal_braintree/source.rb +21 -4
- data/app/models/solidus_paypal_braintree/transaction.rb +0 -1
- data/app/models/solidus_paypal_braintree/transaction_address.rb +4 -5
- data/app/models/solidus_paypal_braintree/transaction_import.rb +3 -3
- data/app/views/spree/shared/_braintree_errors.html.erb +19 -0
- data/app/views/spree/shared/_braintree_hosted_fields.html.erb +2 -0
- data/config/locales/en.yml +22 -0
- data/config/locales/it.yml +11 -0
- data/lib/solidus_paypal_braintree.rb +1 -1
- data/lib/solidus_paypal_braintree/engine.rb +11 -11
- data/lib/solidus_paypal_braintree/factories.rb +2 -2
- data/lib/solidus_paypal_braintree/request_protection.rb +18 -0
- data/lib/solidus_paypal_braintree/version.rb +1 -1
- data/lib/views/backend_v2.4/spree/admin/shared/preference_fields/_hash.html.erb +12 -0
- data/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb +5 -5
- metadata +33 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76631d8678366736ecdab190121377841bafa0df
|
4
|
+
data.tar.gz: bcc44b77f8ad9172d850676dd1784c7e43bad1cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
13
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
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" :
|
107
|
-
"address_line_2" :
|
108
|
-
"city" :
|
109
|
-
"state_code" :
|
110
|
-
"zip" :
|
111
|
-
"country_code" :
|
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.
|
72
|
-
$.getScript("https://js.braintreegateway.com/web/3.
|
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.
|
7
|
-
$.getScript("https://js.braintreegateway.com/web/3.
|
8
|
-
$.getScript("https://js.braintreegateway.com/web/3.
|
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
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
128
|
-
|
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 =
|
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
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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 <
|
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
|
-
|
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 ||=
|
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
|
@@ -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
|
-
:
|
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.
|
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
|
-
|
85
|
+
payment_difference = order.outstanding_balance - payment_total
|
86
86
|
|
87
|
-
if
|
88
|
-
payment.update!(amount: payment.amount +
|
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" %>
|
data/config/locales/en.yml
CHANGED
@@ -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,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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
@@ -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.
|
6
|
-
<script src="https://js.braintreegateway.com/web/3.
|
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.
|
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.
|
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.
|
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.
|
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:
|
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:
|
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:
|
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: '
|
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: '
|
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:
|
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:
|
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:
|
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.
|
371
|
+
rubygems_version: 2.2.2
|
353
372
|
signing_key:
|
354
373
|
specification_version: 4
|
355
374
|
summary: Officially supported Paypal/Braintree extension
|