Skip to content
This repository has been archived by the owner on Aug 28, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1 from jshmrtn/payments
Browse files Browse the repository at this point in the history
Add payments Endpoint
  • Loading branch information
maennchen authored Feb 26, 2018
2 parents 0f93b46 + 502650a commit cb18cba
Show file tree
Hide file tree
Showing 16 changed files with 2,126 additions and 47 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ env:
elixir:
- 1.6
otp_release:
- 20.0
- 19.3
- 20.2
scripts: mix coveralls.travis
cache:
directories:
Expand Down
17 changes: 17 additions & 0 deletions config/.credo.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
%{
configs: [
%{
name: "default",
files: %{
included: ["lib/", "test", "mix.exs"],
excluded: []
},
checks: [
# For others you can also set parameters
{Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 120},
# Allow Namespaces for broader Alias
{Credo.Check.Design.AliasUsage, priority: :low, if_nested_deeper_than: 2}
]
}
]
}
38 changes: 10 additions & 28 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,30 +1,12 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure your application as:
#
# config :ex_wirecard, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:ex_wirecard, :key)
#
# You can also configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
config :exvcr,
vcr_cassette_library_dir: "priv/fixture/vcr_cassettes",
custom_cassette_library_dir: "priv/fixture/custom_cassettes",
filter_sensitive_data: [
[
pattern: "<merchant-account-id.+</merchant-account-id>",
placeholder: "<merchant-account-id>Foo</merchant-account-id>"
]
],
filter_request_headers: ["authorization"]
36 changes: 27 additions & 9 deletions lib/ex_wirecard.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
defmodule ExWirecard do
@moduledoc """
Documentation for ExWirecard.
API Client for WireCard.
"""

@doc """
Hello world.
defmacro __using__(opts) do
base_uri = Keyword.get(opts, :base_uri, "https://api.wirecard.com/engine/rest/")
schema_module = Keyword.fetch!(opts, :schema_module)

## Examples
quote bind_quoted: [
base_uri: base_uri,
schema_module: schema_module
] do
use Tesla

iex> ExWirecard.hello
:world
plug(Tesla.Middleware.Headers, %{"Accept" => "application/xml"})
@model schema_module.erlsom_model()
plug(ExWirecard.Middleware.XML, model: @model)

"""
def hello do
:world
def new(opts \\ %{}) do
Tesla.build_client([
{Tesla.Middleware.BaseUrl,
Application.get_env(:ex_wirecard, :base_url, unquote(base_uri))},
{Tesla.Middleware.BasicAuth,
Map.merge(
%{
username: Application.fetch_env!(:ex_wirecard, :username),
password: Application.fetch_env!(:ex_wirecard, :password)
},
opts
)}
])
end
end
end
end
154 changes: 154 additions & 0 deletions lib/ex_wirecard/middleware/xml.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
defmodule ExWirecard.Middleware.XML do
@behaviour Tesla.Middleware

alias Tesla.Middleware.Headers
alias Tesla.Multipart

@moduledoc """
Encode requests and decode responses as XML.
This middleware requires [erlsom](https://hex.pm/packages/erlsom) as dependency.
Remember to add `{:erlsom, "~> 1.4"}` to dependencies (and `:erlsom` to applications in `mix.exs`)
Also, you need to recompile tesla after adding `:erlsom` dependency:
```
mix deps.clean ex_wirecard
mix deps.compile ex_wirecard
```
### Example usage
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.XML, model: :erlsom.compile_xsd_file("some.xsd")
end
```
### Options
- `:engine_opts` - optional engine options
- `:decode_content_types` - list of additional decodable content-types
- `:model` - erlsom XML Model
"""
@default_content_types ["application/xml"]

def call(env, next, opts) do
opts = opts || []

env
|> encode(opts)
|> Tesla.run(next)
|> decode(opts)
end

@doc """
Encode request body as XML.
"""
def encode(env, opts) do
if encodable?(env) do
env
|> Map.update!(:body, &encode_body(&1, opts))
|> Headers.call([], %{"content-type" => "application/xml"})
else
env
end
end

defp encode_body(%Stream{} = body, opts), do: encode_stream(body, opts)
defp encode_body(body, opts) when is_function(body), do: encode_stream(body, opts)
defp encode_body(body, opts), do: process(body, :encode, opts)

defp encode_stream(body, opts) do
Stream.map(body, fn item -> encode_body(item, opts) <> "\n" end)
end

defp encodable?(%{body: nil}), do: false
defp encodable?(%{body: body}) when is_binary(body), do: false
defp encodable?(%{body: %Multipart{}}), do: false
defp encodable?(_), do: true

@doc """
Decode response body as XML.
"""
def decode(env, opts) do
if decodable?(env, opts) do
Map.update!(env, :body, &process(&1, :decode, opts))
else
env
end
end

defp decodable?(env, opts), do: decodable_body?(env) && decodable_content_type?(env, opts)

defp decodable_body?(env) do
(is_binary(env.body) && env.body != "") || (is_list(env.body) && env.body != [])
end

