Skip to content

Commit

Permalink
Add support for Subscription Schedules (#480)
Browse files Browse the repository at this point in the history
  • Loading branch information
maartenvanvliet authored and snewcomer committed May 24, 2019
1 parent 28997ea commit 43e3a39
Show file tree
Hide file tree
Showing 4 changed files with 358 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/stripe/converter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ defmodule Stripe.Converter do
source
subscription
subscription_item
subscription_schedule
transfer
transfer_reversal
token
Expand Down
2 changes: 2 additions & 0 deletions lib/stripe/subscriptions/subscription.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ defmodule Stripe.Subscription do
metadata: Stripe.Types.metadata(),
plan: Stripe.Plan.t() | nil,
quantity: integer | nil,
schedule: String.t() | nil,
start: Stripe.timestamp(),
status: String.t(),
tax_percent: float | nil,
Expand Down Expand Up @@ -62,6 +63,7 @@ defmodule Stripe.Subscription do
:metadata,
:plan,
:quantity,
:schedule,
:start,
:status,
:tax_percent,
Expand Down
229 changes: 229 additions & 0 deletions lib/stripe/subscriptions/subscription_schedule.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
defmodule Stripe.SubscriptionSchedule do
@moduledoc """
Work with Stripe subscription schedule objects.
"""

use Stripe.Entity
import Stripe.Request

@type plans :: %{
plan: String.t(),
quantity: pos_integer
}

@type phases :: %{
application_fee_percent: float | nil,
end_date: Stripe.timestamp(),
start_date: Stripe.timestamp(),
tax_percent: float | nil,
trial_end: Stripe.timestamp(),
plans: list(plans)
}

@type t :: %__MODULE__{
id: Stripe.id(),
object: String.t(),
created: Stripe.timestamp(),
canceled_at: Stripe.timestamp() | nil,
released_at: Stripe.timestamp() | nil,
completed_at: Stripe.timestamp() | nil,
livemode: boolean,
invoice_settings: %{
days_until_due: integer
},
current_phase: %{
start_date: Stripe.timestamp(),
end_date: Stripe.timestamp()
},
renewal_behavior: String.t(),
renewal_interval: String.t(),
revision: String.t(),
status: String.t(),
subscription: Stripe.id() | Stripe.Subscription.t(),
customer: Stripe.id() | Stripe.Customer.t(),
released_subscription: Stripe.id() | Stripe.Subscription.t() | nil,
phases: list(phases)
}

defstruct [
:id,
:object,
:billing,
:created,
:canceled_at,
:completed_at,
:current_phase,
:customer,
:phases,
:released_at,
:released_subscription,
:status,
:subscription,
:invoice_settings,
:livemode,
:renewal_behavior,
:renewal_interval,
:revision
]

@plural_endpoint "subscription_schedules"

@doc """
Create a subscription schedule.
### Examples
# Create subscription schedule from existing subscription
Stripe.SubscriptionSchedule.create(%{from_subscription: "sub_1234"})
"""
@spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
when params: %{
optional(:customer) => Stripe.id(),
optional(:billing) => String.t(),
optional(:from_subscription) => Stripe.id(),
optional(:invoice_settings) => %{
optional(:days_until_due) => non_neg_integer
},
optional(:phases) => [
%{
:plans => [
%{
:plan => Stripe.id() | Stripe.Plan.t(),
optional(:quantity) => non_neg_integer
}
],
optional(:application_fee_percent) => non_neg_integer,
optional(:coupon) => String.t(),
optional(:end_date) => Stripe.timestamp(),
optional(:iterations) => non_neg_integer,
optional(:start_date) => Stripe.timestamp(),
optional(:tax_percent) => float,
optional(:trial) => boolean(),
optional(:trial_end) => Stripe.timestamp()
}
],
optional(:renewal_behavior) => String.t(),
optional(:renewal_interval) => %{
:renewal_interval => String.t(),
:length => non_neg_integer
},
optional(:start_date) => Stripe.timestamp()
}
def create(params, opts \\ []) do
new_request(opts)
|> put_endpoint(@plural_endpoint)
|> put_params(params)
|> put_method(:post)
|> make_request()
end

@doc """
Retrieve a subscription schedule.
"""
@spec retrieve(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
def retrieve(id, opts \\ []) do
new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}")
|> put_method(:get)
|> make_request()
end

@doc """
Update a subscription schedule.
Takes the `id` and a map of changes.
"""
@spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
when params: %{
optional(:billing) => String.t(),
optional(:invoice_settings) => %{
optional(:days_until_due) => non_neg_integer
},
optional(:phases) => [
%{
:plans => [
%{
:plan => Stripe.id() | Stripe.Plan.t(),
optional(:quantity) => non_neg_integer
}
],
optional(:application_fee_percent) => non_neg_integer,
optional(:coupon) => String.t(),
optional(:end_date) => Stripe.timestamp(),
optional(:iterations) => non_neg_integer,
optional(:start_date) => Stripe.timestamp(),
optional(:tax_percent) => float,
optional(:trial) => boolean(),
optional(:trial_end) => Stripe.timestamp()
}
],
optional(:renewal_behavior) => String.t(),
optional(:prorate) => boolean(),
optional(:renewal_interval) => %{
:renewal_interval => String.t(),
:length => non_neg_integer
}
}
def update(id, params, opts \\ []) do
new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}")
|> put_method(:post)
|> put_params(params)
|> make_request()
end

