Skip to content

Commit

Permalink
Add support for object expansion (#393)
Browse files Browse the repository at this point in the history
Add support for object expansion
  • Loading branch information
swelham authored and begedin committed Jun 29, 2018
1 parent 4e8d2d4 commit eb1d0f1
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 11 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,43 @@ Create a connect standalone account. Grab your development `client_id`. Put it i
</p>
</details>

# Object Expansion

Some Stripe API endpoints support returning related objects via the object expansion query parameter. To take advantage of this feature, stripity_stripe accepts
a list of strings to be passed into `opts` under the `:expand` key indicating which objects should be expanded.

For example, calling `Charge.retrieve("ch_123")` would return a charge without expanding any objects.

```elixir
%Charge{
id: "ch_123",
balance_transaction: "txn_123",
...
}
```

However if we now include an expansion on the `balance_transaction` field using

```elixir
Charge.retrieve("ch_123", expand: ["balance_transaction"])
```

We will get the full object back as well.

```elixir
%Charge{
id: "ch_123",
balance_transaction: %BalanceTransaction{
id: "txn_123",
fee: 125,
...
},
...
}
```

For details on which objects can be expanded check out the [stripe object expansion](https://stripe.com/docs/api#expanding_objects) docs.

# Contributing

Feedback, feature requests, and fixes are welcomed and encouraged. Please make appropriate use of [Issues](https://github.com/code-corps/stripity-stripe/issues) and [Pull Requests](https://github.com/code-corps/stripity-stripe/pulls). All code should have accompanying tests.
Expand Down
15 changes: 14 additions & 1 deletion lib/stripe/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,10 @@ defmodule Stripe.API do
@spec request(body, method, String.t(), headers, list) ::
{:ok, map} | {:error, Stripe.Error.t()}
def request(body, method, endpoint, headers, opts) do
{expansion, opts} = Keyword.pop(opts, :expand)
base_url = get_base_url()
req_url = base_url <> endpoint

req_url = add_object_expansion("#{base_url}#{endpoint}", expansion)

req_body =
body
Expand Down Expand Up @@ -273,4 +275,15 @@ defmodule Stripe.API do
_ -> body
end
end

defp add_object_expansion(url, expansion) when is_list(expansion) do
expand_str =
expansion
|> Enum.map(&"expand[]=#{&1}")
|> Enum.join("&")

"#{url}?#{expand_str}"
end

defp add_object_expansion(url, _), do: url
end
1 change: 1 addition & 0 deletions lib/stripe/core_resources/balance.ex
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ defmodule Stripe.Balance do
}
def list(params \\ %{}, opts \\ []) do
new_request(opts)
|> prefix_expansions("data")
|> put_endpoint(@endpoint <> "/history")
|> put_method(:get)
|> put_params(params)
Expand Down
1 change: 1 addition & 0 deletions lib/stripe/core_resources/charge.ex
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ defmodule Stripe.Charge do
}
def list(params \\ %{}, opts \\ []) do
new_request(opts)
|> prefix_expansions("data")
|> put_endpoint(@plural_endpoint)
|> put_method(:get)
|> put_params(params)
Expand Down
1 change: 1 addition & 0 deletions lib/stripe/core_resources/customer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ defmodule Stripe.Customer do
} | %{}
def list(params \\ %{}, opts \\ []) do
new_request(opts)
|> prefix_expansions("data")
|> put_endpoint(@plural_endpoint)
|> put_method(:get)
|> put_params(params)
Expand Down
1 change: 1 addition & 0 deletions lib/stripe/core_resources/dispute.ex
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ defmodule Stripe.Dispute do
} | %{}
def list(params \\ %{}, opts \\ []) do
new_request(opts)
|> prefix_expansions("data")
|> put_endpoint(@plural_endpoint)
|> put_method(:get)
|> put_params(params)
Expand Down
1 change: 1 addition & 0 deletions lib/stripe/core_resources/payout.ex
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ defmodule Stripe.Payout do
} | %{}
def list(params \\ %{}, opts \\ []) do
new_request(opts)
|> prefix_expansions("data")
|> put_endpoint(@plural_endpoint)
|> put_method(:get)
|> put_params(params)
Expand Down
1 change: 1 addition & 0 deletions lib/stripe/core_resources/refund.ex
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ defmodule Stripe.Refund do
} | %{}
def list(params \\ %{}, opts \\ []) do
new_request(opts)
|> prefix_expansions("data")
|> put_endpoint(@plural_endpoint)
|> put_method(:get)
|> put_params(params)
Expand Down
57 changes: 47 additions & 10 deletions lib/stripe/request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ defmodule Stripe.Request do
:endpoint_fun_invalid_result
| :invalid_endpoint

defstruct opts: [], endpoint: nil, headers: nil, method: nil, params: %{}, cast_to_id: MapSet.new()
defstruct opts: [],
endpoint: nil,
headers: nil,
method: nil,
params: %{},
cast_to_id: MapSet.new()

