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