Skip to content

Commit

Permalink
New custom_method DSL for defining custom API request methods as stat…
Browse files Browse the repository at this point in the history
…ic methods
  • Loading branch information
ob-stripe committed Mar 29, 2019
1 parent bb25d09 commit 9883454
Show file tree
Hide file tree
Showing 27 changed files with 205 additions and 15 deletions.
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ inherit_from: .rubocop_todo.yml

AllCops:
DisplayCopNames: true
TargetRubyVersion: 2.0
TargetRubyVersion: 2.1

Layout/CaseIndentation:
EnforcedStyle: end
Expand Down
1 change: 1 addition & 0 deletions lib/stripe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
require "stripe/country_spec"
require "stripe/coupon"
require "stripe/customer"
require "stripe/discount"
require "stripe/dispute"
require "stripe/ephemeral_key"
require "stripe/event"
Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class Account < APIResource

OBJECT_NAME = "account".freeze

custom_method :reject, http_verb: :post

save_nested_resource :external_account
nested_resource_class_methods :external_account,
operations: %i[create retrieve update delete list]
Expand Down
24 changes: 24 additions & 0 deletions lib/stripe/api_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,30 @@ def self.save_nested_resource(name)
end
end

# Adds a custom method to a resource class. This is used to add support for
# non-CRUDL API requests, e.g. capturing charges. custom_method takes the
# following parameters:
# - name: the name of the custom method to create (as a symbol)
# - http_verb: the HTTP verb for the API request (:get, :post, or :delete)
# - http_path: the path to append to the resource's URL. If not provided,
# the name is used as the path
#
# For example, this call:
# custom_method :capture, http_verb: post
# adds a `capture` class method to the resource class that, when called,
# will send a POST request to `/v1/<object_name>/capture`.
def self.custom_method(name, http_verb:, http_path: nil)
unless %i[get post delete].include?(http_verb)
raise ArgumentError, "Invalid http_verb value: #{http_verb.inspect}. Should be one of :get, :post or :delete."
end
http_path ||= name.to_s
define_singleton_method(name) do |id, params = {}, opts = {}|
url = "#{resource_url}/#{CGI.escape(id)}/#{CGI.escape(http_path)}"
resp, opts = request(http_verb, url, params, opts)
Util.convert_to_stripe_object(resp.data, opts)
end
end

def resource_url
unless (id = self["id"])
raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", "id")
Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/charge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class Charge < APIResource

OBJECT_NAME = "charge".freeze

custom_method :capture, http_verb: :post

def refund(params = {}, opts = {})
# Old versions of charge objects included a `refunds` field that was just
# a vanilla array instead of a Stripe list object.
Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/customer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class Customer < APIResource

OBJECT_NAME = "customer".freeze

custom_method :delete_discount, http_verb: :delete, http_path: "discount"

save_nested_resource :source
nested_resource_class_methods :source,
operations: %i[create retrieve update delete list]
Expand Down
7 changes: 7 additions & 0 deletions lib/stripe/discount.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module Stripe
class Discount < StripeObject
OBJECT_NAME = "discount".freeze
end
end
2 changes: 2 additions & 0 deletions lib/stripe/dispute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class Dispute < APIResource

OBJECT_NAME = "dispute".freeze

custom_method :close, http_verb: :post

def close(params = {}, opts = {})
resp, opts = request(:post, close_url, params, opts)
initialize_from(resp.data, opts)
Expand Down
6 changes: 6 additions & 0 deletions lib/stripe/invoice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ class Invoice < APIResource

OBJECT_NAME = "invoice".freeze

custom_method :finalize_invoice, http_verb: :post, http_path: "finalize"
custom_method :mark_uncollectible, http_verb: :post
custom_method :pay, http_verb: :post
custom_method :send_invoice, http_verb: :post, http_path: "send"
custom_method :void_invoice, http_verb: :post, http_path: "void"

def finalize_invoice(params = {}, opts = {})
url = resource_url + "/finalize"
resp, opts = request(:post, url, params, opts)
Expand Down
3 changes: 3 additions & 0 deletions lib/stripe/order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ class Order < APIResource

OBJECT_NAME = "order".freeze

custom_method :pay, http_verb: :post
custom_method :return_order, http_verb: :post

def pay(params, opts = {})
resp, opts = request(:post, pay_url, params, opts)
initialize_from(resp.data, opts)
Expand Down
4 changes: 4 additions & 0 deletions lib/stripe/payment_intent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ class PaymentIntent < APIResource

OBJECT_NAME = "payment_intent".freeze

custom_method :cancel, http_verb: :post
custom_method :capture, http_verb: :post
custom_method :confirm, http_verb: :post

