Support persisted use of Stripe-Account header everywhere

Including implicit use in /v1/accounts/ endpoints
This commit is contained in:
Brian Krausz 2015-02-08 18:56:01 -08:00
parent 085ad6cfb3
commit 4d611c62f7
22 changed files with 207 additions and 174 deletions

View File

@ -14,6 +14,7 @@ require 'stripe/api_operations/create'
require 'stripe/api_operations/update'
require 'stripe/api_operations/delete'
require 'stripe/api_operations/list'
require 'stripe/api_operations/request'
# Resources
require 'stripe/util'
@ -56,6 +57,7 @@ module Stripe
DEFAULT_CA_BUNDLE_PATH = File.dirname(__FILE__) + '/data/ca-certificates.crt'
@api_base = 'https://api.stripe.com'
@connect_base = 'https://connect.stripe.com'
@uploads_base = 'https://uploads.stripe.com'
@ssl_bundle_path = DEFAULT_CA_BUNDLE_PATH
@verify_ssl_certs = true
@ -63,7 +65,7 @@ module Stripe
class << self
attr_accessor :api_key, :api_base, :verify_ssl_certs, :api_version, :connect_base
attr_accessor :api_key, :api_base, :verify_ssl_certs, :api_version, :connect_base, :uploads_base
end
def self.api_url(url='', api_base_url=nil)
@ -88,6 +90,12 @@ module Stripe
'email support@stripe.com if you have any questions.)')
end
if method == :get && headers.key?(:idempotency_key)
raise ArgumentError.new(
'Idempotency keys cannot be used for GET requests.'
)
end
request_opts = { :verify_ssl => false }
if ssl_preflight_passed?

View File

