braintree 2.15.0 → 2.16.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.
- data/lib/braintree.rb +5 -0
- data/lib/braintree/digest.rb +14 -1
- data/lib/braintree/exceptions.rb +3 -0
- data/lib/braintree/gateway.rb +8 -0
- data/lib/braintree/plan_gateway.rb +1 -1
- data/lib/braintree/transaction.rb +5 -0
- data/lib/braintree/version.rb +1 -1
- data/lib/braintree/webhook_notification.rb +38 -0
- data/lib/braintree/webhook_notification_gateway.rb +36 -0
- data/lib/braintree/webhook_testing.rb +7 -0
- data/lib/braintree/webhook_testing_gateway.rb +41 -0
- data/spec/integration/braintree/plan_spec.rb +8 -2
- data/spec/integration/braintree/transaction_spec.rb +21 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/unit/braintree/webhook_notification_spec.rb +56 -0
- metadata +9 -4
data/lib/braintree.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'base64'
|
1
2
|
require "bigdecimal"
|
2
3
|
require "cgi"
|
3
4
|
require "date"
|
@@ -66,6 +67,10 @@ require "braintree/util"
|
|
66
67
|
require "braintree/validation_error"
|
67
68
|
require "braintree/validation_error_collection"
|
68
69
|
require "braintree/version"
|
70
|
+
require "braintree/webhook_notification"
|
71
|
+
require "braintree/webhook_notification_gateway"
|
72
|
+
require "braintree/webhook_testing"
|
73
|
+
require "braintree/webhook_testing_gateway"
|
69
74
|
require "braintree/xml"
|
70
75
|
require "braintree/xml/generator"
|
71
76
|
require "braintree/xml/libxml"
|
data/lib/braintree/digest.rb
CHANGED
@@ -4,6 +4,20 @@ module Braintree
|
|
4
4
|
_hmac_sha1(private_key, string)
|
5
5
|
end
|
6
6
|
|
7
|
+
def self.secure_compare(left, right)
|
8
|
+
return false unless left && right
|
9
|
+
|
10
|
+
left_bytes = left.unpack("C*")
|
11
|
+
right_bytes = right.unpack("C*")
|
12
|
+
return false if left_bytes.size != right_bytes.size
|
13
|
+
|
14
|
+
result = 0
|
15
|
+
left_bytes.zip(right_bytes).each do |left_byte, right_byte|
|
16
|
+
result |= left_byte ^ right_byte
|
17
|
+
end
|
18
|
+
result == 0
|
19
|
+
end
|
20
|
+
|
7
21
|
def self._hmac_sha1(key, message)
|
8
22
|
key_digest = ::Digest::SHA1.digest(key)
|
9
23
|
sha1 = OpenSSL::Digest::Digest.new("sha1")
|
@@ -11,4 +25,3 @@ module Braintree
|
|
11
25
|
end
|
12
26
|
end
|
13
27
|
end
|
14
|
-
|
data/lib/braintree/exceptions.rb
CHANGED
@@ -21,6 +21,9 @@ module Braintree # :nodoc:
|
|
21
21
|
# See http://www.braintreepayments.com/docs/ruby/general/exceptions
|
22
22
|
class ForgedQueryString < BraintreeError; end
|
23
23
|
|
24
|
+
# See http://www.braintreepayments.com/docs/ruby/general/exceptions
|
25
|
+
class InvalidSignature < BraintreeError; end
|
26
|
+
|
24
27
|
# See http://www.braintreepayments.com/docs/ruby/general/exceptions
|
25
28
|
class NotFoundError < BraintreeError; end
|
26
29
|
|
data/lib/braintree/gateway.rb
CHANGED
@@ -126,6 +126,11 @@ module Braintree
|
|
126
126
|
Configuration.gateway.transaction.refund(id, amount)
|
127
127
|
end
|
128
128
|
|
129
|
+
# See http://www.braintreepayments.com/docs/ruby/transactions/refund
|
130
|
+
def self.refund!(id, amount = nil)
|
131
|
+
return_object_or_raise(:transaction) { refund(id, amount) }
|
132
|
+
end
|
133
|
+
|
129
134
|
# See http://www.braintreepayments.com/docs/ruby/transactions/create
|
130
135
|
def self.sale(attributes)
|
131
136
|
Configuration.gateway.transaction.sale(attributes)
|
data/lib/braintree/version.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Braintree
|
2
|
+
class WebhookNotification
|
3
|
+
include BaseModule
|
4
|
+
|
5
|
+
module Kind
|
6
|
+
SubscriptionCanceled = "subscription_canceled"
|
7
|
+
SubscriptionChargedSuccessfully = "subscription_charged_successfully"
|
8
|
+
SubscriptionChargedUnsuccessfully = "subscription_charged_unsuccessfully"
|
9
|
+
SubscriptionExpired = "subscription_expired"
|
10
|
+
SubscriptionTrialEnded = "subscription_trial_ended"
|
11
|
+
SubscriptionWentActive = "subscription_went_active"
|
12
|
+
SubscriptionWentPastDue = "subscription_went_past_due"
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :subscription, :kind, :timestamp
|
16
|
+
|
17
|
+
def self.parse(signature, payload)
|
18
|
+
Configuration.gateway.webhook_notification.parse(signature, payload)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.verify(challenge)
|
22
|
+
Configuration.gateway.webhook_notification.verify(challenge)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(gateway, attributes) # :nodoc:
|
26
|
+
@gateway = gateway
|
27
|
+
set_instance_variables_from_hash(attributes)
|
28
|
+
@subscription = Subscription._new(gateway, @subject[:subscription]) if @subject.has_key?(:subscription)
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
protected :new
|
33
|
+
def _new(*args) # :nodoc:
|
34
|
+
self.new *args
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Braintree
|
2
|
+
class WebhookNotificationGateway # :nodoc:
|
3
|
+
def initialize(gateway)
|
4
|
+
@gateway = gateway
|
5
|
+
@config = gateway.config
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse(signature_string, payload)
|
9
|
+
_verify_signature(signature_string, payload)
|
10
|
+
attributes = Xml.hash_from_xml(Base64.decode64(payload))
|
11
|
+
WebhookNotification._new(@gateway, attributes[:notification])
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify(challenge)
|
15
|
+
digest = Braintree::Digest.hexdigest(@config.private_key, challenge)
|
16
|
+
"#{@config.public_key}|#{digest}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def _matching_signature_pair(signature_string)
|
20
|
+
signature_pairs = signature_string.split("&")
|
21
|
+
valid_pairs = signature_pairs.select { |pair| pair.include?("|") }.map { |pair| pair.split("|") }
|
22
|
+
|
23
|
+
valid_pairs.detect do |public_key, signature|
|
24
|
+
public_key == @config.public_key
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def _verify_signature(signature, payload)
|
29
|
+
public_key, signature = _matching_signature_pair(signature)
|
30
|
+
payload_signature = Braintree::Digest.hexdigest(@config.private_key, payload)
|
31
|
+
|
32
|
+
raise InvalidSignature if public_key.nil?
|
33
|
+
raise InvalidSignature unless Braintree::Digest.secure_compare(signature, payload_signature)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Braintree
|
2
|
+
class WebhookTestingGateway # :nodoc:
|
3
|
+
def initialize(gateway)
|
4
|
+
@gateway = gateway
|
5
|
+
@config = gateway.config
|
6
|
+
end
|
7
|
+
|
8
|
+
def sample_notification(kind, id)
|
9
|
+
payload = Base64.encode64(_sample_xml(kind, id))
|
10
|
+
signature_string = "#{Braintree::Configuration.public_key}|#{Braintree::Digest.hexdigest(Braintree::Configuration.private_key, payload)}"
|
11
|
+
|
12
|
+
return signature_string, payload
|
13
|
+
end
|
14
|
+
|
15
|
+
def _sample_xml(kind, id)
|
16
|
+
<<-XML
|
17
|
+
<notification>
|
18
|
+
<timestamp type="datetime">#{Time.now.utc.iso8601}</timestamp>
|
19
|
+
<kind>#{kind}</kind>
|
20
|
+
<subject>
|
21
|
+
#{_subscription_sample_xml(id)}
|
22
|
+
</subject>
|
23
|
+
</notification>
|
24
|
+
XML
|
25
|
+
end
|
26
|
+
|
27
|
+
def _subscription_sample_xml(id)
|
28
|
+
<<-XML
|
29
|
+
<subscription>
|
30
|
+
<id>#{id}</id>
|
31
|
+
<transactions type="array">
|
32
|
+
</transactions>
|
33
|
+
<add_ons type="array">
|
34
|
+
</add_ons>
|
35
|
+
<discounts type="array">
|
36
|
+
</discounts>
|
37
|
+
</subscription>
|
38
|
+
XML
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -22,8 +22,8 @@ describe Braintree::Plan do
|
|
22
22
|
|
23
23
|
add_on_name = "ruby_add_on"
|
24
24
|
discount_name = "ruby_discount"
|
25
|
-
create_modification_for_tests(
|
26
|
-
create_modification_for_tests(
|
25
|
+
create_modification_for_tests(:kind => "add_on", :plan_id => plan_token, :amount => "1.00", :name => add_on_name)
|
26
|
+
create_modification_for_tests(:kind => "discount", :plan_id => plan_token, :amount => "1.00", :name => discount_name)
|
27
27
|
|
28
28
|
plans = Braintree::Plan.all
|
29
29
|
plan = plans.select { |plan| plan.id == plan_token }.first
|
@@ -44,6 +44,12 @@ describe Braintree::Plan do
|
|
44
44
|
plan.add_ons.first.name.should == add_on_name
|
45
45
|
plan.discounts.first.name.should == discount_name
|
46
46
|
end
|
47
|
+
|
48
|
+
it "returns an empty array if there are no plans" do
|
49
|
+
gateway = Braintree::Gateway.new(SpecHelper::TestMerchantConfig)
|
50
|
+
plans = gateway.plan.all
|
51
|
+
plans.should == []
|
52
|
+
end
|
47
53
|
end
|
48
54
|
|
49
55
|
def create_plan_for_tests(attributes)
|
@@ -934,6 +934,27 @@ describe Braintree::Transaction do
|
|
934
934
|
end
|
935
935
|
end
|
936
936
|
|
937
|
+
describe "self.refund!" do
|
938
|
+
it "returns the refund if valid refund" do
|
939
|
+
transaction = create_transaction_to_refund
|
940
|
+
|
941
|
+
refund_transaction = Braintree::Transaction.refund!(transaction.id)
|
942
|
+
|
943
|
+
refund_transaction.refunded_transaction_id.should == transaction.id
|
944
|
+
refund_transaction.type.should == "credit"
|
945
|
+
transaction.amount.should == refund_transaction.amount
|
946
|
+
end
|
947
|
+
|
948
|
+
it "raises a ValidationsFailed if invalid" do
|
949
|
+
transaction = create_transaction_to_refund
|
950
|
+
invalid_refund_amount = transaction.amount + 1
|
951
|
+
invalid_refund_amount.should be > transaction.amount
|
952
|
+
|
953
|
+
expect do
|
954
|
+
Braintree::Transaction.refund!(transaction.id,invalid_refund_amount)
|
955
|
+
end.to raise_error(Braintree::ValidationsFailed)
|
956
|
+
end
|
957
|
+
end
|
937
958
|
describe "self.sale" do
|
938
959
|
it "returns a successful result with type=sale if successful" do
|
939
960
|
result = Braintree::Transaction.sale(
|
data/spec/spec_helper.rb
CHANGED
@@ -75,6 +75,14 @@ unless defined?(SPEC_HELPER_LOADED)
|
|
75
75
|
Discount11 = "discount_11"
|
76
76
|
Discount15 = "discount_15"
|
77
77
|
|
78
|
+
TestMerchantConfig = Braintree::Configuration.new(
|
79
|
+
:logger => Logger.new("/dev/null"),
|
80
|
+
:environment => :development,
|
81
|
+
:merchant_id => "test_merchant_id",
|
82
|
+
:public_key => "test_public_key",
|
83
|
+
:private_key => "test_private_key"
|
84
|
+
)
|
85
|
+
|
78
86
|
def self.make_past_due(subscription, number_of_days_past_due = 1)
|
79
87
|
Braintree::Configuration.instantiate.http.put(
|
80
88
|
"/subscriptions/#{subscription.id}/make_past_due?days_past_due=#{number_of_days_past_due}"
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
describe Braintree::WebhookNotification do
|
4
|
+
describe "self.sample_notification" do
|
5
|
+
it "builds a sample notification and signature given an identifier and kind" do
|
6
|
+
signature, payload = Braintree::WebhookTesting.sample_notification(
|
7
|
+
Braintree::WebhookNotification::Kind::SubscriptionWentPastDue,
|
8
|
+
"my_id"
|
9
|
+
)
|
10
|
+
|
11
|
+
notification = Braintree::WebhookNotification.parse(signature, payload)
|
12
|
+
|
13
|
+
notification.kind.should == Braintree::WebhookNotification::Kind::SubscriptionWentPastDue
|
14
|
+
notification.subscription.id.should == "my_id"
|
15
|
+
notification.timestamp.should be_close(Time.now.utc, 10)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "includes a valid signature" do
|
19
|
+
signature, payload = Braintree::WebhookTesting.sample_notification(Braintree::WebhookNotification::Kind::SubscriptionWentPastDue, "my_id")
|
20
|
+
expected_signature = Braintree::Digest.hexdigest(Braintree::Configuration.private_key, payload)
|
21
|
+
|
22
|
+
signature.should == "#{Braintree::Configuration.public_key}|#{expected_signature}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "parse" do
|
27
|
+
it "raises InvalidSignature error the signature is completely invalid" do
|
28
|
+
signature, payload = Braintree::WebhookTesting.sample_notification(
|
29
|
+
Braintree::WebhookNotification::Kind::SubscriptionWentPastDue,
|
30
|
+
"my_id"
|
31
|
+
)
|
32
|
+
|
33
|
+
expect do
|
34
|
+
notification = Braintree::WebhookNotification.parse("not a valid signature", payload)
|
35
|
+
end.to raise_error(Braintree::InvalidSignature)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "raises InvalidSignature error the payload has been changed" do
|
39
|
+
signature, payload = Braintree::WebhookTesting.sample_notification(
|
40
|
+
Braintree::WebhookNotification::Kind::SubscriptionWentPastDue,
|
41
|
+
"my_id"
|
42
|
+
)
|
43
|
+
|
44
|
+
expect do
|
45
|
+
notification = Braintree::WebhookNotification.parse(signature, payload + "bad stuff")
|
46
|
+
end.to raise_error(Braintree::InvalidSignature)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "self.verify" do
|
51
|
+
it "creates a verification string" do
|
52
|
+
response = Braintree::WebhookNotification.verify("verification_token")
|
53
|
+
response.should == "integration_public_key|c9f15b74b0d98635cd182c51e2703cffa83388c3"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: braintree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 79
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
-
|
8
|
+
- 16
|
9
9
|
- 0
|
10
|
-
version: 2.
|
10
|
+
version: 2.16.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Braintree
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-04-
|
18
|
+
date: 2012-04-19 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -96,6 +96,10 @@ files:
|
|
96
96
|
- lib/braintree/validation_error.rb
|
97
97
|
- lib/braintree/validation_error_collection.rb
|
98
98
|
- lib/braintree/version.rb
|
99
|
+
- lib/braintree/webhook_notification.rb
|
100
|
+
- lib/braintree/webhook_notification_gateway.rb
|
101
|
+
- lib/braintree/webhook_testing.rb
|
102
|
+
- lib/braintree/webhook_testing_gateway.rb
|
99
103
|
- lib/braintree/xml/generator.rb
|
100
104
|
- lib/braintree/xml/libxml.rb
|
101
105
|
- lib/braintree/xml/parser.rb
|
@@ -145,6 +149,7 @@ files:
|
|
145
149
|
- spec/unit/braintree/util_spec.rb
|
146
150
|
- spec/unit/braintree/validation_error_collection_spec.rb
|
147
151
|
- spec/unit/braintree/validation_error_spec.rb
|
152
|
+
- spec/unit/braintree/webhook_notification_spec.rb
|
148
153
|
- spec/unit/braintree/xml/libxml_spec.rb
|
149
154
|
- spec/unit/braintree/xml/parser_spec.rb
|
150
155
|
- spec/unit/braintree/xml/rexml_spec.rb
|