Add update class method to API resources (#426)

* Rename the `Update` operation to `Save`
* Add the `update` class method to all saveable resources
* Add tests for update method
* Add tests for plans, invoice items, and application fees
This commit is contained in:
Kyle Conroy 2016-06-29 14:13:42 -07:00 committed by GitHub
parent cb8917b7a3
commit 732a494ac4
40 changed files with 328 additions and 37 deletions

View File

@ -14,7 +14,7 @@ require 'stripe/version'
# API operations
require 'stripe/api_operations/create'
require 'stripe/api_operations/update'
require 'stripe/api_operations/save'
require 'stripe/api_operations/delete'
require 'stripe/api_operations/list'
require 'stripe/api_operations/request'

View File

@ -1,9 +1,9 @@
module Stripe
class Account < APIResource
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Delete
extend Stripe::APIOperations::List
include Stripe::APIOperations::Update
include Stripe::APIOperations::Delete
include Stripe::APIOperations::Save
def resource_url
if self['id']

View File

@ -1,6 +1,6 @@
module Stripe
class AlipayAccount < APIResource
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
include Stripe::APIOperations::Delete
def resource_url
@ -9,6 +9,10 @@ module Stripe
end
end
def self.update(id, params=nil, opts=nil)
raise NotImplementedError.new("Alipay accounts cannot be updated without a customer ID. Update an Alipay account by `a = customer.sources.retrieve('alipay_account_id'); a.save`")
end
def self.retrieve(id, opts=nil)
raise NotImplementedError.new("Alipay accounts cannot be retrieved without a customer ID. Retrieve an Alipay account using customer.sources.retrieve('alipay_account_id')")
end

View File

@ -1,6 +1,25 @@
module Stripe
module APIOperations
module Update
module Save
module ClassMethods
# Updates an API resource
#
# Updates the identified resource with the passed in parameters.
#
# ==== Attributes
#
# * +id+ - ID of the resource to update.
# * +params+ - A hash of parameters to pass to the API
# * +opts+ - A Hash of additional options (separate from the params /
# object values) to be added to the request. E.g. to allow for an
# idempotency_key to be passed in the request headers, or for the
# api_key to be overwritten. See {APIOperations::Request.request}.
def update(id, params={}, opts={})
response, opts = request(:post, "#{resource_url}/#{id}", params, opts)
Util.convert_to_stripe_object(response, opts)
end
end
# Creates or updates an API resource.
#
# If the resource doesn't yet have an assigned ID and the resource is one
@ -10,7 +29,9 @@ module Stripe
# ==== Attributes
#
# * +params+ - Overrides any parameters in the resource's serialized data
# and includes them in the create or update.
# and includes them in the create or update. If +:req_url:+ is included
# in the list, it overrides the update URL used for the create or
# update.
# * +opts+ - A Hash of additional options (separate from the params /
# object values) to be added to the request. E.g. to allow for an
# idempotency_key to be passed in the request headers, or for the
@ -36,6 +57,10 @@ module Stripe
self
end
def self.included(base)
base.extend(ClassMethods)
end
private
def save_url

View File

@ -1,12 +1,16 @@
module Stripe
class ApplicationFeeRefund < APIResource
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
extend Stripe::APIOperations::List
def resource_url
"#{ApplicationFee.resource_url}/#{CGI.escape(fee)}/refunds/#{CGI.escape(id)}"
end
def self.update(id, params=nil, opts=nil)
raise NotImplementedError.new("Refunds cannot be updated without an application fee ID. Update a refund by using `a = appfee.refunds.retrieve('refund_id'); a.save`")
end
def self.retrieve(id, api_key=nil)
raise NotImplementedError.new("Refunds cannot be retrieved without an application fee ID. Retrieve a refund using appfee.refunds.retrieve('refund_id')")
end

View File

@ -1,6 +1,6 @@
module Stripe
class BankAccount < APIResource
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
include Stripe::APIOperations::Delete
extend Stripe::APIOperations::List
@ -17,6 +17,10 @@ module Stripe
end
end
def self.update(id, params=nil, opts=nil)
raise NotImplementedError.new("Bank accounts cannot be updated without an account ID. Update a bank account by using `a = account.external_accounts.retrieve('card_id'); a.save`")
end
def self.retrieve(id, opts=nil)
raise NotImplementedError.new("Bank accounts cannot be retrieved without an account ID. Retrieve a bank account using account.external_accounts.retrieve('card_id')")
end

View File

@ -1,7 +1,7 @@
module Stripe
class BitcoinReceiver < APIResource
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
include Stripe::APIOperations::Delete
extend Stripe::APIOperations::List

View File

@ -1,6 +1,6 @@
module Stripe
class Card < APIResource
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
include Stripe::APIOperations::Delete
extend Stripe::APIOperations::List
@ -14,6 +14,10 @@ module Stripe
end
end
def self.update(id, params=nil, opts=nil)
raise NotImplementedError.new("Cards cannot be updated without a customer ID. Update a card using `c = customer.sources.retrieve('card_id'); c.save`")
end
def self.retrieve(id, opts=nil)
raise NotImplementedError.new("Cards cannot be retrieved without a customer ID. Retrieve a card using customer.sources.retrieve('card_id')")
end

View File

@ -2,7 +2,7 @@ module Stripe
class Charge < APIResource
extend Stripe::APIOperations::List
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
def refund(params={}, opts={})
# Old versions of charge objects included a `refunds` field that was just

View File

@ -1,7 +1,7 @@
module Stripe
class Coupon < APIResource
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
include Stripe::APIOperations::Delete
extend Stripe::APIOperations::List
end

View File

@ -2,7 +2,7 @@ module Stripe
class Customer < APIResource
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Delete
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
extend Stripe::APIOperations::List
def add_invoice_item(params, opts={})

View File

@ -2,7 +2,7 @@ module Stripe
class Dispute < APIResource
extend Stripe::APIOperations::List
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
def close(params={}, opts={})
response, opts = request(:post, close_url, params, opts)

View File

@ -1,7 +1,7 @@
module Stripe
class Invoice < APIResource
extend Stripe::APIOperations::List
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
extend Stripe::APIOperations::Create
def self.upcoming(params, opts={})

View File

@ -3,6 +3,6 @@ module Stripe
extend Stripe::APIOperations::List
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Delete
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
end
end

View File

@ -2,7 +2,7 @@ module Stripe
class Order < APIResource
extend Stripe::APIOperations::List
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
def pay(params, opts={})
response, opts = request(:post, pay_url, params, opts)

View File

@ -3,6 +3,6 @@ module Stripe
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Delete
extend Stripe::APIOperations::List
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
end
end

View File

@ -2,7 +2,7 @@ module Stripe
class Product < APIResource
extend Stripe::APIOperations::List
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
include Stripe::APIOperations::Delete
end
end

View File

@ -2,7 +2,7 @@ module Stripe
class Recipient < APIResource
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Delete
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
extend Stripe::APIOperations::List
def transfers

View File

@ -2,6 +2,6 @@ module Stripe
class Refund < APIResource
extend Stripe::APIOperations::Create
extend Stripe::APIOperations::List
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
end
end

View File

@ -1,12 +1,16 @@
module Stripe
class Reversal < APIResource
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
extend Stripe::APIOperations::List
def resource_url
"#{Transfer.resource_url}/#{CGI.escape(transfer)}/reversals/#{CGI.escape(id)}"
end
def self.update(id, params=nil, opts=nil)
raise NotImplementedError.new("Reversals cannot be updated without a transfer ID. Update a reversal using `r = transfer.reversals.retrieve('reversal_id'); r.save`")
end
def self.retrieve(id, opts={})
raise NotImplementedError.new("Reversals cannot be retrieved without a transfer ID. Retrieve a reversal using transfer.reversals.retrieve('reversal_id')")
end

View File

@ -2,8 +2,7 @@ module Stripe
class SKU < APIResource
extend Stripe::APIOperations::List
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
include Stripe::APIOperations::Delete
end
end

View File

@ -1,9 +1,9 @@
module Stripe
class Subscription < APIResource
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Update
include Stripe::APIOperations::Delete
extend Stripe::APIOperations::List
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Save
include Stripe::APIOperations::Delete
def delete_discount
_, opts = request(:delete, discount_url)

View File

@ -2,7 +2,7 @@ module Stripe
class Transfer < APIResource
extend Stripe::APIOperations::List
extend Stripe::APIOperations::Create
include Stripe::APIOperations::Update
include Stripe::APIOperations::Save
def cancel
response, api_key = self.request(:post, cancel_url)
@ -12,6 +12,5 @@ module Stripe
def cancel_url
resource_url + '/cancel'
end
end
end

View File

@ -68,7 +68,7 @@ module Stripe
account.reject(:reason => 'fraud')
end
should "be updatable" do
should "be saveable" do
resp = {
:id => 'acct_foo',
:legal_entity => {
@ -93,6 +93,31 @@ module Stripe
a.save
end
should "be updatable" do
resp = {
:id => 'acct_foo',
:legal_entity => {
:first_name => 'Bob',
:address => {
:line1 => '2 Three Four'
}
}
}
@mock.expects(:post).
once.
with('https://api.stripe.com/v1/accounts/acct_foo', nil, 'legal_entity[address][line1]=2+Three+Four&legal_entity[first_name]=Bob').
returns(make_response(resp))
a = Stripe::Account.update('acct_foo', :legal_entity => {
:first_name => 'Bob',
:address => {
:line1 => '2 Three Four'
}
})
assert_equal('Bob', a.legal_entity.first_name)
assert_equal('2 Three Four', a.legal_entity.address.line1)
end
should 'disallow direct overrides of legal_entity' do
account = Stripe::Account.construct_from(make_account({
:keys => {

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
require File.expand_path('../../test_helper', __FILE__)
module Stripe
class ApiOperationsTest < Test::Unit::TestCase
class Updater < APIResource
include Stripe::APIOperations::Save
end
should "the Update API operation should post the correct parameters to the resource URL" do
@mock.expects(:post).once.
with("#{Stripe.api_base}/v1/updaters/id", nil, 'foo=bar').
returns(make_response({foo: 'bar'}))
resource = Updater::update("id", {foo: "bar"})
assert_equal('bar', resource.foo)
end
end
end

View File

@ -42,6 +42,14 @@ module Stripe
end
should "charges should be updateable" do
@mock.expects(:post).once.
with('https://api.stripe.com/v1/charges/test_charge', nil, 'metadata[foo]=bar').
returns(make_response(make_charge(metadata: {'foo' => 'bar'})))
c = Stripe::Charge.update("test_charge", metadata: {foo: 'bar'})
assert_equal('bar', c.metadata['foo'])
end
should "charges should be saveable" do
@mock.expects(:get).once.returns(make_response(make_charge))
@mock.expects(:post).once.returns(make_response(make_charge))
c = Stripe::Charge.new("test_charge")

View File

@ -8,7 +8,7 @@ module Stripe
assert_equal "co_test_coupon", c.id
end
should "coupons should be updateable" do
should "coupons should be saveable" do
@mock.expects(:get).once.returns(make_response(make_coupon))
@mock.expects(:post).once.returns(make_response(make_coupon))
c = Stripe::Coupon.new("test_coupon")
@ -16,5 +16,13 @@ module Stripe
c.metadata['foo'] = 'bar'
c.save
end
should "coupons should be updateable" do
@mock.expects(:post).once.
with("https://api.stripe.com/v1/coupons/test_coupon", nil, "metadata[foo]=bar").
returns(make_response(make_coupon(metadata: {foo: 'bar'})))
c = Stripe::Coupon.update("test_coupon", metadata: {foo: 'bar'})
assert_equal('bar', c.metadata['foo'])
end
end
end

View File

@ -16,7 +16,7 @@ module Stripe
assert c.deleted
end
should "customers should be updateable" do
should "customers should be saveable" do
@mock.expects(:get).once.returns(make_response(make_customer({:mnemonic => "foo"})))
@mock.expects(:post).once.returns(make_response(make_customer({:mnemonic => "bar"})))
c = Stripe::Customer.new("test_customer").refresh
@ -26,6 +26,14 @@ module Stripe
assert_equal "bar", c.mnemonic
end
should "customers should be updateable" do
@mock.expects(:post).once.
with("https://api.stripe.com/v1/customers/test_customer", nil, "metadata[foo]=bar").
returns(make_response(make_customer(metadata: {foo: 'bar'})))
c = Stripe::Customer.update("test_customer", metadata: {foo: 'bar'})
assert_equal('bar', c.metadata['foo'])
end
should "create should return a new customer" do
@mock.expects(:post).once.returns(make_response(make_customer))
c = Stripe::Customer.create

View File

@ -29,6 +29,14 @@ module Stripe
end
should "disputes should be updateable" do
@mock.expects(:post).once.
with("https://api.stripe.com/v1/disputes/test_dispute", nil, "metadata[foo]=bar").
returns(make_response(make_dispute(metadata: {foo: 'bar'})))
d = Stripe::Dispute.update("test_dispute", metadata: {foo: 'bar'})
assert_equal('bar', d.metadata['foo'])
end
should "disputes should be saveable" do
@mock.expects(:get).once.returns(make_response(make_dispute))
@mock.expects(:post).with(
"#{Stripe.api_base}/v1/disputes/dp_test_dispute",

View File

@ -0,0 +1,19 @@
require File.expand_path('../../test_helper', __FILE__)
module Stripe
class InvoiceItemTest < Test::Unit::TestCase
should "retrieve should retrieve invoice items" do
@mock.expects(:get).once.returns(make_response(make_invoice_item))
ii = Stripe::InvoiceItem.retrieve('in_test_invoice_item')
assert_equal 'ii_test_invoice_item', ii.id
end
should "invoice items should be updateable" do
@mock.expects(:post).once.
with('https://api.stripe.com/v1/invoiceitems/test_invoice_item', nil, 'metadata[foo]=bar').
returns(make_response(make_charge(metadata: {'foo' => 'bar'})))
ii = Stripe::InvoiceItem.update("test_invoice_item", metadata: {foo: 'bar'})
assert_equal('bar', ii.metadata['foo'])
end
end
end

View File

@ -23,6 +23,14 @@ module Stripe
assert_equal nil, i.next_payment_attempt
end
should "invoices should be updateable" do
@mock.expects(:post).once.
with("https://api.stripe.com/v1/invoices/test_invoice", nil, "metadata[foo]=bar").
returns(make_response(make_invoice(metadata: {foo: 'bar'})))
i = Stripe::Invoice.update("test_invoice", metadata: {foo: 'bar'})
assert_equal('bar', i.metadata['foo'])
end
should "pay with extra opts should pay an invoice" do
@mock.expects(:get).once.returns(make_response(make_invoice))
i = Stripe::Invoice.retrieve('in_test_invoice', {:api_key => 'foobar'})

View File

@ -19,7 +19,7 @@ module Stripe
end
end
should "orders should be updateable" do
should "orders should be saveable" do
@mock.expects(:get).once.returns(make_response(make_order))
@mock.expects(:post).once.returns(make_response(make_order))
p = Stripe::Order.new("test_order")
@ -28,6 +28,14 @@ module Stripe
p.save
end
should "orders should be updateable" do
@mock.expects(:post).once.
with('https://api.stripe.com/v1/orders/test_order', nil, 'status=fulfilled').
returns(make_response(make_order(status: 'fulfilled')))
ii = Stripe::Order.update("test_order", status: 'fulfilled')
assert_equal('fulfilled', ii.status)
end
should "orders should allow metadata updates" do
@mock.expects(:get).once.returns(make_response(make_order))
@mock.expects(:post).once.returns(make_response(make_order))

31
test/stripe/plan_test.rb Normal file
View File

@ -0,0 +1,31 @@
require File.expand_path('../../test_helper', __FILE__)
module Stripe
class PlanTest < Test::Unit::TestCase
should "plans should be listable" do
@mock.expects(:get).once.returns(make_response(make_plan_array))
plans = Stripe::Plan.list
assert plans.data.kind_of?(Array)
plans.each do |plan|
assert plan.kind_of?(Stripe::Plan)
end
end
should "plans should be saveable" do
@mock.expects(:get).once.returns(make_response(make_plan))
@mock.expects(:post).once.returns(make_response(make_plan))
p = Stripe::Plan.new("test_plan")
p.refresh
p.metadata['foo'] = 'bar'
p.save
end
should "plans should be updateable" do
@mock.expects(:post).once.
with('https://api.stripe.com/v1/plans/test_plan', nil, 'metadata[foo]=bar').
returns(make_response(make_plan(metadata: {foo: 'bar'})))
p = Stripe::Plan.update("test_plan", metadata: {foo: 'bar'})
assert_equal('bar', p.metadata['foo'])
end
end
end

View File

@ -21,7 +21,7 @@ module Stripe
assert p.deleted
end
should "products should be updateable" do
should "products should be saveable" do
@mock.expects(:get).once.returns(make_response(make_product))
@mock.expects(:post).once.returns(make_response(make_product))
p = Stripe::Product.new("test_product")
@ -30,6 +30,14 @@ module Stripe
p.save
end
should "products should be updateable" do
@mock.expects(:post).once.
with('https://api.stripe.com/v1/products/test_product', nil, 'description=update').
returns(make_response(make_product(description: 'update')))
p = Stripe::Product.update("test_product", description: 'update')
assert_equal('update', p.description)
end
should "products should allow metadata updates" do
@mock.expects(:get).once.returns(make_response(make_product))
@mock.expects(:post).once.returns(make_response(make_product))

View File

@ -0,0 +1,21 @@
require File.expand_path('../../test_helper', __FILE__)
module Stripe
class RecipientTest < Test::Unit::TestCase
should "recipient should be retrievable" do
@mock.expects(:get).once.returns(make_response(make_recipient))
r = Stripe::Recipient.retrieve('test_recipient')
assert_equal 'rp_test_recipient', r.id
end
should "recipient should be updateable" do
@mock.expects(:post).once.
with("https://api.stripe.com/v1/recipients/test_recipient", nil, "metadata[foo]=bar").
returns(make_response(make_recipient(metadata: {foo: 'bar'})))
r = Stripe::Recipient.update('test_recipient', metadata: {foo: 'bar'})
assert_equal 'bar', r.metadata['foo']
end
end
end

View File

@ -24,7 +24,7 @@ module Stripe
assert_equal 'refreshed_refund', refund.id
end
should "refunds should be updateable" do
should "refunds should be saveable" do
@mock.expects(:get).
with("#{Stripe.api_base}/v1/refunds/get_refund", nil, nil).
once.returns(make_response(make_refund(:id => 'save_refund')))
@ -43,6 +43,15 @@ module Stripe
assert_equal 'value', refund.metadata['key']
end
should "refunds should be updateable" do
@mock.expects(:post).
with("#{Stripe.api_base}/v1/refunds/update_refund", nil, 'metadata[key]=value').
once.returns(make_response(make_refund(:metadata => {'key' => 'value'})))
refund = Stripe::Refund.update('update_refund', metadata: {key: 'value'})
assert_equal 'value', refund.metadata['key']
end
should "create should return a new refund" do
@mock.expects(:post).
with("#{Stripe.api_base}/v1/refunds", nil, 'charge=test_charge').

View File

@ -12,6 +12,14 @@ module Stripe
end
end
should "SKUs should be updateable" do
@mock.expects(:post).once.
with("#{Stripe.api_base}/v1/skus/test_sku", nil, 'metadata[foo]=bar').
returns(make_response(make_sku(:metadata => {foo: 'bar'})))
s = Stripe::SKU.update("test_sku", metadata: {foo: 'bar'})
assert_equal 'bar', s.metadata['foo']
end
should "SKUs should be deletable" do
@mock.expects(:get).once.returns(make_response(make_sku))
@mock.expects(:delete).once.returns(make_response(make_sku(:deleted => true)))

View File

@ -77,6 +77,17 @@ module Stripe
end
should "subscriptions should be updateable" do
sid = 's_test_subscription'
@mock.expects(:post).once.with do |url, api_key, params|
url == "#{Stripe.api_base}/v1/subscriptions/#{sid}" && api_key.nil? && CGI.parse(params) == {'status' => ['active']}
end.returns(make_response(make_subscription(:status => 'active')))
sub = Stripe::Subscription.update(sid, :status => 'active')
assert_equal 'active', sub.status
end
should "subscriptions should be saveable" do
@mock.expects(:get).once.returns(make_response(make_subscription))
sub = Stripe::Subscription.retrieve('s_test_subscription')
assert_equal 'trialing', sub.status

View File

@ -14,6 +14,14 @@ module Stripe
assert_equal "tr_test_transfer", transfer.id
end
should "create should update a transfer" do
@mock.expects(:post).once.
with("#{Stripe.api_base}/v1/transfers/test_transfer", nil, "metadata[foo]=bar").
returns(make_response(make_transfer(metadata: {foo: 'bar'})))
transfer = Stripe::Transfer.update("test_transfer", metadata: {foo: 'bar'})
assert_equal "bar", transfer.metadata['foo']
end
should "cancel should cancel a transfer" do
@mock.expects(:get).once.returns(make_response(make_transfer))
transfer = Stripe::Transfer.retrieve('tr_test_transfer')

View File

@ -323,7 +323,7 @@ module Stripe
}
end
def make_invoice
def make_invoice(params={})
{
:id => 'in_test_invoice',
:object => 'invoice',
@ -361,7 +361,7 @@ module Stripe
:discount => nil,
:ending_balance => nil,
:next_payment_attempt => 1349825350,
}
}.merge(params)
end
def make_paid_invoice
@ -376,6 +376,17 @@ module Stripe
})
end
def make_invoice_item(params={})
{
id: "ii_test_invoice_item",
object: "invoiceitem",
date: 1466982411,
invoice: "in_test_invoice",
livemode: false,
metadata: {},
}.merge(params)
end
def make_invoice_customer_array
{
:data => [make_invoice],
@ -793,5 +804,34 @@ module Stripe
}
}.merge(params)
end
def make_plan(params={})
{
id: "silver",
object: "plan",
amount: 1000,
created: 1463962497,
currency: "usd",
interval: "year",
interval_count: 1,
livemode: false,
metadata: {},
name: "Silver",
statement_descriptor: nil,
trial_period_days: nil,
}.merge(params)
end
def make_plan_array
{
:object => "list",
:resource_url => "/v1/plans",
:data => [
make_plan,
make_plan,
make_plan,
]
}
end
end
end