spreedly 1.4.0 → 2.0.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/.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
|