-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added stripe client and listing products endpoint
- Loading branch information
1 parent
04b14da
commit e1a1be4
Showing
8 changed files
with
299 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
defmodule Buildel.Clients.StripeBehaviour do | ||
@type list_products_params :: %{ | ||
active: boolean() | ||
} | ||
|
||
@callback list_products(list_products_params()) :: | ||
{:ok, [Buildel.Clients.Stripe.Product.t()]} | {:error, term} | ||
end | ||
|
||
defmodule Buildel.Clients.Stripe do | ||
@behaviour Buildel.Clients.StripeBehaviour | ||
|
||
defmodule Price do | ||
@type t :: %__MODULE__{ | ||
id: binary(), | ||
amount: integer(), | ||
currency: binary() | ||
} | ||
|
||
defstruct [:id, :currency, :amount] | ||
end | ||
|
||
defmodule Product do | ||
@type t :: %__MODULE__{ | ||
id: binary(), | ||
name: binary(), | ||
description: binary(), | ||
active: boolean(), | ||
price: Price.t() | nil | ||
} | ||
|
||
defstruct [:id, :name, :description, :active, :price] | ||
end | ||
|
||
@impl Buildel.Clients.StripeBehaviour | ||
def list_products(attrs \\ %{}) do | ||
url = "/products?expand[]=data.default_price" | ||
|
||
url = | ||
Enum.reduce(Map.to_list(attrs), url, fn | ||
{:active, active}, url when is_boolean(active) -> | ||
"#{url}&active=#{active}" | ||
|
||
_, url -> | ||
url | ||
end) | ||
|
||
with {:ok, %Req.Response{body: body, status: 200}} <- | ||
request(url) do | ||
{:ok, body["data"] |> map_products()} | ||
end | ||
end | ||
|
||
def get_price(price_id) do | ||
request(price_id) | ||
end | ||
|
||
def new(options \\ []) when is_list(options) do | ||
Req.new( | ||
base_url: "https://api.stripe.com/v1", | ||
auth: {:bearer, Application.fetch_env!(:buildel, :stripe_api_key)} | ||
) | ||
|> Req.Request.append_request_steps( | ||
post: fn req -> | ||
with %{method: :get, body: <<_::binary>>} <- req do | ||
%{req | method: :post} | ||
end | ||
end | ||
) | ||
|> Req.merge(options) | ||
end | ||
|
||
def request(url, options \\ []), do: Req.request(new(url: parse_url(url)), options) | ||
|
||
def request!(url, options \\ []), do: Req.request!(new(url: parse_url(url)), options) | ||
|
||
defp parse_url("prod_" <> _ = id), do: "/products/#{id}" | ||
defp parse_url("price_" <> _ = id), do: "/prices/#{id}" | ||
defp parse_url("sub_" <> _ = id), do: "/subscriptions/#{id}" | ||
defp parse_url("cus_" <> _ = id), do: "/customers/#{id}" | ||
defp parse_url("cs_" <> _ = id), do: "/checkout/sessions/#{id}" | ||
defp parse_url("inv_" <> _ = id), do: "/invoices/#{id}" | ||
defp parse_url("evt_" <> _ = id), do: "/events/#{id}" | ||
defp parse_url(url) when is_binary(url), do: url | ||
|
||
defp map_products(products) do | ||
Enum.map(products, fn %{ | ||
"id" => id, | ||
"name" => name, | ||
"description" => description, | ||
"active" => active, | ||
"default_price" => price | ||
} -> | ||
%Product{ | ||
id: id, | ||
name: name, | ||
description: description, | ||
active: active, | ||
price: map_price(price) | ||
} | ||
end) | ||
end | ||
|
||
defp map_price(nil), do: nil | ||
|
||
defp map_price(%{ | ||
"id" => id, | ||
"currency" => currency, | ||
"unit_amount" => amount | ||
}) do | ||
%Price{ | ||
id: id, | ||
currency: currency, | ||
amount: amount | ||
} | ||
end | ||
end |
58 changes: 58 additions & 0 deletions
58
apps/api/lib/buildel_web/controllers/organizations/subscriptions/subscriptions_controller.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
defmodule BuildelWeb.OrganizationSubscriptionController do | ||
use BuildelWeb, :controller | ||
use OpenApiSpex.ControllerSpecs | ||
|
||
import BuildelWeb.UserAuth | ||
|
||
alias Buildel.Clients.Stripe | ||
|
||
alias Buildel.Organizations | ||
|
||
action_fallback(BuildelWeb.FallbackController) | ||
|
||
plug(:fetch_current_user) | ||
plug(:require_authenticated_user) | ||
|
||
plug OpenApiSpex.Plug.CastAndValidate, | ||
json_render_error_v2: true, | ||
render_error: BuildelWeb.ErrorRendererPlug, | ||
replace_params: false | ||
|
||
tags ["subscriptions"] | ||
|
||
operation :list_products, | ||
summary: "List products", | ||
parameters: [ | ||
organization_id: [ | ||
in: :path, | ||
description: "Organization ID", | ||
type: :integer, | ||
required: true | ||
] | ||
], | ||
request_body: nil, | ||
responses: [ | ||
ok: {"success", "application/json", BuildelWeb.Schemas.Subscriptions.ListProductsResponse}, | ||
unprocessable_entity: | ||
{"unprocessable entity", "application/json", | ||
BuildelWeb.Schemas.Errors.UnprocessableEntity}, | ||
unauthorized: | ||
{"unauthorized", "application/json", BuildelWeb.Schemas.Errors.UnauthorizedResponse}, | ||
forbidden: {"forbidden", "application/json", BuildelWeb.Schemas.Errors.ForbiddenResponse} | ||
], | ||
security: [%{"authorization" => []}] | ||
|
||
def list_products(conn, _params) do | ||
%{"organization_id" => organization_id} = conn.params | ||
|
||
user = conn.assigns.current_user | ||
|
||
with {:ok, _organization} <- Organizations.get_user_organization(user, organization_id), | ||
{:ok, products} <- | ||
Stripe.list_products(%{ | ||
active: true | ||
}) do | ||
render(conn, :list_products, products: products) | ||
end | ||
end | ||
end |
31 changes: 31 additions & 0 deletions
31
apps/api/lib/buildel_web/controllers/organizations/subscriptions/subscriptions_json.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
defmodule BuildelWeb.OrganizationSubscriptionJSON do | ||
alias Buildel.Clients.Stripe | ||
|
||
def list_products(%{ | ||
products: products | ||
}) do | ||
%{ | ||
data: for(product <- products, do: product(product)) | ||
} | ||
end | ||
|
||
defp product(%Stripe.Product{} = product) do | ||
%{ | ||
id: product.id, | ||
name: product.name, | ||
description: product.description, | ||
active: product.active, | ||
price: price(product.price) | ||
} | ||
end | ||
|
||
defp price(nil), do: nil | ||
|
||
defp price(%Stripe.Price{} = price) do | ||
%{ | ||
id: price.id, | ||
amount: price.amount, | ||
currency: price.currency | ||
} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
apps/api/lib/buildel_web/schemas/organizations/subscriptions/subscriptions.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
defmodule BuildelWeb.Schemas.Subscriptions do | ||
alias OpenApiSpex.Schema | ||
|
||
defmodule Price do | ||
require OpenApiSpex | ||
|
||
OpenApiSpex.schema(%{ | ||
title: "SubscriptionPrice", | ||
type: :object, | ||
properties: %{ | ||
id: %Schema{type: :string, description: "Price ID"}, | ||
amount: %Schema{type: :number, description: "Price amount"}, | ||
currency: %Schema{type: :string, description: "Price currency"} | ||
}, | ||
required: [:id, :amount, :currency] | ||
}) | ||
end | ||
|
||
defmodule Product do | ||
require OpenApiSpex | ||
|
||
OpenApiSpex.schema(%{ | ||
title: "SubscriptionProduct", | ||
type: :object, | ||
properties: %{ | ||
id: %Schema{type: :string, description: "Product ID"}, | ||
name: %Schema{type: :string, description: "Product name"}, | ||
description: %Schema{type: :string, description: "Product description"}, | ||
active: %Schema{type: :boolean, description: "Product active status"}, | ||
price: Price | ||
}, | ||
required: [:id, :name, :description, :active] | ||
}) | ||
end | ||
|
||
defmodule ListProductsResponse do | ||
require OpenApiSpex | ||
|
||
OpenApiSpex.schema(%{ | ||
title: "SubscriptionListProductsResponse", | ||
type: :object, | ||
properties: %{ | ||
data: %Schema{ | ||
type: :array, | ||
items: Product | ||
} | ||
}, | ||
required: [:data] | ||
}) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
defmodule Buildel.ClientMocks.Stripe do | ||
@behaviour Buildel.Clients.StripeBehaviour | ||
|
||
@impl Buildel.Clients.StripeBehaviour | ||
def list_products(attrs \\ %{}) do | ||
{:ok, | ||
[ | ||
%Buildel.Clients.Stripe.Product{ | ||
id: "prod_1", | ||
name: "Product 1", | ||
description: "Description 1", | ||
active: true, | ||
price: %Buildel.Clients.Stripe.Price{ | ||
id: "price_1", | ||
currency: "usd", | ||
amount: 1000 | ||
} | ||
}, | ||
%Buildel.Clients.Stripe.Product{ | ||
id: "prod_2", | ||
name: "Product 2", | ||
description: "Description 2", | ||
active: true, | ||
price: %Buildel.Clients.Stripe.Price{ | ||
id: "price_2", | ||
currency: "usd", | ||
amount: 2000 | ||
} | ||
} | ||
]} | ||
end | ||
end |