Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Subscription Schedules #480

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/stripe/converter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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: %{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maartenvanvliet Last question before we merge - how did you know what params go with the create action?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Nice work! Just saving a mental note to look at again once out of beta. 👍

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