@doc """
Cancels a subscription schedule and its associated subscription immediately
(if the subscription schedule has an active subscription). A subscription
schedule can only be canceled if its status is not_started or active.
Takes the subscription schedule `id`.
"""

@spec cancel(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
def cancel(id, opts \\ []) when is_list(opts) do
new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/cancel")
|> put_method(:post)
|> make_request()
end

@doc """
Releases a subscription schedule
Takes the subscription schedule `id.
"""

@spec release(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
def release(id, opts \\ []) when is_list(opts) do
new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/release")
|> put_method(:post)
|> make_request()
end

@doc """
Retrieves the list of your subscription schedules.
"""
@spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()}
when params: %{
optional(:canceled_at) => Stripe.date_query(),
optional(:completed_at) => Stripe.date_query(),
optional(:created) => Stripe.date_query(),
optional(:customer) => Stripe.Customer.t() | Stripe.id(),
optional(:ending_before) => t | Stripe.id(),
optional(:limit) => 1..100,
optional(:released_at) => Stripe.date_query(),
optional(:scheduled) => boolean(),
optional(:starting_after) => t | Stripe.id()
}
def list(params \\ %{}, opts \\ []) do
new_request(opts)
|> prefix_expansions()
|> put_endpoint(@plural_endpoint)
|> put_method(:get)
|> put_params(params)
|> make_request()
end
end
126 changes: 126 additions & 0 deletions test/stripe/subscriptions/subscription_schedule_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
defmodule Stripe.SubscriptionScheduleTest do
use Stripe.StripeCase, async: true

@invalid_params %{
customer: "cus_123",
renewal_behavior: "release",
phases: [
%{
coupon: nil,
default_tax_rates: [],
end_date: 1_557_566_037,
start_date: 1_554_974_037,
tax_percent: 0
}
]
}
describe "retrieve/2" do
test "retrieves a subscription" do
assert {:ok, %Stripe.SubscriptionSchedule{}} =
Stripe.SubscriptionSchedule.retrieve("sub_sched_123")

assert_stripe_requested(:get, "/v1/subscription_schedules/sub_sched_123")
end
end

describe "create/2" do
test "creates a subscription schedule" do
params = %{
customer: "cus_123",
renewal_behavior: "release",
phases: [
%{
coupon: nil,
default_tax_rates: [],
end_date: 1_557_566_037,
plans: [
%{
billing_thresholds: nil,
plan: "some plan",
quantity: 2,
tax_rates: []
}
],
start_date: 1_554_974_037,
tax_percent: 0
}
]
}

assert {:ok, %Stripe.SubscriptionSchedule{}} = Stripe.SubscriptionSchedule.create(params)

assert_stripe_requested(:post, "/v1/subscription_schedules")
end

test "fails with missing plans in phases" do
assert {:error, %Stripe.Error{} = error} =
Stripe.SubscriptionSchedule.create(@invalid_params)

assert_stripe_requested(:post, "/v1/subscription_schedules")
end
end

describe "update/2" do
test "updates a subscription" do
params = %{
renewal_behavior: "release",
phases: [
%{
coupon: nil,
default_tax_rates: [],
end_date: 1_557_566_037,
plans: [
%{
billing_thresholds: nil,
plan: "some plan",
quantity: 2,
tax_rates: []
}
],
start_date: 1_554_974_037,
tax_percent: 0
}
]
}

assert {:ok, subscription} = Stripe.SubscriptionSchedule.update("sub_sched_123", params)
assert_stripe_requested(:post, "/v1/subscription_schedules/#{subscription.id}")
end

test "fails with missing plans in phases" do
assert {:error, %Stripe.Error{}} =
Stripe.SubscriptionSchedule.update("sub_sched_123", @invalid_params)

assert_stripe_requested(:post, "/v1/subscription_schedules/sub_sched_123")
end
end

describe "list/2" do
test "lists all subscriptions" do
assert {:ok, %Stripe.List{data: subscriptions}} = Stripe.SubscriptionSchedule.list()
assert_stripe_requested(:get, "/v1/subscription_schedules")
assert is_list(subscriptions)
assert %Stripe.SubscriptionSchedule{} = hd(subscriptions)
end
end

describe "cancel/2" do
test "cancels a subscription schedule" do
{:ok, subscription} = Stripe.SubscriptionSchedule.retrieve("sub_sched_123")
assert_stripe_requested(:get, "/v1/subscription_schedules/#{subscription.id}")

assert {:ok, _} = Stripe.SubscriptionSchedule.cancel("sub_sched_123")
assert_stripe_requested(:post, "/v1/subscription_schedules/#{subscription.id}/cancel")
end
end

describe "release/2" do
test "releases a subscription schedule" do
{:ok, subscription} = Stripe.SubscriptionSchedule.retrieve("sub_sched_123")
assert_stripe_requested(:get, "/v1/subscription_schedules/#{subscription.id}")

assert {:ok, _} = Stripe.SubscriptionSchedule.release("sub_sched_123")
assert_stripe_requested(:post, "/v1/subscription_schedules/#{subscription.id}/release")
end
end
end

0 comments on commit 43e3a39

Please sign in to comment.