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
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ Gemfile.lock
2
+ doc/
3
+ pkg
4
+ *.gem
5
+ .bundle
6
+ tmp
7
+ vendor/ruby
8
+ test/credentials/personal_credentials.yml
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
data/HISTORY.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0
4
+ * This gem is now intended for the Spreedly API. For the Spreedly
5
+ Subscriptions API, you can use the spreedly_subscriptions gem
6
+ which has everything that 1.4.0 had.
7
+
3
8
  ## 1.4.0
4
9
 
5
10
  * Add accessor for invoices
data/README.md CHANGED
@@ -1,42 +1,375 @@
1
- # Spreedly gem
1
+ # Spreedly Gem
2
2
 
3
- * https://github.com/spreedly/spreedly-gem
3
+ A convenient Ruby wrapper for the Spreedly API.
4
4
 
5
- ## Description
5
+ ## Philosophy
6
6
 
7
- The Spreedly gem provides a convenient Ruby wrapper for the Spreedly
8
- Subscriptions API.
7
+ * No global configuration of authentication credentials.
8
+ * No implicit calls to the Spreedly server.
9
+ * Don't be too clever. The real goal is for this to be a thin and simple layer over the API. The correlation between an API call and a ruby method should be as clear as possible.
10
+ * Avoid the approach of using a proxy that at some point gets filled in with data from the server.
11
+ * Avoid self-mutation and prefer value objects. This isn't ActiveRecord so we won't be doing things like gateway.update_attributes(attributes).
12
+ * Don't try to improve the interface of the API by doing cool Ruby things in the gem which fix or hide icky parts of the API. Instead, improve the underlying API to reflect the improvements and then adjust the gem to use the improved underlying API.
13
+ * Limit the number of dependencies on other gems to make it as easy possible to incorporate the gem into any project. Resist the temptation to use the goodness in gems like ActiveSupport or ActiveModel.
9
14
 
10
- ## Features
11
15
 
12
- * Makes it easy to get started.
13
- * Fully tested.
14
- * (Mostly) fully substitutable mock implementation for fast tests.
15
- * Great example code.
16
+ ## Installation
16
17
 
17
- ## Synopsis
18
+ If you're using bundler, add the gem to your Gemfile:
18
19
 
19
- # For real
20
- require 'spreedly'
21
- Spreedly.configure('site short name', 'crazy hash token')
22
- url = Spreedly.subscribe_url('customer id', 'plan id')
23
- subscriber = Spreedly::Subscriber.find('customer id')
24
- subscriber.active?
20
+ gem 'spreedly'
25
21
 
26
- # For fast tests
27
- require 'spreedly/mock'
28
- Spreedly.configure('site short name', 'crazy hash token')
29
- url = Spreedly.subscribe_url('customer id', 'plan id')
30
- subscriber = Spreedly::Subscriber.find('customer id')
31
- subscriber.active?
22
+ Otherwise gem install:
32
23
 
33
- Yup, they're exactly the same except for the require and the speed!
24
+ $ gem install spreedly
34
25
 
35
- ## Requirements
36
26
 
37
- * A Spreedly Subscriptions account.
38
- * HTTParty
27
+ ## Usage
39
28
 
40
- ## Install
29
+ #### Basic purchase
41
30
 