def cancel(params = {}, opts = {})
resp, opts = request(:post, resource_url + "/cancel", params, opts)
initialize_from(resp.data, opts)
Expand Down
3 changes: 3 additions & 0 deletions lib/stripe/payment_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ class PaymentMethod < APIResource

OBJECT_NAME = "payment_method".freeze

custom_method :attach, http_verb: :post
custom_method :detach, http_verb: :post

def attach(params = {}, opts = {})
url = resource_url + "/attach"
resp, opts = request(:post, url, params, opts)
Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/payout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class Payout < APIResource

OBJECT_NAME = "payout".freeze

custom_method :cancel, http_verb: :post

def cancel
resp, api_key = request(:post, cancel_url)
initialize_from(resp.data, api_key)
Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/review.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class Review < APIResource

OBJECT_NAME = "review".freeze

custom_method :approve, http_verb: :post

def approve(params = {}, opts = {})
resp, opts = request(:post, resource_url + "/approve", params, opts)
initialize_from(resp.data, opts)
Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class Source < APIResource

OBJECT_NAME = "source".freeze

custom_method :verify, http_verb: :post

def detach(params = {}, opts = {})
if !respond_to?(:customer) || customer.nil? || customer.empty?
raise NotImplementedError,
Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/subscription.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class Subscription < APIResource

OBJECT_NAME = "subscription".freeze

custom_method :delete_discount, http_verb: :delete, http_path: "discount"

save_nested_resource :source

def delete_discount
Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/subscription_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class SubscriptionItem < APIResource

OBJECT_NAME = "subscription_item".freeze

custom_method :usage_record_summaries, http_verb: :get

def usage_record_summaries(params = {}, opts = {})
resp, opts = request(:get, resource_url + "/usage_record_summaries", params, Util.normalize_opts(opts))
Util.convert_to_stripe_object(resp.data, opts)
Expand Down
4 changes: 4 additions & 0 deletions lib/stripe/subscription_schedule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class SubscriptionSchedule < APIResource

OBJECT_NAME = "subscription_schedule".freeze

custom_method :cancel, http_verb: :post
custom_method :release, http_verb: :post
custom_method :revisions, http_verb: :get

nested_resource_class_methods :revision,
operations: %i[retrieve list]

Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/topup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class Topup < APIResource

OBJECT_NAME = "topup".freeze

custom_method :cancel, http_verb: :post

def cancel
resp, api_key = request(:post, resource_url + "/cancel")
initialize_from(resp.data, api_key)
Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/transfer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class Transfer < APIResource

OBJECT_NAME = "transfer".freeze

custom_method :cancel, http_verb: :post

nested_resource_class_methods :reversal, operations: %i[create retrieve update list]

def cancel
Expand Down
1 change: 1 addition & 0 deletions lib/stripe/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def self.object_classes # rubocop:disable Metrics/MethodLength
CountrySpec::OBJECT_NAME => CountrySpec,
Coupon::OBJECT_NAME => Coupon,
Customer::OBJECT_NAME => Customer,
Discount::OBJECT_NAME => Discount,
Dispute::OBJECT_NAME => Dispute,
EphemeralKey::OBJECT_NAME => EphemeralKey,
Event::OBJECT_NAME => Event,
Expand Down
19 changes: 11 additions & 8 deletions test/stripe/account_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@ class AccountTest < Test::Unit::TestCase
end

should "be rejectable" do
account_data = { id: "acct_foo" }
stub_request(:get, "#{Stripe.api_base}/v1/accounts/acct_foo")
.to_return(body: JSON.generate(account_data))

stub_request(:post, "#{Stripe.api_base}/v1/accounts/acct_foo/reject")
.to_return(body: JSON.generate(account_data))

account = Stripe::Account.retrieve("acct_foo")
account.reject(reason: "fraud")
account = account.reject(reason: "fraud")
assert_requested :post, "#{Stripe.api_base}/v1/accounts/#{account.id}/reject"
assert account.is_a?(Stripe::Account)
end

context ".reject" do
should "reject the account" do
account = Stripe::Account.reject("acct_foo", reason: "fraud")
assert_requested :post, "#{Stripe.api_base}/v1/accounts/#{account.id}/reject"
assert account.is_a?(Stripe::Account)
end
end

should "be creatable" do
Expand Down
10 changes: 10 additions & 0 deletions test/stripe/charge_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ class ChargeTest < Test::Unit::TestCase
assert charge.is_a?(Stripe::Charge)
end

context ".capture" do
should "capture the charge" do
charge = Stripe::Charge.capture("ch_123", amount: 100)
assert_requested :post,
"#{Stripe.api_base}/v1/charges/ch_123/capture",
body: { amount: 100 }
assert charge.is_a?(Stripe::Charge)
end
end

