mirror of
https://github.com/stripe/stripe-ruby.git
synced 2025-08-10 00:01:09 -04:00
1.5.0 release
This commit is contained in:
commit
0813418b74
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/stripe-*.gem
|
24
History.txt
Normal file
24
History.txt
Normal file
@ -0,0 +1,24 @@
|
||||
=== 1.5.0 2011-05-09
|
||||
|
||||
* 1 major enhancement:
|
||||
* Update for new RESTful API
|
||||
|
||||
=== 1.3.4 2011-01-07
|
||||
|
||||
* 1 major enhancement:
|
||||
* Rename to Stripe
|
||||
|
||||
=== 1.2 2010-06-06
|
||||
|
||||
* 1 major enhancement:
|
||||
* Support for the set_customer_subscription and delete_customer API methods
|
||||
|
||||
=== 1.1 2010-03-14
|
||||
|
||||
* 1 major enhancement:
|
||||
* Support for recurring billing
|
||||
|
||||
=== 1.0 2010-01-05
|
||||
|
||||
* 1 major enhancement:
|
||||
* Initial release
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010 Stripe (https://stripe.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
15
README.rdoc
Normal file
15
README.rdoc
Normal file
@ -0,0 +1,15 @@
|
||||
= Stripe Ruby bindings
|
||||
|
||||
== Installation
|
||||
|
||||
You don't need this source code unless you want to modify the gem. If you just want to use the Stripe Ruby bindings, you should run:
|
||||
|
||||
sudo gem install --source https://gems.stripe.com stripe
|
||||
|
||||
If you want to build the gem from source:
|
||||
|
||||
sudo gem build stripe.gemspec
|
||||
|
||||
== REQUIREMENTS:
|
||||
|
||||
* rest-client, json
|
7
bin/stripe-console
Executable file
7
bin/stripe-console
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env ruby
|
||||
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
||||
|
||||
libs = " -r irb/completion"
|
||||
libs << " -r #{File.dirname(__FILE__) + '/../lib/stripe'}"
|
||||
puts "Loading stripe gem"
|
||||
exec "#{irb} #{libs} --simple-prompt"
|
3524
lib/data/ca-certificates.crt
Normal file
3524
lib/data/ca-certificates.crt
Normal file
File diff suppressed because it is too large
Load Diff
511
lib/stripe.rb
Normal file
511
lib/stripe.rb
Normal file
@ -0,0 +1,511 @@
|
||||
# Stripe Ruby bindings
|
||||
# API spec at http://stripe.com/api/spec
|
||||
# Authors: Ross Boucher <boucher@stripe.com> and Greg Brockman <gdb@stripe.com>
|
||||
require 'set'
|
||||
|
||||
require 'rubygems'
|
||||
require 'json'
|
||||
require 'openssl'
|
||||
require 'rest_client'
|
||||
|
||||
module Stripe
|
||||
@@version = '1.5.0'
|
||||
@@ssl_bundle_path = File.join(File.dirname(__FILE__), 'data/ca-certificates.crt')
|
||||
@@api_key = nil
|
||||
@@api_base = 'https://api.stripe.com/v1'
|
||||
|
||||
module Util
|
||||
def self.objects_to_ids(h)
|
||||
case h
|
||||
when APIResource
|
||||
h.id
|
||||
when Hash
|
||||
res = {}
|
||||
h.each { |k, v| res[k] = objects_to_ids(v) }
|
||||
res
|
||||
else
|
||||
h
|
||||
end
|
||||
end
|
||||
|
||||
def self.convert_to_stripe_object(resp, api_key)
|
||||
types = {
|
||||
'charge' => Charge,
|
||||
'customer' => Customer,
|
||||
'invoice_item' => InvoiceItem,
|
||||
'invoice' => Invoice
|
||||
}
|
||||
case resp
|
||||
when Array
|
||||
resp.map { |i| convert_to_stripe_object(i, api_key) }
|
||||
when Hash
|
||||
# Try converting to a known object class. If none available, fall back to generic APIResource
|
||||
if klass_name = resp[:object]
|
||||
klass = types[klass_name]
|
||||
end
|
||||
klass ||= StripeObject
|
||||
klass.construct_from(resp, api_key)
|
||||
else
|
||||
resp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module APIOperations
|
||||
module Create
|
||||
module ClassMethods
|
||||
def create(params={}, api_key=nil)
|
||||
response, api_key = Stripe.request(:post, self.url, api_key, params)
|
||||
Util.convert_to_stripe_object(response, api_key)
|
||||
end
|
||||
end
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
|
||||
module Update
|
||||
def save
|
||||
if @unsaved_values.length > 0
|
||||
values = {}
|
||||
@unsaved_values.each { |k| values[k] = @values[k] }
|
||||
response, api_key = Stripe.request(:post, url, @api_key, values)
|
||||
refresh_from(response, api_key)
|
||||
end
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
module Delete
|
||||
def delete
|
||||
response, api_key = Stripe.request(:delete, url, @api_key)
|
||||
refresh_from(response, api_key)
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
module List
|
||||
module ClassMethods
|
||||
def all(filters={}, api_key=nil)
|
||||
response, api_key = Stripe.request(:get, url, api_key, filters)
|
||||
Util.convert_to_stripe_object(response, api_key)
|
||||
end
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class StripeObject
|
||||
attr_accessor :api_key
|
||||
@@permanent_attributes = Set.new([:api_key])
|
||||
@@ignored_attributes = Set.new([:id, :api_key, :object])
|
||||
|
||||
# The default :id method is deprecated and isn't useful to us
|
||||
if method_defined?(:id)
|
||||
undef :id
|
||||
end
|
||||
|
||||
def initialize(id=nil, api_key=nil)
|
||||
@api_key = api_key
|
||||
@values = {}
|
||||
# This really belongs in APIResource, but not putting it there allows us
|
||||
# to have a unified inspect method
|
||||
@unsaved_values = Set.new
|
||||
@transient_values = Set.new
|
||||
self.id = id if id
|
||||
end
|
||||
|
||||
def self.construct_from(values, api_key=nil)
|
||||
obj = self.new(values[:id], api_key)
|
||||
obj.refresh_from(values, api_key)
|
||||
obj
|
||||
end
|
||||
|
||||
def to_s(*args); inspect(*args); end
|
||||
def inspect(verbose=false, nested=false)
|
||||
str = ["#<#{self.class}"]
|
||||
if (desc = ident.compact).length > 0
|
||||
str << "[#{desc.join(', ')}]"
|
||||
end
|
||||
|
||||
if !verbose and nested
|
||||
str << ' ...'
|
||||
else
|
||||
content = @values.keys.sort { |a, b| a.to_s <=> b.to_s }.map do |k|
|
||||
if @@ignored_attributes.include?(k)
|
||||
nil
|
||||
else
|
||||
v = @values[k]
|
||||
v = v.kind_of?(StripeObject) ? v.inspect(verbose, true) : v.inspect
|
||||
v = @unsaved_values.include?(k) ? "#{v} (unsaved)" : v
|
||||
"#{k}=#{v}"
|
||||
end
|
||||
end.compact.join(', ')
|
||||
|
||||
str << ' '
|
||||
if content.length > 0
|
||||
str << content
|
||||
else
|
||||
str << '(no attributes)'
|
||||
end
|
||||
end
|
||||
|
||||
str << ">"
|
||||
str.join
|
||||
end
|
||||
|
||||
def refresh_from(values, api_key, partial=false)
|
||||
@api_key = api_key
|
||||
|
||||
removed = partial ? Set.new : Set.new(@values.keys - values.keys)
|
||||
added = Set.new(values.keys - @values.keys)
|
||||
# Wipe old state before setting new. This is useful for e.g. updating a
|
||||
# customer, where there is no persistent card parameter. Mark those values
|
||||
# which don't persist as transient
|
||||
|
||||
instance_eval do
|
||||
remove_accessors(removed)
|
||||
add_accessors(added)
|
||||
end
|
||||
removed.each do |k|
|
||||
@values.delete(k)
|
||||
@transient_values.add(k)
|
||||
@unsaved_values.delete(k)
|
||||
end
|
||||
values.each do |k, v|
|
||||
@values[k] = Util.convert_to_stripe_object(v, api_key)
|
||||
@transient_values.delete(k)
|
||||
@unsaved_values.delete(k)
|
||||
end
|
||||
end
|
||||
|
||||
def [](k)
|
||||
k = k.to_sym if k.kind_of?(String)
|
||||
@values[k]
|
||||
end
|
||||
def []=(k, v)
|
||||
send(:"#{k}=", v)
|
||||
end
|
||||
def keys; @values.keys; end
|
||||
def values; @values.values; end
|
||||
def to_json(*a); @values.to_json(*a); end
|
||||
|
||||
protected
|
||||
|
||||
def ident
|
||||
[@values[:object], @values[:id]]
|
||||
end
|
||||
|
||||
def metaclass
|
||||
class << self; self; end
|
||||
end
|
||||
|
||||
def remove_accessors(keys)
|
||||
metaclass.instance_eval do
|
||||
keys.each do |k|
|
||||
next if @@permanent_attributes.include?(k)
|
||||
k_eq = :"#{k}="
|
||||
remove_method(k) if method_defined?(k)
|
||||
remove_method(k_eq) if method_defined?(k_eq)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_accessors(keys)
|
||||
metaclass.instance_eval do
|
||||
keys.each do |k|
|
||||
next if @@permanent_attributes.include?(k)
|
||||
k_eq = :"#{k}="
|
||||
define_method(k) { @values[k] }
|
||||
define_method(k_eq) do |v|
|
||||
@values[k] = v
|
||||
@unsaved_values.add(k) unless @@ignored_attributes.include?(k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(name, *args)
|
||||
# TODO: only allow setting in updateable classes.
|
||||
if name.to_s.end_with?('=')
|
||||
attr = name.to_s[0...-1].to_sym
|
||||
@values[attr] = args[0]
|
||||
@unsaved_values.add(attr)
|
||||
add_accessors([attr])
|
||||
return
|
||||
else
|
||||
return @values[name] if @values.has_key?(name)
|
||||
end
|
||||
|
||||
begin
|
||||
super
|
||||
rescue NoMethodError => e
|
||||
if @transient_values.include?(name)
|
||||
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(', ')}")
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class APIResource < StripeObject
|
||||
def self.url
|
||||
if self == APIResource
|
||||
raise NotImplementedError.new("APIResource is an abstract class. You should perform actions on its subclasses (Charge, Customer, etc.)")
|
||||
end
|
||||
shortname = self.name.split('::')[-1]
|
||||
"/#{CGI.escape(shortname.downcase)}s"
|
||||
end
|
||||
def url; "#{self.class.url}/#{CGI.escape(id)}"; end
|
||||
|
||||
def refresh
|
||||
response, api_key = Stripe.request(:get, url, @api_key)
|
||||
refresh_from(response, api_key)
|
||||
self
|
||||
end
|
||||
|
||||
def self.retrieve(id, api_key=nil)
|
||||
instance = self.new(id, api_key)
|
||||
instance.refresh
|
||||
instance
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def ident
|
||||
[@values[:id]]
|
||||
end
|
||||
end
|
||||
|
||||
class Customer < APIResource
|
||||
include Stripe::APIOperations::Create
|
||||
include Stripe::APIOperations::Delete
|
||||
include Stripe::APIOperations::Update
|
||||
include Stripe::APIOperations::List
|
||||
|
||||
def add_invoice_item(params)
|
||||
InvoiceItem.create(params.merge(:customer_id => id), @api_key)
|
||||
end
|
||||
|
||||
def invoices
|
||||
Invoice.all({ :customer_id => id }, @api_key)
|
||||
end
|
||||
|
||||
def invoice_items
|
||||
InvoiceItem.all({ :customer_id => id }, @api_key)
|
||||
end
|
||||
|
||||
def charges
|
||||
Charge.all({ :customer_id => id }, @api_key)
|
||||
end
|
||||
|
||||
def cancel_subscription
|
||||
response, api_key = Stripe.request(:delete, subscription_url, @api_key)
|
||||
refresh_from({ :subscription => response }, api_key, true)
|
||||
subscription
|
||||
end
|
||||
|
||||
def update_subscription(params)
|
||||
response, api_key = Stripe.request(:post, subscription_url, @api_key)
|
||||
refresh_from({ :subscription => response }, api_key, true)
|
||||
subscription
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subscription_url
|
||||
url + '/subscription'
|
||||
end
|
||||
end
|
||||
|
||||
class Invoice < APIResource
|
||||
include Stripe::APIOperations::List
|
||||
|
||||
def self.upcoming(params)
|
||||
response, api_key = Stripe.request(:get, upcoming_url, @api_key, params)
|
||||
Util.convert_to_stripe_object(response, api_key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.upcoming_url
|
||||
url + '/upcoming'
|
||||
end
|
||||
end
|
||||
|
||||
class InvoiceItem < APIResource
|
||||
include Stripe::APIOperations::List
|
||||
include Stripe::APIOperations::Create
|
||||
include Stripe::APIOperations::Delete
|
||||
include Stripe::APIOperations::Update
|
||||
end
|
||||
|
||||
class Charge < APIResource
|
||||
include Stripe::APIOperations::List
|
||||
include Stripe::APIOperations::Create
|
||||
|
||||
def refund
|
||||
response, api_key = Stripe.request(:post, refund_url, @api_key)
|
||||
refresh_from(response, api_key)
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refund_url
|
||||
url + '/refund'
|
||||
end
|
||||
end
|
||||
|
||||
class StripeError < StandardError; end
|
||||
class APIError < StripeError; end
|
||||
class APIConnectionError < StripeError; end
|
||||
class CardError < StripeError
|
||||
attr_reader :param, :code
|
||||
|
||||
def initialize(message, param, code)
|
||||
super(message)
|
||||
@param = param
|
||||
@code = code
|
||||
end
|
||||
end
|
||||
class InvalidRequestError < StripeError
|
||||
attr_accessor :param
|
||||
|
||||
def initialize(message, param)
|
||||
super(message)
|
||||
@param = param
|
||||
end
|
||||
end
|
||||
class AuthenticationError < StripeError; end
|
||||
|
||||
def self.api_url(url=''); @@api_base + url; end
|
||||
def self.api_key=(api_key); @@api_key = api_key; end
|
||||
def self.api_key; @@api_key; end
|
||||
def self.api_base=(api_base); @@api_base = api_base; end
|
||||
def self.api_base; @@api_base; end
|
||||
def self.version; @@version; end
|
||||
|
||||
def self.request(method, url, api_key, params=nil, headers={})
|
||||
api_key ||= @@api_key
|
||||
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
|
||||
|
||||
if !File.exists?(@@ssl_bundle_path)
|
||||
unless @no_bundle
|
||||
$stderr.puts "WARNING: Running without SSL cert verification because #{@@ssl_bundle_path} does not exist"
|
||||
@no_bundle = true
|
||||
end
|
||||
ssl_opts = { :verify_ssl => false }
|
||||
else
|
||||
ssl_opts = {
|
||||
:verify_ssl => OpenSSL::SSL::VERIFY_PEER,
|
||||
:ssl_ca_file => @@ssl_bundle_path
|
||||
}
|
||||
end
|
||||
uname = (@@uname ||= RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil)
|
||||
lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
|
||||
ua = {
|
||||
:bindings_version => Stripe.version,
|
||||
:lang => 'ruby',
|
||||
:lang_version => lang_version,
|
||||
:platform => RUBY_PLATFORM,
|
||||
:publisher => 'stripe',
|
||||
:uname => uname
|
||||
}
|
||||
headers = {
|
||||
:x_stripe_client_user_agent => JSON.dump(ua),
|
||||
:user_agent => "Stripe/v1 RubyBindings/#{Stripe.version}"
|
||||
}.merge(headers)
|
||||
opts = {
|
||||
:method => method,
|
||||
:url => self.api_url(url),
|
||||
:user => api_key,
|
||||
:headers => headers,
|
||||
:payload => Util.objects_to_ids(params),
|
||||
:open_timeout => 30,
|
||||
:timeout => 80
|
||||
}.merge(ssl_opts)
|
||||
|
||||
begin
|
||||
response = execute_request(opts)
|
||||
rescue SocketError => e
|
||||
self.handle_restclient_error(e)
|
||||
rescue NoMethodError => e
|
||||
# Work around RestClient bug
|
||||
if e.message =~ /\WRequestFailed\W/
|
||||
e = APIConnectionError.new('Unexpected HTTP response code')
|
||||
self.handle_restclient_error(e)
|
||||
else
|
||||
raise
|
||||
end
|
||||
rescue RestClient::ExceptionWithResponse => e
|
||||
if rcode = e.http_code and rbody = e.http_body
|
||||
self.handle_api_error(rcode, rbody)
|
||||
else
|
||||
self.handle_restclient_error(e)
|
||||
end
|
||||
rescue RestClient::Exception, Errno::ECONNREFUSED => e
|
||||
self.handle_restclient_error(e)
|
||||
end
|
||||
|
||||
rbody = response.body
|
||||
rcode = response.code
|
||||
begin
|
||||
resp = JSON.parse(rbody, :symbolize_names => true)
|
||||
rescue JSON::ParseError
|
||||
raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})")
|
||||
end
|
||||
|
||||
[resp, api_key]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.execute_request(opts)
|
||||
RestClient::Request.execute(opts)
|
||||
end
|
||||
|
||||
def self.handle_api_error(rcode, rbody)
|
||||
begin
|
||||
error_obj = JSON.parse(rbody, :symbolize_names => true)
|
||||
error = error_obj[:error] or raise StripeError.new
|
||||
rescue JSON::ParserError, StripeError
|
||||
raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})")
|
||||
end
|
||||
|
||||
case rcode
|
||||
when 400, 404 then
|
||||
raise invalid_request_error(error)
|
||||
when 401
|
||||
raise authentication_error(error)
|
||||
when 402
|
||||
raise card_error(error)
|
||||
else
|
||||
raise api_error(error)
|
||||
end
|
||||
end
|
||||
|
||||
def self.invalid_request_error(error); InvalidRequestError.new(error[:message], error[:param]); end
|
||||
def self.authentication_error(error); AuthenticationError.new(error[:message]); end
|
||||
def self.card_error(error); CardError.new(error[:message], error[:param], error[:code]); end
|
||||
def self.api_error(error); APIError.new(error[:message]); end
|
||||
|
||||
def self.handle_restclient_error(e)
|
||||
case e
|
||||
when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
|
||||
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."
|
||||
when RestClient::SSLCertificateNotVerified
|
||||
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 in your browser.) If this problem persists, let us know at support@stripe.com."
|
||||
when SocketError
|
||||
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."
|
||||
else
|
||||
message = "Unexpected error communicating with Stripe. If this problem persists, let us know at support@stripe.com."
|
||||
end
|
||||
message += "\n\n(Network error: #{e.message})"
|
||||
raise APIConnectionError.new(message)
|
||||
end
|
||||
end
|
24
stripe.gemspec
Normal file
24
stripe.gemspec
Normal file
@ -0,0 +1,24 @@
|
||||
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
||||
|
||||
require 'stripe'
|
||||
|
||||
spec = Gem::Specification.new do |s|
|
||||
s.name = 'stripe'
|
||||
s.version = Stripe.version
|
||||
s.summary = 'Ruby bindings for the Stripe API'
|
||||
s.description = 'Stripe is the easiest way to accept payments online. See https://stripe.com for details.'
|
||||
s.authors = ['Ross Boucher', 'Greg Brockman']
|
||||
s.email = ['boucher@stripe.com', 'gdb@stripe.com']
|
||||
s.homepage = 'https://stripe.com/api'
|
||||
s.executables = 'stripe-console'
|
||||
s.require_paths = %w{lib}
|
||||
|
||||
s.add_dependency('json')
|
||||
s.add_dependency('rest-client')
|
||||
|
||||
s.files = %w{
|
||||
bin/stripe-console
|
||||
lib/stripe.rb
|
||||
lib/data/ca-certificates.crt
|
||||
}
|
||||
end
|
136
test/test_helper.rb
Normal file
136
test/test_helper.rb
Normal file
@ -0,0 +1,136 @@
|
||||
require 'stringio'
|
||||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/stripe'
|
||||
|
||||
require 'mocha'
|
||||
include Mocha
|
||||
|
||||
#monkeypatch request methods
|
||||
module Stripe
|
||||
@mock_rest_client = nil
|
||||
|
||||
def self.mock_rest_client=(mock_client)
|
||||
@mock_rest_client = mock_client
|
||||
end
|
||||
|
||||
def self.execute_request(opts)
|
||||
case opts[:method]
|
||||
when :get: @mock_rest_client.get opts[:url]
|
||||
when :post: @mock_rest_client.post opts[:url], opts[:payload]
|
||||
when :delete: @mock_rest_client.delete opts[:url]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_response(body, code=200)
|
||||
# When an exception is raised, restclient clobbers method_missing. Hence we
|
||||
# can't just use the stubs interface.
|
||||
body = body.to_json if !(body.kind_of? String)
|
||||
m = mock
|
||||
m.instance_variable_set('@stripe_values', { :body => body, :code => code })
|
||||
def m.body; @stripe_values[:body]; end
|
||||
def m.code; @stripe_values[:code]; end
|
||||
m
|
||||
end
|
||||
|
||||
def test_customer(params={})
|
||||
{
|
||||
:subscription_history => [],
|
||||
:bills => [],
|
||||
:charges => [],
|
||||
:livemode => false,
|
||||
:object => "customer",
|
||||
:id => "c_test_customer",
|
||||
:active_card => {
|
||||
:type => "Visa",
|
||||
:last4 => "4242",
|
||||
:exp_month => 11,
|
||||
:country => "US",
|
||||
:exp_year => 2012,
|
||||
:id => "cc_test_card",
|
||||
:object => "card"
|
||||
},
|
||||
:created => 1304114758
|
||||
}.merge(params)
|
||||
end
|
||||
|
||||
def test_customer_array
|
||||
[test_customer, test_customer, test_customer]
|
||||
end
|
||||
|
||||
def test_charge(params={})
|
||||
{
|
||||
:refunded => false,
|
||||
:paid => true,
|
||||
:amount => 100,
|
||||
:card => {
|
||||
:type => "Visa",
|
||||
:last4 => "4242",
|
||||
:exp_month => 11,
|
||||
:country => "US",
|
||||
:exp_year => 2012,
|
||||
:id => "cc_test_card",
|
||||
:object => "card"
|
||||
},
|
||||
:id => "ch_test_charge",
|
||||
:reason => "execute_charge",
|
||||
:livemode => false,
|
||||
:currency => "usd",
|
||||
:object => "charge",
|
||||
:created => 1304114826
|
||||
}.merge(params)
|
||||
end
|
||||
|
||||
def test_charge_array
|
||||
[test_charge, test_charge, test_charge]
|
||||
end
|
||||
|
||||
def test_card(params={})
|
||||
{
|
||||
:type => "Visa",
|
||||
:last4 => "4242",
|
||||
:exp_month => 11,
|
||||
:country => "US",
|
||||
:exp_year => 2012,
|
||||
:id => "cc_test_card",
|
||||
:object => "card"
|
||||
}.merge(params)
|
||||
end
|
||||
|
||||
def test_invalid_api_key_error
|
||||
{
|
||||
"error" => {
|
||||
"type" => "invalid_request_error",
|
||||
"message" => "Invalid API Key provided: invalid"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def test_invalid_exp_year_error
|
||||
{
|
||||
"error" => {
|
||||
"code" => "invalid_expiry_year",
|
||||
"param" => "exp_year",
|
||||
"type" => "card_error",
|
||||
"message" => "Your card's expiration year is invalid"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def test_missing_id_error
|
||||
{
|
||||
:error => {
|
||||
:param => "id",
|
||||
:type => "invalid_request_error",
|
||||
:message => "Missing id"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def test_api_error
|
||||
{
|
||||
:error => {
|
||||
:type => "api_error"
|
||||
}
|
||||
}
|
||||
end
|
301
test/test_stripe.rb
Normal file
301
test/test_stripe.rb
Normal file
@ -0,0 +1,301 @@
|
||||
require File.dirname(__FILE__) + '/test_helper.rb'
|
||||
require 'test/unit'
|
||||
require 'context'
|
||||
require 'mocha'
|
||||
require 'pp'
|
||||
require 'rest-client'
|
||||
|
||||
class TestStripeRuby < Test::Unit::TestCase
|
||||
include Mocha
|
||||
|
||||
context "API Bindings" do
|
||||
before do
|
||||
@mock = mock
|
||||
Stripe.mock_rest_client = @mock
|
||||
end
|
||||
|
||||
after do
|
||||
Stripe.mock_rest_client = nil
|
||||
end
|
||||
|
||||
test "creating a new APIResource should not fetch over the network" do
|
||||
@mock.expects(:get).never
|
||||
c = Stripe::Customer.new("someid")
|
||||
end
|
||||
|
||||
test "creating a new APIResource from a hash should not fetch over the network" do
|
||||
@mock.expects(:get).never
|
||||
c = Stripe::Customer.construct_from({
|
||||
:id => "somecustomer",
|
||||
:card => {:id => "somecard", :object => "card"},
|
||||
:object => "customer"
|
||||
})
|
||||
end
|
||||
|
||||
test "setting an attribute should not cause a network request" do
|
||||
@mock.expects(:get).never
|
||||
@mock.expects(:post).never
|
||||
c = Stripe::Customer.new("test_customer");
|
||||
c.card = {:id => "somecard", :object => "card"}
|
||||
end
|
||||
|
||||
test "accessing id should not issue a fetch" do
|
||||
@mock.expects(:get).never
|
||||
c = Stripe::Customer.new("test_customer");
|
||||
c.id
|
||||
end
|
||||
|
||||
test "not specifying api credentials should raise an exception" do
|
||||
assert_raises Stripe::AuthenticationError do
|
||||
Stripe::Customer.new("test_customer").refresh
|
||||
end
|
||||
end
|
||||
|
||||
test "specifying invalid api credentials should raise an exception" do
|
||||
Stripe.api_key="invalid"
|
||||
response = test_response(test_invalid_api_key_error, 401)
|
||||
assert_raises Stripe::AuthenticationError do
|
||||
@mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 401))
|
||||
Stripe::Customer.retrieve("failing_customer")
|
||||
end
|
||||
end
|
||||
|
||||
context "with valid credentials" do
|
||||
before do
|
||||
Stripe.api_key="foo"
|
||||
end
|
||||
|
||||
after do
|
||||
Stripe.api_key=nil
|
||||
end
|
||||
|
||||
test "requesting with a unicode ID should result in a request" do
|
||||
response = test_response(test_missing_id_error, 404)
|
||||
@mock.expects(:get).once.with("https://api.stripe.com/v1/customers/%E2%98%83").raises(RestClient::ExceptionWithResponse.new(response, 404))
|
||||
c = Stripe::Customer.new("☃")
|
||||
assert_raises(Stripe::InvalidRequestError) { c.refresh }
|
||||
end
|
||||
|
||||
test "loading an object should issue a GET request" do
|
||||
@mock.expects(:get).once.returns(test_response(test_customer))
|
||||
c = Stripe::Customer.new("test_customer")
|
||||
c.refresh
|
||||
end
|
||||
|
||||
test "using array accessors should be the same as the method interface" do
|
||||
@mock.expects(:get).once.returns(test_response(test_customer))
|
||||
c = Stripe::Customer.new("test_customer")
|
||||
c.refresh
|
||||
assert_equal c.created, c[:created]
|
||||
assert_equal c.created, c['created']
|
||||
c['created'] = 12345
|
||||
assert_equal c.created, 12345
|
||||
end
|
||||
|
||||
test "accessing a property other than id or parent on an unfetched object should fetch it" do
|
||||
@mock.expects(:get).once.returns(test_response(test_customer))
|
||||
c = Stripe::Customer.new("test_customer")
|
||||
c.charges
|
||||
end
|
||||
|
||||
test "updating an object should issue a POST request with only the changed properties" do
|
||||
@mock.expects(:post).with("https://api.stripe.com/v1/customers/c_test_customer", {:mnemonic => 'another_mn'}).once.returns(test_response(test_customer))
|
||||
c = Stripe::Customer.construct_from(test_customer)
|
||||
c.mnemonic = "another_mn"
|
||||
c.save
|
||||
end
|
||||
|
||||
test "updating should merge in returned properties" do
|
||||
@mock.expects(:post).once.returns(test_response(test_customer))
|
||||
c = Stripe::Customer.new("c_test_customer")
|
||||
c.mnemonic = "another_mn"
|
||||
c.save
|
||||
assert_equal false, c.livemode
|
||||
end
|
||||
|
||||
test "deleting should send no props and result in an object that has no props other deleted" do
|
||||
@mock.expects(:get).never
|
||||
@mock.expects(:post).never
|
||||
@mock.expects(:delete).with("https://api.stripe.com/v1/customers/c_test_customer").once.returns(test_response({ "id" => "test_customer", "deleted" => true }))
|
||||
|
||||
c = Stripe::Customer.construct_from(test_customer)
|
||||
c.delete
|
||||
assert_equal true, c.deleted
|
||||
|
||||
assert_raises NoMethodError do
|
||||
c.livemode
|
||||
end
|
||||
end
|
||||
|
||||
test "loading an object with properties that have specific types should instantiate those classes" do
|
||||
@mock.expects(:get).once.returns(test_response(test_charge))
|
||||
c = Stripe::Charge.retrieve("test_charge")
|
||||
assert c.card.kind_of?(Stripe::StripeObject) && c.card.object == 'card'
|
||||
end
|
||||
|
||||
test "loading all of an APIResource should return an array of recursively instantiated objects" do
|
||||
@mock.expects(:get).once.returns(test_response(test_charge_array))
|
||||
c = Stripe::Charge.all
|
||||
assert c.kind_of? Array
|
||||
assert c[0].kind_of? Stripe::Charge
|
||||
assert c[0].card.kind_of?(Stripe::StripeObject) && c[0].card.object == 'card'
|
||||
end
|
||||
|
||||
context "charge tests" do
|
||||
|
||||
test "charges should be listable" do
|
||||
@mock.expects(:get).once.returns(test_response(test_charge_array))
|
||||
c = Stripe::Charge.all
|
||||
assert c.kind_of? Array
|
||||
end
|
||||
|
||||
test "charges should be refundable" do
|
||||
@mock.expects(:get).never
|
||||
@mock.expects(:post).once.returns(test_response({:id => "ch_test_charge", :refunded => true}))
|
||||
c = Stripe::Charge.new("test_charge")
|
||||
c.refund
|
||||
assert c.refunded
|
||||
end
|
||||
|
||||
test "charges should not be deletable" do
|
||||
assert_raises NoMethodError do
|
||||
@mock.expects(:get).once.returns(test_response(test_charge))
|
||||
c = Stripe::Charge.retrieve("test_charge")
|
||||
c.delete
|
||||
end
|
||||
end
|
||||
|
||||
test "charges should not be updateable" do
|
||||
assert_raises NoMethodError do
|
||||
@mock.expects(:get).once.returns(test_response(test_charge))
|
||||
c = Stripe::Charge.new("test_charge")
|
||||
c.refresh
|
||||
c.mnemonic= "YAY PASSING TEST!"
|
||||
c.save
|
||||
end
|
||||
end
|
||||
|
||||
test "charges should have Card objects associated with their Card property" do
|
||||
@mock.expects(:get).once.returns(test_response(test_charge))
|
||||
c = Stripe::Charge.retrieve("test_charge")
|
||||
assert c.card.kind_of?(Stripe::StripeObject) && c.card.object == 'card'
|
||||
end
|
||||
|
||||
test "execute should return a new, fully executed charge when passed correct parameters" do
|
||||
@mock.expects(:post).with('https://api.stripe.com/v1/charges', {
|
||||
:currency => 'usd', :amount => 100,
|
||||
:card => {:exp_year => 2012, :number => '4242424242424242', :exp_month => 11}
|
||||
}).once.returns(test_response(test_charge))
|
||||
|
||||
c = Stripe::Charge.create({
|
||||
:amount => 100,
|
||||
:card => {
|
||||
:number => "4242424242424242",
|
||||
:exp_month => 11,
|
||||
:exp_year => 2012,
|
||||
},
|
||||
:currency => "usd"
|
||||
})
|
||||
assert c.paid
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "customer tests" do
|
||||
|
||||
test "customers should be listable" do
|
||||
@mock.expects(:get).once.returns(test_response(test_customer_array))
|
||||
c = Stripe::Customer.all
|
||||
assert c.kind_of? Array
|
||||
assert c[0].kind_of? Stripe::Customer
|
||||
end
|
||||
|
||||
test "customers should be deletable" do
|
||||
@mock.expects(:delete).once.returns(test_response(test_customer({:deleted => true})))
|
||||
c = Stripe::Customer.new("test_customer")
|
||||
c.delete
|
||||
assert c.deleted
|
||||
end
|
||||
|
||||
test "customers should be updateable" do
|
||||
@mock.expects(:get).once.returns(test_response(test_customer({:mnemonic => "foo"})))
|
||||
@mock.expects(:post).once.returns(test_response(test_customer({:mnemonic => "bar"})))
|
||||
c = Stripe::Customer.new("test_customer").refresh
|
||||
assert_equal c.mnemonic, "foo"
|
||||
c.mnemonic = "bar"
|
||||
c.save
|
||||
assert_equal c.mnemonic, "bar"
|
||||
end
|
||||
|
||||
test "customers should have Card objects associated with their active_ard property" do
|
||||
@mock.expects(:get).once.returns(test_response(test_customer))
|
||||
c = Stripe::Customer.retrieve("test_customer")
|
||||
assert c.active_card.kind_of?(Stripe::StripeObject) && c.active_card.object == 'card'
|
||||
end
|
||||
|
||||
test "create should return a new customer" do
|
||||
@mock.expects(:post).once.returns(test_response(test_customer))
|
||||
c = Stripe::Customer.create
|
||||
assert_equal "c_test_customer", c.id
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "card tests" do
|
||||
end
|
||||
|
||||
context "error checking" do
|
||||
|
||||
test "404s should raise an InvalidRequestError" do
|
||||
response = test_response(test_missing_id_error, 404)
|
||||
@mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404))
|
||||
|
||||
begin
|
||||
Stripe::Customer.new("test_customer").refresh
|
||||
assert false #shouldn't get here either
|
||||
rescue Stripe::InvalidRequestError => e # we don't use assert_raises because we want to examine e
|
||||
assert e.kind_of? Stripe::InvalidRequestError
|
||||
assert_equal "id", e.param
|
||||
assert_equal "Missing id", e.message
|
||||
return
|
||||
end
|
||||
|
||||
assert false #shouldn't get here
|
||||
end
|
||||
|
||||
test "5XXs should raise an APIError" do
|
||||
response = test_response(test_api_error, 500)
|
||||
@mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 500))
|
||||
|
||||
begin
|
||||
Stripe::Customer.new("test_customer").refresh
|
||||
assert false #shouldn't get here either
|
||||
rescue Stripe::APIError => e # we don't use assert_raises because we want to examine e
|
||||
assert e.kind_of? Stripe::APIError
|
||||
return
|
||||
end
|
||||
|
||||
assert false #shouldn't get here
|
||||
end
|
||||
|
||||
test "402s should raise a CardError" do
|
||||
response = test_response(test_invalid_exp_year_error, 402)
|
||||
@mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 402))
|
||||
|
||||
begin
|
||||
Stripe::Customer.new("test_customer").refresh
|
||||
assert false #shouldn't get here either
|
||||
rescue Stripe::CardError => e # we don't use assert_raises because we want to examine e
|
||||
assert e.kind_of? Stripe::CardError
|
||||
assert_equal "invalid_expiry_year", e.code
|
||||
assert_equal "exp_year", e.param
|
||||
assert_equal "Your card's expiration year is invalid", e.message
|
||||
return
|
||||
end
|
||||
|
||||
assert false #shouldn't get here
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user