stripe 1.5.11

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.
@@ -0,0 +1,564 @@
1
+ # Stripe Ruby bindings
2
+ # API spec at http://stripe.com/api/spec
3
+ # Authors: Ross Boucher <boucher@stripe.com> and Greg Brockman <gdb@stripe.com>
4
+ require 'cgi'
5
+ require 'set'
6
+
7
+ require 'rubygems'
8
+ require 'openssl'
9
+ require 'rest_client'
10
+
11
+ begin
12
+ require 'json'
13
+ rescue LoadError
14
+ raise if defined?(JSON)
15
+ require File.join(File.dirname(__FILE__), '../vendor/stripe-json/lib/json/pure')
16
+ end
17
+
18
+ require File.join(File.dirname(__FILE__), 'stripe/version')
19
+
20
+ module Stripe
21
+ @@ssl_bundle_path = File.join(File.dirname(__FILE__), 'data/ca-certificates.crt')
22
+ @@api_key = nil
23
+ @@api_base = 'https://api.stripe.com/v1'
24
+ @@verify_ssl_certs = true
25
+
26
+ module Util
27
+ def self.objects_to_ids(h)
28
+ case h
29
+ when APIResource
30
+ h.id
31
+ when Hash
32
+ res = {}
33
+ h.each { |k, v| res[k] = objects_to_ids(v) }
34
+ res
35
+ when Array
36
+ h.map { |v| objects_to_ids(v) }
37
+ else
38
+ h
39
+ end
40
+ end
41
+
42
+ def self.convert_to_stripe_object(resp, api_key)
43
+ types = {
44
+ 'charge' => Charge,
45
+ 'customer' => Customer,
46
+ 'invoiceitem' => InvoiceItem,
47
+ 'invoice' => Invoice
48
+ }
49
+ case resp
50
+ when Array
51
+ resp.map { |i| convert_to_stripe_object(i, api_key) }
52
+ when Hash
53
+ # Try converting to a known object class. If none available, fall back to generic APIResource
54
+ if klass_name = resp[:object]
55
+ klass = types[klass_name]
56
+ end
57
+ klass ||= StripeObject
58
+ klass.construct_from(resp, api_key)
59
+ else
60
+ resp
61
+ end
62
+ end
63
+
64
+ def self.file_readable(file)
65
+ begin
66
+ File.open(file) { |f| }
67
+ rescue
68
+ false
69
+ else
70
+ true
71
+ end
72
+ end
73
+ end
74
+
75
+ module APIOperations
76
+ module Create
77
+ module ClassMethods
78
+ def create(params={}, api_key=nil)
79
+ response, api_key = Stripe.request(:post, self.url, api_key, params)
80
+ Util.convert_to_stripe_object(response, api_key)
81
+ end
82
+ end
83
+ def self.included(base)
84
+ base.extend(ClassMethods)
85
+ end
86
+ end
87
+
88
+ module Update
89
+ def save
90
+ if @unsaved_values.length > 0
91
+ values = {}
92
+ @unsaved_values.each { |k| values[k] = @values[k] }
93
+ response, api_key = Stripe.request(:post, url, @api_key, values)
94
+ refresh_from(response, api_key)
95
+ end
96
+ self
97
+ end
98
+ end
99
+
100
+ module Delete
101
+ def delete
102
+ response, api_key = Stripe.request(:delete, url, @api_key)
103
+ refresh_from(response, api_key)
104
+ self
105
+ end
106
+ end
107
+
108
+ module List
109
+ module ClassMethods
110
+ def all(filters={}, api_key=nil)
111
+ response, api_key = Stripe.request(:get, url, api_key, filters)
112
+ Util.convert_to_stripe_object(response, api_key)
113
+ end
114
+ end
115
+
116
+ def self.included(base)
117
+ base.extend(ClassMethods)
118
+ end
119
+ end
120
+ end
121
+
122
+ class StripeObject
123
+ attr_accessor :api_key
124
+ @@permanent_attributes = Set.new([:api_key])
125
+ @@ignored_attributes = Set.new([:id, :api_key, :object])
126
+
127
+ # The default :id method is deprecated and isn't useful to us
128
+ if method_defined?(:id)
129
+ undef :id
130
+ end
131
+
132
+ def initialize(id=nil, api_key=nil)
133
+ @api_key = api_key
134
+ @values = {}
135
+ # This really belongs in APIResource, but not putting it there allows us
136
+ # to have a unified inspect method
137
+ @unsaved_values = Set.new
138
+ @transient_values = Set.new
139
+ self.id = id if id
140
+ end
141
+
142
+ def self.construct_from(values, api_key=nil)
143
+ obj = self.new(values[:id], api_key)
144
+ obj.refresh_from(values, api_key)
145
+ obj
146
+ end
147
+
148
+ def to_s(*args); inspect(*args); end
149
+ def inspect(verbose=false, nested=false)
150
+ str = ["#<#{self.class}"]
151
+ if (desc = ident.compact).length > 0
152
+ str << "[#{desc.join(', ')}]"
153
+ end
154
+
155
+ if !verbose and nested
156
+ str << ' ...'
157
+ else
158
+ content = @values.keys.sort { |a, b| a.to_s <=> b.to_s }.map do |k|
159
+ if @@ignored_attributes.include?(k)
160
+ nil
161
+ else
162
+ v = @values[k]
163
+ v = v.kind_of?(StripeObject) ? v.inspect(verbose, true) : v.inspect
164
+ v = @unsaved_values.include?(k) ? "#{v} (unsaved)" : v
165
+ "#{k}=#{v}"
166
+ end
167
+ end.compact.join(', ')
168
+
169
+ str << ' '
170
+ if content.length > 0
171
+ str << content
172
+ else
173
+ str << '(no attributes)'
174
+ end
175
+ end
176
+
177
+ str << ">"
178
+ str.join
179
+ end
180
+
181
+ def refresh_from(values, api_key, partial=false)
182
+ @api_key = api_key
183
+
184
+ removed = partial ? Set.new : Set.new(@values.keys - values.keys)
185
+ added = Set.new(values.keys - @values.keys)
186
+ # Wipe old state before setting new. This is useful for e.g. updating a
187
+ # customer, where there is no persistent card parameter. Mark those values
188
+ # which don't persist as transient
189
+
190
+ instance_eval do
191
+ remove_accessors(removed)
192
+ add_accessors(added)
193
+ end
194
+ removed.each do |k|
195
+ @values.delete(k)
196
+ @transient_values.add(k)
197
+ @unsaved_values.delete(k)
198
+ end
199
+ values.each do |k, v|
200
+ @values[k] = Util.convert_to_stripe_object(v, api_key)
201
+ @transient_values.delete(k)
202
+ @unsaved_values.delete(k)
203
+ end
204
+ end
205
+
206
+ def [](k)
207
+ k = k.to_sym if k.kind_of?(String)
208
+ @values[k]
209
+ end
210
+ def []=(k, v)
211
+ send(:"#{k}=", v)
212
+ end
213
+ def keys; @values.keys; end
214
+ def values; @values.values; end
215
+ def to_json(*a); @values.to_json(*a); end
216
+
217
+ protected
218
+
219
+ def ident
220
+ [@values[:object], @values[:id]]
221
+ end
222
+
223
+ def metaclass
224
+ class << self; self; end
225
+ end
226
+
227
+ def remove_accessors(keys)
228
+ metaclass.instance_eval do
229
+ keys.each do |k|
230
+ next if @@permanent_attributes.include?(k)
231
+ k_eq = :"#{k}="
232
+ remove_method(k) if method_defined?(k)
233
+ remove_method(k_eq) if method_defined?(k_eq)
234
+ end
235
+ end
236
+ end
237
+
238
+ def add_accessors(keys)
239
+ metaclass.instance_eval do
240
+ keys.each do |k|
241
+ next if @@permanent_attributes.include?(k)
242
+ k_eq = :"#{k}="
243
+ define_method(k) { @values[k] }
244
+ define_method(k_eq) do |v|
245
+ @values[k] = v
246
+ @unsaved_values.add(k) unless @@ignored_attributes.include?(k)
247
+ end
248
+ end
249
+ end
250
+ end
251
+
252
+ def method_missing(name, *args)
253
+ # TODO: only allow setting in updateable classes.
254
+ if name.to_s.end_with?('=')
255
+ attr = name.to_s[0...-1].to_sym
256
+ @values[attr] = args[0]
257
+ @unsaved_values.add(attr) unless @@ignored_attributes.include?(attr)
258
+ add_accessors([attr])
259
+ return
260
+ else
261
+ return @values[name] if @values.has_key?(name)
262
+ end
263
+
264
+ begin
265
+ super
266
+ rescue NoMethodError => e
267
+ if @transient_values.include?(name)
268
+ raise NoMethodError.new(e.message + ". HINT: The '#{name}' attribute was set in the past, however. It was then wiped when refreshing the object with the result returned by Stripe's API, probably as a result of a save(). The attributes currently available on this object are: #{@values.keys.join(', ')}")
269
+ else
270
+ raise
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ class APIResource < StripeObject
277
+ def self.url
278
+ if self == APIResource
279
+ raise NotImplementedError.new("APIResource is an abstract class. You should perform actions on its subclasses (Charge, Customer, etc.)")
280
+ end
281
+ shortname = self.name.split('::')[-1]
282
+ "/#{CGI.escape(shortname.downcase)}s"
283
+ end
284
+ def url
285
+ id = self['id']
286
+ raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", 'id') unless id
287
+ "#{self.class.url}/#{CGI.escape(id)}"
288
+ end
289
+
290
+ def refresh
291
+ response, api_key = Stripe.request(:get, url, @api_key)
292
+ refresh_from(response, api_key)
293
+ self
294
+ end
295
+
296
+ def self.retrieve(id, api_key=nil)
297
+ instance = self.new(id, api_key)
298
+ instance.refresh
299
+ instance
300
+ end
301
+
302
+ protected
303
+
304
+ def ident
305
+ [@values[:id]]
306
+ end
307
+ end
308
+
309
+ class Customer < APIResource
310
+ include Stripe::APIOperations::Create
311
+ include Stripe::APIOperations::Delete
312
+ include Stripe::APIOperations::Update
313
+ include Stripe::APIOperations::List
314
+
315
+ def add_invoice_item(params)
316
+ InvoiceItem.create(params.merge(:customer => id), @api_key)
317
+ end
318
+
319
+ def invoices
320
+ Invoice.all({ :customer => id }, @api_key)
321
+ end
322
+
323
+ def invoice_items
324
+ InvoiceItem.all({ :customer => id }, @api_key)
325
+ end
326
+
327
+ def charges
328
+ Charge.all({ :customer => id }, @api_key)
329
+ end
330
+
331
+ def cancel_subscription(params={})
332
+ response, api_key = Stripe.request(:delete, subscription_url, @api_key, params)
333
+ refresh_from({ :subscription => response }, api_key, true)
334
+ subscription
335
+ end
336
+
337
+ def update_subscription(params)
338
+ response, api_key = Stripe.request(:post, subscription_url, @api_key, params)
339
+ refresh_from({ :subscription => response }, api_key, true)
340
+ subscription
341
+ end
342
+
343
+ private
344
+
345
+ def subscription_url
346
+ url + '/subscription'
347
+ end
348
+ end
349
+
350
+ class Invoice < APIResource
351
+ include Stripe::APIOperations::List
352
+
353
+ def self.upcoming(params)
354
+ response, api_key = Stripe.request(:get, upcoming_url, @api_key, params)
355
+ Util.convert_to_stripe_object(response, api_key)
356
+ end
357
+
358
+ private
359
+
360
+ def self.upcoming_url
361
+ url + '/upcoming'
362
+ end
363
+ end
364
+
365
+ class InvoiceItem < APIResource
366
+ include Stripe::APIOperations::List
367
+ include Stripe::APIOperations::Create
368
+ include Stripe::APIOperations::Delete
369
+ include Stripe::APIOperations::Update
370
+ end
371
+
372
+ class Charge < APIResource
373
+ include Stripe::APIOperations::List
374
+ include Stripe::APIOperations::Create
375
+
376
+ def refund(params={})
377
+ response, api_key = Stripe.request(:post, refund_url, @api_key, params)
378
+ refresh_from(response, api_key)
379
+ self
380
+ end
381
+
382
+ def capture(params={})
383
+ response, api_key = Stripe.request(:post, capture_url, @api_key, params)
384
+ refresh_from(response, api_key)
385
+ self
386
+ end
387
+
388
+ private
389
+
390
+ def refund_url
391
+ url + '/refund'
392
+ end
393
+
394
+ def capture_url
395
+ url + '/capture'
396
+ end
397
+ end
398
+
399
+ class StripeError < StandardError; end
400
+ class APIError < StripeError; end
401
+ class APIConnectionError < StripeError; end
402
+ class CardError < StripeError
403
+ attr_reader :param, :code
404
+
405
+ def initialize(message, param, code)
406
+ super(message)
407
+ @param = param
408
+ @code = code
409
+ end
410
+ end
411
+ class InvalidRequestError < StripeError
412
+ attr_accessor :param
413
+
414
+ def initialize(message, param)
415
+ super(message)
416
+ @param = param
417
+ end
418
+ end
419
+ class AuthenticationError < StripeError; end
420
+
421
+ def self.api_url(url=''); @@api_base + url; end
422
+ def self.api_key=(api_key); @@api_key = api_key; end
423
+ def self.api_key; @@api_key; end
424
+ def self.api_base=(api_base); @@api_base = api_base; end
425
+ def self.api_base; @@api_base; end
426
+ def self.verify_ssl_certs=(verify); @@verify_ssl_certs = verify; end
427
+ def self.verify_ssl_certs; @@verify_ssl_certs; end
428
+
429
+ def self.request(method, url, api_key, params=nil, headers={})
430
+ api_key ||= @@api_key
431
+ raise AuthenticationError.new('No API key provided. (HINT: set your API key using "Stripe.api_key = <API-KEY>". You can generate API keys from the Stripe web interface. See https://stripe.com/api for details, or email support@stripe.com if you have any questions.)') unless api_key
432
+
433
+ if !verify_ssl_certs
434
+ unless @no_verify
435
+ $stderr.puts "WARNING: Running without SSL cert verification. Execute 'Stripe.verify_ssl_certs = true' to enable verification."
436
+ @no_verify = true
437
+ end
438
+ ssl_opts = { :verify_ssl => false }
439
+ elsif !Util.file_readable(@@ssl_bundle_path)
440
+ unless @no_bundle
441
+ $stderr.puts "WARNING: Running without SSL cert verification because #{@@ssl_bundle_path} isn't readable"
442
+ @no_bundle = true
443
+ end
444
+ ssl_opts = { :verify_ssl => false }
445
+ else
446
+ ssl_opts = {
447
+ :verify_ssl => OpenSSL::SSL::VERIFY_PEER,
448
+ :ssl_ca_file => @@ssl_bundle_path
449
+ }
450
+ end
451
+ uname = (@@uname ||= RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil)
452
+ lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
453
+ ua = {
454
+ :bindings_version => Stripe::VERSION,
455
+ :lang => 'ruby',
456
+ :lang_version => lang_version,
457
+ :platform => RUBY_PLATFORM,
458
+ :publisher => 'stripe',
459
+ :uname => uname
460
+ }
461
+
462
+ params = Util.objects_to_ids(params)
463
+ case method.to_s.downcase.to_sym
464
+ when :get, :head, :delete
465
+ # Make params into GET parameters
466
+ headers = { :params => params }.merge(headers)
467
+ payload = nil
468
+ else
469
+ payload = params
470
+ end
471
+
472
+ headers = {
473
+ :x_stripe_client_user_agent => JSON.dump(ua),
474
+ :user_agent => "Stripe/v1 RubyBindings/#{Stripe::VERSION}"
475
+ }.merge(headers)
476
+ opts = {
477
+ :method => method,
478
+ :url => self.api_url(url),
479
+ :user => api_key,
480
+ :headers => headers,
481
+ :open_timeout => 30,
482
+ :payload => payload,
483
+ :timeout => 80
484
+ }.merge(ssl_opts)
485
+
486
+ begin
487
+ response = execute_request(opts)
488
+ rescue SocketError => e
489
+ self.handle_restclient_error(e)
490
+ rescue NoMethodError => e
491
+ # Work around RestClient bug
492
+ if e.message =~ /\WRequestFailed\W/
493
+ e = APIConnectionError.new('Unexpected HTTP response code')
494
+ self.handle_restclient_error(e)
495
+ else
496
+ raise
497
+ end
498
+ rescue RestClient::ExceptionWithResponse => e
499
+ if rcode = e.http_code and rbody = e.http_body
500
+ self.handle_api_error(rcode, rbody)
501
+ else
502
+ self.handle_restclient_error(e)
503
+ end
504
+ rescue RestClient::Exception, Errno::ECONNREFUSED => e
505
+ self.handle_restclient_error(e)
506
+ end
507
+
508
+ rbody = response.body
509
+ rcode = response.code
510
+ begin
511
+ resp = JSON.parse(rbody, :symbolize_names => true)
512
+ rescue JSON::ParseError
513
+ raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})")
514
+ end
515
+
516
+ [resp, api_key]
517
+ end
518
+
519
+ private
520
+
521
+ def self.execute_request(opts)
522
+ RestClient::Request.execute(opts)
523
+ end
524
+
525
+ def self.handle_api_error(rcode, rbody)
526
+ begin
527
+ error_obj = JSON.parse(rbody, :symbolize_names => true)
528
+ error = error_obj[:error] or raise StripeError.new
529
+ rescue JSON::ParserError, StripeError
530
+ raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})")
531
+ end
532
+
533
+ case rcode
534
+ when 400, 404 then
535
+ raise invalid_request_error(error)
536
+ when 401
537
+ raise authentication_error(error)
538
+ when 402
539
+ raise card_error(error)
540
+ else
541
+ raise api_error(error)
542
+ end
543
+ end
544
+
545
+ def self.invalid_request_error(error); InvalidRequestError.new(error[:message], error[:param]); end
546
+ def self.authentication_error(error); AuthenticationError.new(error[:message]); end
547
+ def self.card_error(error); CardError.new(error[:message], error[:param], error[:code]); end
548
+ def self.api_error(error); APIError.new(error[:message]); end
549
+
550
+ def self.handle_restclient_error(e)
551
+ case e
552
+ when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
553
+ message = "Could not connect to Stripe (#{@@api_base}). Please check your internet connection and try again. If this problem persists, you should check Stripe's service status at https://twitter.com/stripe, or let us know at support@stripe.com."
554
+ when RestClient::SSLCertificateNotVerified
555
+ message = "Could not verify Stripe's SSL certificate. Please make sure that your network is not intercepting certificates. (Try going to https://api.stripe.com/v1 in your browser.) If this problem persists, let us know at support@stripe.com."
556
+ when SocketError
557
+ message = "Unexpected error communicating when trying to connect to Stripe. HINT: You may be seeing this message because your DNS is not working. To check, try running 'host stripe.com' from the command line."
558
+ else
559
+ message = "Unexpected error communicating with Stripe. If this problem persists, let us know at support@stripe.com."
560
+ end
561
+ message += "\n\n(Network error: #{e.message})"
562
+ raise APIConnectionError.new(message)
563
+ end
564
+ end