@ -1,9 +1,27 @@
module Stripe
class Account < SingletonAPIResource
class Account < APIResource
include Stripe::APIOperations::Create
include Stripe::APIOperations::List
include Stripe::APIOperations::Update
def url
if self['id']
super
else
"/v1/account"
end
end
# @override To make id optional
def self.retrieve(id=nil, opts={})
super
end
def deauthorize(client_id, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(:post, '/oauth/deauthorize', api_key, { 'client_id' => client_id, 'stripe_user_id' => self.id }, headers, Stripe.connect_base)
Util.convert_to_stripe_object(response, api_key)
opts = {:api_base => Stripe.connect_base}.merge(Util.normalize_opts(opts))
response, opts = request(:post, '/oauth/deauthorize', { 'client_id' => client_id, 'stripe_user_id' => self.id }, opts)
opts.delete(:api_base) # the api_base here is a one-off, don't persist it
Util.convert_to_stripe_object(response, opts)
end
end
end

View File

@ -3,9 +3,8 @@ module Stripe
module Create
module ClassMethods
def create(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(:post, self.url, api_key, params, headers)
Util.convert_to_stripe_object(response, api_key)
response, opts = request(:post, url, params, opts)
Util.convert_to_stripe_object(response, opts)
end
end

View File

@ -1,10 +1,10 @@
module Stripe
module APIOperations
module Delete
def delete(params = {}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(:delete, url, api_key || @api_key, params, headers)
refresh_from(response, api_key)
def delete(params={}, opts={})
opts = Util.normalize_opts(opts)
response, opts = request(:delete, url, params, opts)
refresh_from(response, opts)
end
end
end

View File

@ -3,9 +3,9 @@ module Stripe
module List
module ClassMethods
def all(filters={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(:get, url, api_key, filters, headers)
Util.convert_to_stripe_object(response, api_key)
opts = Util.normalize_opts(opts)
response, opts = request(:get, url, filters, opts)
Util.convert_to_stripe_object(response, opts)
end
end

View File

@ -0,0 +1,32 @@
module Stripe
module APIOperations
module Request
module ClassMethods
@@opts_to_persist = Set.new([:api_key, :api_base, :stripe_account, :stripe_version])
def request(method, url, params={}, opts={})
opts = Util.normalize_opts(opts)
headers = opts.clone
api_key = headers.delete(:api_key)
api_base = headers.delete(:api_base)
# Assume all remaining opts must be headers
response, opts[:api_key] = Stripe.request(method, url, api_key, params, headers, api_base)
[response, opts.select {|k, _| @@opts_to_persist.include?(k)}]
end
end
def self.included(base)
base.extend(ClassMethods)
end
protected
def request(method, url, params={}, opts={})
opts = @opts.merge(Util.normalize_opts(opts))
self.class.request(method, url, params, opts)
end
end
end
end

View File

@ -1,8 +1,8 @@
module Stripe
module APIOperations
module Update
def save(opts={})
values = serialize_params(self).merge(opts)
def save(params={})
values = serialize_params(self).merge(params)
if @values[:metadata]
values[:metadata] = serialize_metadata
@ -11,8 +11,8 @@ module Stripe
if values.length > 0
values.delete(:id)
response, api_key = Stripe.request(:post, url, @api_key, values)
refresh_from(response, api_key)
response, opts = request(:post, url, values)
refresh_from(response, opts)
end
self
end

View File

@ -1,5 +1,7 @@
module Stripe
class APIResource < StripeObject
include Stripe::APIOperations::Request
def self.class_name
self.name.split('::')[-1]
end
@ -19,12 +21,13 @@ module Stripe
end
def refresh
response, api_key = Stripe.request(:get, url, @api_key, @retrieve_options)
refresh_from(response, api_key)
response, opts = request(:get, url)
refresh_from(response, opts)
end
def self.retrieve(id, api_key=nil)
instance = self.new(id, api_key)
def self.retrieve(id, opts=nil)
opts = Util.normalize_opts(opts)
instance = self.new(id, opts)
instance.refresh
instance
end

View File

@ -7,9 +7,8 @@ module Stripe
end
def refund(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(:post, refund_url, api_key || @api_key, params, headers)
refresh_from(response, api_key)
response, opts = request(:post, refund_url, params, opts)
refresh_from(response, opts)
end
private

View File

@ -12,7 +12,7 @@ module Stripe
end
end
def self.retrieve(id, api_key=nil)
def self.retrieve(id, opts=nil)
raise NotImplementedError.new("Cards cannot be retrieved without a customer ID. Retrieve a card using customer.cards.retrieve('card_id')")
end
end

View File

@ -5,48 +5,40 @@ module Stripe
include Stripe::APIOperations::Update
def refund(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(
:post, refund_url, api_key || @api_key, params, headers)
refresh_from(response, api_key)
response, opts = request(:post, refund_url, params, opts)
refresh_from(response, opts)
end
def capture(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(
:post, capture_url, api_key || @api_key, params, headers)
refresh_from(response, api_key)
response, opts = request(:post, capture_url, params, opts)
refresh_from(response, opts)
end
def update_dispute(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(
:post, dispute_url, api_key || @api_key, params, headers)
refresh_from({ :dispute => response }, api_key, true)
response, opts = request(:post, dispute_url, params, opts)
refresh_from({ :dispute => response }, opts, true)
dispute
end
def close_dispute(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(
:post, close_dispute_url, api_key || @api_key, params, headers)
refresh_from(response, api_key)
response, opts = request(:post, close_dispute_url, params, opts)
refresh_from(response, opts)
end
def mark_as_fraudulent
params = {
:fraud_details => { :user_report => 'fraudulent' }
}
response, api_key = Stripe.request(:post, url, @api_key, params)
refresh_from(response, api_key)
response, opts = request(:post, url, params)
refresh_from(response, opts)
end
def mark_as_safe
params = {
:fraud_details => { :user_report => 'safe' }
}
response, api_key = Stripe.request(:post, url, @api_key, params)
refresh_from(response, api_key)
response, opts = request(:post, url, params)
refresh_from(response, opts)
end
private

View File

@ -6,58 +6,52 @@ module Stripe
include Stripe::APIOperations::List
def add_invoice_item(params, opts={})
opts[:api_key] = @api_key
opts = @opts.merge(Util.normalize_opts(opts))
InvoiceItem.create(params.merge(:customer => id), opts)
end
def invoices
Invoice.all({ :customer => id }, @api_key)
Invoice.all({ :customer => id }, @opts)
end
def invoice_items
InvoiceItem.all({ :customer => id }, @api_key)
InvoiceItem.all({ :customer => id }, @opts)
end
def upcoming_invoice
Invoice.upcoming({ :customer => id }, @api_key)
Invoice.upcoming({ :customer => id }, @opts)
end
def charges
Charge.all({ :customer => id }, @api_key)
Charge.all({ :customer => id }, @opts)
end
def create_upcoming_invoice(params={}, opts={})
opts[:api_key] = @api_key
opts = @opts.merge(Util.normalize_opts(opts))
Invoice.create(params.merge(:customer => id), opts)
end
def cancel_subscription(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(
:delete, subscription_url, api_key || @api_key, params, headers)
refresh_from({ :subscription => response }, api_key, true)
response, opts = request(:delete, subscription_url, params, opts)
refresh_from({ :subscription => response }, opts, true)
subscription
end
def update_subscription(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(
:post, subscription_url, api_key || @api_key, params, headers)
refresh_from({ :subscription => response }, api_key, true)
response, opts = request(:post, subscription_url, params, opts)
refresh_from({ :subscription => response }, opts, true)
subscription
end
def create_subscription(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(
:post, subscriptions_url, api_key || @api_key, params, headers)
refresh_from({ :subscription => response }, api_key, true)
response, opts = request(:post, subscriptions_url, params, opts)
refresh_from({ :subscription => response }, opts, true)
subscription
end
def delete_discount
Stripe.request(:delete, discount_url, @api_key)
refresh_from({ :discount => nil }, api_key, true)
_, opts = request(:delete, discount_url)
refresh_from({ :discount => nil }, opts, true)
end
private

View File

@ -1,34 +1,22 @@
module Stripe
class FileUpload < APIResource
UPLOADS_API_BASE = "https://uploads.stripe.com"
def self.url
"/v1/files"
end
def self.request_headers
{
:content_type => 'multipart/form-data',
}
end
def self.create(params={}, api_key=nil)
response, api_key = Stripe.request(
:post, self.url, api_key, params, self.request_headers, UPLOADS_API_BASE)
Util.convert_to_stripe_object(response, api_key)
def self.create(params={}, opts={})
opts = {
content_type: 'multipart/form-data',
api_base: Stripe::uploads_base
}.merge(opts)
response, opts = request(:post, url, params, opts)
Util.convert_to_stripe_object(response, opts)
end
def self.all(filters={}, opts={})
api_key, headers = Util.parse_opts(opts)
response, api_key = Stripe.request(
:get, self.url, api_key, filters, headers, UPLOADS_API_BASE)
Util.convert_to_stripe_object(response, api_key)
end
def refresh
response, api_key = Stripe.request(
:get, url, @api_key, @retrieve_options, self.class.request_headers, UPLOADS_API_BASE)
refresh_from(response, api_key)
opts = {api_base: Stripe::uploads_base}.merge(opts)
response, opts = request(:get, url, filters, opts)
Util.convert_to_stripe_object(response, opts)
end
end
end

View File

@ -4,14 +4,15 @@ module Stripe
include Stripe::APIOperations::Update
include Stripe::APIOperations::Create
def self.upcoming(params, api_key = nil)
response, api_key = Stripe.request(:get, upcoming_url, api_key, params)
Util.convert_to_stripe_object(response, api_key)
def self.upcoming(params, opts={})
opts = Util.normalize_opts(opts)
response, api_key = Stripe.request(:get, upcoming_url, opts, params)
Util.convert_to_stripe_object(response, opts)
end
def pay
response, api_key = Stripe.request(:post, pay_url, @api_key)
refresh_from(response, api_key)
response, opts = Stripe.request(:post, pay_url, @opts, {})
refresh_from(response, opts)
end
private

View File

@ -1,5 +1,6 @@
module Stripe
class ListObject < StripeObject
include Stripe::APIOperations::Request
def [](k)
case k
@ -14,24 +15,19 @@ module Stripe
self.data.each(&blk)
end
def retrieve(id, api_key=nil)
api_key ||= @api_key
response, api_key = Stripe.request(:get,"#{url}/#{CGI.escape(id)}", api_key)
Util.convert_to_stripe_object(response, api_key)
def retrieve(id, opts={})
response, opts = request(:get,"#{url}/#{CGI.escape(id)}", {}, opts)
Util.convert_to_stripe_object(response, opts)
end
def create(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
api_key ||= @api_key
response, api_key = Stripe.request(:post, url, api_key, params, headers)
Util.convert_to_stripe_object(response, api_key)
response, opts = request(:post, url, params, opts)
Util.convert_to_stripe_object(response, opts)
end
def all(params={}, opts={})
api_key, headers = Util.parse_opts(opts)
api_key ||= @api_key
response, api_key = Stripe.request(:get, url, api_key, params, headers)
Util.convert_to_stripe_object(response, api_key)
response, opts = request(:get, url, params, opts)
Util.convert_to_stripe_object(response, opts)
end
end
end

View File

@ -2,25 +2,24 @@ module Stripe
class StripeObject
include Enumerable
attr_accessor :api_key
@@permanent_attributes = Set.new([:api_key, :id])
@@permanent_attributes = Set.new([:id])
# 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)
def initialize(id=nil, opts={})
# parameter overloading!
if id.kind_of?(Hash)
@retrieve_options = id.dup
@retrieve_options.delete(:id)
@retrieve_params = id.dup
@retrieve_params.delete(:id)
id = id[:id]
else
@retrieve_options = {}
@retrieve_params = {}
end
@api_key = api_key
@opts = opts
@values = {}
# This really belongs in APIResource, but not putting it there allows us
# to have a unified inspect method
@ -29,8 +28,8 @@ module Stripe
@values[:id] = id if id
end
def self.construct_from(values, api_key=nil)
self.new(values[:id], api_key).refresh_from(values, api_key)
def self.construct_from(values, opts={})
self.new(values[:id]).refresh_from(values, opts)
end
def to_s(*args)
@ -42,9 +41,8 @@ module Stripe
"#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + JSON.pretty_generate(@values)
end
def refresh_from(values, api_key, partial=false)
@api_key = api_key
def refresh_from(values, opts, partial=false)
@opts = opts
@previous_metadata = values[:metadata]
removed = partial ? Set.new : Set.new(@values.keys - values.keys)
added = Set.new(values.keys - @values.keys)
@ -62,7 +60,7 @@ module Stripe
@unsaved_values.delete(k)
end
values.each do |k, v|
@values[k] = Util.convert_to_stripe_object(v, api_key)
@values[k] = Util.convert_to_stripe_object(v, @opts)
@transient_values.delete(k)
@unsaved_values.delete(k)
end
@ -106,12 +104,12 @@ module Stripe
end
def _dump(level)
Marshal.dump([@values, @api_key])
Marshal.dump([@values, @opts])
end
def self._load(args)
values, api_key = Marshal.load(args)
construct_from(values, api_key)
values, opts = Marshal.load(args)
construct_from(values, opts)
end
if RUBY_VERSION < '1.9.2'

View File

@ -7,13 +7,13 @@ module Stripe
"#{Customer.url}/#{CGI.escape(customer)}/subscriptions/#{CGI.escape(id)}"
end
def self.retrieve(id, api_key=nil)
def self.retrieve(id, opts=nil)
raise NotImplementedError.new("Subscriptions cannot be retrieved without a customer ID. Retrieve a subscription using customer.subscriptions.retrieve('subscription_id')")
end
def delete_discount
Stripe.request(:delete, discount_url, @api_key)
refresh_from({ :discount => nil }, api_key, true)
response, opts = request(:delete, discount_url)
refresh_from({ :discount => nil }, opts, true)
end
private

View File

@ -43,13 +43,13 @@ module Stripe
}
end
def self.convert_to_stripe_object(resp, api_key)
def self.convert_to_stripe_object(resp, opts)
case resp
when Array
resp.map { |i| convert_to_stripe_object(i, api_key) }
resp.map { |i| convert_to_stripe_object(i, opts) }
when Hash
# Try converting to a known object class. If none available, fall back to generic StripeObject
object_classes.fetch(resp[:object], StripeObject).construct_from(resp, api_key)
object_classes.fetch(resp[:object], StripeObject).construct_from(resp, opts)
else
resp
end
@ -119,23 +119,16 @@ module Stripe
# The secondary opts argument can either be a string or hash
# Turn this value into an api_key and a set of headers
def self.parse_opts(opts)
def self.normalize_opts(opts)
case opts
when NilClass
return nil, {}
{}
when String
return opts, {}
{api_key: opts}
when Hash
headers = {}
if opts[:idempotency_key]
headers[:idempotency_key] = opts[:idempotency_key]
end
if opts[:stripe_account]
headers[:stripe_account] = opts[:stripe_account]
end
return opts[:api_key], headers
opts.clone
else
raise TypeError.new("parse_opts expects a string or a hash")
raise TypeError.new('normalize_opts expects a string or a hash')
end
end
end

View File

@ -4,13 +4,28 @@ module Stripe
class AccountTest < Test::Unit::TestCase
should "account should be retrievable" do
resp = {:email => "test+bindings@stripe.com", :charge_enabled => false, :details_submitted => false}
@mock.expects(:get).once.returns(test_response(resp))
@mock.expects(:get).
once.
with('https://api.stripe.com/v1/account', nil, nil).
returns(test_response(resp))
a = Stripe::Account.retrieve
assert_equal "test+bindings@stripe.com", a.email
assert !a.charge_enabled
assert !a.details_submitted
end
should "account should be retrievable via plural endpoint" do
resp = {:email => "test+bindings@stripe.com", :charge_enabled => false, :details_submitted => false}
@mock.expects(:get).
once.
with('https://api.stripe.com/v1/accounts/acct_foo', nil, nil).
returns(test_response(resp))
a = Stripe::Account.retrieve('acct_foo')
assert_equal "test+bindings@stripe.com", a.email
assert !a.charge_enabled
assert !a.details_submitted
end
should "be able to deauthorize an account" do
resp = {:id => 'acct_1234', :email => "test+bindings@stripe.com", :charge_enabled => false, :details_submitted => false}
@mock.expects(:get).once.returns(test_response(resp))

View File

@ -294,7 +294,6 @@ module Stripe
@mock.expects(:get).never
@mock.expects(:post).never
@mock.expects(:delete).with("#{Stripe.api_base}/v1/customers/c_test_customer", nil, nil).once.returns(test_response({ "id" => "test_customer", "deleted" => true }))
c = Stripe::Customer.construct_from(test_customer)
c.delete
assert_equal true, c.deleted
@ -318,6 +317,33 @@ module Stripe
assert c[0].card.kind_of?(Stripe::StripeObject) && c[0].card.object == 'card'
end
should "passing in a stripe_account header should pass it through on call" do
Stripe.expects(:execute_request).with do |opts|
opts[:method] == :get &&
opts[:url] == "#{Stripe.api_base}/v1/customers/c_test_customer" &&
opts[:headers][:stripe_account] == 'acct_abc'
end.once.returns(test_response(test_customer))
c = Stripe::Customer.retrieve("c_test_customer", {:stripe_account => 'acct_abc'})
end
should "passing in a stripe_account header should pass it through on save" do
Stripe.expects(:execute_request).with do |opts|
opts[:method] == :get &&
opts[:url] == "#{Stripe.api_base}/v1/customers/c_test_customer" &&
opts[:headers][:stripe_account] == 'acct_abc'
end.once.returns(test_response(test_customer))
c = Stripe::Customer.retrieve("c_test_customer", {:stripe_account => 'acct_abc'})
Stripe.expects(:execute_request).with do |opts|
opts[:method] == :post &&
opts[:url] == "#{Stripe.api_base}/v1/customers/c_test_customer" &&
opts[:headers][:stripe_account] == 'acct_abc' &&
opts[:payload] == 'description=FOO'
end.once.returns(test_response(test_customer))
c.description = 'FOO'
c.save
end
context "error checking" do
should "404s should raise an InvalidRequestError" do

View File

@ -10,11 +10,12 @@ module Stripe
end
should "marshal a stripe object correctly" do
obj = Stripe::StripeObject.construct_from({ :id => 1, :name => 'Stripe' }, 'apikey')
obj = Stripe::StripeObject.construct_from({ :id => 1, :name => 'Stripe' }, {:api_key => 'apikey'})
m = Marshal.load(Marshal.dump(obj))
assert_equal 1, m.id
assert_equal 'Stripe', m.name
assert_equal 'apikey', m.api_key
expected_hash = {:api_key => 'apikey'}
assert_equal expected_hash, m.instance_variable_get('@opts')
end
should "recursively call to_hash on its values" do

View File

@ -25,35 +25,5 @@ module Stripe
symbolized = Stripe::Util.symbolize_names(start)
assert_equal(finish, symbolized)
end
should "parse a nil opts argument" do
api_key, headers = Stripe::Util.parse_opts(nil)
assert_equal({}, headers)
assert_equal(nil, api_key)
end
should "parse a string opts argument" do
api_key, headers = Stripe::Util.parse_opts('foo')
assert_equal({}, headers)
assert_equal('foo', api_key)
end
should "parse a hash opts argument with just api_key" do
api_key, headers = Stripe::Util.parse_opts({:api_key => 'foo'})
assert_equal({}, headers)
assert_equal('foo', api_key)
end
should "parse a hash opts argument with just idempotency_key" do
api_key, headers = Stripe::Util.parse_opts({:idempotency_key => 'foo'})
assert_equal({:idempotency_key => 'foo'}, headers)
assert_equal(nil, api_key)
end
should "parse a hash opts argument both idempotency_key and api_key" do
api_key, headers = Stripe::Util.parse_opts({:api_key => 'bar', :idempotency_key => 'foo'})
assert_equal({:idempotency_key => 'foo'}, headers)
assert_equal('bar', api_key)
end
end
end