@doc """
Creates a new request.
Expand Down Expand Up @@ -65,7 +70,8 @@ defmodule Stripe.Request do
`:put`, `:patch` or `:delete`.
"""
@spec put_method(t, Stripe.API.method()) :: t
def put_method(%Request{} = request, method) when method in [:get, :post, :put, :patch, :delete] do
def put_method(%Request{} = request, method)
when method in [:get, :post, :put, :patch, :delete] do
%{request | method: method}
end

Expand Down Expand Up @@ -144,12 +150,43 @@ defmodule Stripe.Request do

def get_id!(_), do: raise("You must provide an ID or a struct with an ID to this operation.")

@doc ~S"""
Prefixes all `:expand` values provided in `opts` with the given prefix.
When using object expansion on a `list` function for a resource, the values must
be prefixed with `data.`. This is required because the stripe api nests the
returned objects within `data: {}`.
For all `create`, `update`, `cancel` and `retrieve` functions this is not required.
```
opts = [expand: ["balance_transaction"]]
request = prefix_expansions(%Request{opts: opts}, "data")
request.opts == ["data.balance_transaction"]
```
"""
@spec prefix_expansions(t, binary) :: t
def prefix_expansions(%Request{opts: opts} = request, prefix) do
case Keyword.get(opts, :expand) do
nil ->
request

expansions ->
mapped_expansions = Enum.map(expansions, &"#{prefix}.#{&1}")
opts = Keyword.replace!(opts, :expand, mapped_expansions)

%{request | opts: opts}
end
end

@doc """
Executes the request and returns the response.
"""
@spec make_request(t) :: {:ok, struct} | {:error, Stripe.Error.t()}
def make_request(
%Request{params: params, endpoint: endpoint, method: method, headers: headers, opts: opts} = request
%Request{params: params, endpoint: endpoint, method: method, headers: headers, opts: opts} =
request
) do
with {:ok, params} <- do_cast_to_id(params, request.cast_to_id),
{:ok, endpoint} <- consolidate_endpoint(endpoint, params),
Expand All @@ -161,13 +198,13 @@ defmodule Stripe.Request do
@doc """
Executes the request and returns the response for file uploads
"""
@spec make_file_upload_request(t) :: {:ok, struct} | {:error, Stripe.Error.t}
def make_file_upload_request(%Request{params: params, endpoint: endpoint, method: method, opts: opts} = request) do
with\
{:ok, params} <- do_cast_to_id(params, request.cast_to_id),
{:ok, endpoint} <- consolidate_endpoint(endpoint, params),
{:ok, result} <- API.request_file_upload(params, method, endpoint, %{}, opts)
do
@spec make_file_upload_request(t) :: {:ok, struct} | {:error, Stripe.Error.t()}
def make_file_upload_request(
%Request{params: params, endpoint: endpoint, method: method, opts: opts} = request
) do
with {:ok, params} <- do_cast_to_id(params, request.cast_to_id),
{:ok, endpoint} <- consolidate_endpoint(endpoint, params),
{:ok, result} <- API.request_file_upload(params, method, endpoint, %{}, opts) do
{:ok, Converter.convert_result(result)}
end
end
Expand Down
7 changes: 7 additions & 0 deletions test/stripe/core_resources/charge_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,11 @@ defmodule Stripe.ChargeTest do
assert {:ok, %Stripe.Charge{}} = Stripe.Charge.capture(charge, %{amount: 1000})
assert_stripe_requested(:post, "/v1/charges/ch_123/capture")
end

test "is retrievable with expansions opts" do
opts = [expand: ["balance_transaction"]]
assert {:ok, %Stripe.Charge{}} = Stripe.Charge.retrieve("ch_123", opts)

assert_stripe_requested(:get, "/v1/charges/ch_123")
end
end
30 changes: 30 additions & 0 deletions test/stripe/request_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule Stripe.RequestTest do
use ExUnit.Case

alias Stripe.Request

describe "object expansion" do
test "prefix_expansions/2 should apply the given prefix to the expansion values" do
opts = [expand: ["balance_transaction"]]
request = Request.prefix_expansions(%Request{opts: opts}, "data")
expansions = Keyword.get(request.opts, :expand)

assert expansions == ["data.balance_transaction"]
end

test "prefix_expansions/2 should apply the given prefix to multiple expansion values" do
opts = [expand: ["balance_transaction", "customer"]]
request = Request.prefix_expansions(%Request{opts: opts}, "data")
expansions = Keyword.get(request.opts, :expand)

assert expansions == ["data.balance_transaction", "data.customer"]
end

test "prefix_expansions/2 should return the original opts if no expansions specified" do
opts = []
request = Request.prefix_expansions(%Request{opts: opts}, "data")

assert request.opts == opts
end
end
end

0 comments on commit eb1d0f1

Please sign in to comment.