spreedly 1.4.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/HISTORY.md +5 -0
- data/README.md +362 -29
- data/Rakefile +32 -0
- data/lib/certs/cacert.pem +7815 -0
- data/lib/spreedly.rb +24 -282
- data/lib/spreedly/common/errors_parser.rb +15 -0
- data/lib/spreedly/common/fields.rb +90 -0
- data/lib/spreedly/connection.rb +40 -0
- data/lib/spreedly/environment.rb +176 -0
- data/lib/spreedly/error.rb +50 -0
- data/lib/spreedly/gateway.rb +10 -0
- data/lib/spreedly/model.rb +17 -0
- data/lib/spreedly/payment_methods/credit_card.rb +9 -0
- data/lib/spreedly/payment_methods/payment_method.rb +34 -0
- data/lib/spreedly/payment_methods/paypal.rb +7 -0
- data/lib/spreedly/payment_methods/sprel.rb +7 -0
- data/lib/spreedly/ssl_requester.rb +65 -0
- data/lib/spreedly/transactions/add_payment_method.rb +16 -0
- data/lib/spreedly/transactions/auth_purchase.rb +17 -0
- data/lib/spreedly/transactions/authorization.rb +7 -0
- data/lib/spreedly/transactions/capture.rb +14 -0
- data/lib/spreedly/transactions/gateway_transaction.rb +31 -0
- data/lib/spreedly/transactions/purchase.rb +7 -0
- data/lib/spreedly/transactions/redact_payment_method.rb +14 -0
- data/lib/spreedly/transactions/refund.rb +14 -0
- data/lib/spreedly/transactions/retain_payment_method.rb +14 -0
- data/lib/spreedly/transactions/transaction.rb +41 -0
- data/lib/spreedly/transactions/void.rb +9 -0
- data/lib/spreedly/urls.rb +64 -0
- data/lib/spreedly/version.rb +1 -1
- data/spreedly.gemspec +29 -0
- data/test/credentials/credentials.yml +9 -0
- data/test/credentials/test_credentials.rb +43 -0
- data/test/helpers/assertions.rb +29 -0
- data/test/helpers/communication_helper.rb +31 -0
- data/test/helpers/creation_helper.rb +26 -0
- data/test/helpers/stub_response.rb +18 -0
- data/test/remote/remote_add_credit_card_test.rb +62 -0
- data/test/remote/remote_add_gateway_test.rb +30 -0
- data/test/remote/remote_authorize_test.rb +48 -0
- data/test/remote/remote_capture_test.rb +71 -0
- data/test/remote/remote_find_gateway_test.rb +31 -0
- data/test/remote/remote_find_payment_method_test.rb +29 -0
- data/test/remote/remote_find_transaction_test.rb +33 -0
- data/test/remote/remote_list_transactions_test.rb +36 -0
- data/test/remote/remote_purchase_test.rb +69 -0
- data/test/remote/remote_redact_test.rb +38 -0
- data/test/remote/remote_refund_test.rb +65 -0
- data/test/remote/remote_retain_test.rb +39 -0
- data/test/remote/remote_void_test.rb +64 -0
- data/test/test_helper.rb +23 -0
- data/test/unit/add_credit_card_test.rb +74 -0
- data/test/unit/add_gateway_test.rb +26 -0
- data/test/unit/authorize_test.rb +87 -0
- data/test/unit/capture_test.rb +91 -0
- data/test/unit/environment_test.rb +18 -0
- data/test/unit/fields_test.rb +75 -0
- data/test/unit/find_gateway_test.rb +28 -0
- data/test/unit/find_payment_method_test.rb +90 -0
- data/test/unit/find_transaction_test.rb +31 -0
- data/test/unit/list_transactions_test.rb +46 -0
- data/test/unit/purchase_test.rb +95 -0
- data/test/unit/redact_payment_method_test.rb +51 -0
- data/test/unit/refund_test.rb +91 -0
- data/test/unit/response_stubs/add_credit_card_stubs.rb +43 -0
- data/test/unit/response_stubs/add_gateway_stubs.rb +39 -0
- data/test/unit/response_stubs/authorization_stubs.rb +139 -0
- data/test/unit/response_stubs/capture_stubs.rb +87 -0
- data/test/unit/response_stubs/find_gateway_stubs.rb +38 -0
- data/test/unit/response_stubs/find_payment_method_stubs.rb +108 -0
- data/test/unit/response_stubs/find_transaction_stubs.rb +43 -0
- data/test/unit/response_stubs/list_transactions_stubs.rb +110 -0
- data/test/unit/response_stubs/purchase_stubs.rb +139 -0
- data/test/unit/response_stubs/redact_payment_method_stubs.rb +54 -0
- data/test/unit/response_stubs/refund_stubs.rb +87 -0
- data/test/unit/response_stubs/retain_payment_method_stubs.rb +85 -0
- data/test/unit/response_stubs/void_stubs.rb +79 -0
- data/test/unit/retain_payment_method_test.rb +44 -0
- data/test/unit/timeout_test.rb +20 -0
- data/test/unit/void_test.rb +96 -0
- metadata +215 -29
- checksums.yaml +0 -15
- data/lib/spreedly/common.rb +0 -44
- data/lib/spreedly/mock.rb +0 -221
- data/lib/spreedly/test_hacks.rb +0 -27
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Spreedly
|
5
|
+
class Environment
|
6
|
+
|
7
|
+
include SslRequester
|
8
|
+
include Urls
|
9
|
+
|
10
|
+
attr_reader :key, :currency_code
|
11
|
+
|
12
|
+
def initialize(environment_key, access_secret, options={})
|
13
|
+
@key, @access_secret = environment_key, access_secret
|
14
|
+
@currency_code = options[:currency_code] || 'USD'
|
15
|
+
end
|
16
|
+
|
17
|
+
def transparent_redirect_form_action
|
18
|
+
"#{base_url}/v1/payment_methods"
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_payment_method(token)
|
22
|
+
xml_doc = ssl_get(find_payment_method_url(token), headers)
|
23
|
+
PaymentMethod.new_from(xml_doc)
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_transaction(token)
|
27
|
+
xml_doc = ssl_get(find_transaction_url(token), headers)
|
28
|
+
Transaction.new_from(xml_doc)
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_gateway(token)
|
32
|
+
xml_doc = ssl_get(find_gateway_url(token), headers)
|
33
|
+
Gateway.new(xml_doc)
|
34
|
+
end
|
35
|
+
|
36
|
+
def purchase_on_gateway(gateway_token, payment_method_token, amount, options = {})
|
37
|
+
body = auth_purchase_body(amount, payment_method_token, options)
|
38
|
+
api_post(purchase_url(gateway_token), body)
|
39
|
+
end
|
40
|
+
|
41
|
+
def authorize_on_gateway(gateway_token, payment_method_token, amount, options = {})
|
42
|
+
body = auth_purchase_body(amount, payment_method_token, options)
|
43
|
+
api_post(authorize_url(gateway_token), body)
|
44
|
+
end
|
45
|
+
|
46
|
+
def capture_transaction(authorization_token, options = {})
|
47
|
+
api_post(capture_url(authorization_token), capture_body(options))
|
48
|
+
end
|
49
|
+
|
50
|
+
def void_transaction(token, options = {})
|
51
|
+
api_post(void_transaction_url(token), void_body(options))
|
52
|
+
end
|
53
|
+
|
54
|
+
def refund_transaction(token, options = {})
|
55
|
+
api_post(refund_transaction_url(token), refund_body(options))
|
56
|
+
end
|
57
|
+
|
58
|
+
def retain_payment_method(payment_method_token)
|
59
|
+
xml_doc = ssl_put(retain_payment_method_url(payment_method_token), '', headers)
|
60
|
+
Transaction.new_from(xml_doc)
|
61
|
+
end
|
62
|
+
|
63
|
+
def redact_payment_method(payment_method_token, options = {})
|
64
|
+
body = redact_payment_method_body(options)
|
65
|
+
xml_doc = ssl_put(redact_payment_method_url(payment_method_token), body, headers)
|
66
|
+
Transaction.new_from(xml_doc)
|
67
|
+
end
|
68
|
+
|
69
|
+
def list_transactions(since_token = nil)
|
70
|
+
xml_doc = ssl_get(list_transactions_url(since_token), headers)
|
71
|
+
Transaction.new_list_from(xml_doc)
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_gateway(gateway_type, options = {})
|
75
|
+
body = add_gateway_body(gateway_type, options)
|
76
|
+
xml_doc = ssl_post(add_gateway_url, body, headers)
|
77
|
+
Gateway.new(xml_doc)
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_credit_card(options)
|
81
|
+
api_post(add_payment_method_url, add_credit_card_body(options))
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def headers
|
86
|
+
{
|
87
|
+
'Authorization' => ('Basic ' + Base64.strict_encode64("#{@key}:#{@access_secret}").chomp),
|
88
|
+
'Content-Type' => 'text/xml'
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def auth_purchase_body(amount, payment_method_token, options)
|
93
|
+
build_xml_request('transaction') do |doc|
|
94
|
+
doc.amount amount
|
95
|
+
doc.currency_code(options[:currency_code] || currency_code)
|
96
|
+
doc.payment_method_token(payment_method_token)
|
97
|
+
add_to_doc(doc, options, :retain_on_success)
|
98
|
+
add_extra_options_for_basic_ops(doc, options)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def capture_body(options)
|
103
|
+
return '' if options.empty?
|
104
|
+
|
105
|
+
build_xml_request('transaction') do |doc|
|
106
|
+
add_to_doc(doc, options, :amount, :currency_code)
|
107
|
+
add_extra_options_for_basic_ops(doc, options)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def void_body(options)
|
112
|
+
return '' if options.empty?
|
113
|
+
|
114
|
+
build_xml_request('transaction') do |doc|
|
115
|
+
add_extra_options_for_basic_ops(doc, options)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def refund_body(options)
|
120
|
+
return '' if options.empty?
|
121
|
+
|
122
|
+
build_xml_request('transaction') do |doc|
|
123
|
+
add_to_doc(doc, options, :amount, :currency_code)
|
124
|
+
add_extra_options_for_basic_ops(doc, options)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def redact_payment_method_body(options)
|
129
|
+
return '' if options.empty?
|
130
|
+
build_xml_request('transaction') do |doc|
|
131
|
+
add_to_doc(doc, options, :remove_from_gateway)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def add_gateway_body(gateway_type, options)
|
136
|
+
build_xml_request('gateway') do |doc|
|
137
|
+
doc.gateway_type gateway_type
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def add_credit_card_body(options)
|
142
|
+
build_xml_request('payment_method') do |doc|
|
143
|
+
add_to_doc(doc, options, :data, :retained, :email)
|
144
|
+
doc.credit_card do
|
145
|
+
add_to_doc(doc, options, :number, :month, :first_name, :last_name, :year,
|
146
|
+
:address1, :address2, :city, :state, :zip, :country, :phone_number)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def add_to_doc(doc, options, *attributes)
|
152
|
+
attributes.each do |attr|
|
153
|
+
doc.send(attr, options[attr.to_sym]) if options[attr.to_sym]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def add_extra_options_for_basic_ops(doc, options)
|
158
|
+
add_to_doc(doc, options, :order_id, :description, :ip, :merchant_name_descriptor,
|
159
|
+
:merchant_location_descriptor)
|
160
|
+
end
|
161
|
+
|
162
|
+
def build_xml_request(root)
|
163
|
+
builder = Nokogiri::XML::Builder.new
|
164
|
+
builder.__send__(root) do |doc|
|
165
|
+
yield(doc)
|
166
|
+
end
|
167
|
+
builder.to_xml
|
168
|
+
end
|
169
|
+
|
170
|
+
def api_post(url, body)
|
171
|
+
xml_doc = ssl_post(url, body, headers)
|
172
|
+
Transaction.new_from(xml_doc)
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Spreedly
|
2
|
+
class Error < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class XmlErrorsList < Error
|
6
|
+
attr_reader :errors
|
7
|
+
include ErrorsParser
|
8
|
+
|
9
|
+
def initialize(xml_doc)
|
10
|
+
@errors = errors_from(xml_doc)
|
11
|
+
end
|
12
|
+
|
13
|
+
def message
|
14
|
+
@errors.map { |each| each[:message] }.join("\n")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class AuthenticationError < XmlErrorsList
|
19
|
+
end
|
20
|
+
|
21
|
+
class NotFoundError < XmlErrorsList
|
22
|
+
end
|
23
|
+
|
24
|
+
class TransactionCreationError < XmlErrorsList
|
25
|
+
end
|
26
|
+
|
27
|
+
class PaymentRequiredError < XmlErrorsList
|
28
|
+
end
|
29
|
+
|
30
|
+
class TimeoutError < Error
|
31
|
+
def initialize(message = "The payment system is not responding.")
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class UnexpectedResponseError < Error
|
37
|
+
attr_reader :response
|
38
|
+
|
39
|
+
def initialize(response)
|
40
|
+
@response = response
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
"Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Spreedly
|
2
|
+
|
3
|
+
class CreditCard < PaymentMethod
|
4
|
+
field :first_name, :last_name, :full_name, :month, :year
|
5
|
+
field :number, :last_four_digits, :card_type, :verification_value
|
6
|
+
field :address1, :address2, :city, :state, :zip, :country, :phone_number
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Spreedly
|
4
|
+
|
5
|
+
class PaymentMethod < Model
|
6
|
+
|
7
|
+
include ErrorsParser
|
8
|
+
|
9
|
+
field :email, :storage_state, :data
|
10
|
+
attr_reader :errors
|
11
|
+
|
12
|
+
def initialize(xml_doc)
|
13
|
+
super
|
14
|
+
@errors = errors_from(xml_doc)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.new_from(xml_doc)
|
18
|
+
case xml_doc.at_xpath('.//payment_method_type').inner_text
|
19
|
+
when 'credit_card'
|
20
|
+
return CreditCard.new(xml_doc)
|
21
|
+
when 'paypal'
|
22
|
+
return Paypal.new(xml_doc)
|
23
|
+
when 'sprel'
|
24
|
+
return Sprel.new(xml_doc)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid?
|
29
|
+
@errors.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Spreedly
|
2
|
+
|
3
|
+
module SslRequester
|
4
|
+
|
5
|
+
def ssl_get(endpoint, headers = {})
|
6
|
+
ssl_request(:get, endpoint, nil, headers)
|
7
|
+
end
|
8
|
+
|
9
|
+
def ssl_post(endpoint, body, headers = {})
|
10
|
+
ssl_request(:post, endpoint, body, headers)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ssl_put(endpoint, body, headers = {})
|
14
|
+
ssl_request(:put, endpoint, body, headers)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def ssl_request(method, endpoint, body, headers)
|
19
|
+
raw_response = Timeout::timeout(70) do
|
20
|
+
raw_ssl_request(method, endpoint, body, headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
show_raw_response(raw_response)
|
24
|
+
handle_response(raw_response)
|
25
|
+
|
26
|
+
rescue Timeout::Error => e
|
27
|
+
raise Spreedly::TimeoutError.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def raw_ssl_request(method, endpoint, body, headers = {})
|
31
|
+
connection = Spreedly::Connection.new(endpoint)
|
32
|
+
connection.request(method, body, headers)
|
33
|
+
end
|
34
|
+
|
35
|
+
def handle_response(response)
|
36
|
+
xml_doc = Nokogiri::XML(response.body)
|
37
|
+
|
38
|
+
case response.code.to_i
|
39
|
+
when 200...300
|
40
|
+
xml_doc
|
41
|
+
when 401
|
42
|
+
raise AuthenticationError.new(xml_doc)
|
43
|
+
when 404
|
44
|
+
raise NotFoundError.new(xml_doc)
|
45
|
+
when 402
|
46
|
+
raise PaymentRequiredError.new(xml_doc)
|
47
|
+
when 422
|
48
|
+
if xml_doc.at_xpath('.//errors/error')
|
49
|
+
raise TransactionCreationError.new(xml_doc)
|
50
|
+
else
|
51
|
+
xml_doc
|
52
|
+
end
|
53
|
+
else
|
54
|
+
raise UnexpectedResponseError.new(response)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def show_raw_response(raw_response)
|
59
|
+
return unless ENV['SHOW_RAW_RESPONSE'] == 'true'
|
60
|
+
puts "\nraw_response.code: #{raw_response.code}\nraw_response.body:\n#{raw_response.body}"
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Spreedly
|
2
|
+
|
3
|
+
class AddPaymentMethod < Transaction
|
4
|
+
|
5
|
+
field :retained, type: :boolean
|
6
|
+
|
7
|
+
attr_reader :payment_method
|
8
|
+
|
9
|
+
def initialize(xml_doc)
|
10
|
+
super
|
11
|
+
@payment_method = PaymentMethod.new_from(xml_doc.at_xpath('.//payment_method'))
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Spreedly
|
2
|
+
|
3
|
+
class AuthPurchase < GatewayTransaction
|
4
|
+
|
5
|
+
field :currency_code
|
6
|
+
field :amount, type: :integer
|
7
|
+
|
8
|
+
attr_reader :payment_method
|
9
|
+
|
10
|
+
def initialize(xml_doc)
|
11
|
+
super
|
12
|
+
@payment_method = PaymentMethod.new_from(xml_doc.at_xpath('.//payment_method'))
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|