defp decodable_content_type?(env, opts) do
case env.headers["content-type"] do
nil -> false
content_type -> Enum.any?(content_types(opts), &String.starts_with?(content_type, &1))
end
end

defp content_types(opts),
do: @default_content_types ++ Keyword.get(opts, :decode_content_types, [])

defp process(data, op, opts) do
with {:ok, value} <- do_process(data, op, opts) do
value
else
{:error, reason} ->
raise %Tesla.Error{message: "XML #{op} error: #{inspect(reason)}", reason: reason}

{:error, msg, position} ->
reason = {msg, position}
raise %Tesla.Error{message: "XML #{op} error: #{inspect(reason)}", reason: reason}
end
end

defp do_process(data, :encode, opts) do
{:ok, encoded} = :erlsom.write(data, Keyword.fetch!(opts, :model))
{:ok, to_string(encoded)}
end

defp do_process(data, :decode, opts) do
case :erlsom.scan(data, Keyword.fetch!(opts, :model)) do
{:ok, decoded, _} -> {:ok, decoded}
{:error, error} -> {:error, error}
end
end
end

defmodule ExWirecard.Middleware.DecodeXML do
@moduledoc """
Decode XML
"""

alias ExWirecard.Middleware.XML

def call(env, next, opts) do
opts = opts || []

env
|> Tesla.run(next)
|> XML.decode(opts)
end
end

defmodule ExWirecard.Middleware.EncodeXML do
@moduledoc """
Encode XML
"""

alias ExWirecard.Middleware.XML

def call(env, next, opts) do
opts = opts || []

env
|> XML.encode(opts)
|> Tesla.run(next)
end
end
10 changes: 10 additions & 0 deletions lib/ex_wirecard/payment.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule ExWirecard.Payment do
@moduledoc """
Payments Endpoint Client
"""

use ExWirecard,
base_uri: "https://api.wirecard.com/engine/rest/",
schema_module: ExWirecard.Schema.Payment,
name: "payment"
end
30 changes: 30 additions & 0 deletions lib/ex_wirecard/schema.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule ExWirecard.Schema do
@moduledoc """
Base to add Schema
"""

defmacro __using__(opts) do
quote bind_quoted: [name: Keyword.fetch!(opts, :name)] do
hrl_path = Application.app_dir(:ex_wirecard, "priv/hrl/#{name}.hrl")
xsd_path = Application.app_dir(:ex_wirecard, "priv/xsd/#{name}.xsd")

require Record

for {name, fields} <- Record.extract_all(from: hrl_path) do
nice_name =
name
|> Atom.to_string()
|> String.replace(~r/-/, "_")
|> String.to_atom()

Record.defrecord(nice_name, name, fields)
end

@erlsom_model :erlsom.compile_xsd_file(xsd_path)
def erlsom_model do
{:ok, model} = @erlsom_model
model
end
end
end
end
7 changes: 7 additions & 0 deletions lib/ex_wirecard/schema/payment.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule ExWirecard.Schema.Payment do
@moduledoc """
Schema for payments endpoint
"""

use ExWirecard.Schema, name: "payment"
end
30 changes: 30 additions & 0 deletions lib/mix/tasks/ex_wirecard/xsd_to_hrl.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule Mix.Tasks.ExWirecard.XsdToHrl do
@moduledoc """
Convert all XSD Files in `priv/xsd` to HRL into `priv/hrl`.
"""
@shortdoc "Convert XSD to HRL"

use Mix.Task

@xsd_dir Application.app_dir(:ex_wirecard, "priv/xsd")
@hrl_dir Application.app_dir(:ex_wirecard, "priv/hrl")

def run(_) do
for xsd <- xsds() do
hrl = hrl_path(xsd)
IO.puts("Converting #{xsd} to #{hrl}")
:erlsom.write_xsd_hrl_file(xsd, hrl)
end
end

defp xsds do
@xsd_dir
|> File.ls!()
|> Enum.map(&Path.join(@xsd_dir, &1))
end

defp hrl_path(xsd) do
name = Path.basename(xsd, ".xsd")
Path.join(@hrl_dir, name <> ".hrl")
end
end
14 changes: 13 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ defmodule ExWirecard.MixProject do
start_permanent: Mix.env() == :prod,
test_coverage: [tool: ExCoveralls],
deps: deps(),
dialyzer: [ignore_warnings: "dialyzer.ignore-warnings"]
dialyzer: [
ignore_warnings: "dialyzer.ignore-warnings",
plt_add_apps: [:mix]
],
preferred_cli_env: [
vcr: :test,
"vcr.delete": :test,
"vcr.check": :test,
"vcr.show": :test
]
]
end

Expand All @@ -23,6 +32,9 @@ defmodule ExWirecard.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:tesla, "~> 0.10.0"},
{:exvcr, "~> 0.10", only: :test},
{:erlsom, "~> 1.4"},
{:ex_doc, "~> 0.13", only: [:dev, :docs], runtime: false},
{:excoveralls, "~> 0.5", only: [:dev, :test], runtime: false},
{:inch_ex, "~> 0.5", only: [:dev, :docs], runtime: false},
Expand Down
Loading

0 comments on commit cb18cba

Please sign in to comment.