spreedly 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +4 -0
  3. data/HISTORY.md +5 -0
  4. data/README.md +362 -29
  5. data/Rakefile +32 -0
  6. data/lib/certs/cacert.pem +7815 -0
  7. data/lib/spreedly.rb +24 -282
  8. data/lib/spreedly/common/errors_parser.rb +15 -0
  9. data/lib/spreedly/common/fields.rb +90 -0
  10. data/lib/spreedly/connection.rb +40 -0
  11. data/lib/spreedly/environment.rb +176 -0
  12. data/lib/spreedly/error.rb +50 -0
  13. data/lib/spreedly/gateway.rb +10 -0
  14. data/lib/spreedly/model.rb +17 -0
  15. data/lib/spreedly/payment_methods/credit_card.rb +9 -0
  16. data/lib/spreedly/payment_methods/payment_method.rb +34 -0
  17. data/lib/spreedly/payment_methods/paypal.rb +7 -0
  18. data/lib/spreedly/payment_methods/sprel.rb +7 -0
  19. data/lib/spreedly/ssl_requester.rb +65 -0
  20. data/lib/spreedly/transactions/add_payment_method.rb +16 -0
  21. data/lib/spreedly/transactions/auth_purchase.rb +17 -0
  22. data/lib/spreedly/transactions/authorization.rb +7 -0
  23. data/lib/spreedly/transactions/capture.rb +14 -0
  24. data/lib/spreedly/transactions/gateway_transaction.rb +31 -0
  25. data/lib/spreedly/transactions/purchase.rb +7 -0
  26. data/lib/spreedly/transactions/redact_payment_method.rb +14 -0
  27. data/lib/spreedly/transactions/refund.rb +14 -0
  28. data/lib/spreedly/transactions/retain_payment_method.rb +14 -0
  29. data/lib/spreedly/transactions/transaction.rb +41 -0
  30. data/lib/spreedly/transactions/void.rb +9 -0
  31. data/lib/spreedly/urls.rb +64 -0
  32. data/lib/spreedly/version.rb +1 -1
  33. data/spreedly.gemspec +29 -0
  34. data/test/credentials/credentials.yml +9 -0
  35. data/test/credentials/test_credentials.rb +43 -0
  36. data/test/helpers/assertions.rb +29 -0
  37. data/test/helpers/communication_helper.rb +31 -0
  38. data/test/helpers/creation_helper.rb +26 -0
  39. data/test/helpers/stub_response.rb +18 -0
  40. data/test/remote/remote_add_credit_card_test.rb +62 -0
  41. data/test/remote/remote_add_gateway_test.rb +30 -0
  42. data/test/remote/remote_authorize_test.rb +48 -0
  43. data/test/remote/remote_capture_test.rb +71 -0
  44. data/test/remote/remote_find_gateway_test.rb +31 -0
  45. data/test/remote/remote_find_payment_method_test.rb +29 -0
  46. data/test/remote/remote_find_transaction_test.rb +33 -0
  47. data/test/remote/remote_list_transactions_test.rb +36 -0
  48. data/test/remote/remote_purchase_test.rb +69 -0
  49. data/test/remote/remote_redact_test.rb +38 -0
  50. data/test/remote/remote_refund_test.rb +65 -0
  51. data/test/remote/remote_retain_test.rb +39 -0
  52. data/test/remote/remote_void_test.rb +64 -0
  53. data/test/test_helper.rb +23 -0
  54. data/test/unit/add_credit_card_test.rb +74 -0
  55. data/test/unit/add_gateway_test.rb +26 -0
  56. data/test/unit/authorize_test.rb +87 -0
  57. data/test/unit/capture_test.rb +91 -0
  58. data/test/unit/environment_test.rb +18 -0
  59. data/test/unit/fields_test.rb +75 -0
  60. data/test/unit/find_gateway_test.rb +28 -0
  61. data/test/unit/find_payment_method_test.rb +90 -0
  62. data/test/unit/find_transaction_test.rb +31 -0
  63. data/test/unit/list_transactions_test.rb +46 -0
  64. data/test/unit/purchase_test.rb +95 -0
  65. data/test/unit/redact_payment_method_test.rb +51 -0
  66. data/test/unit/refund_test.rb +91 -0
  67. data/test/unit/response_stubs/add_credit_card_stubs.rb +43 -0
  68. data/test/unit/response_stubs/add_gateway_stubs.rb +39 -0
  69. data/test/unit/response_stubs/authorization_stubs.rb +139 -0
  70. data/test/unit/response_stubs/capture_stubs.rb +87 -0
  71. data/test/unit/response_stubs/find_gateway_stubs.rb +38 -0
  72. data/test/unit/response_stubs/find_payment_method_stubs.rb +108 -0
  73. data/test/unit/response_stubs/find_transaction_stubs.rb +43 -0
  74. data/test/unit/response_stubs/list_transactions_stubs.rb +110 -0
  75. data/test/unit/response_stubs/purchase_stubs.rb +139 -0
  76. data/test/unit/response_stubs/redact_payment_method_stubs.rb +54 -0
  77. data/test/unit/response_stubs/refund_stubs.rb +87 -0
  78. data/test/unit/response_stubs/retain_payment_method_stubs.rb +85 -0
  79. data/test/unit/response_stubs/void_stubs.rb +79 -0
  80. data/test/unit/retain_payment_method_test.rb +44 -0
  81. data/test/unit/timeout_test.rb +20 -0
  82. data/test/unit/void_test.rb +96 -0
  83. metadata +215 -29
  84. checksums.yaml +0 -15
  85. data/lib/spreedly/common.rb +0 -44
  86. data/lib/spreedly/mock.rb +0 -221
  87. 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,10 @@
1
+
2
+ module Spreedly
3
+
4
+ class Gateway < Model
5
+
6
+ field :gateway_type, :state, :name
7
+
8
+ end
9
+
10
+ end
@@ -0,0 +1,17 @@
1
+
2
+ module Spreedly
3
+
4
+ class Model
5
+
6
+ include Fields
7
+
8
+ field :token
9
+ field :created_at, :updated_at, type: :date_time
10
+
11
+ def initialize(xml_doc)
12
+ initialize_fields(xml_doc)
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -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,7 @@
1
+ module Spreedly
2
+
3
+ class Paypal < PaymentMethod
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ module Spreedly
2
+
3
+ class Sprel < PaymentMethod
4
+
5
+ end
6
+
7
+ 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
@@ -0,0 +1,7 @@
1
+ module Spreedly
2
+
3
+ class Authorization < AuthPurchase
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,14 @@
1
+ module Spreedly
2
+
3
+ class Capture < GatewayTransaction
4
+
5
+ field :currency_code, :reference_token
6
+ field :amount, type: :integer
7
+
8
+ def initialize(xml_doc)
9
+ super
10
+ end
11
+
12
+ end
13
+
14
+ end