42
- $ gem install spreedly
31
+ Let's start with a simple purchase when you already have a gateway token and a payment method token:
32
+
33
+ ``` ruby
34
+ env = Spreedly::Environment.new(environment_key, access_secret)
35
+
36
+ transaction = env.purchase_on_gateway(gateway_token, payment_method_token, 4432)
37
+
38
+ transaction.succeeded? # => true
39
+ transaction.token # => "aGJlY5srn7TFeYKxO5pmwi3CyJd"
40
+ ```
41
+
42
+ The amount specified in that example was 4432. Amounts are always in cents so in this case, we're charging $44.32.
43
+
44
+ #### Add a gateway
45
+ What if you don't have a gateway token yet? It's pretty easy to add a test gateway:
46
+
47
+ ``` ruby
48
+ gateway = env.add_gateway(:test)
49
+ gateway.token # => "DnbEJaaY2egcVkCvg3s8qT38xgt"
50
+ ```
51
+
52
+
53
+ #### Add a payment method
54
+ Need a payment method token to try things out? With Spreedly it's pretty straightforward to use a
55
+ [transparent redirect](https://core.spreedly.com/manual/quickstart#submit-payment-form) to give you a
56
+ payment method token. A payment form in your application could look something like this:
57
+
58
+ ``` html
59
+ <form action="<%= env.transparent_redirect_form_action %>" method="POST">
60
+ <fieldset>
61
+ <input name="redirect_url" type="hidden" value="http://yourdomain.com/transparent_redirect_done" />
62
+ <input name="environment_key" type="hidden" value="<%= env.key %>" />
63
+ <label for="credit_card_full_name">Name</label>
64
+ <input id="credit_card_full_name" name="credit_card[full_name]" type="text" />
65
+
66
+ <label for="credit_card_number">Card Number</label>
67
+ <input id="credit_card_number" name="credit_card[number]" type="text" />
68
+
69
+ <label for="credit_card_verification_value">Security Code</label>
70
+ <input id="credit_card_verification_value" name="credit_card[verification_value]" type="text" />
71
+
72
+ <label for="credit_card_month">Expires on</label>
73
+ <input id="credit_card_month" name="credit_card[month]" type="text" />
74
+ <input id="credit_card_year" name="credit_card[year]" type="text" />
75
+
76
+ <button type='submit'>Submit Payment</button>
77
+ </fieldset>
78
+ </form>
79
+ ```
80
+
81
+ Notice that we can ask the environment for the form action url and that the environment knows its key to use in the hidden field.
82
+
83
+ Once Spreedly has recorded the information, it will redirect the browser to the url specified in the `redirect_url` field, tacking on a token that represents the credit card your customer entered. This is the payment_method token you're looking for. In this case your customer would be sent to this url:
84
+
85
+ http://yourdomain.com/transparent_redirect_done?token=OEj2G2QJZM4C10AfTLYTrsKIsZH
86
+
87
+ Once you have the payment method token (OEj2G2QJZM4C10AfTLYTrsKIsZH in this case), you can remember it and use it whenever you'd like. These [test cards](https://core.spreedly.com/manual/test-data) will help.
88
+
89
+ #### Retrieve a payment method
90
+ Let's say you'd like some additional information about the payment method. You can find a payment method like so:
91
+
92
+ ``` ruby
93
+ credit_card = env.find_payment_method(token)
94
+ credit_card.last_name # => "Jones"
95
+ credit_card.valid? # => false
96
+
97
+ credit_card.errors
98
+ # Returns => [
99
+ # { attribute: "first_name", key: "errors.blank", message: "First name can't be blank" },
100
+ # { attribute: "year", key: "errors.expired", message: "Year is expired" },
101
+ # { attribute: "year", key: "errors.invalid", message: "Year is invalid" },
102
+ # { attribute: "number", key: "errors.blank", message: "Number can't be blank" }
103
+ # ]
104
+ ```
105
+
106
+ #### Authorize and Capture
107
+
108
+ ``` ruby
109
+ auth_transaction = env.authorize_on_gateway(gateway_token, payment_method_token, 250)
110
+
111
+ if auth_transaction.succeeded?
112
+ capture_transaction = env.capture_transaction(auth_transaction.token)
113
+ end
114
+ ```
115
+
116
+ You can also specify an optional amount to capture.
117
+
118
+ ``` ruby
119
+ capture_transaction = env.capture_transaction(auth_transaction.token, amount: 100)
120
+ ```
121
+
122
+
123
+ #### Void and refund
124
+
125
+ ``` ruby
126
+ transaction = env.void_transaction(transaction_token)
127
+
128
+ # Refund the entire amount
129
+ transaction = env.refund_transaction(transaction_token)
130
+
131
+ # Specify an amount to be refunded
132
+ transaction = env.refund_transaction(transaction_token, amount: 104)
133
+ ```
134
+
135
+ #### Retain and redact
136
+
137
+ ``` ruby
138
+ transaction = env.retain_payment_method(payment_method_token)
139
+
140
+ transaction = env.redact_payment_method(payment_method_token)
141
+ ```
142
+
143
+ #### Currencies
144
+ When you instantiate an environment, you can specify a default currency code like so:
145
+
146
+ ``` ruby
147
+ env = Spreedly::Environment.new(environment_key, access_secret, currency_code: 'EUR')
148
+ ```
149
+
150
+ If you don't specify a default currency code, we default to 'USD'. Calls requiring a currency code by default use the environment's currency code. And of course, you can always override it for a particular call like so:
151
+
152
+ ``` ruby
153
+ env.purchase_on_gateway(gateway_token, payment_method_token, amount, currency_code: "GBP")
154
+ ```
155
+
156
+
157
+ #### Extra options for the basic operations
158
+ For Purchase, Authorize, Capture, Refund, and Void calls, you can specify additional options:
159
+
160
+ ``` ruby
161
+ env.purchase_on_gateway(gateway_token, payment_method_token, amount,
162
+ order_id: "123",
163
+ description: "The Description",
164
+ ip: "192.31.123.112",
165
+ merchant_name_descriptor: "SuperDuper Corp",
166
+ merchant_location_descriptor: "http://super.com"
167
+ )
168
+ ```
169
+
170
+ #### Retain on success
171
+ Retain a payment method automatically if the purchase or authorize transaction succeeded. Saves you a separate call to retain:
172
+
173
+ ``` ruby
174
+ env.purchase_on_gateway(gateway_token, payment_method_token, amount, retain_on_success: true)
175
+ ```
176
+
177
+ #### Retrieving gateways
178
+
179
+ ``` ruby
180
+ gateways = env.list_gateways
181
+
182
+ # Iterate over the next chunk
183
+ next_set = env.list_gateways(gateways.last.token)
184
+ ```
185
+
186
+ #### Retrieving payment methods
187
+
188
+ ``` ruby
189
+ payment_methods = env.list_payment_methods
190
+
191
+ # Iterate over the next chunk
192
+ next_set = env.list_payment_methods(payment_methods.last.token)
193
+ ```
194
+
195
+ #### Retrieving transactions
196
+
197
+ ``` ruby
198
+ transactions = env.list_transactions
199
+
200
+ # Iterate over the next chunk
201
+ next_set = env.list_transactions(transactions.last.token)
202
+ ```
203
+
204
+ #### Retrieving transactions for a payment method
205
+
206
+ ``` ruby
207
+ transactions = env.list_transactions(nil, payment_method_token)
208
+
209
+ # Iterate over the next chunk
210
+ next_set = env.list_transactions(transactions.last.token, payment_method_token)
211
+ ```
212
+
213
+ #### Retrieving one gateway
214
+
215
+ ``` ruby
216
+ gateway = env.find_gateway(token)
217
+ gateway.gateway_type # => 'paypal'
218
+ ```
219
+
220
+ #### Retrieving one transaction
221
+
222
+ ``` ruby
223
+ transaction = env.find_transaction(token)
224
+ transaction.order_id # => '30-9904-31114'
225
+ ```
226
+
227
+ #### Retrieving the transcript for a transaction
228
+
229
+ ``` ruby
230
+ env.find_transaction_transcript(transaction_token)
231
+ ```
232
+
233
+ #### Updating a payment method
234
+
235
+ ``` ruby
236
+ env.update_payment_method(payment_method_token, first_name: 'JimBob', last_name: 'Jones')
237
+ ```
238
+
239
+ #### Adding other types of gateways
240
+
241
+ ``` ruby
242
+ gateway = env.add_gateway(:paypal, mode: 'delegate', email: 'fred@example.com')
243
+ gateway.token # => "2nQEJaaY3egcVkCvg2s9qT37xrb"
244
+ ```
245
+
246
+ #### Adding credit cards
247
+
248
+ The primary mechanism to add a credit card is to use the transparent redirect payment form. This allows all of the sensitive information to be captured without ever touching your servers.
249
+
250
+ There are times though when you may want to add a credit card in a more "manual" fashion with an API call.
251
+
252
+ PLEASE NOTE: Using this API call can significantly increase your PCI compliance requirements.
253
+
254
+ Here's how you can do it:
255
+
256
+ ``` ruby
257
+ options = {
258
+ email: 'perrin@wot.com', number: '5555555555554444', month: 1, year: 2019, last_name: 'Aybara', first_name: 'Perrin', data: "occupation: Blacksmith"
259
+ }
260
+ transaction = env.add_credit_card(options)
261
+
262
+ transaction.token # => "2nQEJaaY3egcVkCvg2s9qT37xrb"
263
+ transaction.card.token # => "7rbEKaaY0egcBkCrg2sbqTo7Qrb"
264
+ transaction.card.last_name # => "Aybara"
265
+ ```
266
+
267
+ You can also retain the card immediately like so:
268
+
269
+ ``` ruby
270
+ options = {
271
+ email: 'perrin@wot.com', number: '5555555555554444', month: 1, year: 2019, last_name: 'Aybara', first_name: 'Perrin', data: "occupation: Blacksmith", retained: true
272
+ }
273
+ transaction = env.add_credit_card(options)
274
+
275
+ transaction.card.storage_state # => "retained"
276
+ ```
277
+
278
+ And you might want to specify a number of other details like the billing address, etc:
279
+
280
+ ``` ruby
281
+ options = {
282
+ email: 'leavenworth@free.com', number: '9555555555554444', month: 3, year: 2021, last_name: 'Smedry', first_name: 'Leavenworth', data: "talent: Late", address1: '10 Dragon Lane', address2: 'Suite 9', city: 'Tuki Tuki', state: 'Mokia', zip: '1122', country: 'Free Kingdoms', phone_number: '81Ab', retained: true
283
+ }
284
+
285
+ transaction = env.add_credit_card(options)
286
+
287
+ transaction.card.last_name # => "Smedry"
288
+
289
+ ```
290
+
291
+
292
+ ## Error Handling
293
+
294
+ When you make a call to the API, there are times when things don't go as expected. For the most part, when a call is made, a Transaction is created behind the scenes at Spreedly. In general,
295
+ you can inquire whether that transaction succeeded? or not and get it's message. There are times when a Transaction cannot be created, and in general, an exception is raised for these cases.
296
+
297
+ You can be as specific as you'd like in handling these exceptions or, you could simply rescue Spreedly::Error to handle all of them.
298
+
299
+ #### Declined purchase
300
+
301
+ ``` ruby
302
+ transaction = env.purchase_on_gateway(gateway_token, payment_method_token, 4432)
303
+
304
+ transaction.succeeded? # => false
305
+ transaction.message # => "Unable to process the purchase transaction."
306
+ ```
307
+
308
+ #### Invalid payment method
309
+
310
+ ``` ruby
311
+ transaction = env.purchase_on_gateway(gateway_token, payment_method_token, 4432)
312
+
313
+ transaction.succeeded? # => false
314
+ transaction.message # => "The payment method is invalid."
315
+
316
+ transaction.payment_method.errors
317
+ transaction.payment_method.errors
318
+ # Returns => [
319
+ # { attribute: "last_name", key: "errors.blank", message: "Last name can't be blank" },
320
+ # { attribute: "number", key: "errors.blank", message: "Number can't be blank" }
321
+ # ]
322
+
323
+ ```
324
+
325
+ #### Failure to find
326
+
327
+ ``` ruby
328
+ env.find_transaction("Some Unknown Token") # raises a Spreedly::NotFoundError
329
+ ```
330
+
331
+ #### Invalid environment credentials
332
+
333
+ ``` ruby
334
+ env = Spreedly::Environment.new(environment_key, "some bogus secret")
335
+ env.purchase_on_gateway(gateway_token, payment_method_token, 4432) # Raises Spreedly::AuthenticationError
336
+ ```
337
+
338
+ #### Unknown payment method trying to make a purchase
339
+
340
+ ``` ruby
341
+ env.purchase_on_gateway(gateway_token, "Some Unknown Token", 4432) # Raises Spreedly::TransactionCreationError
342
+ ```
343
+
344
+ #### Trying to use a non-test gateway or a non-test payment method with an inactive account
345
+
346
+ You're free to use [test card data](https://core.spreedly.com/manual/test-data) and a [Test gateway](https://core.spreedly.com/manual/payment-gateways/test) to integrate Spreedly without having a
347
+ paid Spreedly account. If you try to use a real card or a real gateway when your account isn't yet paid for, we'll raise an exception:
348
+
349
+ ``` ruby
350
+ env.purchase_on_gateway(gateway_token, "Payment Method Token for a real card", 4432) # Raises Spreedly::PaymentRequiredError
351
+ ```
352
+
353
+ #### Timeout errors
354
+
355
+ If Spreedly is not responding, we'll raise an exception. Spreedly itself has a timeout so that if a gateway isn't responding, it'll reflect that in the response. The gem has its own timeout to
356
+ handle the case of Spreedly itself not responding. Here's an example:
357
+
358
+ ``` ruby
359
+ env.purchase_on_gateway(gateway_token, payment_method_token, 802) # Raises Spreedly::TimeoutError
360
+ ```
361
+
362
+ ## Sample applications using the gem
363
+
364
+ There are some sample applications with source code using this gem. You can [find them here](https://core.spreedly.com/manual/sample_applications).
365
+
366
+ ## Contributing
367
+
368
+ We're happy to consider [pull requests](https://help.github.com/articles/using-pull-requests).
369
+
370
+ There are two rake tasks to help run the tests:
371
+
372
+ ```
373
+ rake test:remote # Run remote tests that actually hit the Spreedly site
374
+ rake test:units # Run unit tests
375
+ ```
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ # Rake::TestTask.new do |t|
6
+ # t.libs << 'test'
7
+ # end
8
+
9
+ # desc "Run tests"
10
+ # task :default => :test
11
+
12
+
13
+
14
+ desc "Run the unit test suite"
15
+ task :default => 'test:units'
16
+
17
+ namespace :test do
18
+
19
+ Rake::TestTask.new(:units) do |t|
20
+ t.pattern = 'test/unit/**/*_test.rb'
21
+ t.libs << 'test'
22
+ t.verbose = true
23
+ end
24
+
25
+ Rake::TestTask.new(:remote) do |t|
26
+ t.pattern = 'test/remote/**/*_test.rb'
27
+ t.libs << 'test'
28
+ t.verbose = true
29
+ end
30
+
31
+ end
32
+