context "#mark_as_fraudulent" do
should "charges should be able to be marked as fraudulent" do
charge = Stripe::Charge.retrieve("ch_123")
Expand Down
16 changes: 12 additions & 4 deletions test/stripe/customer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class CustomerTest < Test::Unit::TestCase
should "create a new subscription" do
customer = Stripe::Customer.retrieve("cus_123")
subscription = customer.create_subscription(items: [{ plan: "silver" }])
assert_requested :post, "#{Stripe.api_base}/v1/customers/#{customer.id}/subscriptions"
assert subscription.is_a?(Stripe::Subscription)
end
end
Expand All @@ -55,6 +56,7 @@ class CustomerTest < Test::Unit::TestCase
should "create a new invoice" do
customer = Stripe::Customer.retrieve("cus_123")
invoice = customer.create_upcoming_invoice
assert_requested :post, "#{Stripe.api_base}/v1/invoices"
assert invoice.is_a?(Stripe::Invoice)
end
end
Expand Down Expand Up @@ -88,11 +90,17 @@ class CustomerTest < Test::Unit::TestCase
context "#delete_discount" do
should "delete a discount" do
customer = Stripe::Customer.retrieve("cus_123")
customer = customer.delete_discount
assert_requested :delete, "#{Stripe.api_base}/v1/customers/#{customer.id}/discount"
assert customer.is_a?(Stripe::Customer)
end
end

stub_request(:delete, "#{Stripe.api_base}/v1/customers/#{customer.id}/discount")
.to_return(body: JSON.generate(object: "customer"))
discount = customer.delete_discount
assert discount.is_a?(Stripe::Customer)
context ".delete_discount" do
should "delete a discount" do
discount = Stripe::Customer.delete_discount("cus_123")
assert_requested :delete, "#{Stripe.api_base}/v1/customers/cus_123/discount"
assert discount.is_a?(Stripe::Discount)
end
end

Expand Down
46 changes: 44 additions & 2 deletions test/stripe/invoice_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ class InvoiceTest < Test::Unit::TestCase
end
end

context ".finalize" do
should "finalize invoice" do
invoice = Stripe::Invoice.finalize_invoice("in_123")
assert_requested :post, "#{Stripe.api_base}/v1/invoices/in_123/finalize"
assert invoice.is_a?(Stripe::Invoice)
end
end

context "#mark_uncollectible" do
should "mark invoice as uncollectible" do
invoice = Stripe::Invoice.retrieve("in_123")
Expand All @@ -65,6 +73,14 @@ class InvoiceTest < Test::Unit::TestCase
end
end

context ".mark_uncollectible" do
should "mark invoice as uncollectible" do
invoice = Stripe::Invoice.mark_uncollectible("in_123")
assert_requested :post, "#{Stripe.api_base}/v1/invoices/in_123/mark_uncollectible"
assert invoice.is_a?(Stripe::Invoice)
end
end

context "#pay" do
should "pay invoice" do
invoice = Stripe::Invoice.retrieve("in_123")
Expand All @@ -88,7 +104,17 @@ class InvoiceTest < Test::Unit::TestCase
end
end

context "#send" do
context ".pay" do
should "pay invoice" do
invoice = Stripe::Invoice.pay("in_123", source: "src_123")
assert_requested :post,
"#{Stripe.api_base}/v1/invoices/in_123/pay",
body: { source: "src_123" }
assert invoice.is_a?(Stripe::Invoice)
end
end

context "#send_invoice" do
should "send invoice" do
invoice = Stripe::Invoice.retrieve("in_123")
invoice = invoice.send_invoice
Expand All @@ -98,6 +124,14 @@ class InvoiceTest < Test::Unit::TestCase
end
end

context ".send_invoice" do
should "send invoice" do
invoice = Stripe::Invoice.send_invoice("in_123")
assert_requested :post, "#{Stripe.api_base}/v1/invoices/in_123/send"
assert invoice.is_a?(Stripe::Invoice)
end
end

context "#upcoming" do
should "retrieve upcoming invoices" do
invoice = Stripe::Invoice.upcoming(
Expand Down Expand Up @@ -148,7 +182,7 @@ class InvoiceTest < Test::Unit::TestCase
end
end

context "#void" do
context "#void_invoice" do
should "void invoice" do
invoice = Stripe::Invoice.retrieve("in_123")
invoice = invoice.void_invoice
Expand All @@ -157,5 +191,13 @@ class InvoiceTest < Test::Unit::TestCase
assert invoice.is_a?(Stripe::Invoice)
end
end

context ".void_invoice" do
should "void invoice" do
invoice = Stripe::Invoice.void_invoice("in_123")
assert_requested :post, "#{Stripe.api_base}/v1/invoices/in_123/void"
assert invoice.is_a?(Stripe::Invoice)
end
end
end
end
Loading

0 comments on commit 9883454

Please sign in to comment.