diff --git a/README.md b/README.md index 4124e9a..f7aeb5c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Elixir implementation of [Chargebee API](https://apidocs.chargebee.com/docs/api). -## v0.1.4 +## v0.2.0 This is a work in progress: right now, we only implement those methods: - list - retrieve @@ -11,18 +11,22 @@ This is a work in progress: right now, we only implement those methods: on those resources: - addon +- coupon_code +- coupon_set - customer +- event +- gift - hosted_page - - also checkout_new - - also checkout_existing -- subscription - - also create_for_customer +- invoice +- item_family +- item_price +- item +- payment_intent +- payment_source - plan - portal_session - subscription -- invoice - - also close - - also import_invoice + ## Installation The package can be installed by adding `chargebee_elixir` to your list of dependencies in `mix.exs`: @@ -30,7 +34,7 @@ The package can be installed by adding `chargebee_elixir` to your list of depend ```elixir def deps do [ - {:chargebee_elixir, "~> 0.1.4"} + {:chargebee_elixir, "~> 0.2.0"} ] end ``` diff --git a/config/config.exs b/config/config.exs index c82c5f9..6570450 100644 --- a/config/config.exs +++ b/config/config.exs @@ -5,7 +5,7 @@ # is restricted to this project. # General application configuration -use Mix.Config +import Config # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/config/dev.exs b/config/dev.exs index d2d855e..becde76 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -1 +1 @@ -use Mix.Config +import Config diff --git a/config/test.exs b/config/test.exs index a13f4a0..b632b79 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,4 +1,4 @@ -use Mix.Config +import Config config :chargebee_elixir, namespace: "test-namespace", diff --git a/lib/chargebee_elixir.ex b/lib/chargebee_elixir.ex index 94d97e2..279c569 100644 --- a/lib/chargebee_elixir.ex +++ b/lib/chargebee_elixir.ex @@ -1,3 +1,5 @@ defmodule ChargebeeElixir do - + @moduledoc """ + Resources for interacting with Chargebee API Resources + """ end diff --git a/lib/chargebee_elixir/addon.ex b/lib/chargebee_elixir/addon.ex index eece7f7..afe20b0 100644 --- a/lib/chargebee_elixir/addon.ex +++ b/lib/chargebee_elixir/addon.ex @@ -1,3 +1,6 @@ defmodule ChargebeeElixir.Addon do + @moduledoc """ + an interface for interacting with Addons + """ use ChargebeeElixir.Resource, "addon" end diff --git a/lib/chargebee_elixir/coupon.ex b/lib/chargebee_elixir/coupon.ex new file mode 100644 index 0000000..afb66f4 --- /dev/null +++ b/lib/chargebee_elixir/coupon.ex @@ -0,0 +1,26 @@ +defmodule ChargebeeElixir.Coupon do + @moduledoc """ + an interface for interacting with Coupons + """ + use ChargebeeElixir.Resource, "coupon" + + def create_for_items(params) do + create(params, "/create_for_items") + end + + def update_for_items(coupon_id, params) do + post_resource(coupon_id, "/update_for_items", params) + end + + def delete(coupon_id) do + post_resource(coupon_id, "/delete", %{}) + end + + def copy(coupon_id, params) do + post_resource(coupon_id, "/copy", params) + end + + def unarchive(coupon_id) do + post_resource(coupon_id, "/unarchive", %{}) + end +end diff --git a/lib/chargebee_elixir/coupon_code.ex b/lib/chargebee_elixir/coupon_code.ex new file mode 100644 index 0000000..3494908 --- /dev/null +++ b/lib/chargebee_elixir/coupon_code.ex @@ -0,0 +1,14 @@ +defmodule ChargebeeElixir.CouponCode do + @moduledoc """ + an interface for interacting with Coupon Codes + + Supports: + - List + - Retrieve + """ + use ChargebeeElixir.Resource, "coupon_code" + + def archive(coupon_code) do + post_resource(coupon_code, "/archive", %{}) + end +end diff --git a/lib/chargebee_elixir/coupon_set.ex b/lib/chargebee_elixir/coupon_set.ex new file mode 100644 index 0000000..08cf49c --- /dev/null +++ b/lib/chargebee_elixir/coupon_set.ex @@ -0,0 +1,24 @@ +defmodule ChargebeeElixir.CouponSet do + @moduledoc """ + an interface for interacting with Coupon Sets + + Supports + - List + - Retrieve + - Create + - Update + """ + use ChargebeeElixir.Resource, "coupon_set" + + def add_coupon_codes(coupon_id, params) do + post_resource(coupon_id, "/add_coupon_codes", params) + end + + def delete(coupon_id) do + post_resource(coupon_id, "/delete", %{}) + end + + def delete_unused_coupon_codes(coupon_id) do + post_resource(coupon_id, "/delete_unused_coupon_codes", %{}) + end +end diff --git a/lib/chargebee_elixir/customer.ex b/lib/chargebee_elixir/customer.ex index 7bf7548..53d15a2 100644 --- a/lib/chargebee_elixir/customer.ex +++ b/lib/chargebee_elixir/customer.ex @@ -1,3 +1,10 @@ defmodule ChargebeeElixir.Customer do + @moduledoc """ + an interface for interacting with Customers + """ use ChargebeeElixir.Resource, "customer" + + def merge(args) do + create(args, "/merge") + end end diff --git a/lib/chargebee_elixir/event.ex b/lib/chargebee_elixir/event.ex new file mode 100644 index 0000000..18169bd --- /dev/null +++ b/lib/chargebee_elixir/event.ex @@ -0,0 +1,33 @@ +defmodule ChargebeeElixir.Event do + @moduledoc """ + an interface for interacting with Events + """ + @resource "event" + alias ChargebeeElixir.Interface + + def retrieve(id) do + id |> resource_path() |> Interface.get() |> Map.get(@resource) + rescue + ChargebeeElixir.NotFoundError -> nil + end + + def list(params \\ %{}) do + # Should pagination be by default? + case Interface.get(resource_base_path(), params) do + %{"list" => current_list, "next_offset" => next_offset} -> + Enum.map(current_list, &Map.get(&1, @resource)) ++ + __MODULE__.list(Map.merge(params, %{"offset" => next_offset})) + + %{"list" => current_list} -> + Enum.map(current_list, &Map.get(&1, @resource)) + end + end + + defp resource_base_path do + "/#{@resource}s" + end + + defp resource_path(id) do + "#{resource_base_path()}/#{id}" + end +end diff --git a/lib/chargebee_elixir/gift.ex b/lib/chargebee_elixir/gift.ex new file mode 100644 index 0000000..6e41d3d --- /dev/null +++ b/lib/chargebee_elixir/gift.ex @@ -0,0 +1,18 @@ +defmodule ChargebeeElixir.Gift do + @moduledoc """ + An interface for interacting with Chargebee Gifts + """ + use ChargebeeElixir.Resource, "gift" + + def create_for_items(params) do + create(params, "/create_for_items") + end + + def cancel(coupon_id) do + post_resource(coupon_id, "/cancel", %{}) + end + + def claim(coupon_id) do + post_resource(coupon_id, "/claim", %{}) + end +end diff --git a/lib/chargebee_elixir/hosted_page.ex b/lib/chargebee_elixir/hosted_page.ex index cd3d937..e988291 100644 --- a/lib/chargebee_elixir/hosted_page.ex +++ b/lib/chargebee_elixir/hosted_page.ex @@ -1,6 +1,14 @@ defmodule ChargebeeElixir.HostedPage do + @moduledoc """ + an interface for interacting with HostedPages + """ use ChargebeeElixir.Resource, "hosted_page" - def checkout_new(params) do create(params, "/checkout_new") end - def checkout_existing(params) do create(params, "/checkout_existing") end + def checkout_new(params) do + create(params, "/checkout_new") + end + + def checkout_existing(params) do + create(params, "/checkout_existing") + end end diff --git a/lib/chargebee_elixir/interface.ex b/lib/chargebee_elixir/interface.ex index 6715fe6..82ef5be 100644 --- a/lib/chargebee_elixir/interface.ex +++ b/lib/chargebee_elixir/interface.ex @@ -1,42 +1,53 @@ defmodule ChargebeeElixir.Interface do + @moduledoc """ + A low level http interface for interacting with Chargebee V2 HTTP Endpoints + + Configuration: + - Authorization loaded from Application env `:chargebee_elixir, :api_key` + - Chargebee namespace scoping loaded from Application env `:chargebee_elixir, :namespace` + - Alternative HTTP Clients configured from Application env `:chargebee_elixir, :http_client` (i.e. in testing) + """ def get(path) do get(path, %{}) end def get(path, params) do - params_string = params - |> URI.encode_query() + params_string = URI.encode_query(params) - url = [fullpath(path), params_string] - |> Enum.filter(fn(s) -> String.length(s) > 0 end) + url = + [fullpath(path), params_string] + |> Enum.filter(fn s -> String.length(s) > 0 end) |> Enum.join("?") - http_client().get!(url,headers()) - |> handle_response() + http_client().get!(url, headers()) + |> handle_response() end def post(path, data) do - body = data - |> transform_arrays_for_chargebee - |> Plug.Conn.Query.encode() + body = + data + |> serialize() + |> URI.encode_query() + http_client().post!( fullpath(path), body, headers() ++ [{"Content-Type", "application/x-www-form-urlencoded"}] ) - |> handle_response() + |> handle_response() end defp handle_response(%{body: body, status_code: 200}) do - body - |> Jason.decode! + Jason.decode!(body) end defp handle_response(%{body: body, status_code: 400}) do - message = body - |> Jason.decode! + message = + body + |> Jason.decode!() |> Map.get("message") + raise ChargebeeElixir.InvalidRequestError, message: message end @@ -52,54 +63,79 @@ defmodule ChargebeeElixir.Interface do raise ChargebeeElixir.UnknownError end - defp http_client() do + defp http_client do Application.get_env(:chargebee_elixir, :http_client, HTTPoison) end defp fullpath(path) do + # TODO someday: Allow multiple Chargebee Interfaces with multiple namespaces namespace = Application.get_env(:chargebee_elixir, :namespace) "https://#{namespace}.chargebee.com/api/v2#{path}" end - defp headers() do - api_key = Application.get_env(:chargebee_elixir, :api_key) + defp headers do + api_key = + :chargebee_elixir + |> Application.get_env(:api_key) + |> Kernel.<>(":") + |> Base.encode64() + [ - {"Authorization", "Basic #{"#{api_key}:" |> Base.encode64}"} + {"Authorization", "Basic " <> api_key} ] end - def transform_arrays_for_chargebee(data) do - case data do - map_data when is_map(map_data) -> - map_data - |> Enum.map(fn {k, v} -> - {k, transform_arrays_for_chargebee(v)} - end) - |> Enum.into(%{}) - list_data when is_list(list_data) -> - transformed_list_data = list_data - |> Enum.map(fn item -> transform_arrays_for_chargebee(item) end) - transformed_list_data - |> Enum.map(fn (item) -> case item do - map_item when is_map(map_item) -> Map.keys(map_item) - _ -> raise ChargebeeElixir.IncorrectDataFormatError, - message: "Unsupported data: lists should contains objects only" - end - end) - |> List.flatten - |> Enum.uniq - |> Enum.map(fn (key) -> - { - key, - transformed_list_data - |> Enum.with_index - |> Enum.map(fn {item, index} -> {index, item[key]} end) - |> Enum.filter(fn {_index, item} -> !is_nil(item) end) - |> Enum.into(%{}) - } - end) - |> Enum.into(%{}) - other_data -> other_data - end + # serialize/3 is a 1:1 adaptation of Chargebee-Ruby `Chargebee::Util.serialize/3` + # from https://github.com/chargebee/chargebee-ruby/blob/42f4aa5e58d5760d9f66d3aff02f8389faa6e68f/lib/chargebee/util.rb#L5 + def serialize(value, prefix \\ nil, index \\ nil) + + def serialize(value, prefix, index) when is_map(value) do + Enum.flat_map(value, fn + {_k, nil} -> + [] + + {k, v} when k in ["metadata", :metadata] and is_map(v) -> + [{to_string(k), Jason.encode!(v)}] + + {k, v} when is_map(v) or is_list(v) -> + pre = if is_nil(prefix), do: to_string(k), else: "#{prefix}[#{k}]" + # fix = if is_nil(index), do: "", else: "[#{index}]" + + serialize(v, pre, index) + + {k, v} -> + pre = if is_nil(prefix), do: to_string(k), else: "#{prefix}[#{k}]" + fix = if is_nil(index), do: "", else: "[#{index}]" + + key = pre <> fix + [{key, to_string(v)}] + end) + |> Map.new() + end + + def serialize(value, prefix, nil) when is_list(value) do + value + |> Enum.with_index() + |> Enum.flat_map(fn {item, i} -> serialize(item, prefix, i) end) + |> Map.new() + end + + # Apparently Second Degree nested arrays are just encoded as json values + def serialize(value, prefix, index) when is_list(value) do + value = Jason.encode!(value) + + key = "#{prefix}[#{index}]" + + [{key, value}] + end + + def serialize(_value, nil, nil) do + raise ArgumentError, "Only hash or arrays are allowed as value" + end + + def serialize(value, prefix, index) do + key = "#{prefix}[#{index}]" + + [{key, value}] end end diff --git a/lib/chargebee_elixir/invoice.ex b/lib/chargebee_elixir/invoice.ex index 1e49fa9..51e2be7 100644 --- a/lib/chargebee_elixir/invoice.ex +++ b/lib/chargebee_elixir/invoice.ex @@ -1,7 +1,14 @@ defmodule ChargebeeElixir.Invoice do + @moduledoc """ + an interface for interacting with Invoices + """ use ChargebeeElixir.Resource, "invoice" - def close(id, params \\ %{}) do post_endpoint(id, "/close", params) end - def import_invoice(params \\ %{}) do create(params, "/import_invoice") end -end - + def import_invoice(params \\ %{}) do + create(params, "/import_invoice") + end + + def close(id, params \\ %{}) do + post_resource(id, "/close", params) + end +end diff --git a/lib/chargebee_elixir/item.ex b/lib/chargebee_elixir/item.ex new file mode 100644 index 0000000..f7b6cf3 --- /dev/null +++ b/lib/chargebee_elixir/item.ex @@ -0,0 +1,6 @@ +defmodule ChargebeeElixir.Item do + @moduledoc """ + an interface for interacting with Items + """ + use ChargebeeElixir.Resource, "item" +end diff --git a/lib/chargebee_elixir/item_family.ex b/lib/chargebee_elixir/item_family.ex new file mode 100644 index 0000000..b8530fd --- /dev/null +++ b/lib/chargebee_elixir/item_family.ex @@ -0,0 +1,6 @@ +defmodule ChargebeeElixir.ItemFamily do + @moduledoc """ + an interface for interacting with Items + """ + use ChargebeeElixir.Resource, "item_family" +end diff --git a/lib/chargebee_elixir/item_price.ex b/lib/chargebee_elixir/item_price.ex new file mode 100644 index 0000000..9255f97 --- /dev/null +++ b/lib/chargebee_elixir/item_price.ex @@ -0,0 +1,6 @@ +defmodule ChargebeeElixir.ItemPrice do + @moduledoc """ + an interface for interacting with Items + """ + use ChargebeeElixir.Resource, "item_price" +end diff --git a/lib/chargebee_elixir/payment_intent.ex b/lib/chargebee_elixir/payment_intent.ex new file mode 100644 index 0000000..1f5a8b1 --- /dev/null +++ b/lib/chargebee_elixir/payment_intent.ex @@ -0,0 +1,6 @@ +defmodule ChargebeeElixir.PaymentIntent do + @moduledoc """ + an interface for interacting with PaymentIntents + """ + use ChargebeeElixir.Resource, "payment_intent" +end diff --git a/lib/chargebee_elixir/payment_source.ex b/lib/chargebee_elixir/payment_source.ex new file mode 100644 index 0000000..d884735 --- /dev/null +++ b/lib/chargebee_elixir/payment_source.ex @@ -0,0 +1,6 @@ +defmodule ChargebeeElixir.PaymentSource do + @moduledoc """ + an interface for interacting with PaymentSources + """ + use ChargebeeElixir.Resource, "payment_source" +end diff --git a/lib/chargebee_elixir/plan.ex b/lib/chargebee_elixir/plan.ex index 79feb34..62b2f8a 100644 --- a/lib/chargebee_elixir/plan.ex +++ b/lib/chargebee_elixir/plan.ex @@ -1,3 +1,6 @@ defmodule ChargebeeElixir.Plan do + @moduledoc """ + an interface for interacting with Plans + """ use ChargebeeElixir.Resource, "plan" end diff --git a/lib/chargebee_elixir/portal_session.ex b/lib/chargebee_elixir/portal_session.ex index ab467e2..b9c1b98 100644 --- a/lib/chargebee_elixir/portal_session.ex +++ b/lib/chargebee_elixir/portal_session.ex @@ -1,3 +1,6 @@ defmodule ChargebeeElixir.PortalSession do + @moduledoc """ + an interface for interacting with PortalSessions + """ use ChargebeeElixir.Resource, "portal_session" end diff --git a/lib/chargebee_elixir/resource.ex b/lib/chargebee_elixir/resource.ex index 37a1837..04cad28 100644 --- a/lib/chargebee_elixir/resource.ex +++ b/lib/chargebee_elixir/resource.ex @@ -1,50 +1,69 @@ defmodule ChargebeeElixir.Resource do + @moduledoc false defmacro __using__(resource) do - quote do + quote location: :keep do alias ChargebeeElixir.Interface @resource unquote(resource) + @resource_plural Inflex.pluralize(@resource) def retrieve(id) do - Interface.get(resource_path(id))[@resource] + id |> resource_path() |> Interface.get() |> Map.get(@resource) rescue - e in ChargebeeElixir.NotFoundError -> nil + ChargebeeElixir.NotFoundError -> nil end - def list do - __MODULE__.list(%{}) - end - - def list(params) do + def list(params \\ %{}) do + # Should pagination be by default? case Interface.get(resource_base_path(), params) do %{"list" => current_list, "next_offset" => next_offset} -> - Enum.map(current_list, fn(hash) -> hash[@resource] end) ++ __MODULE__.list(Map.merge(params, %{"offset" => next_offset})) + Enum.map(current_list, &Map.get(&1, @resource)) ++ + __MODULE__.list(Map.merge(params, %{"offset" => next_offset})) + %{"list" => current_list} -> - Enum.map(current_list, fn(hash) -> hash[@resource] end) + Enum.map(current_list, &Map.get(&1, @resource)) end end def create(params, path \\ "") do - Interface.post("#{resource_base_path()}#{path}", params)[@resource] + resource_base_path() + |> Kernel.<>(path) + |> Interface.post(params) + |> Map.get(@resource) end - def post_endpoint(id, endpoint, params) do - Interface.post("#{resource_path(id)}#{endpoint}", params)[@resource] + def post_resource(resource_id, endpoint, params) do + resource_id + |> resource_path() + |> Kernel.<>(endpoint) + |> Interface.post(params) + |> Map.get(@resource) end def create_for_parent(parent_path, params, path \\ "") do - Interface.post( - "#{parent_path}#{resource_base_path()}#{path}", - params - )[@resource] + parent_path + |> Kernel.<>(resource_base_path()) + |> Kernel.<>(path) + |> Interface.post(params) + |> Map.get(@resource) end - def resource_base_path() do - "/#{@resource}s" + def update(resource_id, params, path \\ "") do + resource_id + |> resource_path() + |> Kernel.<>(path) + |> Interface.post(params) + |> Map.get(@resource) + end + + def resource_base_path do + "/#{@resource_plural}" end def resource_path(id) do - "#{resource_base_path()}/#{id}" + encoded_id = id |> to_string |> URI.encode() + + "#{resource_base_path()}/#{encoded_id}" end end end diff --git a/lib/chargebee_elixir/subscription.ex b/lib/chargebee_elixir/subscription.ex index ae3407d..54ed281 100644 --- a/lib/chargebee_elixir/subscription.ex +++ b/lib/chargebee_elixir/subscription.ex @@ -1,10 +1,12 @@ defmodule ChargebeeElixir.Subscription do + @moduledoc """ + an interface for interacting with Subscriptions + """ use ChargebeeElixir.Resource, "subscription" def create_for_customer(customer_id, params) do - create_for_parent( - ChargebeeElixir.Customer.resource_path(customer_id), - params - ) + customer_id + |> ChargebeeElixir.Customer.resource_path() + |> create_for_parent(params) end end diff --git a/mix.exs b/mix.exs index 44fd3b6..e9c763e 100644 --- a/mix.exs +++ b/mix.exs @@ -14,7 +14,7 @@ defmodule ChargebeeElixir.MixProject do }, source_url: "https://github.com/NicolasMarlier/chargebee-elixir", homepage_url: "https://github.com/NicolasMarlier/chargebee-elixir", - version: "0.1.4", + version: "0.2.0", elixir: "~> 1.11", start_permanent: Mix.env() == :prod, deps: deps() @@ -31,11 +31,14 @@ defmodule ChargebeeElixir.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ + {:inflex, "~> 2.0"}, {:jason, "~> 1.0"}, {:httpoison, "~> 1.7"}, - {:plug, "~>1.11"}, {:ex_doc, "~> 0.23", only: :dev, runtime: false}, - {:mox, "~>1.0", only: [:test]} + {:mox, "~>1.0", only: [:test]}, + {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, + {:dialyxir, ">= 0.0.0", only: [:dev], runtime: false}, + {:ex_check, "~> 0.14.0", only: [:dev], runtime: false} ] end end diff --git a/mix.lock b/mix.lock index 5a778c1..6de337a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,22 +1,26 @@ %{ - "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, - "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, - "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, - "httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"}, - "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, - "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, - "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.15.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, + "credo": {:hex, :credo, "1.6.4", "ddd474afb6e8c240313f3a7b0d025cc3213f0d171879429bf8535d7021d9ad78", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c28f910b61e1ff829bffa056ef7293a8db50e87f2c57a9b5c3f57eee124536b7"}, + "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, + "ex_doc": {:hex, :ex_doc, "0.28.3", "6eea2f69995f5fba94cd6dd398df369fe4e777a47cd887714a0976930615c9e6", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "05387a6a2655b5f9820f3f627450ed20b4325c25977b2ee69bed90af6688e718"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, + "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mox": {:hex, :mox, "1.0.0", "4b3c7005173f47ff30641ba044eb0fe67287743eec9bd9545e37f3002b0a9f8b", [:mix], [], "hexpm", "201b0a20b7abdaaab083e9cf97884950f8a30a1350a1da403b3145e213c6f4df"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, - "plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, + "mox": {:hex, :mox, "1.0.1", "b651bf0113265cda0ba3a827fcb691f848b683c373b77e7d7439910a8d754d6e", [:mix], [], "hexpm", "35bc0dea5499d18db4ef7fe4360067a59b06c74376eb6ab3bd67e6295b133469"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, + "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, - "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/test/chargebee_elixir/addon_test.exs b/test/chargebee_elixir/addon_test.exs index 95d0955..a5aceb6 100644 --- a/test/chargebee_elixir/addon_test.exs +++ b/test/chargebee_elixir/addon_test.exs @@ -9,9 +9,10 @@ defmodule ChargebeeElixir.AddonTest do expect( ChargebeeElixir.HTTPoisonMock, :get!, - fn (url, headers) -> + fn url, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/addons/1234" assert headers == [{"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}] + %{ status_code: 401 } @@ -27,9 +28,10 @@ defmodule ChargebeeElixir.AddonTest do expect( ChargebeeElixir.HTTPoisonMock, :get!, - fn (url, headers) -> + fn url, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/addons/1234" assert headers == [{"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}] + %{ status_code: 404 } @@ -43,9 +45,10 @@ defmodule ChargebeeElixir.AddonTest do expect( ChargebeeElixir.HTTPoisonMock, :get!, - fn (url, headers) -> + fn url, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/addons/1234" assert headers == [{"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}] + %{ status_code: 200, body: '{"addon": {"id": 1234}}' @@ -62,9 +65,10 @@ defmodule ChargebeeElixir.AddonTest do expect( ChargebeeElixir.HTTPoisonMock, :get!, - fn (url, headers) -> + fn url, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/addons" assert headers == [{"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}] + %{ status_code: 401 } @@ -80,9 +84,10 @@ defmodule ChargebeeElixir.AddonTest do expect( ChargebeeElixir.HTTPoisonMock, :get!, - fn (url, headers) -> + fn url, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/addons" assert headers == [{"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}] + %{ status_code: 200, body: '{"list": [{"addon": {"id": 1234}}]}' @@ -97,9 +102,12 @@ defmodule ChargebeeElixir.AddonTest do expect( ChargebeeElixir.HTTPoisonMock, :get!, - fn (url, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/addons?id%5Bin%5D=%5B1234%2C1235%5D" + fn url, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/addons?id%5Bin%5D=%5B1234%2C1235%5D" + assert headers == [{"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}] + %{ status_code: 200, body: '{ @@ -113,9 +121,12 @@ defmodule ChargebeeElixir.AddonTest do expect( ChargebeeElixir.HTTPoisonMock, :get!, - fn (url, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/addons?id%5Bin%5D=%5B1234%2C1235%5D&offset=1235" + fn url, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/addons?id%5Bin%5D=%5B1234%2C1235%5D&offset=1235" + assert headers == [{"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}] + %{ status_code: 200, body: '{ @@ -125,7 +136,10 @@ defmodule ChargebeeElixir.AddonTest do end ) - assert ChargebeeElixir.Addon.list(%{"id[in]": "[1234,1235]"}) == [%{"id" => 1234}, %{"id" => 1235}] + assert ChargebeeElixir.Addon.list(%{"id[in]": "[1234,1235]"}) == [ + %{"id" => 1234}, + %{"id" => 1235} + ] end end @@ -134,13 +148,15 @@ defmodule ChargebeeElixir.AddonTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> + fn url, data, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/addons" assert data == "id=addon-a" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 401 } @@ -156,13 +172,15 @@ defmodule ChargebeeElixir.AddonTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> + fn url, data, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/addons" assert data == "id=addon-a" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 400, body: '{"message": "Unknown"}' @@ -179,13 +197,15 @@ defmodule ChargebeeElixir.AddonTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> + fn url, data, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/addons" assert data == "id=addon-a" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 200, body: '{"addon": {"id": "addon-a"}}' diff --git a/test/chargebee_elixir/coupon_test.exs b/test/chargebee_elixir/coupon_test.exs new file mode 100644 index 0000000..c466cbc --- /dev/null +++ b/test/chargebee_elixir/coupon_test.exs @@ -0,0 +1,77 @@ +defmodule ChargebeeElixir.CouponTest do + use ExUnit.Case + doctest ChargebeeElixir.Coupon + alias ChargebeeElixir.Coupon + import Mox + + setup :verify_on_exit! + + def subject do + end + + describe "create_for_items" do + test "works with exmaple data from chargebee" do + expect( + ChargebeeElixir.HTTPoisonMock, + :post!, + fn url, data, _headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/coupons/create_for_items" + + assert URI.decode(data) == + "apply_on=each_specified_item&discount_percentage=10.0&discount_type=percentage&duration_type=forever&id=summer_offer&item_constraints[constraint][0]=all&item_constraints[item_type][0]=plan&name=Summer+Offer" + + %{ + status_code: 200, + body: '{"coupon": {"id": "summer_offer"}}' + } + end + ) + + assert Coupon.create_for_items(%{ + id: "summer_offer", + name: "Summer Offer", + discount_percentage: 10.0, + discount_type: "percentage", + duration_type: "forever", + apply_on: "each_specified_item", + item_constraints: [%{constraint: "all", item_type: "plan"}] + }) == %{"id" => "summer_offer"} + end + + test "works with list of item_price_ids" do + expect( + ChargebeeElixir.HTTPoisonMock, + :post!, + fn url, data, _headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/coupons/create_for_items" + + assert URI.decode(data) == + "apply_on=each_specified_item&discount_percentage=10.0&discount_type=percentage&duration_type=forever&id=summer_offer&item_constraints[constraint][0]=specific&item_constraints[item_price_ids][0]=[\"item_1\"]&item_constraints[item_type][0]=plan&name=Summer+Offer" + + %{ + status_code: 200, + body: '{"coupon": {"id": "summer_offer"}}' + } + end + ) + + assert Coupon.create_for_items(%{ + id: "summer_offer", + name: "Summer Offer", + discount_percentage: 10.0, + discount_type: "percentage", + duration_type: "forever", + apply_on: "each_specified_item", + item_constraints: [ + %{ + constraint: "specific", + item_type: "plan", + item_price_ids: ["item_1"] + } + ] + }) == %{"id" => "summer_offer"} + end + end +end diff --git a/test/chargebee_elixir/hosted_page_test.exs b/test/chargebee_elixir/hosted_page_test.exs index 440278b..eb79ca3 100644 --- a/test/chargebee_elixir/hosted_page_test.exs +++ b/test/chargebee_elixir/hosted_page_test.exs @@ -9,13 +9,17 @@ defmodule ChargebeeElixir.HostedPageTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> + fn url, data, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/hosted_pages/checkout_new" - assert data == "addons[id][0]=addon-a&addons[id][1]=addon-b&subscription[plan_id]=plan-a" + + assert URI.decode(data) == + "addons[id][0]=addon-a&addons[id][1]=addon-b&subscription[plan_id]=plan-a" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 200, body: '{"hosted_page": {"url": "https://doe.com"}}' @@ -24,14 +28,14 @@ defmodule ChargebeeElixir.HostedPageTest do ) assert ChargebeeElixir.HostedPage.checkout_new(%{ - subscription: %{ - plan_id: "plan-a", - }, - addons: [ - %{ id: "addon-a"}, - %{ id: "addon-b"} - ] - }) == %{"url" => "https://doe.com"} + subscription: %{ + plan_id: "plan-a" + }, + addons: [ + %{id: "addon-a"}, + %{id: "addon-b"} + ] + }) == %{"url" => "https://doe.com"} end end @@ -40,13 +44,18 @@ defmodule ChargebeeElixir.HostedPageTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/hosted_pages/checkout_existing" - assert data == "addons[id][0]=addon-a&addons[id][1]=addon-b&customer[id]=cus-a&subscription[id]=subscription-a&subscription[plan_id]=plan-a" + fn url, data, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/hosted_pages/checkout_existing" + + assert URI.decode(data) == + "addons[id][0]=addon-a&addons[id][1]=addon-b&customer[id]=cus-a&subscription[id]=subscription-a&subscription[plan_id]=plan-a" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 200, body: '{"hosted_page": {"url": "https://doe.com"}}' @@ -55,18 +64,18 @@ defmodule ChargebeeElixir.HostedPageTest do ) assert ChargebeeElixir.HostedPage.checkout_existing(%{ - subscription: %{ - id: "subscription-a", - plan_id: "plan-a", - }, - customer: %{ - id: "cus-a" - }, - addons: [ - %{ id: "addon-a"}, - %{ id: "addon-b"} - ] - }) == %{"url" => "https://doe.com"} + subscription: %{ + id: "subscription-a", + plan_id: "plan-a" + }, + customer: %{ + id: "cus-a" + }, + addons: [ + %{id: "addon-a"}, + %{id: "addon-b"} + ] + }) == %{"url" => "https://doe.com"} end end end diff --git a/test/chargebee_elixir/interface_test.exs b/test/chargebee_elixir/interface_test.exs index 30c755a..f7b246e 100644 --- a/test/chargebee_elixir/interface_test.exs +++ b/test/chargebee_elixir/interface_test.exs @@ -1,130 +1,183 @@ defmodule ChargebeeElixir.InterfaceTest do use ExUnit.Case - describe "transform_arrays_for_chargebee" do - test "simple list" do - assert ChargebeeElixir.Interface.transform_arrays_for_chargebee([ - %{id: "object-a"}, - %{id: "object-b"} - ]) == %{ - id: %{ - 0 => "object-a", - 1 => "object-b" + describe "serialize" do + test "chargebee example" do + # from chargebee-ruby test case + input = %{ + :id => "sub_KyVq7DNSNM7CSD", + :plan_id => "free", + :addons => [%{:id => "monitor", :quantity => 2}, %{:id => "ssl"}], + :addon_ids => ["addon_one", "addon_two"], + :card => %{ + :first_name => "Rajaraman", + :last_name => "Santhanam", + :number => "4111111111111111", + :expiry_month => "1", + :expiry_year => "2024", + :cvv => "007" } } - end - test "deep nesting, no lists" do - assert ChargebeeElixir.Interface.transform_arrays_for_chargebee(%{ - addon: %{ - id: "addon-a", - nested: %{ - object: %{ - id: "object-a" - } - } - } - }) == %{ - addon: %{ - id: "addon-a", - nested: %{ - object: %{ - id: "object-a" - } - } - } + output = %{ + "id" => "sub_KyVq7DNSNM7CSD", + "plan_id" => "free", + "addons[id][0]" => "monitor", + "addons[quantity][0]" => "2", + "addons[id][1]" => "ssl", + "addon_ids[0]" => "addon_one", + "addon_ids[1]" => "addon_two", + "card[first_name]" => "Rajaraman", + "card[last_name]" => "Santhanam", + "card[number]" => "4111111111111111", + "card[expiry_month]" => "1", + "card[expiry_year]" => "2024", + "card[cvv]" => "007" } + + assert ChargebeeElixir.Interface.serialize(input) == output end - test "simple nesting" do - assert ChargebeeElixir.Interface.transform_arrays_for_chargebee(%{ + test "chargebeee example 2" do + # From the chargebee-go Test case + input = %{ + plan_id: "cbdemo_grow", + customer: %{ + email: "john@user.com", + first_name: "John", + last_name: "Doe", + locale: "frCA", + phone: "+19499999999", + auto_collection: "on" + }, addons: [ %{ - id: "addon-a", - price: 10 + id: "cbdemo_conciergesupport" }, %{ - id: "addon-b", + id: "cbdemo_additionaluser", quantity: 2 } - ] - }) == %{ - addons: %{ - id: %{ - 0 => "addon-a", - 1 => "addon-b" - }, - price: %{ - 0 => 10 - }, - quantity: %{ - 1 => 2 - } - } + ], + coupon_ids: ["cbdemo_earlybird"] + } + + output = %{ + "coupon_ids[0]" => "cbdemo_earlybird", + "customer[phone]" => "+19499999999", + "plan_id" => "cbdemo_grow", + "customer[last_name]" => "Doe", + "customer[locale]" => "frCA", + "addons[id][0]" => "cbdemo_conciergesupport", + "addons[id][1]" => "cbdemo_additionaluser", + "addons[quantity][1]" => "2", + "customer[email]" => "john@user.com", + "customer[auto_collection]" => "on", + "customer[first_name]" => "John" } + + assert ChargebeeElixir.Interface.serialize(input) == output + end + + test "simple list" do + assert ChargebeeElixir.Interface.serialize([ + %{id: "object-a"}, + %{id: "object-b"} + ]) == %{"id[0]" => "object-a", "id[1]" => "object-b"} end - test "incorrect nesting" do - assert_raise ChargebeeElixir.IncorrectDataFormatError, - "Unsupported data: lists should contains objects only", fn -> - ChargebeeElixir.Interface.transform_arrays_for_chargebee(%{ - addons: [ - %{ - id: "addon-a", - price: 10 - }, - %{ - id: "addon-b", - quantity: 2 - }, - "a" - ] - }) - end + test "deep nesting, no lists" do + assert ChargebeeElixir.Interface.serialize(%{ + addon: %{ + id: "addon-a", + nested: %{ + object: %{ + id: "object-a" + } + } + } + }) == %{"addon[id]" => "addon-a", "addon[nested][object][id]" => "object-a"} end - test "complex nesting" do - assert ChargebeeElixir.Interface.transform_arrays_for_chargebee(%{ - addons: [ - %{ - id: "addon-a", - price: 10, - nested_objects: [ - %{ - id: "object-a" - }, - %{ - id: "object-b" - } - ] - }, + test "simple nesting" do + assert ChargebeeElixir.Interface.serialize(%{ + addons: [ + %{ + id: "addon-a", + price: 10 + }, + %{ + id: "addon-b", + quantity: 2 + } + ] + }) == %{ + "addons[id][0]" => "addon-a", + "addons[id][1]" => "addon-b", + "addons[price][0]" => "10", + "addons[quantity][1]" => "2" + } + end + + test "Example of complex nested list field" do + assert ChargebeeElixir.Interface.serialize(%{ + item_constraints: [ + %{ + constraint: "specific", + item_type: "plan", + item_price_ids: ["item_a"] + } + ] + }) == %{ + "item_constraints[constraint][0]" => "specific", + "item_constraints[item_type][0]" => "plan", + "item_constraints[item_price_ids][0]" => "[\"item_a\"]" + } + end + + test "when query encoded" do + input = %{ + item_constraints: [ %{ - id: "addon-b", - quantity: 2 + constraint: "specific", + item_type: "plan", + item_price_ids: "[\"item_a\"]" } ] - }) == %{ - addons: %{ - id: %{ - 0 => "addon-a", - 1 => "addon-b" - }, - price: %{ - 0 => 10 - }, - quantity: %{ - 1 => 2 - }, - nested_objects: %{ - 0 => %{ - id: %{ - 0 => "object-a", - 1 => "object-b" - } - } - } - } } + + as_encoded_params = + ~s + + assert as_encoded_params == + input + |> ChargebeeElixir.Interface.serialize() + |> URI.encode_query() + |> URI.decode() + end + + test "drops nil fields" do + # from chargebee-ruby test case + input = %{ + :id => "sub_KyVq7DNSNM7CSD", + :plan_id => "free", + :item_family_id => nil + } + + output = %{ + "id" => "sub_KyVq7DNSNM7CSD", + "plan_id" => "free" + } + + assert ChargebeeElixir.Interface.serialize(input) == output + end + + test "serializes metadata as encoded json" do + input = %{metadata: %{some: "value"}} + + output = %{"metadata" => ~S({"some":"value"})} + + assert ChargebeeElixir.Interface.serialize(input) == output end end end diff --git a/test/chargebee_elixir/invoice_test.exs b/test/chargebee_elixir/invoice_test.exs index ba02ece..d468e6f 100644 --- a/test/chargebee_elixir/invoice_test.exs +++ b/test/chargebee_elixir/invoice_test.exs @@ -20,13 +20,17 @@ defmodule ChargebeeElixir.InvoiceTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/invoices/draft_inv_abcde/close" + fn url, data, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/invoices/draft_inv_abcde/close" + assert data == "invoice_note=This+is+a+note" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 401 } @@ -42,13 +46,17 @@ defmodule ChargebeeElixir.InvoiceTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/invoices/draft_inv_abcde/close" + fn url, data, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/invoices/draft_inv_abcde/close" + assert data == "invoice_note=This+is+a+note" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 404 } @@ -64,13 +72,17 @@ defmodule ChargebeeElixir.InvoiceTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/invoices/draft_inv_abcde/close" + fn url, data, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/invoices/draft_inv_abcde/close" + assert data == "invoice_note=This+is+a+note" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 400, body: '{"message": "Unknown"}' @@ -87,13 +99,17 @@ defmodule ChargebeeElixir.InvoiceTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/invoices/draft_inv_abcde/close" + fn url, data, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/invoices/draft_inv_abcde/close" + assert data == "invoice_note=This+is+a+note" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 200, body: '{"invoice": {"id": "abcde"}}' diff --git a/test/chargebee_elixir/portal_session_test.exs b/test/chargebee_elixir/portal_session_test.exs index 6cb08e7..4e31944 100644 --- a/test/chargebee_elixir/portal_session_test.exs +++ b/test/chargebee_elixir/portal_session_test.exs @@ -9,13 +9,15 @@ defmodule ChargebeeElixir.PortalSessionTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> + fn url, data, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/portal_sessions" assert data == "" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 401 } @@ -31,13 +33,15 @@ defmodule ChargebeeElixir.PortalSessionTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> + fn url, data, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/portal_sessions" assert data == "" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 400, body: '{"message": "Unknown"}' @@ -54,13 +58,17 @@ defmodule ChargebeeElixir.PortalSessionTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> + fn url, data, headers -> assert url == "https://test-namespace.chargebee.com/api/v2/portal_sessions" - assert data == "customer[id]=cus_1234&redirect_url=https%3A%2F%2Fredirect.com" + + assert URI.decode(data) == + "customer[id]=cus_1234&redirect_url=https://redirect.com" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 200, body: '{"portal_session": {"url": "https://doe.com"}}' @@ -69,11 +77,11 @@ defmodule ChargebeeElixir.PortalSessionTest do ) assert ChargebeeElixir.PortalSession.create(%{ - redirect_url: "https://redirect.com", - customer: %{ - id: "cus_1234" - } - }) == %{"url" => "https://doe.com"} + redirect_url: "https://redirect.com", + customer: %{ + id: "cus_1234" + } + }) == %{"url" => "https://doe.com"} end end end diff --git a/test/chargebee_elixir/subscription_test.exs b/test/chargebee_elixir/subscription_test.exs index 170ec50..0b96c90 100644 --- a/test/chargebee_elixir/subscription_test.exs +++ b/test/chargebee_elixir/subscription_test.exs @@ -11,7 +11,7 @@ defmodule ChargebeeElixir.SubscriptionTest do plan_id: "plan-a", addons: [ %{id: "addon-a"}, - %{id: "addon-b"}, + %{id: "addon-b"} ] } ) @@ -22,13 +22,17 @@ defmodule ChargebeeElixir.SubscriptionTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/customers/cus_1/subscriptions" - assert data == "addons[id][0]=addon-a&addons[id][1]=addon-b&plan_id=plan-a" + fn url, data, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/customers/cus_1/subscriptions" + + assert URI.decode(data) == "addons[id][0]=addon-a&addons[id][1]=addon-b&plan_id=plan-a" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 401 } @@ -44,13 +48,17 @@ defmodule ChargebeeElixir.SubscriptionTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/customers/cus_1/subscriptions" - assert data == "addons[id][0]=addon-a&addons[id][1]=addon-b&plan_id=plan-a" + fn url, data, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/customers/cus_1/subscriptions" + + assert URI.decode(data) == "addons[id][0]=addon-a&addons[id][1]=addon-b&plan_id=plan-a" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 404 } @@ -66,13 +74,17 @@ defmodule ChargebeeElixir.SubscriptionTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/customers/cus_1/subscriptions" - assert data == "addons[id][0]=addon-a&addons[id][1]=addon-b&plan_id=plan-a" + fn url, data, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/customers/cus_1/subscriptions" + + assert URI.decode(data) == "addons[id][0]=addon-a&addons[id][1]=addon-b&plan_id=plan-a" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 400, body: '{"message": "Unknown"}' @@ -89,13 +101,17 @@ defmodule ChargebeeElixir.SubscriptionTest do expect( ChargebeeElixir.HTTPoisonMock, :post!, - fn (url, data, headers) -> - assert url == "https://test-namespace.chargebee.com/api/v2/customers/cus_1/subscriptions" - assert data == "addons[id][0]=addon-a&addons[id][1]=addon-b&plan_id=plan-a" + fn url, data, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/customers/cus_1/subscriptions" + + assert URI.decode(data) == "addons[id][0]=addon-a&addons[id][1]=addon-b&plan_id=plan-a" + assert headers == [ - {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, - {"Content-Type", "application/x-www-form-urlencoded"} - ] + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + %{ status_code: 200, body: '{"subscription": {"id": "sub-a"}}'