diff --git a/config/config.exs b/config/config.exs index 80547d3..dedc2a0 100644 --- a/config/config.exs +++ b/config/config.exs @@ -29,6 +29,9 @@ config :logger, :default_formatter, # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason +config :oauth2, adapter: Tesla.Adapter.Mint +config :oauth2, middleware: [{Tesla.Middleware.FollowRedirects, max_redirects: 3}, {Tesla.Middleware.Logger, debug: true}] + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index 2858b39..3614ce2 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -51,3 +51,9 @@ config :phoenix, :stacktrace_depth, 20 # Initialize plugs at runtime for faster development compilation config :phoenix, :plug_init_mode, :runtime + +config :oauth2, debug: true + +config :camp_api, :qonto_url, + staging: true, + base_url: "https://thirdparty-sandbox.staging.qonto.co" diff --git a/lib/camp_api/application.ex b/lib/camp_api/application.ex index 329d5ac..bd45d4d 100644 --- a/lib/camp_api/application.ex +++ b/lib/camp_api/application.ex @@ -14,7 +14,8 @@ defmodule CampApi.Application do # Start a worker by calling: CampApi.Worker.start_link(arg) # {CampApi.Worker, arg}, # Start to serve requests, typically the last entry - CampApiWeb.Endpoint + CampApiWeb.Endpoint, + CampApi.PaymentLinks.Qonto ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/camp_api/payment_links.ex b/lib/camp_api/payment_links.ex new file mode 100644 index 0000000..9018fd9 --- /dev/null +++ b/lib/camp_api/payment_links.ex @@ -0,0 +1,110 @@ +defmodule CampApi.PaymentLinks do + @moduledoc """ + The PaymentLinks context. + """ + + # import Ecto.Query, warn: false + alias CampApi.Repo + + alias CampApi.PaymentLinks.Link + + alias CampApi.PaymentLinks.Qonto + + def connect() do + body = %{ + "partner_callback_url" => "http://localhost:4000", + "user_bank_account_id" => "01982d1e-6813-7bdf-a96f-0c583e6fa063", + "user_phone_number" => "+33612345678", + "user_website_url" => "http://localhost:4000", + "business_description" => "This needs a long long long description. This needs a long long long description." +} + Qonto.request(:post, "/v2/payment_links/connections", body) + end + + @doc """ + Returns the list of links. + + ## Examples + + iex> list_links() + [%Link{}, ...] + + """ + def list_links do + raise "TODO" + end + + @doc """ + Gets a single link. + + Raises if the Link does not exist. + + ## Examples + + iex> get_link!(123) + %Link{} + + """ + def get_link!(id), do: raise "TODO" + + @doc """ + Creates a link. + + ## Examples + + iex> create_link(%{field: value}) + {:ok, %Link{}} + + iex> create_link(%{field: bad_value}) + {:error, ...} + + """ + def create_link(attrs) do + raise "TODO" + end + + @doc """ + Updates a link. + + ## Examples + + iex> update_link(link, %{field: new_value}) + {:ok, %Link{}} + + iex> update_link(link, %{field: bad_value}) + {:error, ...} + + """ + def update_link(%Link{} = link, attrs) do + raise "TODO" + end + + @doc """ + Deletes a Link. + + ## Examples + + iex> delete_link(link) + {:ok, %Link{}} + + iex> delete_link(link) + {:error, ...} + + """ + def delete_link(%Link{} = link) do + raise "TODO" + end + + @doc """ + Returns a data structure for tracking link changes. + + ## Examples + + iex> change_link(link) + %Todo{...} + + """ + def change_link(%Link{} = link, _attrs \\ %{}) do + raise "TODO" + end +end diff --git a/lib/camp_api/payment_links/link.ex b/lib/camp_api/payment_links/link.ex new file mode 100644 index 0000000..ac2f906 --- /dev/null +++ b/lib/camp_api/payment_links/link.ex @@ -0,0 +1,3 @@ +defmodule CampApi.PaymentLinks.Link do + defstruct name: "John", age: 27 +end diff --git a/lib/camp_api/payment_links/qonto.ex b/lib/camp_api/payment_links/qonto.ex new file mode 100644 index 0000000..5fe0e6f --- /dev/null +++ b/lib/camp_api/payment_links/qonto.ex @@ -0,0 +1,151 @@ +defmodule CampApi.PaymentLinks.Qonto do + use GenServer + + use CampApiWeb, :verified_routes + + def base_url() do + if Application.get_env(CampApi, :qonto, :staging) do + "https://thirdparty-sandbox.staging.qonto.co" + else + "" + end + end + + def oauth_url() do + if Application.get_env(CampApi, :qonto, :staging) do + "https://oauth-sandbox.staging.qonto.co" + else + "https://oauth.qonto.com" + end + end + + def client_id() do + if Application.get_env(CampApi, :qonto, :staging) do + "d78920aa-e35f-4bfd-97df-e457f8337e2d" + else + "" + end + end + + def client_secret() do + if Application.get_env(CampApi, :qonto, :staging) do + "EqrS29dZYPy1Nfg8V8Bt6aRR1u" + else + "" + end + end + + def start_link(_) do + GenServer.start_link(__MODULE__, %{}, name: __MODULE__) + end + + def request(method, url, body) do + GenServer.call(__MODULE__, {:request, method, url, body}) + end + + def init(%{}) do + oauth_client = + OAuth2.Client.new( + strategy: OAuth2.Strategy.AuthCode, + client_id: client_id(), + client_secret: client_secret(), + site: oauth_url(), + redirect_uri: url(~p"/api/qonto_auth"), + authorize_url: "/oauth2/auth", + token_url: "/oauth2/token" + ) + |> OAuth2.Client.put_serializer("application/json;charset=UTF-8", Jason) + |> OAuth2.Client.put_serializer("application/json", Jason) + + {:ok, %{status: :disconnected, auth_state: nil, oauth_client: oauth_client}, {:continue, nil}} + end + + def handle_continue(_, state) do + auth_state = :crypto.strong_rand_bytes(24) |> Base.url_encode64(padding: false) + + # auth_url = %URI{ + # # scheme: "https", + # # host: oauth_url(), + # URI.parse(oauth_url()) | + # path: "/oauth2/auth", + # query: + # URI.encode_query(%{ + # client_id: client_id(), + # redirect_uri: url(~p"/api/qonto_auth"), + # scope: "payment_link.write payment_link.read", + # response_type: "code", + # state: auth_state + # # organization_id: TODO + # }) + # } + + auth_url = OAuth2.Client.authorize_url!(state.oauth_client, state: auth_state, scope: "payment_link.write payment_link.read") + + IO.puts("Plz go to the following URL:") + IO.puts(auth_url) + + {:noreply, %{state | status: :pending, auth_state: auth_state}} + end + + def create_token(code, auth_state) do + GenServer.call(__MODULE__, {:create_token, code, auth_state}) + end + + def handle_call({:create_token, code, auth_state}, _from, state) do + # with state.status == :pending && state.auth_state == auth_state, + # {:ok, %{body: %{"json" => %{"access_token" => access_token, "refresh_token" => refresh_token}}}} <- Req.post(base_url: oauth_url(), url: "/outh2/token", + # {:reply, :ok, %{state | status: :connected, + # + headers = + if Application.get_env(CampApi, :qonto, :staging) do + [ + {"x-qonto-staging-token", "0CxYRit4ZV7uwjoUluN93XgQvOt7bke96Nn0x5MUg2w="}, + {"authorization", ""} + ] + else + [] + end + + with state.status == :pending && state.auth_state == auth_state, + {:ok, oauth_client} <- OAuth2.Client.get_token(state.oauth_client, [code: code, client_secret: state.oauth_client.client_secret], headers) do + {:reply, :ok, %{state | status: :connected, oauth_client: oauth_client}} + else + _ -> + {:reply, :error, state} + end + end + + # def handle_call({:request, method, url, body}, _from, state) do + # access_token = + # headers = + # if Application.get_env(CampApi, :qonto, :staging) do + # [ + # authorization: "Bearer #{state.access_token}", + # x_qonto_staging_token: "0CxYRit4ZV7uwjoUluN93XgQvOt7bke96Nn0x5MUg2w=" + # ] + # else + # [] + # end + # + # Req.request(method: method, base_url: base_url(), url: url, headers: headers, json: body) + # end + + def handle_call({:request, method, url, body}, _from, state) do + url = base_url() <> url + + headers = + if Application.get_env(CampApi, :qonto, :staging) do + [ + # x_qonto_staging_token: "0CxYRit4ZV7uwjoUluN93XgQvOt7bke96Nn0x5MUg2w=" + {"x-qonto-staging-token", "0CxYRit4ZV7uwjoUluN93XgQvOt7bke96Nn0x5MUg2w="} + ] + else + [] + end + + case OAuth2.Request.request(method, state.oauth_client, url, Jason.encode!(body), headers, []) do + {:ok, resp} -> {:reply, {:ok, resp}, state} + _ -> {:reply, :error, state} + end + end +end diff --git a/lib/camp_api_web/controllers/changeset_json.ex b/lib/camp_api_web/controllers/changeset_json.ex new file mode 100644 index 0000000..df5d474 --- /dev/null +++ b/lib/camp_api_web/controllers/changeset_json.ex @@ -0,0 +1,25 @@ +defmodule CampApiWeb.ChangesetJSON do + @doc """ + Renders changeset errors. + """ + def error(%{changeset: changeset}) do + # When encoded, the changeset returns its errors + # as a JSON object. So we just pass it forward. + %{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)} + end + + defp translate_error({msg, opts}) do + # You can make use of gettext to translate error messages by + # uncommenting and adjusting the following code: + + # if count = opts[:count] do + # Gettext.dngettext(CampApiWeb.Gettext, "errors", msg, msg, count, opts) + # else + # Gettext.dgettext(CampApiWeb.Gettext, "errors", msg, opts) + # end + + Enum.reduce(opts, msg, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end) + end) + end +end diff --git a/lib/camp_api_web/controllers/fallback_controller.ex b/lib/camp_api_web/controllers/fallback_controller.ex new file mode 100644 index 0000000..cfcd3b6 --- /dev/null +++ b/lib/camp_api_web/controllers/fallback_controller.ex @@ -0,0 +1,16 @@ +defmodule CampApiWeb.FallbackController do + @moduledoc """ + Translates controller action results into valid `Plug.Conn` responses. + + See `Phoenix.Controller.action_fallback/1` for more details. + """ + use CampApiWeb, :controller + + # This clause is an example of how to handle resources that cannot be found. + def call(conn, {:error, :not_found}) do + conn + |> put_status(:not_found) + |> put_view(html: CampApiWeb.ErrorHTML, json: CampApiWeb.ErrorJSON) + |> render(:"404") + end +end diff --git a/lib/camp_api_web/controllers/payment_link_controller.ex b/lib/camp_api_web/controllers/payment_link_controller.ex new file mode 100644 index 0000000..1b5ca86 --- /dev/null +++ b/lib/camp_api_web/controllers/payment_link_controller.ex @@ -0,0 +1,58 @@ +defmodule CampApiWeb.PaymentLinkController do + use CampApiWeb, :controller + + alias CampApi.PaymentLinks + alias CampApi.PaymentLinks.Link + alias CampApi.PaymentLinks.Qonto + + action_fallback CampApiWeb.FallbackController + + def qonto_auth(conn, %{"code" => code, "state" => state}) do + case Qonto.create_token(code, state) do + :ok -> + conn + # |> put_resp_content_type + |> send_resp(200, "connected") + + :error -> + conn + # |> put_resp_content_type + |> send_resp(500, "error") + end + end + + # def index(conn, _params) do + # links = PaymentLinks.list_links() + # render(conn, :index, links: links) + # end + + def create(conn, %{"link" => link_params}) do + with {:ok, %Link{} = link} <- PaymentLinks.create_link(link_params) do + conn + |> put_status(:created) + |> put_resp_header("location", ~p"/api/links/#{link}") + |> render(:show, link: link) + end + end + + # def show(conn, %{"id" => id}) do + # link = PaymentLinks.get_link!(id) + # render(conn, :show, link: link) + # end + + # def update(conn, %{"id" => id, "link" => link_params}) do + # link = PaymentLinks.get_link!(id) + # + # with {:ok, %Link{} = link} <- PaymentLinks.update_link(link, link_params) do + # render(conn, :show, link: link) + # end + # end + + # def delete(conn, %{"id" => id}) do + # link = PaymentLinks.get_link!(id) + # + # with {:ok, %Link{}} <- PaymentLinks.delete_link(link) do + # send_resp(conn, :no_content, "") + # end + # end +end diff --git a/lib/camp_api_web/controllers/payment_link_json.ex b/lib/camp_api_web/controllers/payment_link_json.ex new file mode 100644 index 0000000..abc2393 --- /dev/null +++ b/lib/camp_api_web/controllers/payment_link_json.ex @@ -0,0 +1,24 @@ +defmodule CampApiWeb.PaymentLinkJSON do + alias CampApi.PaymentLinks.Link + + @doc """ + Renders a list of links. + """ + def index(%{links: links}) do + %{data: for(link <- links, do: data(link))} + end + + @doc """ + Renders a single link. + """ + def show(%{link: link}) do + %{data: data(link)} + end + + defp data(%Link{} = link) do + %{ + id: link.id, + price: link.price + } + end +end diff --git a/lib/camp_api_web/router.ex b/lib/camp_api_web/router.ex index 0a74d62..669ab00 100644 --- a/lib/camp_api_web/router.ex +++ b/lib/camp_api_web/router.ex @@ -7,6 +7,10 @@ defmodule CampApiWeb.Router do scope "/api", CampApiWeb do pipe_through :api + + resources "/payment_links", PaymentLinkController, only: [:create] + + get "/qonto_auth", PaymentLinkController, :qonto_auth end # Enable LiveDashboard in development diff --git a/mix.exs b/mix.exs index fc07daa..5f82b6e 100644 --- a/mix.exs +++ b/mix.exs @@ -45,7 +45,9 @@ defmodule CampApi.MixProject do {:telemetry_poller, "~> 1.0"}, {:jason, "~> 1.2"}, {:dns_cluster, "~> 0.2.0"}, - {:bandit, "~> 1.5"} + {:bandit, "~> 1.5"}, + {:req, "~> 0.5.0"}, + {:oauth2, "~> 2.0"} ] end diff --git a/mix.lock b/mix.lock index a77cfc2..efa9a94 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,14 @@ %{ "bandit": {:hex, :bandit, "1.9.0", "6dc1ff2c30948dfecf32db574cc3447c7b9d70e0b61140098df3818870b01b76", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "2538aaa1663b40ca9cbd8ca1f8a540cb49e5baf34c6ffef068369cc45f9146f2"}, "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, + "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "oauth2": {:hex, :oauth2, "2.1.0", "beb657f393814a3a7a8a15bd5e5776ecae341fd344df425342a3b6f1904c2989", [:mix], [{:tesla, "~> 1.5", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "8ac07f85b3307dd1acfeb0ec852f64161b22f57d0ce0c15e616a1dfc8ebe2b41"}, "phoenix": {:hex, :phoenix, "1.8.3", "49ac5e485083cb1495a905e47eb554277bdd9c65ccb4fc5100306b350151aa95", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "36169f95cc2e155b78be93d9590acc3f462f1e5438db06e6248613f27c80caec"}, "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, @@ -12,9 +17,11 @@ "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "req": {:hex, :req, "0.5.16", "99ba6a36b014458e52a8b9a0543bfa752cb0344b2a9d756651db1281d4ba4450", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "974a7a27982b9b791df84e8f6687d21483795882a7840e8309abdbe08bb06f09"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, + "tesla": {:hex, :tesla, "1.15.3", "3a2b5c37f09629b8dcf5d028fbafc9143c0099753559d7fe567eaabfbd9b8663", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "98bb3d4558abc67b92fb7be4cd31bb57ca8d80792de26870d362974b58caeda7"}, "thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, diff --git a/script.sh b/script.sh new file mode 100644 index 0000000..787ff2a --- /dev/null +++ b/script.sh @@ -0,0 +1,27 @@ +curl --request POST \ + --url https://thirdparty-sandbox.staging.qonto.co/v2/payment_links \ + --header 'Authorization: Bearer ory_at_l82B_-dFKvmqmw2wZ3qVuDb85M2jKDUmcX9qUIBu1Bw.SxFi-0Cs2L-kjN5pg9_VeEhHUNo52s3bcqaDsnq2gqs' \ + --header 'Content-Type: application/json' \ + --header 'X-Qonto-Staging-Token: 0CxYRit4ZV7uwjoUluN93XgQvOt7bke96Nn0x5MUg2w=' \ + --data ' +{ + "payment_link": { + "items": [{ + "title": "Participation", + "quantity": 1, + "unit_price": { + "value": "10.99", + "currency": "EUR" + }, + "vat_rate": "20.0", + "type": "service", + "description": "Participation", + "measure_unit": "unit" + }], + "potential_payment_methods": [ + "credit_card" + ], + "reusable": false + } +} +' diff --git a/test/camp_api/payment_links_test.exs b/test/camp_api/payment_links_test.exs new file mode 100644 index 0000000..8f43a23 --- /dev/null +++ b/test/camp_api/payment_links_test.exs @@ -0,0 +1,59 @@ +defmodule CampApi.PaymentLinksTest do + use CampApi.DataCase + + alias CampApi.PaymentLinks + + describe "links" do + alias CampApi.PaymentLinks.Link + + import CampApi.PaymentLinksFixtures + + @invalid_attrs %{price: nil} + + test "list_links/0 returns all links" do + link = link_fixture() + assert PaymentLinks.list_links() == [link] + end + + test "get_link!/1 returns the link with given id" do + link = link_fixture() + assert PaymentLinks.get_link!(link.id) == link + end + + test "create_link/1 with valid data creates a link" do + valid_attrs = %{price: "some price"} + + assert {:ok, %Link{} = link} = PaymentLinks.create_link(valid_attrs) + assert link.price == "some price" + end + + test "create_link/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = PaymentLinks.create_link(@invalid_attrs) + end + + test "update_link/2 with valid data updates the link" do + link = link_fixture() + update_attrs = %{price: "some updated price"} + + assert {:ok, %Link{} = link} = PaymentLinks.update_link(link, update_attrs) + assert link.price == "some updated price" + end + + test "update_link/2 with invalid data returns error changeset" do + link = link_fixture() + assert {:error, %Ecto.Changeset{}} = PaymentLinks.update_link(link, @invalid_attrs) + assert link == PaymentLinks.get_link!(link.id) + end + + test "delete_link/1 deletes the link" do + link = link_fixture() + assert {:ok, %Link{}} = PaymentLinks.delete_link(link) + assert_raise Ecto.NoResultsError, fn -> PaymentLinks.get_link!(link.id) end + end + + test "change_link/1 returns a link changeset" do + link = link_fixture() + assert %Ecto.Changeset{} = PaymentLinks.change_link(link) + end + end +end diff --git a/test/camp_api_web/controllers/link_controller_test.exs b/test/camp_api_web/controllers/link_controller_test.exs new file mode 100644 index 0000000..a9d4c8f --- /dev/null +++ b/test/camp_api_web/controllers/link_controller_test.exs @@ -0,0 +1,84 @@ +defmodule CampApiWeb.LinkControllerTest do + use CampApiWeb.ConnCase + + import CampApi.PaymentLinksFixtures + alias CampApi.PaymentLinks.Link + + @create_attrs %{ + price: "some price" + } + @update_attrs %{ + price: "some updated price" + } + @invalid_attrs %{price: nil} + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + describe "index" do + test "lists all links", %{conn: conn} do + conn = get(conn, ~p"/api/links") + assert json_response(conn, 200)["data"] == [] + end + end + + describe "create link" do + test "renders link when data is valid", %{conn: conn} do + conn = post(conn, ~p"/api/links", link: @create_attrs) + assert %{"id" => id} = json_response(conn, 201)["data"] + + conn = get(conn, ~p"/api/links/#{id}") + + assert %{ + "id" => ^id, + "price" => "some price" + } = json_response(conn, 200)["data"] + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/api/links", link: @invalid_attrs) + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "update link" do + setup [:create_link] + + test "renders link when data is valid", %{conn: conn, link: %Link{id: id} = link} do + conn = put(conn, ~p"/api/links/#{link}", link: @update_attrs) + assert %{"id" => ^id} = json_response(conn, 200)["data"] + + conn = get(conn, ~p"/api/links/#{id}") + + assert %{ + "id" => ^id, + "price" => "some updated price" + } = json_response(conn, 200)["data"] + end + + test "renders errors when data is invalid", %{conn: conn, link: link} do + conn = put(conn, ~p"/api/links/#{link}", link: @invalid_attrs) + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "delete link" do + setup [:create_link] + + test "deletes chosen link", %{conn: conn, link: link} do + conn = delete(conn, ~p"/api/links/#{link}") + assert response(conn, 204) + + assert_error_sent 404, fn -> + get(conn, ~p"/api/links/#{link}") + end + end + end + + defp create_link(_) do + link = link_fixture() + + %{link: link} + end +end diff --git a/test/support/fixtures/payment_links_fixtures.ex b/test/support/fixtures/payment_links_fixtures.ex new file mode 100644 index 0000000..f6874ed --- /dev/null +++ b/test/support/fixtures/payment_links_fixtures.ex @@ -0,0 +1,20 @@ +defmodule CampApi.PaymentLinksFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `CampApi.PaymentLinks` context. + """ + + @doc """ + Generate a link. + """ + def link_fixture(attrs \\ %{}) do + {:ok, link} = + attrs + |> Enum.into(%{ + price: "some price" + }) + |> CampApi.PaymentLinks.create_link() + + link + end +end