Date: Thu, 11 Jan 2018 00:48:28 +0530
Subject: [PATCH 12/60] Update README.md
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index ebad2c06..e89605a0 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
- Gringotts is a payment processing library in Elixir integrating various payment gateways, this draws motivation for shopify's activemerchant gem.
+ Gringotts is a payment processing library in Elixir integrating various payment gateways, this draws motivation for shopify's activemerchant gem. Checkout the Demo here.
@@ -101,6 +101,7 @@ end
[stripe]: https://www.stripe.com/
[trexle]: https://www.trexle.com/
[wirecard]: http://www.wirecard.com
+[demo]: https://gringottspay.herokuapp.com/
## Road Map
From 9a4c89bbf66e3276e8b22bbe087b3a945acd56b4 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Tue, 9 Jan 2018 19:41:10 +0530
Subject: [PATCH 13/60] Adds a super useful mix task: gringotts.new
* Generates a barebones implementation
* Also generates docs dynamically,
- If no required_keys are supplied when the task is run, the docs do
not contain an empty table.
- Otherwise, the table and config examples include the keys.
---
lib/mix/new.ex | 97 +++++++++++++++
templates/gateway.eex | 275 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 372 insertions(+)
create mode 100644 lib/mix/new.ex
create mode 100644 templates/gateway.eex
diff --git a/lib/mix/new.ex b/lib/mix/new.ex
new file mode 100644
index 00000000..2165e47b
--- /dev/null
+++ b/lib/mix/new.ex
@@ -0,0 +1,97 @@
+defmodule Mix.Tasks.Gringotts.New do
+ @shortdoc """
+ Generates a barebones implementation for a gateway.
+ """
+
+ @moduledoc """
+ Generates a barebones implementation for a gateway.
+
+ It expects the (brand) name of the gateway as argument. This will not
+ necessarily be the module name, but we recommend the name be capitalized.
+
+ mix gringotts.new NAME [-m, --module MODULE] [--url URL]
+
+ A barebones implementation of the gateway will be created along with skeleton
+ mock and integration tests in `lib/gringotts/gateways/`. The command will
+ prompt for the module name, and other metadata.
+
+ ## Options
+
+ > ***Tip!***
+ > You can supply the extra arguments to `gringotts.new` to skip (some of) the
+ > prompts.
+
+ * `-m` `--module` - The module name for the Gateway.
+ * `--url` - The homepage of the gateway.
+
+ ## Examples
+
+ mix gringotts.new foobar
+
+ The prompts for this will be:
+ MODULE = `Foobar`
+ URL = `https://www.foobar.com`
+ REQUIRED_KEYS = []
+ """
+
+ use Mix.Task
+ import Mix.Generator
+
+ @long_msg ~s{
+Comma separated list of required configuration keys:
+(This can be skipped by hitting `Enter`)
+> }
+
+ def run(args) do
+ {key_list, [name], []} =
+ OptionParser.parse(
+ args,
+ switches: [module: :string, url: :string],
+ aliases: [m: :module]
+ )
+
+ Mix.Shell.IO.info("Generating barebones implementation for #{name}.")
+ Mix.Shell.IO.info("Hit enter to select the suggestion.")
+
+ module_name =
+ case Keyword.fetch(key_list, :module) do
+ :error -> prompt_with_suggestion("\nModule name", String.capitalize(name))
+ {:ok, mod_name} -> mod_name
+ end
+
+ url =
+ case Keyword.fetch(key_list, :url) do
+ :error -> prompt_with_suggestion("\nHomepage URL", "https://www.#{String.Casing.downcase(name)}.com")
+ {:ok, url} -> url
+ end
+
+ required_keys =
+ case Mix.Shell.IO.prompt(@long_msg) |> String.trim do
+ "" -> []
+ keys -> String.split(keys, ",") |> Enum.map(&(String.trim(&1))) |> Enum.map(&(String.to_atom(&1)))
+ end
+
+ bindings = [
+ gateway: name,
+ gateway_module: module_name,
+ gateway_underscore: Macro.underscore(name),
+ required_config_keys: required_keys,
+ gateway_url: url
+ ]
+
+ if (Mix.Shell.IO.yes? "\nDoes this look good?\n#{inspect(bindings, pretty: true)}\n>") do
+ gateway = EEx.eval_file("templates/gateway.eex", bindings)
+ # mock = ""
+ # integration = ""
+ create_file("lib/gringotts/gateways/#{bindings[:gateway_underscore]}.ex", gateway)
+ else
+ Mix.Shell.IO.info("Doing nothing, bye!")
+ end
+ end
+
+ defp prompt_with_suggestion(message, suggestion) do
+ decorated_message = "#{message} [#{suggestion}]"
+ response = Mix.Shell.IO.prompt(decorated_message) |> String.trim
+ if response == "", do: suggestion, else: response
+ end
+end
diff --git a/templates/gateway.eex b/templates/gateway.eex
new file mode 100644
index 00000000..78165a5e
--- /dev/null
+++ b/templates/gateway.eex
@@ -0,0 +1,275 @@
+defmodule Gringotts.Gateways.<%= gateway_module %> do
+ @moduledoc """
+ [<%= gateway %>][home] gateway implementation.
+
+ ## Instructions!
+
+ ***This is an example `moduledoc`, and suggests some items that should be
+ documented in here.***
+
+ The quotation boxes like the one below will guide you in writing excellent
+ documentation for your gateway. All our gateways are documented in this manner
+ and we aim to keep our docs as consistent with each other as possible.
+ **Please read them and do as they suggest**. Feel free to add or skip sections
+ though.
+
+ If you'd like to make edits to the template docs, they exist at
+ `templates/gateway.eex`. We encourage you to make corrections and open a PR
+ and tag it with the label `template`.
+
+ ***Actual docs begin below this line!***
+
+ --------------------------------------------------------------------------------
+
+ > List features that have been implemented, and what "actions" they map to as
+ > per the <%= gateway %> gateway docs.
+ > A table suits really well for this.
+
+ ## Optional or extra parameters
+
+ Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply
+ optional arguments for transactions with the gateway.
+
+ > List all available (ie, those that will be supported by this module) keys, a
+ > description of their function/role and whether they have been implemented
+ > and tested.
+ > A table suits really well for this.
+
+ ## Registering your <%= gateway %> account at `Gringotts`
+
+ Explain how to make an account with the gateway and show how to put the
+ `required_keys` (like authentication info) to the configuration.
+ <%= if required_config_keys != [] do %>
+ > Here's how the secrets map to the required configuration parameters for <%= gateway %>:
+ >
+ > | Config parameter | <%= gateway %> secret |
+ > | ------- | ---- |
+ <%= for key <- required_config_keys do %>> | `<%= inspect(key) %>` | **<%= Macro.camelize("#{key}") %>** |
+ <% end %><% end %>
+ > Your Application config<%= if required_config_keys != [] do %> **must include the `<%= inspect(required_config_keys) %>` field(s)** and<% end %> would look
+ > something like this:
+ >
+ > config :gringotts, Gringotts.Gateways.<%= gateway_module %>,
+ > adapter: Gringotts.Gateways.<%= gateway_module %><%= if required_config_keys != [] do %>,<% end %>
+ <%= for key <- required_config_keys do %>> <%= "#{key}" %>: "your_secret_<%= "#{key}" %>"
+ <% end %>
+
+ ## Scope of this module
+
+ > It's unlikely that your first iteration will support all features of the
+ > gateway, so list down those items that are missing.
+
+ ## Supported currencies and countries
+
+ > It's enough if you just add a link to the gateway's docs or FAQ that provide
+ > info about this.
+
+ ## Following the examples
+
+ 1. First, set up a sample application and configure it to work with MONEI.
+ - You could do that from scratch by following our [Getting Started][gs] guide.
+ - To save you time, we recommend [cloning our example
+ repo][example] that gives you a pre-configured sample app ready-to-go.
+ + You could use the same config or update it the with your "secrets"
+ as described [above](#module-registering-your-monei-account-at-<%=
+ gateway %>).
+
+ 2. Run an `iex` session with `iex -S mix` and add some variable bindings and
+ aliases to it (to save some time):
+ ```
+ iex> alias Gringotts.{Response, CreditCard, Gateways.<%= gateway_module %>}
+ iex> card = %CreditCard{first_name: "Jo",
+ last_name: "Doe",
+ number: "4200000000000000",
+ year: 2099, month: 12,
+ verification_code: "123", brand: "VISA"}
+ ```
+
+ > Add any other frequently used bindings up here.
+
+ We'll be using these in the examples below.
+
+ [gs]: https://github.com/aviabird/gringotts/wiki/
+ [home]: <%= gateway_url %>
+ [example]: https://github.com/aviabird/gringotts_example
+ """
+
+ # The Base module has the (abstract) public API, and some utility
+ # implementations.
+ use Gringotts.Gateways.Base
+
+ # The Adapter module provides the `validate_config/1`
+ # Add the keys that must be present in the Application config in the
+ # `required_config` list
+ use Gringotts.Adapter, required_config: <%= inspect(required_config_keys) %>
+
+ import Poison, only: [decode: 1]
+
+ alias Gringotts.{Money,
+ CreditCard,
+ Response}
+
+ @doc """
+ Performs a (pre) Authorize operation.
+
+ The authorization validates the `card` details with the banking network,
+ places a hold on the transaction `amount` in the customer’s issuing bank.
+
+ > ** You could perhaps:**
+ > 1. describe what are the important fields in the Response struct
+ > 2. mention what a merchant can do with these important fields (ex:
+ > `capture/3`, etc.)
+
+ ## Note
+
+ > If there's anything noteworthy about this operation, it comes here.
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def authorize(amount, card = %CreditCard{}, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Captures a pre-authorized `amount`.
+
+ `amount` is transferred to the merchant account by <%= gateway %> used in the
+ pre-authorization referenced by `payment_id`.
+
+ ## Note
+
+ > If there's anything noteworthy about this operation, it comes here.
+ > For example, does the gateway support partial, multiple captures?
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec capture(Money.t, String.t(), keyword) :: {:ok | :error, Response}
+ def capture(amount, payment_id, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Transfers `amount` from the customer to the merchant.
+
+ <%= gateway %> attempts to process a purchase on behalf of the customer, by
+ debiting `amount` from the customer's account by charging the customer's
+ `card`.
+
+ ## Note
+
+ > If there's anything noteworthy about this operation, it comes here.
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec purchase(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def purchase(amount, card = %CreditCard{}, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Voids the referenced payment.
+
+ This method attempts a reversal of a previous transaction referenced by
+ `payment_id`.
+
+ > As a consequence, the customer will never see any booking on his statement.
+
+ ## Note
+
+ > Which transactions can be voided?
+ > Is there a limited time window within which a void can be perfomed?
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec void(String.t(), keyword) :: {:ok | :error, Response}
+ def void(payment_id, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Refunds the `amount` to the customer's account with reference to a prior transfer.
+
+ > Refunds are allowed on which kinds of "prior" transactions?
+
+ ## Note
+
+ > The end customer will usually see two bookings/records on his statement. Is
+ > that true for <%= gateway %>?
+ > Is there a limited time window within which a void can be perfomed?
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec refund(Money.t, String.t(), keyword) :: {:ok | :error, Response}
+ def refund(amount, <>, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Stores the payment-source data for later use.
+
+ > This usually enable "One Click" and/or "Recurring Payments"
+
+ ## Note
+
+ > If there's anything noteworthy about this operation, it comes here.
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def store(%CreditCard{} = card, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Removes card or payment info that was previously `store/2`d
+
+ Deletes previously stored payment-source data.
+
+ ## Note
+
+ > If there's anything noteworthy about this operation, it comes here.
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec unstore(String.t(), keyword) :: {:ok | :error, Response}
+ def unstore(<>, opts) do
+ # commit(args, ...)
+ end
+
+ ###############################################################################
+ # PRIVATE METHODS #
+ ###############################################################################
+
+ # Makes the request to <%= gateway %>'s network.
+ # For consistency with other gateway implementations, make your (final)
+ # network request in here, and parse it using another private method called
+ # `respond`.
+ @spec commit(_) :: {:ok | :error, Response}
+ defp commit(_) do
+ # resp = HTTPoison.request(args, ...)
+ # respond(resp, ...)
+ end
+
+ # Parses <%= gateway %>'s response and returns a `Gringotts.Response` struct
+ # in a `:ok`, `:error` tuple.
+ @spec respond(term) :: {:ok | :error, Response}
+ defp respond(<%= gateway_underscore %>_response)
+ defp respond({:ok, %{status_code: 200, body: body}}), do: "something"
+ defp respond({:ok, %{status_code: status_code, body: body}}), do: "something"
+ defp respond({:error, %HTTPoison.Error{} = error}), do: "something"
+end
From 365c3f12ab06b3618f48786664698afaae1bd7bb Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Fri, 19 Jan 2018 15:31:03 +0530
Subject: [PATCH 14/60] [money-protocol] Expansion: `to_string` and
`to_integer` (#86)
* Adds `to_string` and `to_integer` to the protocol
* Fixes #62 and #85
* Implements all protocol methods for `ex_money`
* Adds integration test with ex_money
* Adapted Monei with protocol updates
* Adds test for `Any`
* The default rounding strategy for implementations of Gringotts.Money
protocol is HALF-EVEN.
* Updated public API docs with "perils of rounding".
---
lib/gringotts.ex | 72 +++++++++------
lib/gringotts/gateways/monei.ex | 46 ++++++----
lib/gringotts/money.ex | 155 +++++++++++++++++++++++++++++---
test/integration/money.exs | 61 +++++++++++++
4 files changed, 274 insertions(+), 60 deletions(-)
create mode 100644 test/integration/money.exs
diff --git a/lib/gringotts.ex b/lib/gringotts.ex
index 0bd368e9..13d0eb07 100644
--- a/lib/gringotts.ex
+++ b/lib/gringotts.ex
@@ -27,24 +27,45 @@ defmodule Gringotts do
This argument represents the "amount", annotated with the currency unit for
the transaction. `amount` is polymorphic thanks to the `Gringotts.Money`
- protocol which can be implemented by your custom Money type.
+ protocol which can even be implemented by your very own custom Money type!
#### Note
- We support [`ex_money`][ex_money] and [`monetized`][monetized] out of the
- box, and you can drop their types in this argument and everything will work
- as expected.
- Otherwise, just wrap your `amount` with the `currency` together in a `Map` like so,
- money = %{amount: Decimal.new(100.50), currency: "USD"}
+ Gringotts supports [`ex_money`][ex_money] out of the box, just drop `ex_money`
+ types in this argument and everything will work as expected.
+
+ > Support for [`monetized`][monetized] and [`money`][money] is on the
+ > way, track it [here][iss-money-lib-support]!
+ Otherwise, just wrap your `amount` with the `currency` together in a `Map` like so,
+ money = %{value: Decimal.new("100.50"), currency: "USD"}
+
+ > When this highly precise `amount` is serialized into the network request, we
+ > use a potentially lossy `Gringotts.Money.to_string/1` or
+ > `Gringotts.Money.to_integer/1` to perform rounding (if required) using the
+ > [`half-even`][wiki-half-even] strategy.
+ >
+ > **Hence, to ensure transparency, protect sanity and save _real_ money, we
+ > STRONGLY RECOMMEND that merchants perform any required rounding and handle
+ > remainders in their application logic -- before passing the `amount` to
+ > Gringotts's API.**
+
#### Example
If you use `ex_money` in your project, and want to make an authorization for
- $2.99 to MONEI, you'll do the following:
+ $2.99 to the `XYZ` Gateway, you'll do the following:
+
+ # the money lib is aliased as "MoneyLib"
- amount = Money.new(2.99, :USD)
- Gringotts.authorize(Gringotts.Gateways.Monei, amount, some_card, extra_options)
+ amount = MoneyLib.new("2.99", :USD)
+ Gringotts.authorize(Gringotts.Gateways.XYZ, amount, some_card, extra_options)
+ [ex_money]: https://hexdocs.pm/ex_money/readme.html
+ [monetized]: https://hexdocs.pm/monetized/
+ [money]: https://hexdocs.pm/money/Money.html
+ [iss-money-lib-support]: https://github.com/aviabird/gringotts/projects/3#card-6801146
+ [wiki-half-even]: https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
+
### `card`, a payment source
Gringotts provides a `Gringotts.CreditCard` type to hold card parameters
@@ -52,6 +73,7 @@ defmodule Gringotts do
card details.
#### Note
+
Gringotts only supports payment by debit or credit card, even though the
gateways might support payment via other instruments such as e-wallets,
vouchers, bitcoins or banks. Support for these instruments is planned in
@@ -71,10 +93,7 @@ defmodule Gringotts do
`opts` is a `keyword` list of other options/information accepted by the
gateway. The format, use and structure is gateway specific and documented in
the Gateway's docs.
-
- [ex_money]: https://hexdocs.pm/ex_money/readme.html
- [monetized]: https://hexdocs.pm/monetized/
-
+
## Configuration
Merchants must provide Gateway specific configuration in their application
@@ -140,9 +159,9 @@ defmodule Gringotts do
To (pre) authorize a payment of $4.20 on a sample `card` with the `XYZ`
gateway,
- amount = Money.new(4.2, :USD)
- # IF YOU DON'T USE ex_money OR monetized
- # amount = %{value: Decimal.new(4.2), currency: "EUR"}
+ amount = Money.new("4.2", :USD)
+ # IF YOU DON'T USE ex_money
+ # amount = %{value: Decimal.new("4.2"), currency: "EUR"}
card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
{:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.XYZ, amount, card, opts)
"""
@@ -163,9 +182,9 @@ defmodule Gringotts do
To capture $4.20 on a previously authorized payment worth $4.20 by referencing
the obtained authorization `id` with the `XYZ` gateway,
- amount = Money.new(4.2, :USD)
- # IF YOU DON'T USE ex_money OR monetized
- # amount = %{value: Decimal.new(4.2), currency: "EUR"}
+ amount = Money.new("4.2", :USD)
+ # IF YOU DON'T USE ex_money
+ # amount = %{value: Decimal.new("4.2"), currency: "EUR"}
card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
Gringotts.capture(Gringotts.Gateways.XYZ, amount, auth_result.id, opts)
"""
@@ -191,9 +210,9 @@ defmodule Gringotts do
To process a purchase worth $4.2, with the `XYZ` gateway,
- amount = Money.new(4.2, :USD)
- # IF YOU DON'T USE ex_money OR monetized
- # amount = %{value: Decimal.new(4.2), currency: "EUR"}
+ amount = Money.new("4.2", :USD)
+ # IF YOU DON'T USE ex_money
+ # amount = %{value: Decimal.new("4.2"), currency: "EUR"}
card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
Gringotts.purchase(Gringotts.Gateways.XYZ, amount, card, opts)
"""
@@ -212,9 +231,9 @@ defmodule Gringotts do
To refund a previous purchase worth $4.20 referenced by `id`, with the `XYZ`
gateway,
- amount = Money.new(4.2, :USD)
- # IF YOU DON'T USE ex_money OR monetized
- # amount = %{value: Decimal.new(4.2), currency: "EUR"}
+ amount = Money.new("4.2", :USD)
+ # IF YOU DON'T USE ex_money
+ # amount = %{value: Decimal.new("4.2"), currency: "EUR"}
Gringotts.purchase(Gringotts.Gateways.XYZ, amount, id, opts)
"""
def refund(gateway, amount, id, opts \\ []) do
@@ -282,9 +301,6 @@ defmodule Gringotts do
call(:payment_worker, {:void, gateway, id, opts})
end
-
- # TODO: This is runtime error reporting fix this so that it does compile
- # time error reporting.
defp validate_config(gateway) do
# Keep the key name and adapter the same in the config in application
config = Application.get_env(:gringotts, gateway)
diff --git a/lib/gringotts/gateways/monei.ex b/lib/gringotts/gateways/monei.ex
index 4087bf0c..bebf8eb1 100644
--- a/lib/gringotts/gateways/monei.ex
+++ b/lib/gringotts/gateways/monei.ex
@@ -178,17 +178,19 @@ defmodule Gringotts.Gateways.Monei do
The following session shows how one would (pre) authorize a payment of $40 on a sample `card`.
iex> amount = %{value: Decimal.new(42), currency: "EUR"}
- iex> card = %Gringotts.CreditCard{first_name: "Jo", last_name: "Doe", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> auth_result = Gringotts.authorize(Gringotts.Gateways.Monei, amount, card, opts)
iex> auth_result.id # This is the authorization ID
"""
- @spec authorize(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response}
+ @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
def authorize(amount, card = %CreditCard{}, opts) do
+ {currency, value} = Money.to_string(amount)
+
params =
[
paymentType: "PA",
- amount: amount |> Money.value |> Decimal.to_float |> :erlang.float_to_binary(decimals: 2),
- currency: Money.currency(amount)
+ amount: value,
+ currency: currency
] ++ card_params(card)
auth_info = Keyword.fetch!(opts, :config)
@@ -215,17 +217,19 @@ defmodule Gringotts.Gateways.Monei do
authorized a payment worth $35 by referencing the obtained authorization `id`.
iex> amount = %{value: Decimal.new(42), currency: "EUR"}
- iex> card = %Gringotts.CreditCard{first_name: "Jo", last_name: "Doe", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> capture_result = Gringotts.capture(Gringotts.Gateways.Monei, 35, auth_result.id, opts)
"""
- @spec capture(Money.t, String.t(), keyword) :: {:ok | :error, Response}
+ @spec capture(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
def capture(amount, payment_id, opts)
def capture(amount, <>, opts) do
+ {currency, value} = Money.to_string(amount)
+
params = [
paymentType: "CP",
- amount: amount |> Money.value |> Decimal.to_float |> :erlang.float_to_binary(decimals: 2),
- currency: Money.currency(amount)
+ amount: value,
+ currency: currency
]
auth_info = Keyword.fetch!(opts, :config)
@@ -243,18 +247,20 @@ defmodule Gringotts.Gateways.Monei do
The following session shows how one would process a payment in one-shot,
without (pre) authorization.
- iex> amount = %{value: Decimal.new(42), currency: "EUR"}
- iex> card = %Gringotts.CreditCard{first_name: "Jo", last_name: "Doe", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> amount = %{value: Decimal.new(42), currency: "EUR"}
+ iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> purchase_result = Gringotts.purchase(Gringotts.Gateways.Monei, amount, card, opts)
"""
- @spec purchase(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response}
+ @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
def purchase(amount, card = %CreditCard{}, opts) do
+ {currency, value} = Money.to_string(amount)
+
params =
card_params(card) ++
[
paymentType: "DB",
- amount: amount |> Money.value |> Decimal.to_float |> :erlang.float_to_binary(decimals: 2),
- currency: Money.currency(amount)
+ amount: value,
+ currency: currency
]
auth_info = Keyword.fetch!(opts, :config)
@@ -290,7 +296,7 @@ defmodule Gringotts.Gateways.Monei do
authorization. Remember that our `capture/3` example only did a partial
capture.
- iex> card = %Gringotts.CreditCard{first_name: "Jo", last_name: "Doe", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> void_result = Gringotts.void(Gringotts.Gateways.Monei, auth_result.id, opts)
"""
@spec void(String.t(), keyword) :: {:ok | :error, Response}
@@ -319,15 +325,17 @@ defmodule Gringotts.Gateways.Monei do
similarily for captures).
iex> amount = %{value: Decimal.new(42), currency: "EUR"}
- iex> card = %Gringotts.CreditCard{first_name: "Jo", last_name: "Doe", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> refund_result = Gringotts.refund(Gringotts.Gateways.Monei, purchase_result.id, amount)
"""
- @spec refund(Money.t, String.t(), keyword) :: {:ok | :error, Response}
+ @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
def refund(amount, <>, opts) do
+ {currency, value} = Money.to_string(amount)
+
params = [
paymentType: "RF",
- amount: amount |> Money.value |> Decimal.to_float |> :erlang.float_to_binary(decimals: 2),
- currency: Money.currency(amount)
+ amount: value,
+ currency: currency
]
auth_info = Keyword.fetch!(opts, :config)
@@ -354,7 +362,7 @@ defmodule Gringotts.Gateways.Monei do
The following session shows how one would store a card (a payment-source) for
future use.
- iex> card = %Gringotts.CreditCard{first_name: "Jo", last_name: "Doe", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> store_result = Gringotts.store(Gringotts.Gateways.Monei, card, [])
"""
@spec store(CreditCard.t(), keyword) :: {:ok | :error, Response}
diff --git a/lib/gringotts/money.ex b/lib/gringotts/money.ex
index 431ce442..304cc764 100644
--- a/lib/gringotts/money.ex
+++ b/lib/gringotts/money.ex
@@ -8,38 +8,148 @@ defprotocol Gringotts.Money do
If your application is already using a supported Money library, just pass in
the Money struct and things will work out of the box.
- Otherwise, just wrap your `amount` with the `currency` together in a `Map` like so,
- money = %{amount: Decimal.new(2017.18), currency: "USD"}
+ Otherwise, just wrap your `amount` with the `currency` together in a `Map`
+ like so,
- and the API will accept it (as long as the currency is valid [ISO 4217 currency
- code](https://www.iso.org/iso-4217-currency-codes.html)).
+ price = %{value: Decimal.new("20.18"), currency: "USD"}
+
+ and the API will accept it (as long as the currency is valid [ISO 4217
+ currency code](https://www.iso.org/iso-4217-currency-codes.html)).
+
+ ## Note on the `Any` implementation
+
+ Both `to_string/1` and `to_integer/1` assume that the precision for the `currency`
+ is 2 digits after decimal.
"""
@fallback_to_any true
- @type t :: Gringotts.Money.t
-
- @spec currency(t) :: String.t
+ @type t :: Gringotts.Money.t()
+
@doc """
Returns the ISO 4217 compliant currency code associated with this sum of money.
This must be an UPCASE `string`
"""
+ @spec currency(t) :: String.t()
def currency(money)
- @spec value(t) :: Decimal.t
@doc """
- Returns a Decimal representing the "worth" of this sum of money in the
+ Returns a `Decimal.t` representing the "worth" of this sum of money in the
associated `currency`.
"""
+ @spec value(t) :: Decimal.t()
def value(money)
+
+ @doc """
+ Returns the ISO4217 `currency` code as string and `value` as an integer.
+
+ Useful for gateways that require amount as integer (like cents instead of
+ dollars).
+
+ ## Note
+
+ Conversion from `Decimal.t` to `integer` is potentially lossy and the rounding
+ (if required) is performed (automatically) by the Money library defining the
+ type, or in the implementation of this protocol method.
+
+ If you want to implement this method for your custom type, please ensure that
+ the rounding strategy (if any rounding is applied) must be
+ [`half_even`][wiki-half-even].
+
+ **To keep things predictable and transparent, merchants should round the
+ `amount` themselves**, perhaps by explicitly calling the relevant method of
+ the Money library in their application _before_ passing it to `Gringotts`'s
+ public API.
+
+ ## Examples
+
+ # the money lib is aliased as "MoneyLib"
+
+ iex> usd_price = MoneyLib.new("4.1234", :USD)
+ #MoneyLib<4.1234, "USD">
+ iex> Gringotts.Money.to_integer(usd_price)
+ {"USD", 412, -2}
+
+ iex> bhd_price = MoneyLib.new("4.1234", :BHD)
+ #MoneyLib<4.1234, "BHD">
+ iex> Gringotts.Money.to_integer(bhd_price)
+ {"BHD", 4123, -3}
+ # the Bahraini dinar is divided into 1000 fils unlike the dollar which is
+ # divided in 100 cents
+
+ [wiki-half-even]: https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
+ """
+ @spec to_integer(Money.t()) ::
+ {currency :: String.t(), value :: integer, exponent :: neg_integer}
+ def to_integer(money)
+
+ @doc """
+ Returns a tuple of ISO4217 `currency` code and the `value` as strings.
+
+ The stringified `value` must match this regex: `~r/-?\\d+\\.\\d\\d{n}/` where
+ `n+1` should match the required precision for the `currency`. There should be
+ no place value separators except the decimal point (like commas).
+
+ > Gringotts will not (and cannot) validate this of course.
+
+ ## Note
+
+ Conversion from `Decimal.t` to `string` is potentially lossy and the rounding
+ (if required) is performed (automatically) by the Money library defining the
+ type, or in the implementation of this protocol method.
+
+ If you want to implement this method for your custom type, please ensure that
+ the rounding strategy (if any rounding is applied) must be
+ [`half_even`][wiki-half-even].
+
+ **To keep things predictable and transparent, merchants should round the
+ `amount` themselves**, perhaps by explicitly calling the relevant method of
+ the Money library in their application _before_ passing it to `Gringotts`'s
+ public API.
+
+ ## Examples
+
+ # the money lib is aliased as "MoneyLib"
+
+ iex> usd_price = MoneyLib.new("4.1234", :USD)
+ #MoneyLib<4.1234, "USD">
+ iex> Gringotts.Money.to_string(usd_price)
+ {"USD", "4.12"}
+
+ iex> bhd_price = MoneyLib.new("4.1234", :BHD)
+ #MoneyLib<4.1234, "BHD">
+ iex> Gringotts.Money.to_string(bhd_price)
+ {"BHD", "4.123"}
+
+ [wiki-half-even]: https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
+ """
+ @spec to_string(t) :: {currency :: String.t(), value :: String.t()}
+ def to_string(money)
end
# this implementation is used for dispatch on ex_money (and will also fire for
# money)
if Code.ensure_compiled?(Money) do
defimpl Gringotts.Money, for: Money do
- def currency(money), do: money.currency |> Atom.to_string
+ def currency(money), do: money.currency |> Atom.to_string()
def value(money), do: money.amount
- end
+
+ def to_integer(money) do
+ {_, int_value, exponent, _} = Money.to_integer_exp(money)
+ {currency(money), int_value, exponent}
+ end
+
+ def to_string(money) do
+ {:ok, currency_data} = Cldr.Currency.currency_for_code(currency(money))
+ reduced = Money.reduce(money)
+
+ {
+ currency(reduced),
+ value(reduced)
+ |> Decimal.round(currency_data.digits)
+ |> Decimal.to_string()
+ }
+ end
+ end
end
if Code.ensure_compiled?(Monetized.Money) do
@@ -49,7 +159,26 @@ if Code.ensure_compiled?(Monetized.Money) do
end
end
+# Assumes that the currency is subdivided into 100 units
defimpl Gringotts.Money, for: Any do
- def currency(money), do: Map.get(money, :currency)
- def value(money), do: Map.get(money, :amount) || Map.get(money, :value)
+ def currency(%{currency: currency}), do: currency
+ def value(%{value: %Decimal{} = value}), do: value
+
+ def to_integer(%{value: %Decimal{} = value, currency: currency}) do
+ {
+ currency,
+ value
+ |> Decimal.mult(Decimal.new(100))
+ |> Decimal.round(0)
+ |> Decimal.to_integer(),
+ -2
+ }
+ end
+
+ def to_string(%{value: %Decimal{} = value, currency: currency}) do
+ {
+ currency,
+ value |> Decimal.round(2) |> Decimal.to_string()
+ }
+ end
end
diff --git a/test/integration/money.exs b/test/integration/money.exs
new file mode 100644
index 00000000..ca42febe
--- /dev/null
+++ b/test/integration/money.exs
@@ -0,0 +1,61 @@
+defmodule Gringotts.Integration.Gateways.MoneyTest do
+ use ExUnit.Case, async: true
+
+ alias Gringotts.Money, as: MoneyProtocol
+
+ @moduletag :integration
+
+ @ex_money Money.new(42, :EUR)
+ @ex_money_long Money.new("42.126456", :EUR)
+ @ex_money_bhd Money.new(42, :BHD)
+
+ @any %{value: Decimal.new(42), currency: "EUR"}
+ @any_long %{value: Decimal.new("42.126456"), currency: "EUR"}
+ @any_bhd %{value: Decimal.new("42"), currency: "BHD"}
+
+ describe "ex_money" do
+ test "value is a Decimal.t" do
+ assert match? %Decimal{}, MoneyProtocol.value(@ex_money)
+ end
+
+ test "currency is an upcase String.t" do
+ the_currency = MoneyProtocol.currency(@ex_money)
+ assert match? currency when is_binary(currency), the_currency
+ assert the_currency == String.upcase(the_currency)
+ end
+
+ test "to_integer" do
+ assert match? {"EUR", 4200, -2}, MoneyProtocol.to_integer(@ex_money)
+ assert match? {"BHD", 42000, -3}, MoneyProtocol.to_integer(@ex_money_bhd)
+ end
+
+ test "to_string" do
+ assert match? {"EUR", "42.00"}, MoneyProtocol.to_string(@ex_money)
+ assert match? {"EUR", "42.13"}, MoneyProtocol.to_string(@ex_money_long)
+ assert match? {"BHD", "42.000"}, MoneyProtocol.to_string(@ex_money_bhd)
+ end
+ end
+
+ describe "Any" do
+ test "value is a Decimal.t" do
+ assert match? %Decimal{}, MoneyProtocol.value(@any)
+ end
+
+ test "currency is an upcase String.t" do
+ the_currency = MoneyProtocol.currency(@any)
+ assert match? currency when is_binary(currency), the_currency
+ assert the_currency == String.upcase(the_currency)
+ end
+
+ test "to_integer" do
+ assert match? {"EUR", 4200, -2}, MoneyProtocol.to_integer(@any)
+ assert match? {"BHD", 4200, -2}, MoneyProtocol.to_integer(@any_bhd)
+ end
+
+ test "to_string" do
+ assert match? {"EUR", "42.00"}, MoneyProtocol.to_string(@any)
+ assert match? {"EUR", "42.13"}, MoneyProtocol.to_string(@any_long)
+ assert match? {"BHD", "42.00"}, MoneyProtocol.to_string(@any_bhd)
+ end
+ end
+end
From 6d1828f3b15b06ec7fc7706b32633c06c9cb3b81 Mon Sep 17 00:00:00 2001
From: arjun289
Date: Wed, 17 Jan 2018 23:35:17 +0530
Subject: [PATCH 15/60] add mix task for tests, mocks and integration
Added mix task to generate test file for the gateways, add
a file to define the mock responses and also to generate
integration test.
---
lib/mix/new.ex | 13 ++++++++++---
templates/integration.eex | 36 ++++++++++++++++++++++++++++++++++++
templates/mock_response.eex | 9 +++++++++
templates/test.eex | 32 ++++++++++++++++++++++++++++++++
4 files changed, 87 insertions(+), 3 deletions(-)
create mode 100644 templates/integration.eex
create mode 100644 templates/mock_response.eex
create mode 100644 templates/test.eex
diff --git a/lib/mix/new.ex b/lib/mix/new.ex
index 2165e47b..4f2bb609 100644
--- a/lib/mix/new.ex
+++ b/lib/mix/new.ex
@@ -76,14 +76,21 @@ Comma separated list of required configuration keys:
gateway_module: module_name,
gateway_underscore: Macro.underscore(name),
required_config_keys: required_keys,
- gateway_url: url
+ gateway_url: url,
+ gateway_mock_test: Macro.underscore(name) <> "_test",
+ gateway_mock_response: Macro.underscore(name) <> "_mock",
]
if (Mix.Shell.IO.yes? "\nDoes this look good?\n#{inspect(bindings, pretty: true)}\n>") do
gateway = EEx.eval_file("templates/gateway.eex", bindings)
- # mock = ""
- # integration = ""
+ mock = EEx.eval_file("templates/test.eex", bindings)
+ mock_response = EEx.eval_file("templates/mock_response.eex", bindings)
+ integration = EEx.eval_file("templates/integration.eex", bindings)
+
create_file("lib/gringotts/gateways/#{bindings[:gateway_underscore]}.ex", gateway)
+ create_file("test/gateways/#{bindings[:gateway_mock_test]}.exs", mock)
+ create_file("test/mocks/#{bindings[:gateway_mock_response]}.exs", mock_response)
+ create_file("test/integration/gateways/#{bindings[:gateway_mock_test]}.exs", integration)
else
Mix.Shell.IO.info("Doing nothing, bye!")
end
diff --git a/templates/integration.eex b/templates/integration.eex
new file mode 100644
index 00000000..65fc4bff
--- /dev/null
+++ b/templates/integration.eex
@@ -0,0 +1,36 @@
+defmodule Gringotts.Integration.Gateways.<%= gateway_module <> "Test"%> do
+ # Integration tests for the <%= gateway_module%>
+
+ use ExUnit.Case, async: false
+ alias Gringotts.Gateways.<%= gateway_module%>
+
+ @moduletag :integration
+
+ setup_all do
+ Application.put_env(:gringotts, Gringotts.Gateways.<%= gateway_module%>,
+ [
+ adapter: Gringotts.Gateways.<%= gateway_module%><%= if required_config_keys != [] do %><%= for key <- Enum.intersperse(required_config_keys, ",") do %><%= if key === "," do %><%= "#{key}" %><%= else %>
+ <%= "#{key}" %>: "your_secret_<%= "#{key}" %>"<% end %><% end %><% end %>
+ ]
+ )
+ end
+
+ # Group the test cases by public api
+ describe "purchase" do
+ end
+
+ describe "authorize" do
+ end
+
+ describe "capture" do
+ end
+
+ describe "void" do
+ end
+
+ describe "refund" do
+ end
+
+ describe "environment setup" do
+ end
+end
diff --git a/templates/mock_response.eex b/templates/mock_response.eex
new file mode 100644
index 00000000..1b0e5b63
--- /dev/null
+++ b/templates/mock_response.eex
@@ -0,0 +1,9 @@
+defmodule Gringotts.Gateways.<%= gateway_module <> "Mock"%> do
+
+ # The module should include mock responses for test cases in <%= gateway_mock_test <> ".exs"%>.
+ # e.g.
+ # def successful_purchase do
+ # {:ok, %HTTPoison.Response{body: ~s[{data: "successful_purchase"}]}
+ # end
+
+end
diff --git a/templates/test.eex b/templates/test.eex
new file mode 100644
index 00000000..db4bb72f
--- /dev/null
+++ b/templates/test.eex
@@ -0,0 +1,32 @@
+defmodule Gringotts.Gateways.<%= gateway_module <> "Test" %> do
+ # The file contains mocked tests for <%= gateway_module%>
+
+ # We recommend using [mock][1] for this, you can place the mock responses from
+ # the Gateway in `test/mocks/<%= gateway_mock_response%>.exs` file, which has also been
+ # generated for you.
+ #
+ # [1]: https://github.com/jjh42/mock
+
+ # Load the mock response file before running the tests.
+ Code.require_file "../mocks/<%= gateway_mock_response <> ".exs"%>", __DIR__
+
+ use ExUnit.Case, async: false
+ alias Gringotts.Gateways.<%= gateway_module%>
+ import Mock
+
+ # Group the test cases by public api
+ describe "purchase" do
+ end
+
+ describe "authorize" do
+ end
+
+ describe "capture" do
+ end
+
+ describe "void" do
+ end
+
+ describe "refund" do
+ end
+end
From a13569227ada1d165960aa8603c8ade48d4552a4 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Sun, 21 Jan 2018 23:50:22 +0530
Subject: [PATCH 16/60] [monei] Implements optional/extra params (#79)
* Validates currency, test for unsupported currency
* Added EUR to the supported currencies.
* Refactored expansion and validation of extras
* Removed duplicate code that extracted auth_info from opts
* Part of the params to Monei are built in the methods, all extra params
are built and validated in commit.
* [extra-params] All optional params covered: billing, shipping and merchant, invoice_id, category, transaction_id, custom, register, customer
* Added examples of these in docs and integration tests
* The inclusion was (partially) implicitly tested in the integration tests, but I moved it to more visible mock tests.
---
README.md | 6 +-
lib/gringotts/gateways/monei.ex | 383 ++++++++++++++++-------
test/gateways/monei_test.exs | 228 ++++++++++----
test/integration/gateways/monei_test.exs | 111 +++++--
4 files changed, 535 insertions(+), 193 deletions(-)
diff --git a/README.md b/README.md
index e89605a0..42e56c2f 100644
--- a/README.md
+++ b/README.md
@@ -63,8 +63,8 @@ alias Gringotts.Gateways.Monei
alias Gringotts.{CreditCard}
card = %CreditCard{
- first_name: "Harry Potter",
- last_name: "Doe",
+ first_name: "Harry",
+ last_name: "Potter",
number: "4200000000000000",
year: 2099, month: 12,
verification_code: "123",
@@ -88,7 +88,7 @@ end
| ------ | ----- |
| [Authorize.Net][anet] | AD, AT, AU, BE, BG, CA, CH, CY, CZ, DE, DK, ES, FI, FR, GB, GB, GI, GR, HU, IE, IT, LI, LU, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, SM, TR, US, VA |
| [CAMS][cams] | AU, US |
-| [MONEI][anet] | DE, EE, ES, FR, IT, US |
+| [MONEI][monei] | DE, EE, ES, FR, IT, US |
| [PAYMILL][paymill] | AD, AT, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HU, IE, IL, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK, TR, VA |
| [Stripe][stripe] | AT, AU, BE, CA, CH, DE, DK, ES, FI, FR, GB, IE, IN, IT, LU, NL, NO, SE, SG, US |
| [TREXLE][trexle] | AD, AE, AT, AU, BD, BE, BG, BN, CA, CH, CY, CZ, DE, DK, EE, EG, ES, FI, FR, GB, GI, GR, HK, HU, ID, IE, IL, IM, IN, IS, IT, JO, KW, LB, LI, LK, LT, LU, LV, MC, MT, MU, MV, MX, MY, NL, NO, NZ, OM, PH, PL, PT, QA, RO, SA, SE, SG, SI, SK, SM, TR, TT, UM, US, VA, VN, ZA |
diff --git a/lib/gringotts/gateways/monei.ex b/lib/gringotts/gateways/monei.ex
index bebf8eb1..124f1fbc 100644
--- a/lib/gringotts/gateways/monei.ex
+++ b/lib/gringotts/gateways/monei.ex
@@ -1,8 +1,8 @@
defmodule Gringotts.Gateways.Monei do
@moduledoc """
- [MONEI](https://www.monei.net) gateway implementation.
+ [MONEI][home] gateway implementation.
- For reference see [MONEI's API (v1) documentation](https://docs.monei.net).
+ For reference see [MONEI's API (v1) documentation][docs].
The following features of MONEI are implemented:
@@ -15,35 +15,51 @@ defmodule Gringotts.Gateways.Monei do
| Debit | `purchase/3` | `DB` |
| Tokenization / Registrations | `store/2` | |
- > **What's this last column `type`?**\
+ > **What's this last column `type`?**
+ >
> That's the `paymentType` of the request, which you can ignore unless you'd
- > like to contribute to this module. Please read the [MONEI
- > Guides](https://docs.monei.net).
+ > like to contribute to this module. Please read the [MONEI Guides][docs].
- ## The `opts` argument
-
- Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply
- optional arguments for transactions with the MONEI gateway. The following keys
- are supported:
+ [home]: https://monei.net
+ [docs]: https://docs.monei.net
- | Key | Remark | Status |
- | ---- | --- | ---- |
- | `billing_address` | | Not implemented |
- | `cart` | | Not implemented |
- | `customParameters` | | Not implemented |
- | `customer` | | Not implemented |
- | `invoice` | | Not implemented |
- | `merchant` | | Not implemented |
- | `shipping_address` | | Not implemented |
- | `shipping_customer` | | Not implemented |
+ ## The `opts` argument
- > All these keys are being implemented, track progress in
- > [issue #36](https://github.com/aviabird/gringotts/issues/36)!
+ Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply
+ [optional arguments][extra-arg-docs] for transactions with the MONEI
+ gateway. The following keys are supported:
+
+ | Key | Remark |
+ | ---- | --- |
+ | [`billing`][ba] | Address of the customer, which can be used for AVS risk check. |
+ | [`cart`][cart] | **Not Implemented** |
+ | [`custom`][custom] | It's a map of "name"-"value" pairs, and all of it is echoed back in the response. |
+ | [`customer`][c] | Annotate transactions with customer info on your Monei account, and helps in risk management. |
+ | [`invoice_id`][b] | Merchant provided invoice identifier, must be unique per transaction with Monei. |
+ | [`transaction_id`][b] | Merchant provided token for a transaction, must be unique per transaction with Monei. |
+ | [`category`][b] | The category of the transaction. |
+ | [`merchant`][m] | Information about the merchant, which overrides the cardholder's bank statement. |
+ | [`register`][t] | Also store payment data included in this request for future use. |
+ | [`shipping`][sa] | Location of recipient of goods, for logistics. |
+ | [`shipping_customer`][c] | Recipient details, could be different from `customer`. |
+
+ > These keys are being implemented, track progress in [issue #36][iss36]!
+
+ [extra-arg-docs]: https://docs.monei.net/reference/parameters
+ [ba]: https://docs.monei.net/reference/parameters#billing-address
+ [cart]: https://docs.monei.net/reference/parameters#cart
+ [custom]: https://docs.monei.net/reference/parameters#custom-parameters
+ [c]: https://docs.monei.net/reference/parameters#customer
+ [b]: https://docs.monei.net/reference/parameters#basic
+ [m]: https://docs.monei.net/reference/parameters#merchant
+ [t]: https://docs.monei.net/reference/parameters#tokenization
+ [sa]: https://docs.monei.net/reference/parameters#shipping-address
+ [iss36]: https://github.com/aviabird/gringotts/issues/36
## Registering your MONEI account at `Gringotts`
- After [making an account on MONEI](https://dashboard.monei.net/signin), head
- to the dashboard and find your account "secrets" in the `Sub-Accounts > Overview` section.
+ After [making an account on MONEI][dashboard], head to the dashboard and find
+ your account "secrets" in the `Sub-Accounts > Overview` section.
Here's how the secrets map to the required configuration parameters for MONEI:
@@ -62,43 +78,52 @@ defmodule Gringotts.Gateways.Monei do
password: "your_secret_password",
entityId: "your_secret_channel_id"
+ [dashboard]: https://dashboard.monei.net/signin
+
+ ## Scope of this module
+
+ * MONEI does not process money in cents, and the `amount` is rounded to 2
+ decimal places.
+ * Although MONEI supports payments from [various][all-card-list]
+ [cards][card-acc], [banks][bank-acc] and [virtual accounts][virtual-acc]
+ (like some wallets), this library only accepts payments by [(supported)
+ cards][all-card-list].
+
+ [all-card-list]: https://support.monei.net/charges-and-refunds/accepted-credit-cards-payment-methods
+ [card-acc]: https://docs.monei.net/reference/parameters#card
+ [bank-acc]: https://docs.monei.net/reference/parameters#bank-account
+ [virtual-acc]: https://docs.monei.net/reference/parameters#virtual-account
- ## Scope of this module, and _quirks_
+ ## Supported countries
- * MONEI does not process money in cents, and the `amount` is rounded to 2 decimal places.
- * Although MONEI supports payments from [various
- cards](https://support.monei.net/charges-and-refunds/accepted-credit-cards-payment-methods),
- banks and virtual accounts (like some wallets), this library only accepts
- payments by (supported) cards.
+ MONEI supports the countries listed [here][all-country-list]
- ## Supported currencies and countries
+ ## Supported currencies
- The following currencies are supported: `USD`, `GBP`, `NAD`, `TWD`, `VUV`,
- `NZD`, `NGN`, `NIO`, `NGN`, `NOK`, `PKR`, `PAB`, `PGK`, `PYG`, `PEN`, `NPR`,
- `ANG`, `AWG`, `PHP`, `QAR`, `RUB`, `RWF`, `SHP`, `STD`, `SAR`, `SCR`, `SLL`,
- `SGD`, `VND`, `SOS`, `ZAR`, `ZWL`, `YER`, `SDG`, `SZL`, `SEK`, `CHF`, `SYP`,
- `TJS`, `THB`, `TOP`, `TTD`, `AED`, `TND`, `TRY`, `AZN`, `UGX`, `MKD`, `EGP`,
- `GBP`, `TZS`, `UYU`, `UZS`, `WST`, `YER`, `RSD`, `ZMW`, `TWD`, `AZN`, `GHS`,
- `RSD`, `MZN`, `AZN`, `MDL`, `TRY`, `XAF`, `XCD`, `XOF`, `XPF`, `MWK`, `SRD`,
- `MGA`, `AFN`, `TJS`, `AOA`, `BYN`, `BGN`, `CDF`, `BAM`, `UAH`, `GEL`, `PLN`,
- `BRL` and `CUC`.
+ MONEI supports the currecncies [listed here][all-currency-list], and ***this
+ module*** supports a subset of those:
- > [Here](https://support.monei.net/international/currencies-supported-by-monei)
- > is the up-to-date currency list. _Please [raise an
- > issue](https://github.com/aviabird/gringotts/issues) if the list above has
- > become out-of-date!_
+ :AED, :AFN, :ANG, :AOA, :AWG, :AZN, :BAM, :BGN, :BRL, :BYN, :CDF, :CHF, :CUC,
+ :EGP, :EUR, :GBP, :GEL, :GHS, :MDL, :MGA, :MKD, :MWK, :MZN, :NAD, :NGN, :NIO,
+ :NOK, :NPR, :NZD, :PAB, :PEN, :PGK, :PHP, :PKR, :PLN, :PYG, :QAR, :RSD, :RUB,
+ :RWF, :SAR, :SCR, :SDG, :SEK, :SGD, :SHP, :SLL, :SOS, :SRD, :STD, :SYP, :SZL,
+ :THB, :TJS, :TOP, :TRY, :TTD, :TWD, :TZS, :UAH, :UGX, :USD, :UYU, :UZS, :VND,
+ :VUV, :WST, :XAF, :XCD, :XOF, :XPF, :YER, :ZAR, :ZMW, :ZWL
- MONEI supports the countries listed
- [here](https://support.monei.net/international/what-countries-does-monei-support).
+ > Please [raise an issue][new-issue] if you'd like us to add support for more
+ > currencies
+
+ [all-currency-list]: https://support.monei.net/international/currencies-supported-by-monei
+ [new-issue]: https://github.com/aviabird/gringotts/issues
+ [all-country-list]: https://support.monei.net/international/what-countries-does-monei-support
## Following the examples
1. First, set up a sample application and configure it to work with MONEI.
- You could do that from scratch by following our [Getting Started](#) guide.
- - To save you time, we recommend [cloning our example
- repo](https://github.com/aviabird/gringotts_example) that gives you a
- pre-configured sample app ready-to-go.
- + You could use the same config or update it the with your "secrets"
+ - To save you time, we recommend [cloning our example repo][example-repo]
+ that gives you a pre-configured sample app ready-to-go.
+ + You could use the same config or update it the with your "secrets"
that you see in `Dashboard > Sub-accounts` as described
[above](#module-registering-your-monei-account-at-gringotts).
@@ -111,12 +136,48 @@ defmodule Gringotts.Gateways.Monei do
last_name: "Potter",
number: "4200000000000000",
year: 2099, month: 12,
- verification_code: "123",
+ verification_code: "123",
brand: "VISA"}
+ iex> customer = %{"givenName": "Harry",
+ "surname": "Potter",
+ "merchantCustomerId": "the_boy_who_lived",
+ "sex": "M",
+ "birthDate": "1980-07-31",
+ "mobile": "+15252525252",
+ "email": "masterofdeath@ministryofmagic.gov",
+ "ip": "1.1.1",
+ "status": "NEW"}
+ iex> merchant = %{"name": "Ollivanders",
+ "city": "South Side",
+ "street": "Diagon Alley",
+ "state": "London",
+ "country": "GB",
+ "submerchantId": "Makers of Fine Wands since 382 B.C."}
+ iex> billing = %{"street1": "301, Gryffindor",
+ "street2": "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
+ "city": "Highlands",
+ "state": "Scotland",
+ "country": "GB"}
+ iex> shipping = %{"street1": "301, Gryffindor",
+ "street2": "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
+ "city": "Highlands",
+ "state": "Scotland",
+ "country": "GB",
+ "method": "SAME_DAY_SERVICE",
+ "comment": "For our valued customer, Mr. Potter"}
+ iex> opts = [customer: customer,
+ merchant: merchant,
+ billing: billing,
+ shipping: shipping,
+ category: "EC",
+ custom: %{"voldemort": "he who must not be named"},
+ register: true]
```
We'll be using these in the examples below.
+ [example-repo]: https://github.com/aviabird/gringotts_example
+
## TODO
* [Backoffice operations](https://docs.monei.net/tutorials/manage-payments/backoffice)
@@ -134,6 +195,16 @@ defmodule Gringotts.Gateways.Monei do
@base_url "https://test.monei-api.net"
@default_headers ["Content-Type": "application/x-www-form-urlencoded", charset: "UTF-8"]
+ @supported_currencies [
+ "AED", "AFN", "ANG", "AOA", "AWG", "AZN", "BAM", "BGN", "BRL", "BYN", "CDF",
+ "CHF", "CUC", "EGP", "EUR", "GBP", "GEL", "GHS", "MDL", "MGA", "MKD", "MWK",
+ "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "PAB", "PEN", "PGK", "PHP",
+ "PKR", "PLN", "PYG", "QAR", "RSD", "RUB", "RWF", "SAR", "SCR", "SDG", "SEK",
+ "SGD", "SHP", "SLL", "SOS", "SRD", "STD", "SYP", "SZL", "THB", "TJS", "TOP",
+ "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VND", "VUV",
+ "WST", "XAF", "XCD", "XOF", "XPF", "YER", "ZAR", "ZMW", "ZWL"
+ ]
+
@version "v1"
@cvc_code_translator %{
@@ -153,8 +224,8 @@ defmodule Gringotts.Gateways.Monei do
nil => {nil, nil}
}
- # MONEI supports payment by card, bank account and even something obscure: virtual account
- # opts has the auth keys.
+ # MONEI supports payment by card, bank account and even something obscure:
+ # virtual account opts has the auth keys.
@doc """
Performs a (pre) Authorize operation.
@@ -168,19 +239,24 @@ defmodule Gringotts.Gateways.Monei do
* `capture/3` _an_ amount.
* `void/2` a pre-authorization.
- ## Note
+ ## Note
- A stand-alone pre-authorization [expires in
- 72hrs](https://docs.monei.net/tutorials/manage-payments/backoffice).
+ * The `:register` option when set to `true` will store this card for future
+ use, and you will recieve a registration `token` in the `:token` field of
+ the `Response` struct.
+ * A stand-alone pre-authorization [expires in
+ 72hrs](https://docs.monei.net/tutorials/manage-payments/backoffice).
## Example
- The following session shows how one would (pre) authorize a payment of $40 on a sample `card`.
+ The following session shows how one would (pre) authorize a payment of $40 on
+ a sample `card`.
iex> amount = %{value: Decimal.new(42), currency: "EUR"}
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> auth_result = Gringotts.authorize(Gringotts.Gateways.Monei, amount, card, opts)
+ iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Monei, amount, card, opts)
iex> auth_result.id # This is the authorization ID
+ iex> auth_result.token # This is the registration ID/token
"""
@spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
def authorize(amount, card = %CreditCard{}, opts) do
@@ -189,12 +265,10 @@ defmodule Gringotts.Gateways.Monei do
params =
[
paymentType: "PA",
- amount: value,
- currency: currency
+ amount: value
] ++ card_params(card)
- auth_info = Keyword.fetch!(opts, :config)
- commit(:post, "payments", params, auth_info)
+ commit(:post, "payments", params, [{:currency, currency} | opts])
end
@doc """
@@ -217,8 +291,7 @@ defmodule Gringotts.Gateways.Monei do
authorized a payment worth $35 by referencing the obtained authorization `id`.
iex> amount = %{value: Decimal.new(42), currency: "EUR"}
- iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> capture_result = Gringotts.capture(Gringotts.Gateways.Monei, 35, auth_result.id, opts)
+ iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VIS iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Monei, 35, auth_result.id, opts)
"""
@spec capture(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
def capture(amount, payment_id, opts)
@@ -228,12 +301,10 @@ defmodule Gringotts.Gateways.Monei do
params = [
paymentType: "CP",
- amount: value,
- currency: currency
+ amount: value
]
- auth_info = Keyword.fetch!(opts, :config)
- commit(:post, "payments/#{payment_id}", params, auth_info)
+ commit(:post, "payments/#{payment_id}", params, [{:currency, currency} | opts])
end
@doc """
@@ -242,6 +313,12 @@ defmodule Gringotts.Gateways.Monei do
MONEI attempts to process a purchase on behalf of the customer, by debiting
`amount` from the customer's account by charging the customer's `card`.
+ ## Note
+
+ * The `:register` option when set to `true` will store this card for future
+ use, and you will recieve a registration `token` in the `:token` field of
+ the `Response` struct.
+
## Example
The following session shows how one would process a payment in one-shot,
@@ -249,22 +326,20 @@ defmodule Gringotts.Gateways.Monei do
iex> amount = %{value: Decimal.new(42), currency: "EUR"}
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> purchase_result = Gringotts.purchase(Gringotts.Gateways.Monei, amount, card, opts)
+ iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.Monei, amount, card, opts)
+ iex> purchase_result.token # This is the registration ID/token
"""
@spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
def purchase(amount, card = %CreditCard{}, opts) do
{currency, value} = Money.to_string(amount)
params =
- card_params(card) ++
- [
- paymentType: "DB",
- amount: value,
- currency: currency
- ]
-
- auth_info = Keyword.fetch!(opts, :config)
- commit(:post, "payments", params, auth_info)
+ [
+ paymentType: "DB",
+ amount: value
+ ] ++ card_params(card)
+
+ commit(:post, "payments", params, [{:currency, currency} | opts])
end
@doc """
@@ -297,15 +372,14 @@ defmodule Gringotts.Gateways.Monei do
capture.
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> void_result = Gringotts.void(Gringotts.Gateways.Monei, auth_result.id, opts)
+ iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.Monei, auth_result.id, opts)
"""
@spec void(String.t(), keyword) :: {:ok | :error, Response}
def void(payment_id, opts)
def void(<>, opts) do
params = [paymentType: "RV"]
- auth_info = Keyword.fetch!(opts, :config)
- commit(:post, "payments/#{payment_id}", params, auth_info)
+ commit(:post, "payments/#{payment_id}", params, opts)
end
@doc """
@@ -326,7 +400,7 @@ defmodule Gringotts.Gateways.Monei do
iex> amount = %{value: Decimal.new(42), currency: "EUR"}
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> refund_result = Gringotts.refund(Gringotts.Gateways.Monei, purchase_result.id, amount)
+ iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.Monei, purchase_result.id, amount)
"""
@spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
def refund(amount, <>, opts) do
@@ -334,12 +408,10 @@ defmodule Gringotts.Gateways.Monei do
params = [
paymentType: "RF",
- amount: value,
- currency: currency
+ amount: value
]
- auth_info = Keyword.fetch!(opts, :config)
- commit(:post, "payments/#{payment_id}", params, auth_info)
+ commit(:post, "payments/#{payment_id}", params, [{:currency, currency} | opts])
end
@doc """
@@ -363,13 +435,12 @@ defmodule Gringotts.Gateways.Monei do
future use.
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> store_result = Gringotts.store(Gringotts.Gateways.Monei, card, [])
+ iex> {:ok, store_result} = Gringotts.store(Gringotts.Gateways.Monei, card, [])
"""
@spec store(CreditCard.t(), keyword) :: {:ok | :error, Response}
def store(%CreditCard{} = card, opts) do
params = card_params(card)
- auth_info = Keyword.fetch!(opts, :config)
- commit(:post, "registrations", params, auth_info)
+ commit(:post, "registrations", params, opts)
end
@doc """
@@ -380,9 +451,10 @@ defmodule Gringotts.Gateways.Monei do
Deletes previously stored payment-source data.
"""
@spec unstore(String.t(), keyword) :: {:ok | :error, Response}
- def unstore(<>, opts) do
- auth_info = Keyword.fetch!(opts, :config)
- commit(:delete, "registrations/#{registrationId}", [], auth_info)
+ def unstore(registration_id, opts)
+
+ def unstore(<>, opts) do
+ commit(:delete, "registrations/#{registration_id}", [], opts)
end
defp card_params(card) do
@@ -397,34 +469,47 @@ defmodule Gringotts.Gateways.Monei do
end
# Makes the request to MONEI's network.
- @spec commit(atom, String.t(), keyword, map) :: {:ok | :error, Response}
+ @spec commit(atom, String.t(), keyword, keyword) :: {:ok | :error, Response}
defp commit(method, endpoint, params, opts) do
auth_params = [
- "authentication.userId": opts[:userId],
- "authentication.password": opts[:password],
- "authentication.entityId": opts[:entityId]
+ "authentication.userId": opts[:config][:userId],
+ "authentication.password": opts[:config][:password],
+ "authentication.entityId": opts[:config][:entityId]
]
- body = params ++ auth_params
url = "#{base_url(opts)}/#{version(opts)}/#{endpoint}"
- network_response =
- case method do
- :post -> HTTPoison.post(url, {:form, body}, @default_headers)
- :delete -> HTTPoison.delete(url <> "?" <> URI.encode_query(auth_params))
- end
-
- respond(network_response)
+ case expand_params(opts, params[:paymentType]) do
+ {:error, reason} ->
+ {:error, Response.error(description: reason)}
+
+ validated_params ->
+ network_response =
+ case method do
+ :post ->
+ HTTPoison.post(
+ url,
+ {:form, params ++ validated_params ++ auth_params},
+ @default_headers
+ )
+
+ :delete ->
+ HTTPoison.delete(url <> "?" <> URI.encode_query(auth_params))
+ end
+
+ respond(network_response)
+ end
end
- # Parses MONEI's response and returns a `Gringotts.Response` struct in a `:ok`, `:error` tuple.
+ # Parses MONEI's response and returns a `Gringotts.Response` struct in a
+ # `:ok`, `:error` tuple.
@spec respond(term) :: {:ok | :error, Response}
defp respond(monei_response)
defp respond({:ok, %{status_code: 200, body: body}}) do
case decode(body) do
{:ok, decoded_json} ->
- case verification_result(decoded_json) do
+ case parse_response(decoded_json) do
{:ok, results} -> {:ok, Response.success([{:id, decoded_json["id"]} | results])}
{:error, errors} -> {:ok, Response.error([{:id, decoded_json["id"]} | errors])}
end
@@ -443,32 +528,100 @@ defmodule Gringotts.Gateways.Monei do
:error,
Response.error(
code: error.id,
- reason: :network_fail?,
+ reason: "network related failure",
description: "HTTPoison says '#{error.reason}'"
)
}
end
- defp verification_result(%{"result" => result} = data) do
+ defp expand_params(params, action_type) do
+ Enum.reduce_while(params, [], fn {k, v}, acc ->
+ case k do
+ :currency ->
+ if valid_currency?(v),
+ do: {:cont, [{:currency, v} | acc]},
+ else: {:halt, {:error, "Invalid currency"}}
+
+ :customer ->
+ {:cont, acc ++ make("customer", v)}
+
+ :merchant ->
+ {:cont, acc ++ make("merchant", v)}
+
+ :billing ->
+ {:cont, acc ++ make("billing", v)}
+
+ :shipping ->
+ {:cont, acc ++ make("shipping", v)}
+
+ :invoice_id ->
+ {:cont, [{"merchantInvoiceId", v} | acc]}
+
+ :transaction_id ->
+ {:cont, [{"merchantTransactionId", v} | acc]}
+
+ :category ->
+ {:cont, [{"transactionCategory", v} | acc]}
+
+ :shipping_customer ->
+ {:cont, acc ++ make("shipping.customer", v)}
+
+ :custom ->
+ {:cont, acc ++ make_custom(v)}
+
+ :register ->
+ {
+ :cont,
+ if action_type in ["PA", "DB"] do
+ [{"createRegistration", true} | acc]
+ else
+ acc
+ end
+ }
+
+ _ ->
+ {:cont, acc}
+ end
+ end)
+ end
+
+ defp valid_currency?(currency) do
+ currency in @supported_currencies
+ end
+
+ defp parse_response(%{"result" => result} = data) do
{address, zip_code} = @avs_code_translator[result["avsResponse"]]
- code = result["code"]
results = [
- code: code,
+ code: result["code"],
description: result["description"],
risk: data["risk"]["score"],
cvc_result: @cvc_code_translator[result["cvvResponse"]],
avs_result: [address: address, zip_code: zip_code],
- raw: data
+ raw: data,
+ token: data["registrationId"]
]
- if String.match?(code, ~r{^(000\.000\.|000\.100\.1|000\.[36])}) do
+ filtered = Enum.filter(results, fn {_, v} -> v != nil end)
+ verify(filtered)
+ end
+
+ defp verify(results) do
+ if String.match?(results[:code], ~r{^(000\.000\.|000\.100\.1|000\.[36])}) do
{:ok, results}
else
- {:error, [{:reason, result["description"]} | results]}
+ {:error, [{:reason, results[:description]} | results]}
end
end
- defp base_url(opts), do: opts[:test_url] || @base_url
- defp version(opts), do: opts[:api_version] || @version
+ defp make(prefix, param) do
+ Enum.into(param, [], fn {k, v} -> {"#{prefix}.#{k}", v} end)
+ end
+
+ defp make_custom(custom_map) do
+ Enum.into(custom_map, [], fn {k, v} -> {"customParameters[#{k}]", "#{v}"} end)
+ end
+
+ defp base_url(opts), do: opts[:config][:test_url] || @base_url
+ defp version(opts), do: opts[:config][:api_version] || @version
end
diff --git a/test/gateways/monei_test.exs b/test/gateways/monei_test.exs
index 3ea51b12..51823a08 100644
--- a/test/gateways/monei_test.exs
+++ b/test/gateways/monei_test.exs
@@ -2,13 +2,14 @@ defmodule Gringotts.Gateways.MoneiTest do
use ExUnit.Case, async: false
alias Gringotts.{
- CreditCard,
+ CreditCard
}
+
alias Gringotts.Gateways.Monei, as: Gateway
@card %CreditCard{
- first_name: "Jo",
- last_name: "Doe",
+ first_name: "Harry",
+ last_name: "Potter",
number: "4200000000000000",
year: 2099,
month: 12,
@@ -17,8 +18,8 @@ defmodule Gringotts.Gateways.MoneiTest do
}
@bad_card %CreditCard{
- first_name: "Jo",
- last_name: "Doe",
+ first_name: "Harry",
+ last_name: "Potter",
number: "4200000000000000",
year: 2000,
month: 12,
@@ -35,6 +36,14 @@ defmodule Gringotts.Gateways.MoneiTest do
"description": "Request successfully processed in 'Merchant in Integrator Test Mode'"}
}]
+ @register_success ~s[
+ {"id": "8a82944960e073640160e92da2204743",
+ "registrationId": "8a82944a60e09c550160e92da144491e",
+ "result": {
+ "code": "000.100.110",
+ "description": "Request successfully processed in 'Merchant in Integrator Test Mode'"}
+ }]
+
@store_success ~s[
{"result":{
"code":"000.100.110",
@@ -49,83 +58,158 @@ defmodule Gringotts.Gateways.MoneiTest do
}
}]
+ @customer %{
+ givenName: "Harry",
+ surname: "Potter",
+ merchantCustomerId: "the_boy_who_lived",
+ sex: "M",
+ birthDate: "1980-07-31",
+ mobile: "+15252525252",
+ email: "masterofdeath@ministryofmagic.gov",
+ ip: "1.1.1",
+ status: "NEW"
+ }
+ @merchant %{
+ name: "Ollivanders",
+ city: "South Side",
+ street: "Diagon Alley",
+ state: "London",
+ country: "GB",
+ submerchantId: "Makers of Fine Wands since 382 B.C."
+ }
+ @billing %{
+ street1: "301, Gryffindor",
+ street2: "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
+ city: "Highlands",
+ state: "Scotland",
+ country: "GB"
+ }
+ @shipping Map.merge(
+ %{method: "SAME_DAY_SERVICE", comment: "For our valued customer, Mr. Potter"},
+ @billing
+ )
+
+ @extra_opts [
+ customer: @customer,
+ merchant: @merchant,
+ billing: @billing,
+ shipping: @shipping,
+ shipping_customer: @customer,
+ category: "EC",
+ register: true,
+ custom: %{"voldemort" => "he who must not be named"}
+ ]
+
# A new Bypass instance is needed per test, so that we can do parallel tests
setup do
- bypass = Bypass.open
+ bypass = Bypass.open()
+
auth = %{
userId: "8a829417539edb400153c1eae83932ac",
password: "6XqRtMGS2N",
entityId: "8a829417539edb400153c1eae6de325e",
test_url: "http://localhost:#{bypass.port}"
}
+
{:ok, bypass: bypass, auth: auth}
end
describe "core" do
- @tag :skip
- test "with unsupported currency.",
- %{bypass: bypass, auth: auth} do
- Bypass.expect_once bypass, "POST", "/v1/payments", fn conn ->
- Plug.Conn.resp(conn, 400, "")
- end
- {:error, response} = Gateway.authorize(@bad_currency, @card, [config: auth])
- assert response.code == :unsupported_currency
+ test "with unsupported currency.", %{auth: auth} do
+ {:error, response} = Gateway.authorize(@bad_currency, @card, config: auth)
+ assert response.description == "Invalid currency"
end
- test "when MONEI is down or unreachable.",
- %{bypass: bypass, auth: auth} do
- Bypass.expect_once bypass, fn conn ->
+ test "when MONEI is down or unreachable.", %{bypass: bypass, auth: auth} do
+ Bypass.expect_once(bypass, fn conn ->
Plug.Conn.resp(conn, 200, @auth_success)
- end
- Bypass.down bypass
- {:error, response} = Gateway.authorize(Money.new(42, :USD), @card, [config: auth])
- assert response.reason == :network_fail?
+ end)
- Bypass.up bypass
- {:ok, _} = Gateway.authorize(Money.new(42, :USD), @card, [config: auth])
+ Bypass.down(bypass)
+ {:error, response} = Gateway.authorize(Money.new(42, :USD), @card, config: auth)
+ assert response.reason == "network related failure"
+
+ Bypass.up(bypass)
+ {:ok, _} = Gateway.authorize(Money.new(42, :USD), @card, config: auth)
end
- end
- describe "authorize" do
- test "when all is good.", %{bypass: bypass, auth: auth} do
- Bypass.expect bypass, "POST", "/v1/payments", fn conn ->
- Plug.Conn.resp(conn, 200, @auth_success)
- end
- {:ok, response} = Gateway.authorize(Money.new(42, :USD), @card, [config: auth])
+ test "with all extra_params.", %{bypass: bypass, auth: auth} do
+ randoms = [
+ invoice_id: Base.encode16(:crypto.hash(:md5, :crypto.strong_rand_bytes(32))),
+ transaction_id: Base.encode16(:crypto.hash(:md5, :crypto.strong_rand_bytes(32)))
+ ]
+
+ Bypass.expect_once(bypass, "POST", "/v1/payments", fn conn ->
+ conn_ = parse(conn)
+ assert conn_.body_params["createRegistration"] == "true"
+ assert conn_.body_params["customParameters"] == @extra_opts[:custom]
+ assert conn_.body_params["merchantInvoiceId"] == randoms[:invoice_id]
+ assert conn_.body_params["merchantTransactionId"] == randoms[:transaction_id]
+ assert conn_.body_params["transactionCategory"] == @extra_opts[:category]
+ assert conn_.body_params["customer.merchantCustomerId"] == @customer[:merchantCustomerId]
+ assert conn_.body_params["shipping.customer.merchantCustomerId"] == @customer[:merchantCustomerId]
+ assert conn_.body_params["merchant.submerchantId"] == @merchant[:submerchantId]
+ assert conn_.body_params["billing.city"] == @billing[:city]
+ assert conn_.body_params["shipping.method"] == @shipping[:method]
+ Plug.Conn.resp(conn, 200, @register_success)
+ end)
+
+ opts = [{:config, auth} | randoms] ++ @extra_opts
+ {:ok, response} = Gateway.purchase(Money.new(42, :USD), @card, opts)
assert response.code == "000.100.110"
+ assert response.token == "8a82944a60e09c550160e92da144491e"
end
- test "when we get non-json.", %{bypass: bypass, auth: auth} do
- Bypass.expect_once bypass, "POST", "/v1/payments", fn conn ->
+ test "when card has expired.", %{bypass: bypass, auth: auth} do
+ Bypass.expect_once(bypass, "POST", "/v1/payments", fn conn ->
Plug.Conn.resp(conn, 400, "")
- end
- {:error, _} = Gateway.authorize(Money.new(42, :USD), @card, [config: auth])
+ end)
+
+ {:error, _} = Gateway.authorize(Money.new(42, :USD), @bad_card, config: auth)
end
+ end
- test "when card has expired.", %{bypass: bypass, auth: auth} do
- Bypass.expect_once bypass, "POST", "/v1/payments", fn conn ->
- Plug.Conn.resp(conn, 400, "")
- end
- {:error, _response} = Gateway.authorize(Money.new(42, :USD), @bad_card, [config: auth])
+ describe "authorize" do
+ test "when all is good.", %{bypass: bypass, auth: auth} do
+ Bypass.expect(bypass, "POST", "/v1/payments", fn conn ->
+ Plug.Conn.resp(conn, 200, @auth_success)
+ end)
+
+ {:ok, response} = Gateway.authorize(Money.new(42, :USD), @card, config: auth)
+ assert response.code == "000.100.110"
end
end
describe "purchase" do
test "when all is good.", %{bypass: bypass, auth: auth} do
- Bypass.expect_once bypass, "POST", "/v1/payments", fn conn ->
+ Bypass.expect_once(bypass, "POST", "/v1/payments", fn conn ->
Plug.Conn.resp(conn, 200, @auth_success)
- end
- {:ok, response} = Gateway.purchase(Money.new(42, :USD), @card, [config: auth])
+ end)
+
+ {:ok, response} = Gateway.purchase(Money.new(42, :USD), @card, config: auth)
+ assert response.code == "000.100.110"
+ end
+
+ test "with createRegistration.", %{bypass: bypass, auth: auth} do
+ Bypass.expect_once(bypass, "POST", "/v1/payments", fn conn ->
+ conn_ = parse(conn)
+ assert conn_.body_params["createRegistration"] == "true"
+ Plug.Conn.resp(conn, 200, @register_success)
+ end)
+
+ {:ok, response} = Gateway.purchase(Money.new(42, :USD), @card, config: auth, register: true)
assert response.code == "000.100.110"
+ assert response.token == "8a82944a60e09c550160e92da144491e"
end
end
describe "store" do
test "when all is good.", %{bypass: bypass, auth: auth} do
- Bypass.expect_once bypass, "POST", "/v1/registrations", fn conn ->
+ Bypass.expect_once(bypass, "POST", "/v1/registrations", fn conn ->
Plug.Conn.resp(conn, 200, @store_success)
- end
- {:ok, response} = Gateway.store(@card, [config: auth])
+ end)
+
+ {:ok, response} = Gateway.store(@card, config: auth)
assert response.code == "000.100.110"
assert response.raw["card"]["holder"] == "Jo Doe"
end
@@ -139,8 +223,29 @@ defmodule Gringotts.Gateways.MoneiTest do
"/v1/payments/7214344242e11af79c0b9e7b4f3f6234",
fn conn ->
Plug.Conn.resp(conn, 200, @auth_success)
- end)
- {:ok, response} = Gateway.capture(Money.new(42, :USD), "7214344242e11af79c0b9e7b4f3f6234", [config: auth])
+ end
+ )
+
+ {:ok, response} =
+ Gateway.capture(Money.new(42, :USD), "7214344242e11af79c0b9e7b4f3f6234", config: auth)
+
+ assert response.code == "000.100.110"
+ end
+
+ test "with createRegistration that is ignored", %{bypass: bypass, auth: auth} do
+ Bypass.expect_once(
+ bypass,
+ "POST",
+ "/v1/payments/7214344242e11af79c0b9e7b4f3f6234",
+ fn conn ->
+ conn_ = parse(conn)
+ assert :error == Map.fetch(conn_.body_params, "createRegistration")
+ Plug.Conn.resp(conn, 200, @auth_success)
+ end
+ )
+
+ {:ok, response} = Gateway.capture(Money.new(42, :USD), "7214344242e11af79c0b9e7b4f3f6234", config: auth, register: true)
+
assert response.code == "000.100.110"
end
end
@@ -153,12 +258,16 @@ defmodule Gringotts.Gateways.MoneiTest do
"/v1/payments/7214344242e11af79c0b9e7b4f3f6234",
fn conn ->
Plug.Conn.resp(conn, 200, @auth_success)
- end)
- {:ok, response} = Gateway.refund(Money.new(3, :USD), "7214344242e11af79c0b9e7b4f3f6234", [config: auth])
+ end
+ )
+
+ {:ok, response} =
+ Gateway.refund(Money.new(3, :USD), "7214344242e11af79c0b9e7b4f3f6234", config: auth)
+
assert response.code == "000.100.110"
end
end
-
+
describe "unstore" do
test "when all is good.", %{bypass: bypass, auth: auth} do
Bypass.expect_once(
@@ -167,8 +276,10 @@ defmodule Gringotts.Gateways.MoneiTest do
"/v1/registrations/7214344242e11af79c0b9e7b4f3f6234",
fn conn ->
Plug.Conn.resp(conn, 200, "")
- end)
- {:error, response} = Gateway.unstore("7214344242e11af79c0b9e7b4f3f6234", [config: auth])
+ end
+ )
+
+ {:error, response} = Gateway.unstore("7214344242e11af79c0b9e7b4f3f6234", config: auth)
assert response.code == :undefined_response_from_monei
end
end
@@ -181,8 +292,10 @@ defmodule Gringotts.Gateways.MoneiTest do
"/v1/payments/7214344242e11af79c0b9e7b4f3f6234",
fn conn ->
Plug.Conn.resp(conn, 200, @auth_success)
- end)
- {:ok, response} = Gateway.void("7214344242e11af79c0b9e7b4f3f6234", [config: auth])
+ end
+ )
+
+ {:ok, response} = Gateway.void("7214344242e11af79c0b9e7b4f3f6234", config: auth)
assert response.code == "000.100.110"
end
end
@@ -197,11 +310,16 @@ defmodule Gringotts.Gateways.MoneiTest do
then = Enum.map(all, &Gateway.respond({:ok, &1}))
assert Keyword.keys(then) == [:ok, :error, :error, :error]
end
+
+ def parse(conn, opts \\ []) do
+ opts = Keyword.put_new(opts, :parsers, [Plug.Parsers.URLENCODED])
+ Plug.Parsers.call(conn, Plug.Parsers.init(opts))
+ end
end
defmodule Gringotts.Gateways.MoneiDocTest do
use ExUnit.Case, async: true
# doctest Gringotts.Gateways.Monei
- # doctests will never work. Track progress: https://github.com/aviabird/gringotts/issues/37
+ # doctests can work. Track progress: https://github.com/aviabird/gringotts/issues/37
end
diff --git a/test/integration/gateways/monei_test.exs b/test/integration/gateways/monei_test.exs
index 51f0f31c..da24cc3e 100644
--- a/test/integration/gateways/monei_test.exs
+++ b/test/integration/gateways/monei_test.exs
@@ -1,59 +1,130 @@
defmodule Gringotts.Integration.Gateways.MoneiTest do
- use ExUnit.Case, async: false
+ use ExUnit.Case, async: true
alias Gringotts.{
CreditCard
}
+
alias Gringotts.Gateways.Monei, as: Gateway
@moduletag :integration
-
+
+ @amount Money.new(42, :EUR)
+
@card %CreditCard{
first_name: "Jo",
last_name: "Doe",
number: "4200000000000000",
year: 2099,
month: 12,
- verification_code: "123",
+ verification_code: "123",
brand: "VISA"
}
+ @customer %{
+ givenName: "Harry",
+ surname: "Potter",
+ merchantCustomerId: "the_boy_who_lived",
+ sex: "M",
+ birthDate: "1980-07-31",
+ mobile: "+15252525252",
+ email: "masterofdeath@ministryofmagic.gov",
+ ip: "1.1.1",
+ status: "NEW"
+ }
+ @merchant %{
+ name: "Ollivanders",
+ city: "South Side",
+ street: "Diagon Alley",
+ state: "London",
+ country: "GB",
+ submerchantId: "Makers of Fine Wands since 382 B.C."
+ }
+ @billing %{
+ street1: "301, Gryffindor",
+ street2: "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
+ city: "Highlands",
+ state: "Scotland",
+ country: "GB"
+ }
+ @shipping Map.merge(
+ %{method: "SAME_DAY_SERVICE", comment: "For our valued customer, Mr. Potter"},
+ @billing
+ )
+
+ @extra_opts [
+ customer: @customer,
+ merchant: @merchant,
+ billing: @billing,
+ shipping: @shipping,
+ shipping_customer: @customer,
+ category: "EC",
+ custom: %{voldemort: "he who must not be named"}
+ ]
+
setup_all do
- Application.put_env(:gringotts, Gringotts.Gateways.Monei, [adapter: Gringotts.Gateways.Monei,
- userId: "8a8294186003c900016010a285582e0a",
- password: "hMkqf2qbWf",
- entityId: "8a82941760036820016010a28a8337f6"])
+ Application.put_env(
+ :gringotts,
+ Gringotts.Gateways.Monei,
+ adapter: Gringotts.Gateways.Monei,
+ userId: "8a8294186003c900016010a285582e0a",
+ password: "hMkqf2qbWf",
+ entityId: "8a82941760036820016010a28a8337f6"
+ )
+ end
+
+ setup do
+ randoms = [
+ invoice_id: Base.encode16(:crypto.hash(:md5, :crypto.strong_rand_bytes(32))),
+ transaction_id: Base.encode16(:crypto.hash(:md5, :crypto.strong_rand_bytes(32)))
+ ]
+
+ {:ok, opts: randoms ++ @extra_opts}
end
- test "authorize." do
- case Gringotts.authorize(Gateway, Money.new(42, :EUR), @card) do
+ test "authorize", %{opts: opts} do
+ case Gringotts.authorize(Gateway, @amount, @card, opts) do
{:ok, response} ->
assert response.code == "000.100.110"
- assert response.description == "Request successfully processed in 'Merchant in Integrator Test Mode'"
+
+ assert response.description ==
+ "Request successfully processed in 'Merchant in Integrator Test Mode'"
+
assert String.length(response.id) == 32
- {:error, _err} -> flunk()
+
+ {:error, _err} ->
+ flunk()
end
end
@tag :skip
- test "capture." do
- case Gringotts.capture(Gateway, Money.new(42, :EUR), "s") do
+ test "capture", %{opts: _opts} do
+ case Gringotts.capture(Gateway, @amount, "s") do
{:ok, response} ->
assert response.code == "000.100.110"
- assert response.description == "Request successfully processed in 'Merchant in Integrator Test Mode'"
+
+ assert response.description ==
+ "Request successfully processed in 'Merchant in Integrator Test Mode'"
+
assert String.length(response.id) == 32
-
- {:error, _err} -> flunk()
+
+ {:error, _err} ->
+ flunk()
end
end
- test "purchase." do
- case Gringotts.purchase(Gateway, Money.new(42, :EUR), @card) do
+ test "purchase", %{opts: opts} do
+ case Gringotts.purchase(Gateway, @amount, @card, opts) do
{:ok, response} ->
assert response.code == "000.100.110"
- assert response.description == "Request successfully processed in 'Merchant in Integrator Test Mode'"
+
+ assert response.description ==
+ "Request successfully processed in 'Merchant in Integrator Test Mode'"
+
assert String.length(response.id) == 32
- {:error, _err} -> flunk()
+
+ {:error, _err} ->
+ flunk()
end
end
From ce7c6bddfc08f7b85bd941142adbf7a51562767d Mon Sep 17 00:00:00 2001
From: Arjun Singh
Date: Sun, 21 Jan 2018 23:52:09 +0530
Subject: [PATCH 17/60] Money integration with ANet and Anet test modification
(#82)
Used Gringotts.Money for Authorize Net currency support.
---
lib/gringotts/gateways/authorize_net.ex | 58 ++++++++++++++-----------
test/gateways/authorize_net_test.exs | 56 +++++++++++++++---------
2 files changed, 67 insertions(+), 47 deletions(-)
diff --git a/lib/gringotts/gateways/authorize_net.ex b/lib/gringotts/gateways/authorize_net.ex
index 149cd74c..42104be7 100644
--- a/lib/gringotts/gateways/authorize_net.ex
+++ b/lib/gringotts/gateways/authorize_net.ex
@@ -26,7 +26,6 @@ defmodule Gringotts.Gateways.AuthorizeNet do
| Key | Remark | Status |
| ---- | --- | ---- |
- | `currency` | | not Implemented |
| `customer` | | implemented |
| `invoice` | | implemented |
| `bill_to` | | implemented |
@@ -46,6 +45,12 @@ defmodule Gringotts.Gateways.AuthorizeNet do
To know more about these keywords visit [Request](https://developer.authorize.net/api/reference/index.html#payment-transactions)
and [Response](https://developer.authorize.net/api/reference/index.html#payment-transactions) key sections for each function.
+ ## Notes
+ Authorize net supports [multiple currencies](https://community.developer.authorize.net/t5/The-Authorize-Net-Developer-Blog/Authorize-Net-UK-Europe-Update/ba-p/35957)
+ however, multiple currencies in one account are not supported. To support multiple currencies merchant needs
+ multiple Authorize.Net accounts, one for every currency. Currently, `Gringotts` supports single Authorize.Net
+ account configuration.
+
To use this module you need to create an account with the [Authorize.Net
gateway](https://www.authorize.net/solutions/merchantsolutions/onlinemerchantaccount/)
which will provide you with a `name` and a `transactionKey`.
@@ -85,7 +90,6 @@ defmodule Gringotts.Gateways.AuthorizeNet do
use Gringotts.Gateways.Base
use Gringotts.Adapter, required_config: [:name, :transaction_key]
-
alias Gringotts.Gateways.AuthorizeNet.ResponseHandler
@test_url "https://apitest.authorize.net/xml/v1/request.api"
@@ -104,7 +108,8 @@ defmodule Gringotts.Gateways.AuthorizeNet do
alias Gringotts.{
CreditCard,
- Response
+ Response,
+ Money
}
# ---------------Interface functions to be used by developer for
@@ -126,11 +131,11 @@ defmodule Gringotts.Gateways.AuthorizeNet do
ref_id: String,
lineitems: %{
item_id: String, name: String, description: String,
- quantity: Integer, unit_price: Float
+ quantity: Integer, unit_price: Gringotts.Money.t()
},
- tax: %{amount: Float, name: String, description: String},
- duty: %{amount: String, name: String, description: String},
- shipping: %{amount: String, name: String, description: String},
+ tax: %{amount: Gringotts.Money.t(), name: String, description: String},
+ duty: %{amount: Gringotts.Money.t(), name: String, description: String},
+ shipping: %{amount: Gringotts.Money.t(), name: String, description: String},
po_number: String,
customer: %{id: String},
bill_to: %{
@@ -152,10 +157,10 @@ defmodule Gringotts.Gateways.AuthorizeNet do
lineitems: %{itemId: "1", name: "vase", description: "Cannes logo", quantity: "18", unit_price: "45.00"}
]
iex> card = %CreditCard{number: "5424000000000015", year: 2020, month: 12, verification_code: "999"}
- iex> amount = 5
+ iex> amount = %{amount: Decimal.new(20.0), currency: 'USD'}
iex> result = Gringotts.purchase(Gringotts.Gateways.AuthorizeNet, amount, card, opts)
"""
- @spec purchase(float, CreditCard.t, Keyword.t) :: {:ok | :error, Response.t}
+ @spec purchase(Money.t, CreditCard.t, Keyword.t) :: {:ok | :error, Response.t}
def purchase(amount, payment, opts) do
request_data =
add_auth_purchase(amount, payment, opts, @transaction_type[:purchase])
@@ -179,11 +184,11 @@ defmodule Gringotts.Gateways.AuthorizeNet do
ref_id: String,
lineitems: %{
item_id: String, name: String, description: String,
- quantity: Integer, unit_price: Float
+ quantity: Integer, unit_price: Gringotts.Money.t()
},
- tax: %{amount: Float, name: String, description: String},
- duty: %{amount: String, name: String, description: String},
- shipping: %{amount: String, name: String, description: String},
+ tax: %{amount: Gringotts.Money.t(), name: String, description: String},
+ duty: %{amount: Gringotts.Money.t(), name: String, description: String},
+ shipping: %{amount: Gringotts.Money.t(), name: String, description: String},
po_number: String,
customer: %{id: String},
bill_to: %{
@@ -206,12 +211,12 @@ defmodule Gringotts.Gateways.AuthorizeNet do
lineitems: %{itemId: "1", name: "vase", description: "Cannes logo", quantity: "18", unit_price: "45.00"}
]
iex> card = %CreditCard{number: "5424000000000015", year: 2020, month: 12, verification_code: "999"}
- iex> amount = 5
+ iex> amount = %{amount: Decimal.new(20.0), currency: 'USD'}
iex> result = Gringotts.authorize(Gringotts.Gateways.AuthorizeNet, amount, card, opts)
"""
- @spec authorize(float, CreditCard.t, Keyword.t) :: {:ok | :error, Response.t}
+ @spec authorize(Money.t, CreditCard.t, Keyword.t) :: {:ok | :error, Response.t}
def authorize(amount, payment, opts) do
- request_data =
+ request_data =
add_auth_purchase(amount, payment, opts, @transaction_type[:authorize])
response_data = commit(:post, request_data, opts)
respond(response_data)
@@ -243,11 +248,11 @@ defmodule Gringotts.Gateways.AuthorizeNet do
iex> opts = [
ref_id: "123456"
]
- iex> amount = 5
+ iex> amount = %{amount: Decimal.new(20.0), currency: 'USD'}
iex> id = "123456"
iex> result = Gringotts.capture(Gringotts.Gateways.AuthorizeNet, id, amount, opts)
"""
- @spec capture(String.t, float, Keyword.t) :: {:ok | :error, Response.t}
+ @spec capture(String.t, Money.t, Keyword.t) :: {:ok | :error, Response.t}
def capture(id, amount, opts) do
request_data = normal_capture(amount, id, opts, @transaction_type[:capture])
response_data = commit(:post, request_data, opts)
@@ -274,10 +279,10 @@ defmodule Gringotts.Gateways.AuthorizeNet do
ref_id: "123456"
]
iex> id = "123456"
- iex> amount = 5
+ iex> amount = %{amount: Decimal.new(20.0), currency: 'USD'}
iex> result = Gringotts.refund(Gringotts.Gateways.AuthorizeNet, amount, id, opts)
"""
- @spec refund(float, String.t, Keyword.t) :: {:ok | :error, Response.t}
+ @spec refund(Money.t, String.t, Keyword.t) :: {:ok | :error, Response.t}
def refund(amount, id, opts) do
request_data = normal_refund(amount, id, opts, @transaction_type[:refund])
response_data = commit(:post, request_data, opts)
@@ -394,7 +399,6 @@ defmodule Gringotts.Gateways.AuthorizeNet do
end
defp respond({:error, %HTTPoison.Error{} = error}) do
- IO.inspect error
{:error, Response.error(error_code: error.id, message: "HTTPoison says '#{error.reason}'")}
end
@@ -561,10 +565,9 @@ defmodule Gringotts.Gateways.AuthorizeNet do
end
defp add_amount(amount) do
- cond do
- is_integer(amount) -> element(:amount, amount)
- is_float(amount) -> element(:amount, amount)
- true -> element(:amount, 0)
+ if amount do
+ amount = amount |> Money.value |> Decimal.to_float
+ element(:amount, amount)
end
end
@@ -596,7 +599,10 @@ defmodule Gringotts.Gateways.AuthorizeNet do
element(:name, opts[:lineitems][:name]),
element(:description, opts[:lineitems][:description]),
element(:quantity, opts[:lineitems][:quantity]),
- element(:unitPrice, opts[:lineitems][:unit_price])
+ element(
+ :unitPrice,
+ opts[:lineitems][:unit_price] |> Money.value |> Decimal.to_float
+ )
])
])
])
diff --git a/test/gateways/authorize_net_test.exs b/test/gateways/authorize_net_test.exs
index 0c155a4c..3c177dc4 100644
--- a/test/gateways/authorize_net_test.exs
+++ b/test/gateways/authorize_net_test.exs
@@ -23,8 +23,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
verification_code: 123
}
- @amount 20
- @bad_amount "a"
+ @amount %{amount: Decimal.new(20.0), currency: 'USD'}
@opts [
config: @auth,
@@ -35,7 +34,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
name: "vase",
description: "Cannes logo",
quantity: "18",
- unit_price: "45.00"
+ unit_price: %{amount: Decimal.new(20.0), currency: 'USD'}
}
]
@opts_refund [
@@ -43,6 +42,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
ref_id: "123456",
payment: %{card: %{number: "5424000000000015", year: 2020, month: 12}}
]
+
@opts_store [
config: @auth,
profile: %{
@@ -53,6 +53,15 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
customer_type: "individual",
validation_mode: "testMode"
]
+ @opts_store_without_validation [
+ config: @auth,
+ profile: %{
+ merchant_customer_id: "123456",
+ description: "Profile description here",
+ email: "customer-profile-email@here.com"
+ }
+ ]
+
@opts_store_no_profile [
config: @auth,
]
@@ -69,7 +78,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
@opts_store [
config: @auth,
profile: %{merchant_customer_id: "123456",
- description: "Profile description here",
+ description: "Profile description here",
email: "customer-profile-email@here.com"
}
]
@@ -79,7 +88,12 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
@opts_customer_profile [
config: @auth,
customer_profile_id: "1814012002",
- validation_mode: "testMode"
+ validation_mode: "testMode",
+ customer_type: "individual"
+ ]
+ @opts_customer_profile_args[
+ config: @auth,
+ customer_profile_id: "1814012002"
]
@refund_id "60036752756"
@@ -102,14 +116,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
end
end
- test "with bad amount" do
- with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.bad_amount_purchase_response end] do
- assert {:error, response} = ANet.purchase(@bad_amount, @card, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Error"
- end
- end
-
test "with bad card" do
with_mock HTTPoison,
[request: fn(_method, _url, _body, _headers) -> MockResponse.bad_card_purchase_response end] do
@@ -128,14 +134,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
end
end
- test "with bad amount" do
- with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.bad_amount_purchase_response end] do
- assert {:error, response} = ANet.authorize(@bad_amount, @card, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Error"
- end
- end
-
test "with bad card" do
with_mock HTTPoison,
[request: fn(_method, _url, _body, _headers) -> MockResponse.bad_card_purchase_response end] do
@@ -216,6 +214,14 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
end
end
+ test "successful response without validation and customer type" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_store_response end] do
+ assert {:ok, response} = ANet.store(@card, @opts_store_without_validation)
+ assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
+ end
+ end
+
test "without any profile" do
with_mock HTTPoison,
[request: fn(_method, _url, _body, _headers) -> MockResponse.store_without_profile_fields end] do
@@ -231,6 +237,14 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
assert response.params["createCustomerPaymentProfileResponse"]["messages"]["resultCode"] == "Ok"
end
end
+
+ test "successful response without valiadtion mode and customer type" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_store_response end] do
+ assert {:ok, response} = ANet.store(@card, @opts_customer_profile_args)
+ assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
+ end
+ end
end
describe "unstore" do
From db9190b583b238dd75cb0870b76a4c62d1c1e268 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Thu, 25 Jan 2018 17:43:47 +0530
Subject: [PATCH 18/60] [ANet] Corrected use of money protocol and examples
(#92)
* Corrected use of money protocol and examples
Fixes #90 and Fixes #91
Adds a new Response field: authorization.
It is set to ["transactionResponse"]["transId"]
* Uses Gringotts.Money.to_string instead of converting to lossy Float
* doc examples updated (authorize, capture, purchase)
* reworded confusing store doc
* Improved docs, stripped whitespace
* Ran the elixir 1.6 formatter
* Used the ~s sigil in mocks
* Also removed an invisible unicode codepoint from mock strings
---
lib/gringotts/gateways/authorize_net.ex | 490 +++++++++++++-----------
test/gateways/authorize_net_test.exs | 190 +++++----
test/mocks/authorize_net_mock.exs | 34 +-
3 files changed, 405 insertions(+), 309 deletions(-)
diff --git a/lib/gringotts/gateways/authorize_net.ex b/lib/gringotts/gateways/authorize_net.ex
index 42104be7..04762db3 100644
--- a/lib/gringotts/gateways/authorize_net.ex
+++ b/lib/gringotts/gateways/authorize_net.ex
@@ -1,88 +1,104 @@
defmodule Gringotts.Gateways.AuthorizeNet do
-
@moduledoc """
- A module for working with the Authorize.net payment gateway.
-
- The module provides a set of functions to perform transactions via this gateway for a merchant.
+ A module for working with the Authorize.Net payment gateway.
- [AuthorizeNet API reference](https://developer.authorize.net/api/reference/index.html)
+ Refer the official Authorize.Net [API docs][docs].
- The following set of functions for Authorize.Net have been provided:
+ The following set of functions for Authorize.Net have been implemented:
| Action | Method |
| ------ | ------ |
| Authorize a Credit Card | `authorize/3` |
- | Capture a Previously Authorized Amount | `capture/3` |
+ | Capture a previously authorized amount | `capture/3` |
| Charge a Credit Card | `purchase/3` |
| Refund a transaction | `refund/3` |
- | Void a Transaction | `void/2` |
+ | Void a transaction | `void/2` |
| Create Customer Profile | `store/2` |
| Create Customer Payment Profile | `store/2` |
| Delete Customer Profile | `unstore/2` |
Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply
- optional arguments for transactions with the Authorize.Net gateway. The following keys
- are supported:
-
- | Key | Remark | Status |
- | ---- | --- | ---- |
- | `customer` | | implemented |
- | `invoice` | | implemented |
- | `bill_to` | | implemented |
- | `ship_to` | | implemented |
- | `customer_ip` | | implemented |
- | `order` | | implemented |
- | `lineitems` | | implemented |
- | `ref_id` | | implemented |
- | `tax` | | implemented |
- | `duty` | | implemented |
- | `shipping` | | implemented |
- | `po_number` | | implemented |
- | `customer_type` | | implemented |
- | `customer_profile_id`| | implemented |
- | `profile` | | implemented |
-
- To know more about these keywords visit [Request](https://developer.authorize.net/api/reference/index.html#payment-transactions)
- and [Response](https://developer.authorize.net/api/reference/index.html#payment-transactions) key sections for each function.
+ optional arguments for transactions with the Authorize.Net gateway. The
+ following keys are supported:
+
+ | Key | Remarks |
+ | ---- | ------- |
+ | `customer` | |
+ | `invoice` | |
+ | `bill_to` | |
+ | `ship_to` | |
+ | `customer_ip` | |
+ | `order` | |
+ | `lineitems` | |
+ | `ref_id` | |
+ | `tax` | |
+ | `duty` | |
+ | `shipping` | |
+ | `po_number` | |
+ | `customer_type` | |
+ | `customer_profile_id` | |
+ | `profile` | |
+
+ To know more about these keywords visit [Request and Response][req-resp] tabs for each
+ API method.
+
+ [docs]: https://developer.authorize.net/api/reference/index.html
+ [req-resp]: https://developer.authorize.net/api/reference/index.html#payment-transactions
## Notes
- Authorize net supports [multiple currencies](https://community.developer.authorize.net/t5/The-Authorize-Net-Developer-Blog/Authorize-Net-UK-Europe-Update/ba-p/35957)
- however, multiple currencies in one account are not supported. To support multiple currencies merchant needs
- multiple Authorize.Net accounts, one for every currency. Currently, `Gringotts` supports single Authorize.Net
- account configuration.
-
- To use this module you need to create an account with the [Authorize.Net
- gateway](https://www.authorize.net/solutions/merchantsolutions/onlinemerchantaccount/)
- which will provide you with a `name` and a `transactionKey`.
+
+ Authorize.Net supports [multiple currencies][currencies] however, multiple
+ currencies in one account are not supported. A merchant would need multiple
+ Authorize.Net accounts, one for each chosen currency.
+
+ > Currently, `Gringotts` supports single Authorize.Net account configuration.
+
+ [currencies]: https://community.developer.authorize.net/t5/The-Authorize-Net-Developer-Blog/Authorize-Net-UK-Europe-Update/ba-p/35957
## Configuring your AuthorizeNet account at `Gringotts`
+ To use this module you need to [create an account][dashboard] with the
+ Authorize.Net gateway and obtain your login secrets: `name` and
+ `transactionKey`.
+
Your Application config **must include the `name` and `transaction_key`
fields** and would look something like this:
-
+
config :gringotts, Gringotts.Gateways.AuthorizeNet,
adapter: Gringotts.Gateways.AuthorizeNet,
name: "name_provided_by_authorize_net",
transaction_key: "transactionKey_provided_by_authorize_net"
-
- ## Scope of this module, and _quirks_
- * Although Authorize.Net supports payments from [various
- sources](https://www.authorize.net/solutions/merchantsolutions/onlinemerchantaccount/),
- this library currently accepts payments by (supported) credit cards only.
+
+ ## Scope of this module
+
+ Although Authorize.Net supports payments from various sources (check your
+ [dashboard][dashboard]), this library currently accepts payments by
+ (supported) credit cards only.
+
+ [dashboard]: https://www.authorize.net/solutions/merchantsolutions/onlinemerchantaccount/
## Following the examples
+
1. First, set up a sample application and configure it to work with Authorize.Net.
- - You could do that from scratch by following our [Getting Started](#) guide.
- - To save you time, we recommend [cloning our example
- repo](https://github.com/aviabird/gringotts_example) that gives you a
- pre-configured sample app ready-to-go.
- + You could use the same config or update it the with your "secrets"
- [above](#Configuring your AuthorizeNet account at `Gringotts`).
+ - You could do that from scratch by following our [Getting Started][gs]
+ guide.
+ - To save you time, we recommend [cloning our example repo][example-repo]
+ that gives you a pre-configured sample app ready-to-go.
+ + You could use the same config or update it the with your "secrets"
+ [above](#module-configuring-your-authorizenet-account-at-gringotts).
2. Run an `iex` session with `iex -S mix` and add some variable bindings and
- aliases to it (to save some time):
+ aliases to it (to save some time):
+
```
- iex> alias Gringotts.{Response, CreditCard, Gateways.AuthorizeNet}
+ iex> alias Gringotts.{Response, CreditCard, Gateways.AuthorizeNet}
+ iex> amount = %{value: Decimal.new(20.0), currency: "USD"}
+ iex> card = %CreditCard{number: "5424000000000015", year: 2099, month: 12, verification_code: "999"}
```
+
+ We'll be using these in the examples below.
+
+ [example-repo]: https://github.com/aviabird/gringotts_example
+ [gs]: https://github.com/aviabird/gringotts/wiki
"""
import XmlBuilder
@@ -112,19 +128,20 @@ defmodule Gringotts.Gateways.AuthorizeNet do
Money
}
- # ---------------Interface functions to be used by developer for
- #----------------making requests to gateway
-
@doc """
- Charge a credit card.
+ Transfers `amount` from the customer to the merchant.
+
+ Charges a credit `card` for the specified `amount`. It performs `authorize`
+ and `capture` at the [same time][auth-cap-same-time].
+
+ Authorize.Net returns `transId` (available in the `Response.authorization`
+ field) which can be used to:
- Function to charge a user credit card for the specified `amount`. It performs `authorize`
- and `capture` at the [same time](https://developer.authorize.net/api/reference/index.html#payment-transactions-charge-a-credit-card).
- For this transaction Authorize.Net returns `transId` which can be used to:
-
* `refund/3` a settled transaction.
* `void/2` a transaction.
+ [auth-cap-same-time]: https://developer.authorize.net/api/reference/index.html#payment-transactions-charge-a-credit-card
+
## Optional Fields
opts = [
order: %{invoice_number: String, description: String},
@@ -140,8 +157,8 @@ defmodule Gringotts.Gateways.AuthorizeNet do
customer: %{id: String},
bill_to: %{
first_name: String, last_name: String, company: String,
- address: String, city: String, state: String, zip: String,
- country: String
+ address: String, city: String, state: String, zip: String,
+ country: String
},
ship_to: %{
first_name: String, last_name: String, company: String, address: String,
@@ -151,19 +168,21 @@ defmodule Gringotts.Gateways.AuthorizeNet do
]
## Example
+ iex> amount = %{value: Decimal.new(20.0), currency: "USD"}
iex> opts = [
ref_id: "123456",
- order: %{invoice_number: "INV-12345", description: "Product Description"},
- lineitems: %{itemId: "1", name: "vase", description: "Cannes logo", quantity: "18", unit_price: "45.00"}
+ order: %{invoice_number: "INV-12345", description: "Product Description"},
+ lineitems: %{item_id: "1", name: "vase", description: "Cannes logo", quantity: 1, unit_price: amount},
+ tax: %{name: "VAT", amount: Money.new("0.1", :EUR), description: "Value Added Tax"},
+ shipping: %{name: "SAME-DAY-DELIVERY", amount: Money.new("0.56", :EUR), description: "Zen Logistics"},
+ duty: %{name: "import_duty", amount: Money.new("0.25", :EUR), description: "Upon import of goods"}
]
- iex> card = %CreditCard{number: "5424000000000015", year: 2020, month: 12, verification_code: "999"}
- iex> amount = %{amount: Decimal.new(20.0), currency: 'USD'}
+ iex> card = %CreditCard{number: "5424000000000015", year: 2099, month: 12, verification_code: "999"}
iex> result = Gringotts.purchase(Gringotts.Gateways.AuthorizeNet, amount, card, opts)
"""
- @spec purchase(Money.t, CreditCard.t, Keyword.t) :: {:ok | :error, Response.t}
+ @spec purchase(Money.t(), CreditCard.t(), Keyword.t()) :: {:ok | :error, Response}
def purchase(amount, payment, opts) do
- request_data =
- add_auth_purchase(amount, payment, opts, @transaction_type[:purchase])
+ request_data = add_auth_purchase(amount, payment, opts, @transaction_type[:purchase])
response_data = commit(:post, request_data, opts)
respond(response_data)
end
@@ -171,10 +190,15 @@ defmodule Gringotts.Gateways.AuthorizeNet do
@doc """
Authorize a credit card transaction.
- Function to authorize a transaction for the specified amount. It needs to be
- followed up with a `capture/3` transaction to transfer the funds to merchant account.
-
- For this transaction Authorize.Net returns a `transId` which can be use for:
+ The authorization validates the `card` details with the banking network,
+ places a hold on the transaction `amount` in the customer’s issuing bank and
+ also triggers risk management. Funds are not transferred.
+
+ To transfer the funds to merchant's account follow this up with a `capture/3`.
+
+ Authorize.Net returns a `transId` (available in the `Response.authorization`
+ field) which can be used for:
+
* `capture/3` an authorized transaction.
* `void/2` a transaction.
@@ -191,10 +215,10 @@ defmodule Gringotts.Gateways.AuthorizeNet do
shipping: %{amount: Gringotts.Money.t(), name: String, description: String},
po_number: String,
customer: %{id: String},
- bill_to: %{
+ bill_to: %{
first_name: String, last_name: String, company: String,
- address: String, city: String, state: String, zip: String,
- country: String
+ address: String, city: String, state: String, zip: String,
+ country: String
},
ship_to: %{
first_name: String, last_name: String, company: String, address: String,
@@ -205,54 +229,62 @@ defmodule Gringotts.Gateways.AuthorizeNet do
## Example
+ iex> amount = %{value: Decimal.new(20.0), currency: "USD"}
iex> opts = [
ref_id: "123456",
- order: %{invoice_number: "INV-12345", description: "Product Description"},
- lineitems: %{itemId: "1", name: "vase", description: "Cannes logo", quantity: "18", unit_price: "45.00"}
+ order: %{invoice_number: "INV-12345", description: "Product Description"},
+ lineitems: %{itemId: "1", name: "vase", description: "Cannes logo", quantity: 1, unit_price: amount},
+ tax: %{name: "VAT", amount: Money.new("0.1", :EUR), description: "Value Added Tax"},
+ shipping: %{name: "SAME-DAY-DELIVERY", amount: Money.new("0.56", :EUR), description: "Zen Logistics"},
+ duty: %{name: "import_duty", amount: Money.new("0.25", :EUR), description: "Upon import of goods"}
]
- iex> card = %CreditCard{number: "5424000000000015", year: 2020, month: 12, verification_code: "999"}
- iex> amount = %{amount: Decimal.new(20.0), currency: 'USD'}
+ iex> card = %CreditCard{number: "5424000000000015", year: 2099, month: 12, verification_code: "999"}
iex> result = Gringotts.authorize(Gringotts.Gateways.AuthorizeNet, amount, card, opts)
"""
- @spec authorize(Money.t, CreditCard.t, Keyword.t) :: {:ok | :error, Response.t}
+ @spec authorize(Money.t(), CreditCard.t(), Keyword.t()) :: {:ok | :error, Response}
def authorize(amount, payment, opts) do
- request_data =
- add_auth_purchase(amount, payment, opts, @transaction_type[:authorize])
+ request_data = add_auth_purchase(amount, payment, opts, @transaction_type[:authorize])
response_data = commit(:post, request_data, opts)
respond(response_data)
end
@doc """
- Capture a transaction.
-
- Function to capture an `amount` for an authorized transaction.
-
- For this transaction Authorize.Net returns a `transId` which can be use to:
- * `refund/3` a settled transaction.
- * `void/2` a transaction.
-
+ Captures a pre-authorized `amount`.
+
+ `amount` is transferred to the merchant account by Authorize.Net when it is smaller or
+ equal to the amount used in the pre-authorization referenced by `id`.
+
+ Authorize.Net returns a `transId` (available in the `Response.authorization`
+ field) which can be used to:
+
+ * `refund/3` a settled transaction.
+ * `void/2` a transaction.
+
## Notes
- * If a `capture` transaction needs to `void` then it should be done before it is settled. For AuthorieNet
- all the transactions are settled after 24 hours.
-
- * AuthorizeNet supports partical capture of the `authorized amount`. But it is advisable to use one
- `authorization code` only [once](https://support.authorize.net/authkb/index?page=content&id=A1720&actp=LIST).
+
+ * Authorize.Net automatically settles authorized transactions after 24
+ hours. Hence, unnecessary authorizations must be `void/2`ed within this
+ period!
+ * Though Authorize.Net supports partial capture of the authorized `amount`, it
+ is [advised][sound-advice] not to do so.
+
+ [sound-advice]: https://support.authorize.net/authkb/index?page=content&id=A1720&actp=LIST
## Optional Fields
opts = [
order: %{invoice_number: String, description: String},
ref_id: String
]
-
+
## Example
iex> opts = [
ref_id: "123456"
]
- iex> amount = %{amount: Decimal.new(20.0), currency: 'USD'}
+ iex> amount = %{value: Decimal.new(20.0), currency: "USD"}
iex> id = "123456"
iex> result = Gringotts.capture(Gringotts.Gateways.AuthorizeNet, id, amount, opts)
"""
- @spec capture(String.t, Money.t, Keyword.t) :: {:ok | :error, Response.t}
+ @spec capture(String.t(), Money.t(), Keyword.t()) :: {:ok | :error, Response}
def capture(id, amount, opts) do
request_data = normal_capture(amount, id, opts, @transaction_type[:capture])
response_data = commit(:post, request_data, opts)
@@ -262,10 +294,10 @@ defmodule Gringotts.Gateways.AuthorizeNet do
@doc """
Refund `amount` for a settled transaction referenced by `id`.
- Use this method to refund a customer for a transaction that was already settled, requires
- transId of the transaction. The `payment` field in the `opts` is used to set the mode of payment.
- The `card` field inside `payment` needs the information of the credit card to be passed in the specified fields
- so as to `refund` to that particular card.
+ The `payment` field in the `opts` is used to set the instrument/mode of
+ payment, which could be different from the original one. Currently, we
+ support only refunds to cards, so put the `card` details in the `payment`.
+
## Required fields
opts = [
payment: %{card: %{number: String, year: Integer, month: Integer}}
@@ -275,14 +307,14 @@ defmodule Gringotts.Gateways.AuthorizeNet do
## Example
iex> opts = [
- payment: %{card: %{number: "5424000000000015", year: 2020, month: 12}}
+ payment: %{card: %{number: "5424000000000015", year: 2099, month: 12}}
ref_id: "123456"
]
iex> id = "123456"
- iex> amount = %{amount: Decimal.new(20.0), currency: 'USD'}
+ iex> amount = %{value: Decimal.new(20.0), currency: "USD"}
iex> result = Gringotts.refund(Gringotts.Gateways.AuthorizeNet, amount, id, opts)
"""
- @spec refund(Money.t, String.t, Keyword.t) :: {:ok | :error, Response.t}
+ @spec refund(Money.t(), String.t(), Keyword.t()) :: {:ok | :error, Response}
def refund(amount, id, opts) do
request_data = normal_refund(amount, id, opts, @transaction_type[:refund])
response_data = commit(:post, request_data, opts)
@@ -290,11 +322,13 @@ defmodule Gringotts.Gateways.AuthorizeNet do
end
@doc """
- To void a transaction
+ Voids the referenced payment.
- Use this method to cancel either an original transaction that is not settled or
- an entire order composed of more than one transaction. It can be submitted against 'purchase', `authorize`
- and `capture`. Requires the `transId` of a transaction.
+ This method attempts a reversal of the either a previous `purchase/3` or
+ `authorize/3` referenced by `id`.
+
+ It can cancel either an original transaction that may not be settled or an
+ entire order composed of more than one transaction.
## Optional fields
opts = [ref_id: String]
@@ -306,7 +340,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
iex> id = "123456"
iex> result = Gringotts.void(Gringotts.Gateways.AuthorizeNet, id, opts)
"""
- @spec void(String.t, Keyword.t) :: {:ok | :error, Response.t}
+ @spec void(String.t(), Keyword.t()) :: {:ok | :error, Response}
def void(id, opts) do
request_data = normal_void(id, opts, @transaction_type[:void])
response_data = commit(:post, request_data, opts)
@@ -314,19 +348,29 @@ defmodule Gringotts.Gateways.AuthorizeNet do
end
@doc """
- Store a customer payment profile.
-
- Use this function to store the customer card information by creating a [customer profile](https://developer.authorize.net/api/reference/index.html#customer-profiles-create-customer-profile) which also
- creates a `payment profile` if `card` inofrmation is provided, and in case the `customer profile` exists without a payment profile, the merchant
- can create customer payment profile by passing the `customer_profile_id` in the `opts`.
- The gateway also provide a provision for a `validation mode`, there are two modes `liveMode`
- and `testMode`, to know more about modes [see](https://developer.authorize.net/api/reference/index.html#customer-profiles-create-customer-profile).
- By default `validation mode` is set to `testMode`.
-
+ Store a customer's profile and optionally associate it with a payment profile.
+
+ Authorize.Net separates a [customer's profile][cust-profile] from their payment
+ profile. Thus a customer can have multiple payment profiles.
+
+ ## Create both profiles
+
+ Add `:customer` details in `opts` and also provide `card` details. The response
+ will contain a `:customer_profile_id`.
+
+ ## Associate payment profile with existing customer profile
+
+ Simply pass the `:customer_profile_id` in the `opts`. This will add the `card`
+ details to the profile referenced by the supplied `:customer_profile_id`.
+
## Notes
- * The current version of this library supports only `credit card` as the payment profile.
- * If a customer profile is created without the card info, then to create a payment profile
- `card` info needs to be passed alongwith `cutomer_profile_id` to create it.
+
+ * Currently only supports `credit card` in the payment profile.
+ * The supplied `card` details can be validated by supplying a
+ [`:validation_mode`][cust-profile], available options are `testMode` and
+ `liveMode`, the deafult is `testMode`.
+
+ [cust-profile]: https://developer.authorize.net/api/reference/index.html#customer-profiles-create-customer-profile
## Required Fields
opts = [
@@ -347,16 +391,18 @@ defmodule Gringotts.Gateways.AuthorizeNet do
profile: %{merchant_customer_id: 123456, description: "test store", email: "test@gmail.com"},
validation_mode: "testMode"
]
- iex> card = %CreditCard{number: "5424000000000015", year: 2020, month: 12, verification_code: "999"}
+ iex> card = %CreditCard{number: "5424000000000015", year: 2099, month: 12, verification_code: "999"}
iex> result = Gringotts.store(Gringotts.Gateways.AuthorizeNet, card, opts)
"""
- @spec store(CreditCard.t, Keyword.t) :: {:ok | :error, Response.t}
+ @spec store(CreditCard.t(), Keyword.t()) :: {:ok | :error, Response}
def store(card, opts) do
- request_data = if opts[:customer_profile_id] do
- card |> create_customer_payment_profile(opts) |> generate
- else
- card |> create_customer_profile(opts) |> generate
- end
+ request_data =
+ if opts[:customer_profile_id] do
+ card |> create_customer_payment_profile(opts) |> generate
+ else
+ card |> create_customer_profile(opts) |> generate
+ end
+
response_data = commit(:post, request_data, opts)
respond(response_data)
end
@@ -366,14 +412,14 @@ defmodule Gringotts.Gateways.AuthorizeNet do
Use this function to unstore the customer card information by deleting the customer profile
present. Requires the customer profile id.
-
+
## Example
iex> id = "123456"
iex> opts = []
iex> result = Gringotts.store(Gringotts.Gateways.AuthorizeNet, id, opts)
"""
-
- @spec unstore(String.t, Keyword.t) :: {:ok | :error, Response.t}
+
+ @spec unstore(String.t(), Keyword.t()) :: {:ok | :error, Response}
def unstore(customer_profile_id, opts) do
request_data = customer_profile_id |> delete_customer_profile(opts) |> generate
response_data = commit(:post, request_data, opts)
@@ -389,7 +435,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
# Function to return a response
defp respond({:ok, %{body: body, status_code: 200}}) do
- raw_response = naive_map(body)
+ raw_response = naive_map(body)
response_type = ResponseHandler.check_response_type(raw_response)
response_check(raw_response[response_type], raw_response)
end
@@ -413,16 +459,18 @@ defmodule Gringotts.Gateways.AuthorizeNet do
{:error, ResponseHandler.parse_gateway_error(raw_response)}
end
- #------------------- Helper functions for the interface functions-------------------
+ ##############################################################################
+ # HELPER METHODS #
+ ##############################################################################
# function for formatting the request as an xml for purchase and authorize method
defp add_auth_purchase(amount, payment, opts, transaction_type) do
:createTransactionRequest
|> element(%{xmlns: @aut_net_namespace}, [
- add_merchant_auth(opts[:config]),
- add_order_id(opts),
- add_purchase_transaction_request(amount, transaction_type, payment, opts),
- ])
+ add_merchant_auth(opts[:config]),
+ add_order_id(opts),
+ add_purchase_transaction_request(amount, transaction_type, payment, opts)
+ ])
|> generate
end
@@ -430,35 +478,35 @@ defmodule Gringotts.Gateways.AuthorizeNet do
defp normal_capture(amount, id, opts, transaction_type) do
:createTransactionRequest
|> element(%{xmlns: @aut_net_namespace}, [
- add_merchant_auth(opts[:config]),
- add_order_id(opts),
- add_capture_transaction_request(amount, id, transaction_type, opts),
- ])
+ add_merchant_auth(opts[:config]),
+ add_order_id(opts),
+ add_capture_transaction_request(amount, id, transaction_type, opts)
+ ])
|> generate
end
- #function to format the request for normal refund
+ # function to format the request for normal refund
defp normal_refund(amount, id, opts, transaction_type) do
:createTransactionRequest
|> element(%{xmlns: @aut_net_namespace}, [
- add_merchant_auth(opts[:config]),
- add_order_id(opts),
- add_refund_transaction_request(amount, id, opts, transaction_type),
- ])
+ add_merchant_auth(opts[:config]),
+ add_order_id(opts),
+ add_refund_transaction_request(amount, id, opts, transaction_type)
+ ])
|> generate
end
- #function to format the request for normal void operation
+ # function to format the request for normal void operation
defp normal_void(id, opts, transaction_type) do
:createTransactionRequest
|> element(%{xmlns: @aut_net_namespace}, [
- add_merchant_auth(opts[:config]),
- add_order_id(opts),
- element(:transactionRequest, [
- add_transaction_type(transaction_type),
- add_ref_trans_id(id)
- ])
- ])
+ add_merchant_auth(opts[:config]),
+ add_order_id(opts),
+ element(:transactionRequest, [
+ add_transaction_type(transaction_type),
+ add_ref_trans_id(id)
+ ])
+ ])
|> generate
end
@@ -470,8 +518,9 @@ defmodule Gringotts.Gateways.AuthorizeNet do
add_billing_info(opts),
add_payment_source(card)
]),
- element(:validationMode,
- (if opts[:validation_mode], do: opts[:validation_mode], else: "testMode")
+ element(
+ :validationMode,
+ if(opts[:validation_mode], do: opts[:validation_mode], else: "testMode")
)
])
end
@@ -484,16 +533,18 @@ defmodule Gringotts.Gateways.AuthorizeNet do
element(:description, opts[:profile][:description]),
element(:email, opts[:profile][:description]),
element(:paymentProfiles, [
- element(:customerType,
- (if opts[:customer_type], do: opts[:customer_type], else: "individual")
+ element(
+ :customerType,
+ if(opts[:customer_type], do: opts[:customer_type], else: "individual")
),
add_billing_info(opts),
add_payment_source(card)
- ]),
+ ])
]),
- element(:validationMode,
- (if opts[:validation_mode], do: opts[:validation_mode], else: "testMode")
- )
+ element(
+ :validationMode,
+ if(opts[:validation_mode], do: opts[:validation_mode], else: "testMode")
+ )
])
end
@@ -504,8 +555,10 @@ defmodule Gringotts.Gateways.AuthorizeNet do
])
end
- #--------------- XMl Builder functions for helper functions to assist
- #---------------in attaching different tags for request
+ ##############################################################################
+ # HELPERS TO ASSIST IN BUILDING AND #
+ # COMPOSING DIFFERENT XmlBuilder TAGS #
+ ##############################################################################
defp add_merchant_auth(opts) do
element(:merchantAuthentication, [
@@ -547,7 +600,8 @@ defmodule Gringotts.Gateways.AuthorizeNet do
element(:payment, [
element(:creditCard, [
element(:cardNumber, opts[:payment][:card][:number]),
- element(:expirationDate,
+ element(
+ :expirationDate,
join_string([opts[:payment][:card][:year], opts[:payment][:card][:month]], "-")
)
])
@@ -566,8 +620,8 @@ defmodule Gringotts.Gateways.AuthorizeNet do
defp add_amount(amount) do
if amount do
- amount = amount |> Money.value |> Decimal.to_float
- element(:amount, amount)
+ {_, value} = amount |> Money.to_string()
+ element(:amount, value)
end
end
@@ -588,10 +642,10 @@ defmodule Gringotts.Gateways.AuthorizeNet do
end
defp add_invoice(transactionType, opts) do
- element(
- [element(:order, [
+ element([
+ element(:order, [
element(:invoiceNumber, opts[:order][:invoice_number]),
- element(:description, opts[:order][:description]),
+ element(:description, opts[:order][:description])
]),
element(:lineItems, [
element(:lineItem, [
@@ -600,8 +654,8 @@ defmodule Gringotts.Gateways.AuthorizeNet do
element(:description, opts[:lineitems][:description]),
element(:quantity, opts[:lineitems][:quantity]),
element(
- :unitPrice,
- opts[:lineitems][:unit_price] |> Money.value |> Decimal.to_float
+ :unitPrice,
+ opts[:lineitems][:unit_price] |> Money.value() |> Decimal.to_float()
)
])
])
@@ -612,7 +666,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
element(:tax, [
add_amount(opts[:tax][:amount]),
element(:name, opts[:tax][:name]),
- element(:description, opts[:tax][:description]),
+ element(:description, opts[:tax][:description])
])
end
@@ -620,7 +674,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
element(:duty, [
add_amount(opts[:duty][:amount]),
element(:name, opts[:duty][:name]),
- element(:description, opts[:duty][:description]),
+ element(:description, opts[:duty][:description])
])
end
@@ -628,7 +682,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
element(:shipping, [
add_amount(opts[:shipping][:amount]),
element(:name, opts[:shipping][:name]),
- element(:description, opts[:shipping][:description]),
+ element(:description, opts[:shipping][:description])
])
end
@@ -673,7 +727,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
element(:city, opts[:ship_to][:city]),
element(:state, opts[:ship_to][:state]),
element(:zip, opts[:ship_to][:zip]),
- element(:country, opts[:ship_to][:country])
+ element(:country, opts[:ship_to][:country])
])
end
@@ -684,11 +738,11 @@ defmodule Gringotts.Gateways.AuthorizeNet do
defp join_string(list, symbol) do
Enum.join(list, symbol)
end
-
+
defp base_url(opts) do
if opts[:config][:mode] == :prod do
@production_url
- else
+ else
@test_url
end
end
@@ -696,7 +750,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
defmodule ResponseHandler do
@moduledoc false
alias Gringotts.Response
-
+
@response_type %{
auth_response: "authenticateTestResponse",
transaction_response: "createTransactionResponse",
@@ -708,39 +762,47 @@ defmodule Gringotts.Gateways.AuthorizeNet do
def parse_gateway_success(raw_response) do
response_type = check_response_type(raw_response)
+ token = raw_response[response_type]["transactionResponse"]["transId"]
message = raw_response[response_type]["messages"]["message"]["text"]
avs_result = raw_response[response_type]["transactionResponse"]["avsResultCode"]
cvc_result = raw_response[response_type]["transactionResponse"]["cavvResultCode"]
[]
- |> status_code(200)
- |> set_message(message)
- |> set_avs_result(avs_result)
- |> set_cvc_result(cvc_result)
- |> set_params(raw_response)
- |> set_success(true)
- |> handle_opts
+ |> status_code(200)
+ |> set_token(token)
+ |> set_message(message)
+ |> set_avs_result(avs_result)
+ |> set_cvc_result(cvc_result)
+ |> set_params(raw_response)
+ |> set_success(true)
+ |> handle_opts
end
def parse_gateway_error(raw_response) do
response_type = check_response_type(raw_response)
-
- {message, error_code} = if raw_response[response_type]["transactionResponse"]["errors"] do
- {raw_response[response_type]["messages"]["message"]["text"] <> " " <>
- raw_response[response_type]["transactionResponse"]["errors"]["error"]["errorText"],
- raw_response[response_type]["transactionResponse"]["errors"]["error"]["errorCode"]}
- else
- {raw_response[response_type]["messages"]["message"]["text"],
- raw_response[response_type]["messages"]["message"]["code"]}
- end
+
+ {message, error_code} =
+ if raw_response[response_type]["transactionResponse"]["errors"] do
+ {
+ raw_response[response_type]["messages"]["message"]["text"] <>
+ " " <>
+ raw_response[response_type]["transactionResponse"]["errors"]["error"]["errorText"],
+ raw_response[response_type]["transactionResponse"]["errors"]["error"]["errorCode"]
+ }
+ else
+ {
+ raw_response[response_type]["messages"]["message"]["text"],
+ raw_response[response_type]["messages"]["message"]["code"]
+ }
+ end
[]
- |> status_code(200)
- |> set_message(message)
- |> set_error_code(error_code)
- |> set_params(raw_response)
- |> set_success(false)
- |> handle_opts
+ |> status_code(200)
+ |> set_message(message)
+ |> set_error_code(error_code)
+ |> set_params(raw_response)
+ |> set_success(false)
+ |> handle_opts
end
def check_response_type(raw_response) do
@@ -752,21 +814,21 @@ defmodule Gringotts.Gateways.AuthorizeNet do
raw_response[@response_type[:delete_customer_profile]] -> "deleteCustomerProfileResponse"
end
end
-
- defp set_success(opts, value), do: opts ++ [success: value]
- defp status_code(opts, code), do: opts ++ [status_code: code]
- defp set_message(opts, message), do: opts ++ [message: message]
- defp set_avs_result(opts, result), do: opts ++ [avs_result: result]
- defp set_cvc_result(opts, result), do: opts ++ [cvc_result: result]
- defp set_params(opts, raw_response), do: opts ++ [params: raw_response]
- defp set_error_code(opts, code), do: opts ++ [error_code: code]
-
+
+ defp set_token(opts, token), do: [{:authorization, token} | opts]
+ defp set_success(opts, value), do: [{:success, value} | opts]
+ defp status_code(opts, code), do: [{:status, code} | opts]
+ defp set_message(opts, message), do: [{:message, message} | opts]
+ defp set_avs_result(opts, result), do: [{:avs, result} | opts]
+ defp set_cvc_result(opts, result), do: [{:cvc, result} | opts]
+ defp set_params(opts, raw_response), do: [{:params, raw_response} | opts]
+ defp set_error_code(opts, code), do: [{:error, code} | opts]
+
defp handle_opts(opts) do
case Keyword.fetch(opts, :success) do
{:ok, true} -> Response.success(opts)
{:ok, false} -> Response.error(opts)
end
end
-
end
end
diff --git a/test/gateways/authorize_net_test.exs b/test/gateways/authorize_net_test.exs
index 3c177dc4..b4906339 100644
--- a/test/gateways/authorize_net_test.exs
+++ b/test/gateways/authorize_net_test.exs
@@ -1,53 +1,63 @@
defmodule Gringotts.Gateways.AuthorizeNetTest do
-
- Code.require_file "../mocks/authorize_net_mock.exs", __DIR__
+ Code.require_file("../mocks/authorize_net_mock.exs", __DIR__)
use ExUnit.Case, async: false
alias Gringotts.Gateways.AuthorizeNetMock, as: MockResponse
alias Gringotts.CreditCard
alias Gringotts.Gateways.AuthorizeNet, as: ANet
-
+
import Mock
@auth %{name: "64jKa6NA", transaction_key: "4vmE338dQmAN6m7B"}
- @card %CreditCard {
+ @card %CreditCard{
number: "5424000000000015",
month: 12,
- year: 2020,
+ year: 2099,
verification_code: 999
}
- @bad_card %CreditCard {
+ @bad_card %CreditCard{
number: "123",
month: 10,
year: 2010,
verification_code: 123
}
- @amount %{amount: Decimal.new(20.0), currency: 'USD'}
+ @amount Money.new("2.99", :USD)
@opts [
config: @auth,
ref_id: "123456",
- order: %{invoice_number: "INV-12345", description: "Product Description"},
+ order: %{invoice_number: "INV-12345", description: "Product Description"},
lineitems: %{
item_id: "1",
name: "vase",
- description: "Cannes logo",
- quantity: "18",
- unit_price: %{amount: Decimal.new(20.0), currency: 'USD'}
+ description: "Cannes logo",
+ quantity: 18,
+ unit_price: Money.mult!(@amount, 18)
+ },
+ tax: %{name: "VAT", amount: Money.new("0.1", :EUR), description: "Value Added Tax"},
+ shipping: %{
+ name: "SAME-DAY-DELIVERY",
+ amount: Money.new("0.56", :EUR),
+ description: "Zen Logistics"
+ },
+ duty: %{
+ name: "import_duty",
+ amount: Money.new("0.25", :EUR),
+ description: "Upon import of goods"
}
]
@opts_refund [
config: @auth,
- ref_id: "123456",
- payment: %{card: %{number: "5424000000000015", year: 2020, month: 12}}
+ ref_id: "123456",
+ payment: %{card: %{number: "5424000000000015", year: 2099, month: 12}}
]
@opts_store [
config: @auth,
profile: %{
- merchant_customer_id: "123456",
- description: "Profile description here",
+ merchant_customer_id: "123456",
+ description: "Profile description here",
email: "customer-profile-email@here.com"
},
customer_type: "individual",
@@ -56,34 +66,35 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
@opts_store_without_validation [
config: @auth,
profile: %{
- merchant_customer_id: "123456",
- description: "Profile description here",
+ merchant_customer_id: "123456",
+ description: "Profile description here",
email: "customer-profile-email@here.com"
}
]
@opts_store_no_profile [
- config: @auth,
+ config: @auth
]
@opts_refund [
config: @auth,
ref_id: "123456",
- payment: %{card: %{number: "5424000000000015", year: 2020, month: 12}}
+ payment: %{card: %{number: "5424000000000015", year: 2099, month: 12}}
]
@opts_refund_bad_payment [
config: @auth,
ref_id: "123456",
- payment: %{card: %{number: "123", year: 2020, month: 12}}
+ payment: %{card: %{number: "123", year: 2099, month: 12}}
]
@opts_store [
config: @auth,
- profile: %{merchant_customer_id: "123456",
+ profile: %{
+ merchant_customer_id: "123456",
description: "Profile description here",
email: "customer-profile-email@here.com"
}
]
@opts_store_no_profile [
- config: @auth,
+ config: @auth
]
@opts_customer_profile [
config: @auth,
@@ -91,11 +102,11 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
validation_mode: "testMode",
customer_type: "individual"
]
- @opts_customer_profile_args[
- config: @auth,
- customer_profile_id: "1814012002"
+ @opts_customer_profile_args [
+ config: @auth,
+ customer_profile_id: "1814012002"
]
-
+
@refund_id "60036752756"
@void_id "60036855217"
@void_invalid_id "60036855211"
@@ -110,17 +121,21 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "purchase" do
test "successful response with right params" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_purchase_response end] do
- assert {:ok, response} = ANet.purchase(@amount, @card, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.successful_purchase_response()
+ end do
+ assert {:ok, response} = ANet.purchase(@amount, @card, @opts)
+ assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
end
end
test "with bad card" do
- with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.bad_card_purchase_response end] do
- assert {:error, response} = ANet.purchase(@amount, @bad_card, @opts)
- assert response.params["ErrorResponse"]["messages"]["resultCode"] == "Error"
+ with_mock HTTPoison,
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.bad_card_purchase_response()
+ end do
+ assert {:error, response} = ANet.purchase(@amount, @bad_card, @opts)
+ assert response.params["ErrorResponse"]["messages"]["resultCode"] == "Error"
end
end
end
@@ -128,17 +143,21 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "authorize" do
test "successful response with right params" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_authorize_response end] do
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.successful_authorize_response()
+ end do
assert {:ok, response} = ANet.authorize(@amount, @card, @opts)
assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
- end
+ end
end
test "with bad card" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.bad_card_purchase_response end] do
- assert {:error, response} = ANet.authorize(@amount, @bad_card, @opts)
- assert response.params["ErrorResponse"]["messages"]["resultCode"] == "Error"
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.bad_card_purchase_response()
+ end do
+ assert {:error, response} = ANet.authorize(@amount, @bad_card, @opts)
+ assert response.params["ErrorResponse"]["messages"]["resultCode"] == "Error"
end
end
end
@@ -146,15 +165,17 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "capture" do
test "successful response with right params" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_capture_response end] do
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.successful_capture_response()
+ end do
assert {:ok, response} = ANet.capture(@capture_id, @amount, @opts)
assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
end
end
-
+
test "with bad transaction id" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.bad_id_capture end] do
+ request: fn _method, _url, _body, _headers -> MockResponse.bad_id_capture() end do
assert {:error, response} = ANet.capture(@capture_invalid_id, @amount, @opts)
assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Error"
end
@@ -164,7 +185,9 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "refund" do
test "successful response with right params" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_refund_response end] do
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.successful_refund_response()
+ end do
assert {:ok, response} = ANet.refund(@amount, @refund_id, @opts_refund)
assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
end
@@ -172,17 +195,17 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
test "bad payment params" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.bad_card_refund end] do
- assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund_bad_payment)
- assert response.params["ErrorResponse"]["messages"]["resultCode"] == "Error"
+ request: fn _method, _url, _body, _headers -> MockResponse.bad_card_refund() end do
+ assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund_bad_payment)
+ assert response.params["ErrorResponse"]["messages"]["resultCode"] == "Error"
end
end
test "debit less than refund amount" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.debit_less_than_refund end] do
- assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Error"
+ request: fn _method, _url, _body, _headers -> MockResponse.debit_less_than_refund() end do
+ assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund)
+ assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Error"
end
end
end
@@ -190,17 +213,17 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "void" do
test "successful response with right params" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_void end] do
- assert {:ok, response} = ANet.void(@void_id, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
+ request: fn _method, _url, _body, _headers -> MockResponse.successful_void() end do
+ assert {:ok, response} = ANet.void(@void_id, @opts)
+ assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
end
end
-
+
test "with bad transaction id" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.void_non_existent_id end] do
- assert {:error, response} = ANet.void(@void_invalid_id, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Error"
+ request: fn _method, _url, _body, _headers -> MockResponse.void_non_existent_id() end do
+ assert {:error, response} = ANet.void(@void_invalid_id, @opts)
+ assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Error"
end
end
end
@@ -208,41 +231,49 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "store" do
test "successful response with right params" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_store_response end] do
- assert {:ok, response} = ANet.store(@card, @opts_store)
- assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
+ request: fn _method, _url, _body, _headers -> MockResponse.successful_store_response() end do
+ assert {:ok, response} = ANet.store(@card, @opts_store)
+ assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
end
end
test "successful response without validation and customer type" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_store_response end] do
- assert {:ok, response} = ANet.store(@card, @opts_store_without_validation)
- assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
+ request: fn _method, _url, _body, _headers -> MockResponse.successful_store_response() end do
+ assert {:ok, response} = ANet.store(@card, @opts_store_without_validation)
+ assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
end
end
test "without any profile" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.store_without_profile_fields end] do
- assert {:error, response} = ANet.store(@card, @opts_store_no_profile)
- assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Error"
- end
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.store_without_profile_fields()
+ end do
+ assert {:error, response} = ANet.store(@card, @opts_store_no_profile)
+
+ assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] ==
+ "Error"
+ end
end
test "with customer profile id" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.customer_payment_profile_success_response end] do
- assert {:ok, response} = ANet.store(@card, @opts_customer_profile)
- assert response.params["createCustomerPaymentProfileResponse"]["messages"]["resultCode"] == "Ok"
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.customer_payment_profile_success_response()
+ end do
+ assert {:ok, response} = ANet.store(@card, @opts_customer_profile)
+
+ assert response.params["createCustomerPaymentProfileResponse"]["messages"]["resultCode"] ==
+ "Ok"
end
end
test "successful response without valiadtion mode and customer type" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_store_response end] do
- assert {:ok, response} = ANet.store(@card, @opts_customer_profile_args)
- assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
+ request: fn _method, _url, _body, _headers -> MockResponse.successful_store_response() end do
+ assert {:ok, response} = ANet.store(@card, @opts_customer_profile_args)
+ assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
end
end
end
@@ -250,19 +281,22 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "unstore" do
test "successful response with right params" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.successful_unstore_response end] do
- assert {:ok, response} = ANet.unstore(@unstore_id, @opts)
- assert response.params["deleteCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.successful_unstore_response()
+ end do
+ assert {:ok, response} = ANet.unstore(@unstore_id, @opts)
+ assert response.params["deleteCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
end
end
end
test "network error type non existent domain" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.netwok_error_non_existent_domain end] do
- assert {:error, response} = ANet.purchase(@amount, @card, @opts)
- assert response.message == "HTTPoison says 'nxdomain'"
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.netwok_error_non_existent_domain()
+ end do
+ assert {:error, response} = ANet.purchase(@amount, @card, @opts)
+ assert response.message == "HTTPoison says 'nxdomain'"
end
end
-
end
diff --git a/test/mocks/authorize_net_mock.exs b/test/mocks/authorize_net_mock.exs
index beabfa21..cb68df4a 100644
--- a/test/mocks/authorize_net_mock.exs
+++ b/test/mocks/authorize_net_mock.exs
@@ -3,7 +3,7 @@
# purchase mock response
def successful_purchase_response do
{:ok,
- %HTTPoison.Response{body: "123456OkI00001
Successful.1C7HPT1YP2600365530965D6782A03246EE3BAFABE8006E32DE970XXXX0015MasterCard1
This transaction has been approved.",
+ %HTTPoison.Response{body: ~s{123456OkI00001
Successful.1C7HPT1YP2600365530965D6782A03246EE3BAFABE8006E32DE970XXXX0015MasterCard1
This transaction has been approved.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -21,7 +21,7 @@
def bad_card_purchase_response do
{:ok,
- %HTTPoison.Response{body: "ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XXXXX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.",
+ %HTTPoison.Response{body: ~s{ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XXXXX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -39,7 +39,7 @@
def bad_amount_purchase_response do
{:ok,
- %HTTPoison.Response{body: "123456ErrorE00027
The transaction was unsuccessful.3P0C7C56F020A2AE2660A87637CD00B4D5C0XXXX0015MasterCard5A valid amount is required.",
+ %HTTPoison.Response{body: ~s{123456ErrorE00027
The transaction was unsuccessful.3P0C7C56F020A2AE2660A87637CD00B4D5C0XXXX0015MasterCard5A valid amount is required.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -58,7 +58,7 @@
# authorize mock response
def successful_authorize_response do
{:ok,
- %HTTPoison.Response{body: "123456OkI00001
Successful.1K6Z0ABYP260036854582A4AD079E22A271D92662CF093CED7A5D0XXXX0015MasterCard1
This transaction has been approved.",
+ %HTTPoison.Response{body: ~s{123456OkI00001
Successful.1K6Z0ABYP260036854582A4AD079E22A271D92662CF093CED7A5D0XXXX0015MasterCard1
This transaction has been approved.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -76,7 +76,7 @@
def bad_card_authorize_response do
{:ok,
- %HTTPoison.Response{body: "ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XXXXX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.",
+ %HTTPoison.Response{body: ~s{ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XXXXX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -94,7 +94,7 @@
def bad_amount_authorize_response do
{:ok,
- %HTTPoison.Response{body: "123456ErrorE00027
The transaction was unsuccessful.3P0C7C56F020A2AE2660A87637CD00B4D5C0XXXX0015MasterCard290There is one or more missing or invalid required fields.",
+ %HTTPoison.Response{body: ~s{123456ErrorE00027
The transaction was unsuccessful.3P0C7C56F020A2AE2660A87637CD00B4D5C0XXXX0015MasterCard290There is one or more missing or invalid required fields.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -114,7 +114,7 @@
def successful_capture_response do
{:ok,
- %HTTPoison.Response{body: "123456OkI00001
Successful.14OKD6YP6003685493160036854931348C4ECD0F764736B012C4655BFA68EF0XXXX0015MasterCard1
This transaction has been approved.",
+ %HTTPoison.Response{body: ~s{123456OkI00001
Successful.14OKD6YP6003685493160036854931348C4ECD0F764736B012C4655BFA68EF0XXXX0015MasterCard1
This transaction has been approved.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -132,7 +132,7 @@
def bad_id_capture do
{:ok,
- %HTTPoison.Response{body: "123456ErrorE00027
The transaction was unsuccessful.3P0A5280E2A6AA1290D451A24286692D1B0033A valid referenced transaction ID is required.",
+ %HTTPoison.Response{body: ~s{123456ErrorE00027
The transaction was unsuccessful.3P0A5280E2A6AA1290D451A24286692D1B0033A valid referenced transaction ID is required.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -151,7 +151,7 @@
# refund mock response
def successful_refund_response do
{:ok,
- %HTTPoison.Response{body: "123456OkI00001
Successful.1P6003685566160036752756169F2381B172A5AA247A01757A3E520A0XXXX0015MasterCard1
This transaction has been approved.",
+ %HTTPoison.Response{body: ~s{123456OkI00001
Successful.1P6003685566160036752756169F2381B172A5AA247A01757A3E520A0XXXX0015MasterCard1
This transaction has been approved.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -169,7 +169,7 @@
def bad_card_refund do
{:ok,
- %HTTPoison.Response{body: "ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.",
+ %HTTPoison.Response{body: ~s{ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -187,7 +187,7 @@
def debit_less_than_refund do
{:ok,
- %HTTPoison.Response{body: "123456ErrorE00027
The transaction was unsuccessful.3P060036752756A5280E2A6AA1290D451A24286692D1B00XXXX0015MasterCard55The sum of credits against the referenced transaction would exceed original debit amount.",
+ %HTTPoison.Response{body: ~s{123456ErrorE00027
The transaction was unsuccessful.3P060036752756A5280E2A6AA1290D451A24286692D1B00XXXX0015MasterCard55The sum of credits against the referenced transaction would exceed original debit amount.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -206,7 +206,7 @@
# void mock response
def successful_void do
{:ok,
- %HTTPoison.Response{body: "123456OkI00001
Successful.1ZJPVRXP6003685521760036855217F09A215511891DCEA91B6CC52B9F4E870XXXX0015MasterCard1
This transaction has been approved.",
+ %HTTPoison.Response{body: ~s{123456OkI00001
Successful.1ZJPVRXP6003685521760036855217F09A215511891DCEA91B6CC52B9F4E870XXXX0015MasterCard1
This transaction has been approved.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -224,7 +224,7 @@
def void_non_existent_id do
{:ok,
- %HTTPoison.Response{body: "123456ErrorE00027
The transaction was unsuccessful.3P060036855219C7C56F020A2AE2660A87637CD00B4D5C016The transaction cannot be found.",
+ %HTTPoison.Response{body: ~s{123456ErrorE00027
The transaction was unsuccessful.3P060036855219C7C56F020A2AE2660A87637CD00B4D5C016The transaction cannot be found.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -244,7 +244,7 @@
def successful_store_response do
{:ok,
- %HTTPoison.Response{body: "OkI00001
Successful.18139914901808649724",
+ %HTTPoison.Response{body: ~s{OkI00001
Successful.18139914901808649724},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -262,7 +262,7 @@
def store_without_profile_fields do
{:ok,
- %HTTPoison.Response{body: "ErrorE00041
One or more fields in the profile must contain a value.",
+ %HTTPoison.Response{body: ~s{ErrorE00041
One or more fields in the profile must contain a value.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -281,7 +281,7 @@
#unstore mock response
def successful_unstore_response do
{:ok,
- %HTTPoison.Response{body: "OkI00001
Successful.",
+ %HTTPoison.Response{body: ~s{OkI00001
Successful.},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
@@ -299,7 +299,7 @@
def customer_payment_profile_success_response do
{:ok,
- %HTTPoison.Response{body: "OkI00001
Successful.181401200218086700051,1,1,(TESTMODE) This transaction has been approved.,000000,P,0,none,Test transaction for ValidateCustomerPaymentProfile.,1.00,CC,auth_only,none,,,,,,,,,,,email@example.com,,,,,,,,,0.00,0.00,0.00,FALSE,none,EA9FD49A9501D0415FE26BAEF9FD8B2C,,,,,,,,,,,,,XXXX0015,MasterCard,,,,,,,,,,,,,,,,,",
+ %HTTPoison.Response{body: ~s{OkI00001
Successful.181401200218086700051,1,1,(TESTMODE) This transaction has been approved.,000000,P,0,none,Test transaction for ValidateCustomerPaymentProfile.,1.00,CC,auth_only,none,,,,,,,,,,,email@example.com,,,,,,,,,0.00,0.00,0.00,FALSE,none,EA9FD49A9501D0415FE26BAEF9FD8B2C,,,,,,,,,,,,,XXXX0015,MasterCard,,,,,,,,,,,,,,,,,},
headers: [{"Cache-Control", "private"},
{"Content-Type", "application/xml; charset=utf-8"},
{"X-OPNET-Transaction-Trace",
From 5e748e711476f5f6b191099ccc0006758d723ecb Mon Sep 17 00:00:00 2001
From: Jyoti Gautam
Date: Thu, 25 Jan 2018 18:39:33 +0530
Subject: [PATCH 19/60] [trexle] Adds Money protocol (#84)
* Integrated Money protocol with trexle
* token and message added to Response
* Used `~s` sigils in mocks
---
lib/gringotts/gateways/trexle.ex | 382 +++++++++++++++++++------------
test/gateways/trexle_test.exs | 184 ++++++---------
test/mocks/trexle_mock.exs | 369 +++++++++++++----------------
3 files changed, 464 insertions(+), 471 deletions(-)
diff --git a/lib/gringotts/gateways/trexle.ex b/lib/gringotts/gateways/trexle.ex
index 05f7d5c8..d89d7767 100644
--- a/lib/gringotts/gateways/trexle.ex
+++ b/lib/gringotts/gateways/trexle.ex
@@ -1,9 +1,8 @@
defmodule Gringotts.Gateways.Trexle do
-
@moduledoc """
- Trexle Payment Gateway Implementation:
+ [Trexle][home] Payment Gateway implementation.
- For further details, please refer [Trexle API documentation](https://docs.trexle.com/).
+ > For further details, please refer [Trexle API documentation][docs].
Following are the features that have been implemented for the Trexle Gateway:
@@ -16,146 +15,213 @@ defmodule Gringotts.Gateways.Trexle do
| Store | `store/2` |
## The `opts` argument
- A `Keyword` list `opts` passed as an optional argument for transactions with the gateway. Following are the keys
+
+ Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply
+ optional arguments for transactions with Trexle. The following keys are
supported:
- * email
- * ip_address
- * description
+ * `email`
+ * `ip_address`
+ * `description`
+
+ [docs]: https://docs.trexle.com/
+ [home]: https://trexle.com/
+
+ ## Registering your Trexle account at `Gringotts`
+
+ After [creating your account][dashboard] successfully on Trexle, head to the dashboard and find
+ your account "secrets" in the [`API keys`][keys] section.
- ## Trexle account registeration with `Gringotts`
- After creating your account successfully on [Trexle](https://docs.trexle.com/) follow the [dashboard link](https://trexle.com/dashboard/api-keys) to fetch the secret api_key.
+ Here's how the secrets map to the required configuration parameters for MONEI:
+
+ | Config parameter | Trexle secret |
+ | ------- | ---- |
+ | `:api_key` | **API key** |
Your Application config must look something like this:
config :gringotts, Gringotts.Gateways.Trexle,
adapter: Gringotts.Gateways.Trexle,
- api_key: "Secret API key",
- default_currency: "USD"
+ api_key: "your-secret-API-key"
+
+ [dashboard]: https://trexle.com/dashboard/
+ [keys]: https://trexle.com/dashboard/api-keys
+
+ ## Scope of this module
+
+ * Trexle processes money in cents.**citation-needed**.
+
+ ## Supported Gateways
+
+ Find the official [list here][gateways].
+
+ [gateways]: https://trexle.com/payment-gateway
+
+ ## Following the examples
+
+ 1. First, set up a sample application and configure it to work with Trexle.
+ - You could do that from scratch by following our [Getting Started][gs] guide.
+ - To save you time, we recommend [cloning our example repo][example-repo]
+ that gives you a pre-configured sample app ready-to-go.
+ + You could use the same config or update it the with your "secrets"
+ that as described
+ [above](#module-registering-your-trexle-account-at-gringotts).
+
+ 2. Run an `iex` session with `iex -S mix` and add some variable bindings and
+ aliases to it (to save some time):
+ ```
+ iex> alias Gringotts.{Response, CreditCard, Gateways.Trexle}
+ iex> card = %CreditCard{
+ first_name: "Harry",
+ last_name: "Potter",
+ number: "4200000000000000",
+ year: 2099, month: 12,
+ verification_code: "123",
+ brand: "VISA"}
+ iex> address = %Address{
+ street1: "301, Gryffindor",
+ street2: "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
+ city: "Highlands",
+ region: "SL",
+ country: "GB",
+ postal_code: "11111",
+ phone: "(555)555-5555"}
+ iex> options = [email: "masterofdeath@ministryofmagic.gov",
+ ip_address: "127.0.0.1",
+ billing_address: address,
+ description: "For our valued customer, Mr. Potter"]
+ ```
+
+ We'll be using these in the examples below.
+
+ [example-repo]: https://github.com/aviabird/gringotts_example
+ [gs]: #
"""
@base_url "https://core.trexle.com/api/v1/"
use Gringotts.Gateways.Base
- use Gringotts.Adapter, required_config: [:api_key, :default_currency]
+ use Gringotts.Adapter, required_config: [:api_key]
import Poison, only: [decode: 1]
- alias Gringotts.{Response, CreditCard, Address}
+ alias Gringotts.{Response, CreditCard, Address, Money}
@doc """
- Performs the authorization of the card to be used for payment.
+ Performs a (pre) Authorize operation.
- Authorizes your card with the given amount and returns a charge token and captured status as false in response.
+ The authorization validates the `card` details with the banking network,
+ places a hold on the transaction `amount` in the customer’s issuing bank and
+ also triggers risk management. Funds are not transferred.
+
+ Trexle returns a "charge token", avaliable in the `Response.authorization`
+ field, which can be used in future to perform a `capture/3`.
### Example
- ```
- iex> amount = 100
- iex> card = %CreditCard{
- number: "5200828282828210",
- month: 12,
- year: 2018,
- first_name: "John",
- last_name: "Doe",
- verification_code: "123",
- brand: "visa"
- }
+ The following session shows how one would (pre) authorize a payment of $100 on
+ a sample `card`.
+ ```
+ iex> amount = %{value: Decimal.new(100),currency: "USD")
+ iex> card = %CreditCard{
+ first_name: "Harry",
+ last_name: "Potter",
+ number: "5200828282828210",
+ year: 2099, month: 12,
+ verification_code: "123",
+ brand: "VISA"}
iex> address = %Address{
- street1: "123 Main",
- street2: "Suite 100",
- city: "New York",
- region: "NY",
- country: "US",
- postal_code: "11111",
- phone: "(555)555-5555"
- }
-
- iex> options = [email: "john@trexle.com", ip_address: "66.249.79.118", billing_address: address, description: "Store Purchase 1437598192"]
-
- iex> Gringotts.authorize(:payment_worker, Gringotts.Gateways.Trexle, amount, card, options)
+ street1: "301, Gryffindor",
+ street2: "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
+ city: "Highlands",
+ region: "SL",
+ country: "GB",
+ postal_code: "11111",
+ phone: "(555)555-5555"}
+ iex> options = [email: "masterofdeath@ministryofmagic.gov",
+ ip_address: "127.0.0.1",
+ billing_address: address,
+ description: "For our valued customer, Mr. Potter"]
+ iex> Gringotts.authorize(Gringotts.Gateways.Trexle, amount, card, options)
```
"""
-
- @spec authorize(float, CreditCard.t, list) :: map
+ @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
def authorize(amount, payment, opts \\ []) do
params = create_params_for_auth_or_purchase(amount, payment, opts, false)
commit(:post, "charges", params, opts)
end
@doc """
- Performs the amount transfer from the customer to the merchant.
+ Captures a pre-authorized `amount`.
+
+ `amount` is transferred to the merchant account by MONEI when it is smaller or
+ equal to the amount used in the pre-authorization referenced by `charge_token`.
+
+ Trexle returns a "charge token", avaliable in the `Response.authorization`
+ field, which can be used in future to perform a `refund/2`.
- The actual amount deduction performed by Trexle using the customer's card info.
+ ## Note
+
+ Multiple captures cannot be performed on the same "charge token". If the
+ captured amount is smaller than the (pre) authorized amount, the "un-captured"
+ amount is released.**citation-needed**
## Example
- ```
- iex> card = %CreditCard{
- number: "5200828282828210",
- month: 12,
- year: 2018,
- first_name: "John",
- last_name: "Doe",
- verification_code: "123",
- brand: "visa"
- }
- iex> address = %Address{
- street1: "123 Main",
- street2: "Suite 100",
- city: "New York",
- region: "NY",
- country: "US",
- postal_code: "11111",
- phone: "(555)555-5555"
- }
-
- iex> options = [email: "john@trexle.com", ip_address: "66.249.79.118" ,billing_address: address, description: "Store Purchase 1437598192"]
-
- iex> @address %Address{
- street1: "123 Main",
- street2: "Suite 100",
- city: "New York",
- region: "NY",
- country: "US",
- postal_code: "11111",
- phone: "(555)555-5555"
- }
-
- iex> options = [email: "john@trexle.com", ip_address: "66.249.79.118" ,billing_address: @address, description: "Store Purchase 1437598192"]
-
- iex> amount = 50
-
- iex> Gringotts.purchase(:payment_worker, Gringotts.Gateways.Trexle, amount, card, options)
+ The following example shows how one would (partially) capture a previously
+ authorized a payment worth $10 by referencing the obtained `charge_token`.
+
+ ```
+ iex> amount = %{value: Decimal.new(100),currency: "USD")
+ iex> token = "some-real-token"
+ iex> Gringotts.capture(Gringotts.Gateways.Trexle, token, amount)
```
"""
-
- @spec purchase(float, CreditCard.t, list) :: map
- def purchase(amount, payment, opts \\ []) do
- params = create_params_for_auth_or_purchase(amount, payment, opts)
- commit(:post, "charges", params, opts)
+ @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response}
+ def capture(charge_token, amount, opts \\ []) do
+ {_, int_value, _} = Money.to_integer(amount)
+ params = [amount: int_value]
+ commit(:put, "charges/#{charge_token}/capture", params, opts)
end
@doc """
- Captures a particular amount using the charge token of a pre authorized card.
+ Transfers `amount` from the customer to the merchant.
- The amount specified should be less than or equal to the amount given prior to capture while authorizing the card.
- If the amount mentioned is less than the amount given in authorization process, the mentioned amount is debited.
- Please note that multiple captures can't be performed for a given charge token from the authorization process.
+ Trexle attempts to process a purchase on behalf of the customer, by debiting
+ `amount` from the customer's account by charging the customer's `card`.
- ### Example
- ```
- iex> amount = 100
+ ## Example
- iex> token = "charge_6a5fcdc6cdbf611ee3448a9abad4348b2afab3ec"
+ The following session shows how one would process a payment worth $100 in
+ one-shot, without (pre) authorization.
- iex> Gringotts.capture(:payment_worker, Gringotts.Gateways.Trexle, token, amount)
+ ```
+ iex> amount = %{value: Decimal.new(100),currency: "USD")
+ iex> card = %CreditCard{
+ first_name: "Harry",
+ last_name: "Potter",
+ number: "5200828282828210",
+ year: 2099, month: 12,
+ verification_code: "123",
+ brand: "VISA"}
+ iex> address = %Address{
+ street1: "301, Gryffindor",
+ street2: "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
+ city: "Highlands",
+ region: "SL",
+ country: "GB",
+ postal_code: "11111",
+ phone: "(555)555-5555"}
+ iex> options = [email: "masterofdeath@ministryofmagic.gov",
+ ip_address: "127.0.0.1",
+ billing_address: address,
+ description: "For our valued customer, Mr. Potter"]
+ iex> Gringotts.purchase(Gringotts.Gateways.Trexle, amount, card, options)
```
"""
-
- @spec capture(String.t, float, list) :: map
- def capture(charge_token, amount, opts \\ []) do
- params = [amount: amount]
- commit(:put, "charges/#{charge_token}/capture", params, opts)
+ @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def purchase(amount, payment, opts \\ []) do
+ params = create_params_for_auth_or_purchase(amount, payment, opts)
+ commit(:post, "charges", params, opts)
end
@doc """
@@ -164,24 +230,28 @@ defmodule Gringotts.Gateways.Trexle do
Trexle processes a full or partial refund worth `amount`, referencing a
previous `purchase/3` or `capture/3`.
- Multiple refund can be performed for the same charge token from purchase or capture done before performing refund action unless the cumulative amount is less than the amount given while authorizing.
+ Trexle returns a "refund token", avaliable in the `Response.authorization`
+ field.
- ## Example
- The following session shows how one would refund a previous purchase (and similarily for captures).
- ```
- iex> amount = 5
+ Multiple, partial refunds can be performed on the same "charge token"
+ referencing a previous `purchase/3` or `capture/3` till the cumulative refunds
+ equals the `capture/3`d or `purchase/3`d amount.
- iex> token = "charge_668d3e169b27d4938b39246cb8c0890b0bd84c3c"
+ ## Example
- iex> options = [email: "john@trexle.com", ip_address: "66.249.79.118", description: "Store Purchase 1437598192"]
+ The following session shows how one would refund $100 of a previous
+ `purchase/3` (and similarily for `capture/3`s).
- iex> Gringotts.refund(:payment_worker, Gringotts.Gateways.Trexle, amount, token, options)
+ ```
+ iex> amount = %{value: Decimal.new(100),currency: "USD")
+ iex> token = "some-real-token"
+ iex> Gringotts.refund(Gringotts.Gateways.Trexle, amount, token)
```
"""
-
- @spec refund(float, String.t, list) :: map
+ @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
def refund(amount, charge_token, opts \\ []) do
- params = [amount: amount]
+ {_, int_value, _} = Money.to_integer(amount)
+ params = [amount: int_value]
commit(:post, "charges/#{charge_token}/refunds", params, opts)
end
@@ -189,52 +259,51 @@ defmodule Gringotts.Gateways.Trexle do
Stores the card information for future use.
## Example
- The following session shows how one would store a card (a payment-source) for future use.
+
+ The following session shows how one would store a card (a payment-source) for
+ future use.
```
iex> card = %CreditCard{
- number: "5200828282828210",
- month: 12,
- year: 2018,
- first_name: "John",
- last_name: "Doe",
- verification_code: "123",
- brand: "visa"
- }
-
+ first_name: "Harry",
+ last_name: "Potter",
+ number: "5200828282828210",
+ year: 2099, month: 12,
+ verification_code: "123",
+ brand: "VISA"}
iex> address = %Address{
- street1: "123 Main",
- street2: "Suite 100",
- city: "New York",
- region: "NY",
- country: "US",
- postal_code: "11111",
- phone: "(555)555-5555"
- }
-
- iex> options = [email: "john@trexle.com", ip_address: "66.249.79.118", billing_address: address, description: "Store Purchase 1437598192"]
-
- iex> Gringotts.store(:payment_worker, Gringotts.Gateways.Trexle, card, options)
+ street1: "301, Gryffindor",
+ street2: "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
+ city: "Highlands",
+ region: "SL",
+ country: "GB",
+ postal_code: "11111",
+ phone: "(555)555-5555"}
+ iex> options = [email: "masterofdeath@ministryofmagic.gov",
+ ip_address: "127.0.0.1",
+ billing_address: address,
+ description: "For our valued customer, Mr. Potter"]
+ iex> Gringotts.store(Gringotts.Gateways.Trexle, card, options)
```
"""
-
- @spec store(CreditCard.t, list) :: map
+ @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response}
def store(payment, opts \\ []) do
- params = [email: opts[:email]]
- ++ card_params(payment)
- ++ address_params(opts[:billing_address])
+ params =
+ [email: opts[:email]] ++ card_params(payment) ++ address_params(opts[:billing_address])
+
commit(:post, "customers", params, opts)
end
defp create_params_for_auth_or_purchase(amount, payment, opts, capture \\ true) do
+ {currency, int_value, _} = Money.to_integer(amount)
+
[
capture: capture,
- amount: amount,
- currency: opts[:config][:default_currency],
+ amount: int_value,
+ currency: currency,
email: opts[:email],
ip_address: opts[:ip_address],
description: opts[:description]
- ] ++ card_params(payment)
- ++ address_params(opts[:billing_address])
+ ] ++ card_params(payment) ++ address_params(opts[:billing_address])
end
defp card_params(%CreditCard{} = card) do
@@ -260,30 +329,39 @@ defmodule Gringotts.Gateways.Trexle do
defp commit(method, path, params, opts) do
auth_token = "Basic #{Base.encode64(opts[:config][:api_key])}"
- headers = [{"Content-Type", "application/x-www-form-urlencoded"}, {"Authorization", auth_token}]
- data = params_to_string(params)
- options = [hackney: [:insecure, basic_auth: {opts[:config][:api_key], "password"}]]
+
+ headers = [
+ {"Content-Type", "application/x-www-form-urlencoded"},
+ {"Authorization", auth_token}
+ ]
+
+ options = [basic_auth: {opts[:config][:api_key], "password"}]
url = "#{@base_url}#{path}"
- response = HTTPoison.request(method, url, data, headers, options)
+ response = HTTPoison.request(method, url, {:form, params}, headers, options)
response |> respond
end
- @spec respond(term) ::
- {:ok, Response} |
- {:error, Response}
+ @spec respond(term) :: {:ok | :error, Response}
defp respond(response)
defp respond({:ok, %{status_code: code, body: body}}) when code in [200, 201] do
- case decode(body) do
- {:ok, results} -> {:ok, Response.success(raw: results, status_code: code)}
- end
+ {:ok, results} = decode(body)
+ token = results["response"]["token"]
+ message = results["response"]["status_message"]
+
+ {
+ :ok,
+ Response.success(authorization: token, message: message, raw: results, status_code: code)
+ }
end
defp respond({:ok, %{status_code: status_code, body: body}}) do
- {:error, Response.error(status_code: status_code, raw: body)}
+ {:ok, results} = decode(body)
+ detail = results["detail"]
+ {:error, Response.error(status_code: status_code, message: detail, raw: results)}
end
defp respond({:error, %HTTPoison.Error{} = error}) do
- {:error, Response.error(code: error.id, reason: :network_fail?, description: "HTTPoison says '#{error.reason}'")}
+ {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")}
end
end
diff --git a/test/gateways/trexle_test.exs b/test/gateways/trexle_test.exs
index 4e2bebeb..f8a50562 100644
--- a/test/gateways/trexle_test.exs
+++ b/test/gateways/trexle_test.exs
@@ -1,9 +1,9 @@
defmodule Gringotts.Gateways.TrexleTest do
-
- Code.require_file "../mocks/trexle_mock.exs", __DIR__
+ Code.require_file("../mocks/trexle_mock.exs", __DIR__)
use ExUnit.Case, async: false
alias Gringotts.Gateways.TrexleMock, as: MockResponse
alias Gringotts.Gateways.Trexle
+
alias Gringotts.{
CreditCard,
Address
@@ -12,100 +12,86 @@ defmodule Gringotts.Gateways.TrexleTest do
import Mock
@valid_card %CreditCard{
- number: "5200828282828210",
+ first_name: "Harry",
+ last_name: "Potter",
+ number: "4200000000000000",
+ year: 2099,
month: 12,
- year: 2018,
- first_name: "John",
- last_name: "Doe",
verification_code: "123",
- brand: "visa"
+ brand: "VISA"
}
@invalid_card %CreditCard{
- number: "5200828282828210",
- month: 12,
+ first_name: "Harry",
+ last_name: "Potter",
+ number: "4200000000000000",
year: 2010,
- first_name: "John",
- last_name: "Doe",
+ month: 12,
verification_code: "123",
- brand: "visa"
+ brand: "VISA"
}
@address %Address{
- street1: "123 Main",
- street2: "Suite 100",
- city: "New York",
- region: "NY",
- country: "US",
+ street1: "301, Gryffindor",
+ street2: "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
+ city: "Highlands",
+ region: "SL",
+ country: "GB",
postal_code: "11111",
phone: "(555)555-5555"
}
- @amount 100
- @bad_amount 20
+ # $2.99
+ @amount Money.new("2.99", :USD)
+ # 50 US cents, trexle does not work with amount smaller than 50 cents.
+ @bad_amount Money.new("0.49", :USD)
- @valid_token "J5RGMpDlFlTfv9mEFvNWYoqHufyukPP4"
- @invalid_token "30"
+ @valid_token "7214344252e11af79c0b9e7b4f3f6234"
+ @invalid_token "14a62fff80f24a25f775eeb33624bbb3"
+ @auth %{api_key: "7214344252e11af79c0b9e7b4f3f6234"}
@opts [
- config: %{api_key: "J5RGMpDlFlTfv9mEFvNWYoqHufyukPP4", default_currency: "USD"},
- email: "john@trexle.com",
- billing_address: @address,
- ip_address: "66.249.79.118",
- description: "Store Purchase 1437598192"
- ]
-
- @missingip_opts [
- config: %{api_key: "J5RGMpDlFlTfv9mEFvNWYoqHufyukPP4", default_currency: "USD"},
- email: "john@trexle.com",
- billing_address: @address,
- description: "Store Purchase 1437598192"
- ]
-
- @invalid_opts [
- config: %{api_key: "J5RGMpDlFlTfv9mEFvNWYoqHufyukPP4"},
- email: "john@trexle.com",
+ config: @auth,
+ email: "masterofdeath@ministryofmagic.gov",
+ ip_address: "127.0.0.1",
billing_address: @address,
- ip_address: "66.249.79.118",
- description: "Store Purchase 1437598192"
+ description: "For our valued customer, Mr. Potter"
]
- describe "validation arguments check" do
- test "with no currency passed in config" do
- assert_raise ArgumentError, fn ->
- Trexle.validate_config(@invalid_opts)
- end
- end
- end
-
describe "purchase" do
test "with valid card" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_purchase_with_valid_card end] do
- {:ok, response} = Trexle.purchase(@amount, @valid_card, @opts)
- assert response.status_code == 201
- assert response.raw["response"]["success"] == true
- assert response.raw["response"]["captured"] == false
+ request: fn _method, _url, _body, _headers, _options ->
+ MockResponse.test_for_purchase_with_valid_card()
+ end do
+ {:ok, response} = Trexle.purchase(@amount, @valid_card, @opts)
+ assert response.status_code == 201
+ assert response.raw["response"]["success"] == true
+ assert response.raw["response"]["captured"] == false
end
end
test "with invalid card" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_purchase_with_invalid_card end] do
+ request: fn _method, _url, _body, _headers, _options ->
+ MockResponse.test_for_purchase_with_invalid_card()
+ end do
{:error, response} = Trexle.purchase(@amount, @invalid_card, @opts)
assert response.status_code == 400
assert response.success == false
- assert response.raw == ~s({"error":"Payment failed","detail":"Your card's expiration year is invalid."})
+ assert response.message == "Your card's expiration year is invalid."
end
end
test "with invalid amount" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_purchase_with_invalid_amount end] do
+ request: fn _method, _url, _body, _headers, _options ->
+ MockResponse.test_for_purchase_with_invalid_amount()
+ end do
{:error, response} = Trexle.purchase(@bad_amount, @valid_card, @opts)
assert response.status_code == 400
assert response.success == false
- assert response.raw == ~s({"error":"Payment failed","detail":"Amount must be at least 50 cents"})
+ assert response.message == "Amount must be at least 50 cents"
end
end
end
@@ -113,94 +99,64 @@ defmodule Gringotts.Gateways.TrexleTest do
describe "authorize" do
test "with valid card" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_authorize_with_valid_card end] do
+ request: fn _method, _url, _body, _headers, _options ->
+ MockResponse.test_for_authorize_with_valid_card()
+ end do
{:ok, response} = Trexle.authorize(@amount, @valid_card, @opts)
assert response.status_code == 201
assert response.raw["response"]["success"] == true
assert response.raw["response"]["captured"] == false
end
end
-
- test "with invalid card" do
- with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_authorize_with_invalid_card end] do
- {:error, response} = Trexle.authorize(@amount, @invalid_card, @opts)
- assert response.status_code == 400
- assert response.success == false
- assert response.raw == ~s({"error":"Payment failed","detail":"Your card's expiration year is invalid."})
- end
- end
-
- test "with invalid amount" do
- with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_authorize_with_invalid_amount end] do
- {:error, response} = Trexle.authorize(@amount, @valid_card, @opts)
- assert response.status_code == 400
- assert response.success == false
- assert response.raw == ~s({"error":"Payment failed","detail":"Amount must be at least 50 cents"})
- end
- end
-
- test "with missing ip address" do
- with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_authorize_with_missing_ip_address end] do
- {:error, response} = Trexle.authorize(@amount, @valid_card, @missingip_opts)
- assert response.status_code == 500
- assert response.success == false
- assert response.raw == ~s({"error":"ip_address is missing"})
- end
- end
end
describe "refund" do
test "with valid token" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_authorize_with_valid_card end] do
+ request: fn _method, _url, _body, _headers, _options ->
+ MockResponse.test_for_authorize_with_valid_card()
+ end do
{:ok, response} = Trexle.refund(@amount, @valid_token, @opts)
assert response.status_code == 201
assert response.raw["response"]["success"] == true
assert response.raw["response"]["captured"] == false
end
end
-
- test "with invalid token" do
- with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_authorize_with_invalid_amount end] do
- {:error, response} = Trexle.refund(@amount, @invalid_token, @opts)
- assert response.status_code == 400
- assert response.success == false
- assert response.raw == ~s({"error":"Payment failed","detail":"Amount must be at least 50 cents"})
- end
- end
end
describe "capture" do
- test "with valid chargetoken" do
+ test "with valid charge token" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_capture_with_valid_chargetoken end] do
+ request: fn _method, _url, _body, _headers, _options ->
+ MockResponse.test_for_capture_with_valid_chargetoken()
+ end do
{:ok, response} = Trexle.capture(@valid_token, @amount, @opts)
assert response.status_code == 200
assert response.raw["response"]["success"] == true
assert response.raw["response"]["captured"] == true
- assert response.raw["response"]["status_message"] == "Transaction approved"
+ assert response.message == "Transaction approved"
end
end
- test "test_for_capture_with_invalid_chargetoken" do
- with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_capture_with_invalid_chargetoken end] do
- {:error, response} = Trexle.capture(@invalid_token, @amount, @opts)
- assert response.status_code == 400
- assert response.success == false
- assert response.raw == ~s({"error":"Capture failed","detail":"invalid token"})
- end
+ test "with invalid charge token" do
+ with_mock HTTPoison,
+ request: fn _method, _url, _body, _headers, _options ->
+ MockResponse.test_for_capture_with_invalid_chargetoken()
+ end do
+ {:error, response} = Trexle.capture(@invalid_token, @amount, @opts)
+ assert response.status_code == 400
+ assert response.success == false
+ assert response.message == "invalid token"
+ end
end
end
describe "store" do
test "with valid card" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_store_with_valid_card end] do
+ request: fn _method, _url, _body, _headers, _options ->
+ MockResponse.test_for_store_with_valid_card()
+ end do
{:ok, response} = Trexle.store(@valid_card, @opts)
assert response.status_code == 201
end
@@ -210,10 +166,12 @@ defmodule Gringotts.Gateways.TrexleTest do
describe "network failure" do
test "with authorization" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers, _options) -> MockResponse.test_for_network_failure end] do
+ request: fn _method, _url, _body, _headers, _options ->
+ MockResponse.test_for_network_failure()
+ end do
{:error, response} = Trexle.authorize(@amount, @valid_card, @opts)
assert response.success == false
- assert response.reason == :network_fail?
+ assert response.message == "HTTPoison says 'some_hackney_error'"
end
end
end
diff --git a/test/mocks/trexle_mock.exs b/test/mocks/trexle_mock.exs
index 1a75f19e..27c4d1c2 100644
--- a/test/mocks/trexle_mock.exs
+++ b/test/mocks/trexle_mock.exs
@@ -1,243 +1,200 @@
defmodule Gringotts.Gateways.TrexleMock do
-
def test_for_purchase_with_valid_card do
- {:ok,
- %HTTPoison.Response{
- body: "{\"response\":{\"token\":\"charge_3e89c6f073606ac1efe62e76e22dd7885441dc72\",\"success\":true,\"captured\":false}}",
- headers: [
- {"Date", "Fri, 22 Dec 2017 11:57:28 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"ETag", "W/\"5a9f44c457a4fdd0478c82ec1af64816\""},
- {"Cache-Control", "max-age=0, private, must-revalidate"},
- {"X-Request-Id", "9b2a1d30-9bca-48f2-862e-4090766689cb"},
- {"X-Runtime", "0.777520"},
- {"Content-Length", "104"},
- {"X-Powered-By", "PleskLin"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 201
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"response":{"token":"charge_3e89c6f073606ac1efe62e76e22dd7885441dc72","success":true,"captured":false}}/,
+ headers: [
+ {"Date", "Fri, 22 Dec 2017 11:57:28 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "max-age=0, private, must-revalidate"},
+ {"X-Request-Id", "9b2a1d30-9bca-48f2-862e-4090766689cb"},
+ {"X-Runtime", "0.777520"},
+ {"Content-Length", "104"},
+ {"X-Powered-By", "PleskLin"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 201
+ }}
end
def test_for_purchase_with_invalid_card do
- {:ok,
- %HTTPoison.Response{
- body: "{\"error\":\"Payment failed\",\"detail\":\"Your card's expiration year is invalid.\"}",
- headers: [
- {"Date", "Fri, 22 Dec 2017 13:20:50 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "eb8100a1-8ffa-47da-9623-8d3b2af51b84"},
- {"X-Runtime", "0.445244"},
- {"Content-Length", "77"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 400
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"error":"Payment failed","detail":"Your card's expiration year is invalid."}/,
+ headers: [
+ {"Date", "Fri, 22 Dec 2017 13:20:50 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "eb8100a1-8ffa-47da-9623-8d3b2af51b84"},
+ {"X-Runtime", "0.445244"},
+ {"Content-Length", "77"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 400
+ }}
end
def test_for_purchase_with_invalid_amount do
- {:ok,
- %HTTPoison.Response{
- body: "{\"error\":\"Payment failed\",\"detail\":\"Amount must be at least 50 cents\"}",
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:16:33 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "4ce2eea4-3ea9-4345-ac85-9bc45f22f5ac"},
- {"X-Runtime", "0.476058"},
- {"Content-Length", "70"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 400
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"error":"Payment failed","detail":"Amount must be at least 50 cents"}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:16:33 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "4ce2eea4-3ea9-4345-ac85-9bc45f22f5ac"},
+ {"X-Runtime", "0.476058"},
+ {"Content-Length", "70"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 400
+ }}
end
def test_for_authorize_with_valid_card do
- {:ok,
- %HTTPoison.Response{
- body: "{\"response\":{\"token\":\"charge_8ab2b21a2f02495f5c36b34d129c8a0e836add32\",\"success\":true,\"captured\":false}}",
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:33:31 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"ETag", "W/\"ec4f2df0607614f67286ac46eb994150\""},
- {"Cache-Control", "max-age=0, private, must-revalidate"},
- {"X-Request-Id", "51d28d13-81e5-41fd-b711-1b6531fdd3dd"},
- {"X-Runtime", "0.738395"},
- {"Content-Length", "104"},
- {"X-Powered-By", "PleskLin"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 201
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"response":{"token":"charge_8ab2b21a2f02495f5c36b34d129c8a0e836add32","success":true,"captured":false}}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:33:31 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "max-age=0, private, must-revalidate"},
+ {"X-Request-Id", "51d28d13-81e5-41fd-b711-1b6531fdd3dd"},
+ {"X-Runtime", "0.738395"},
+ {"Content-Length", "104"},
+ {"X-Powered-By", "PleskLin"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 201
+ }}
end
def test_for_authorize_with_invalid_card do
- {:ok,
- %HTTPoison.Response{
- body: "{\"error\":\"Payment failed\",\"detail\":\"Your card's expiration year is invalid.\"}",
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:25:40 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "239e7054-9500-4a87-bf3b-09456d550b6d"},
- {"X-Runtime", "0.466670"},
- {"Content-Length", "77"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 400
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"error":"Payment failed","detail":"Your card's expiration year is invalid."}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:25:40 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "239e7054-9500-4a87-bf3b-09456d550b6d"},
+ {"X-Runtime", "0.466670"},
+ {"Content-Length", "77"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 400
+ }}
end
def test_for_authorize_with_invalid_amount do
- {:ok,
- %HTTPoison.Response{
- body: "{\"error\":\"Payment failed\",\"detail\":\"Amount must be at least 50 cents\"}",
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:40:10 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "d58db806-8016-4a0e-8519-403a969fa1a7"},
- {"X-Runtime", "0.494636"},
- {"Content-Length", "70"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 400
- }
- }
- end
-
- def test_for_authorize_with_missing_ip_address do
- {:ok,
- %HTTPoison.Response{body: "{\"error\":\"ip_address is missing\"}",
- headers: [
- {"Date", "Thu, 28 Dec 2017 12:22:43 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "97bae548-a446-42e9-b792-8c505c38f4c1"},
- {"X-Runtime", "0.005652"}, {"Content-Length", "33"},
- {"X-Powered-By", "PleskLin"}, {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1/charges",
- status_code: 500
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"error":"Payment failed","detail":"Amount must be at least 50 cents"}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:40:10 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "d58db806-8016-4a0e-8519-403a969fa1a7"},
+ {"X-Runtime", "0.494636"},
+ {"Content-Length", "70"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 400
+ }}
end
def test_for_refund_with_valid_token do
- {:ok,
- %HTTPoison.Response{
- body: "{\"response\":{\"token\":\"refund_a86a757cc6bdabab50d6ebbfcdcd4db4e04198dd\",\"success\":true,\"amount\":50,\"charge\":\"charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b\",\"status_message\":\"Transaction approved\"}}",
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:55:41 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"ETag", "W/\"7410ae0b45094aadada390f5c947a58a\""},
- {"Cache-Control", "max-age=0, private, must-revalidate"},
- {"X-Request-Id", "b1c94703-7fb4-48f2-b1b4-32e3b6a87e57"},
- {"X-Runtime", "1.097186"},
- {"Content-Length", "198"},
- {"X-Powered-By", "PleskLin"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges/charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b/refunds",
- status_code: 201
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"response":{"token":"refund_a86a757cc6bdabab50d6ebbfcdcd4db4e04198dd","success":true,"amount":50,"charge":"charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b","status_message":"Transaction approved"}}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:55:41 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "max-age=0, private, must-revalidate"},
+ {"X-Request-Id", "b1c94703-7fb4-48f2-b1b4-32e3b6a87e57"},
+ {"X-Runtime", "1.097186"},
+ {"Content-Length", "198"},
+ {"X-Powered-By", "PleskLin"}
+ ],
+ request_url:
+ "https://core.trexle.com/api/v1//charges/charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b/refunds",
+ status_code: 201
+ }}
end
def test_for_refund_with_invalid_token do
- {:ok,
- %HTTPoison.Response{
- body: "{\"error\":\"Refund failed\",\"detail\":\"invalid token\"}",
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:53:09 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "276fd8f5-dc21-40be-8add-fa76aabbfc5b"},
- {"X-Runtime", "0.009374"},
- {"Content-Length", "50"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges/34/refunds",
- status_code: 400
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"error":"Refund failed","detail":"invalid token"}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:53:09 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "276fd8f5-dc21-40be-8add-fa76aabbfc5b"},
+ {"X-Runtime", "0.009374"},
+ {"Content-Length", "50"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges/34/refunds",
+ status_code: 400
+ }}
end
def test_for_capture_with_valid_chargetoken do
- {:ok,
- %HTTPoison.Response{
- body: "{\"response\":{\"token\":\"charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b\",\"success\":true,\"captured\":true,\"amount\":50,\"status_message\":\"Transaction approved\"}}",
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:49:50 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"ETag", "W/\"26f05a32c0d0a27b180bbe777488fd5f\""},
- {"Cache-Control", "max-age=0, private, must-revalidate"},
- {"X-Request-Id", "97ca2db6-fd4f-4a5b-ae45-01fae9c13668"},
- {"X-Runtime", "1.092051"},
- {"Content-Length", "155"},
- {"X-Powered-By", "PleskLin"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges/charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b/capture",
- status_code: 200
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"response":{"token":"charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b","success":true,"captured":true,"amount":50,"status_message":"Transaction approved"}}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:49:50 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "max-age=0, private, must-revalidate"},
+ {"X-Request-Id", "97ca2db6-fd4f-4a5b-ae45-01fae9c13668"},
+ {"X-Runtime", "1.092051"},
+ {"Content-Length", "155"},
+ {"X-Powered-By", "PleskLin"}
+ ],
+ request_url:
+ "https://core.trexle.com/api/v1//charges/charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b/capture",
+ status_code: 200
+ }}
end
def test_for_capture_with_invalid_chargetoken do
- {:ok,
- %HTTPoison.Response{
- body: "{\"error\":\"Capture failed\",\"detail\":\"invalid token\"}",
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:47:18 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "b46ecb8d-7df8-4c5f-b87f-c53fae364e79"},
- {"X-Runtime", "0.010255"},
- {"Content-Length", "51"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges/30/capture",
- status_code: 400
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"error":"Capture failed","detail":"invalid token"}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:47:18 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "b46ecb8d-7df8-4c5f-b87f-c53fae364e79"},
+ {"X-Runtime", "0.010255"},
+ {"Content-Length", "51"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges/30/capture",
+ status_code: 400
+ }}
end
def test_for_store_with_valid_card do
- {:ok,
- %HTTPoison.Response{
- body: "{\"response\":{\"token\":\"token_94e333959850270460e89a86bad2246613528cbb\",\"card\":{\"token\":\"token_2a1ba29522e4a377fafa62e8e204f76ad8ba8f1e\",\"scheme\":\"master\",\"display_number\":\"XXXX-XXXX-XXXX-8210\",\"expiry_year\":2018,\"expiry_month\":1,\"cvc\":123,\"name\":\"John Doe\",\"address_line1\":\"456 My Street\",\"address_line2\":null,\"address_city\":\"Ottawa\",\"address_state\":\"ON\",\"address_postcode\":\"K1C2N6\",\"address_country\":\"CA\",\"primary\":true}}}",
- headers: [
- {"Date", "Sat, 23 Dec 2017 19:32:58 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"ETag", "W/\"c4089eabe907fc2327dd565503242b58\""},
- {"Cache-Control", "max-age=0, private, must-revalidate"},
- {"X-Request-Id", "1a334b22-8e01-4e1b-8b58-90dfd0b7c12f"},
- {"X-Runtime", "0.122441"},
- {"Content-Length", "422"},
- {"X-Powered-By", "PleskLin"}
- ],
- request_url: "https://core.trexle.com/api/v1//customers",
- status_code: 201
- }
- }
+ {:ok, %HTTPoison.Response{
+ body: ~s/{"response":{"token":"token_94e333959850270460e89a86bad2246613528cbb","card":{"token":"token_2a1ba29522e4a377fafa62e8e204f76ad8ba8f1e","scheme":"master","display_number":"XXXX-XXXX-XXXX-8210","expiry_year":2018,"expiry_month":1,"cvc":123,"name":"John Doe","address_line1":"456 My Street","address_line2":null,"address_city":"Ottawa","address_state":"ON","address_postcode":"K1C2N6","address_country":"CA","primary":true}}}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 19:32:58 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "max-age=0, private, must-revalidate"},
+ {"X-Request-Id", "1a334b22-8e01-4e1b-8b58-90dfd0b7c12f"},
+ {"X-Runtime", "0.122441"},
+ {"Content-Length", "422"},
+ {"X-Powered-By", "PleskLin"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//customers",
+ status_code: 201
+ }}
end
def test_for_network_failure do
- {:error, %HTTPoison.Error{id: nil, reason: :nxdomain}}
+ {:error, %HTTPoison.Error{id: :some_hackney_error_id, reason: :some_hackney_error}}
end
end
From f3c0d582ca5dd477aeceade58ce0c204287972f5 Mon Sep 17 00:00:00 2001
From: gopalshimpi
Date: Thu, 25 Jan 2018 18:41:19 +0530
Subject: [PATCH 20/60] [CAMS] Adds money protocol (#89)
* Added money protocol for CAMS gateway.
* Modified methods according to money protocol.
* Modified test data as per money protocol.
* Corrected protocol usage, docs and some bugs
* Updated docs
---
lib/gringotts/gateways/cams.ex | 523 ++++++++++++++++++---------------
test/gateways/cams_test.exs | 195 ++++++------
test/mocks/cams_mock.exs | 271 +++++++++--------
3 files changed, 528 insertions(+), 461 deletions(-)
diff --git a/lib/gringotts/gateways/cams.ex b/lib/gringotts/gateways/cams.ex
index b86c2b6b..7e37dd05 100644
--- a/lib/gringotts/gateways/cams.ex
+++ b/lib/gringotts/gateways/cams.ex
@@ -1,320 +1,383 @@
defmodule Gringotts.Gateways.Cams do
- @moduledoc ~S"""
- A module for working with the Cams payment gateway.
-
- You can test gateway operations in [CAMS API TEST MODE](https://secure.centralams.com).
- Test it using these crediantials **username:** `testintegrationc`, **password:** `password9`,
- as well as you can find api docs in this test account under **integration** link.
-
- The following features of CAMS are implemented:
-
- | Action | Method |
- | ------ | ------ |
- | Authorize | `authorize/3` |
- | Capture | `capture/3` |
- | Purchase | `purchase/3` |
- | Refund | `refund/3` |
- | Cancel | `void/2` |
+ @moduledoc """
+ [CAMS][home] gateway implementation.
+
+ CAMS provides a [sandbox account][dashboard] with documentation under the
+ [`integration` tab][docs]. The login credentials are:
+
+ | Key | Credentials |
+ | ------ | -------- |
+ | username | `testintegrationc` |
+ | password | `password9` |
+
+ The [video tutorials][videos] (on vimeo) are excellent.
+
+ The following features of CAMS are implemented:
+
+ | Action | Method |
+ | ------ | ------ |
+ | Authorize | `authorize/3` |
+ | Capture | `capture/3` |
+ | Purchase | `purchase/3` |
+ | Refund | `refund/3` |
+ | Cancel | `void/2` |
## The `opts` argument
- Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply
- optional arguments for transactions with the Cams gateway. The following keys
- are supported:
+ Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply
+ optional arguments for transactions with the CAMS gateway. The following keys
+ are supported:
+
+ | Key | Type | Remark |
+ | ---- | ---- | --- |
+ | `billing_address` | `map` | The address of the customer |
+ | `order_id` | `String.t` | Merchant provided identifier |
+ | `description` | `String.t` | Merchant provided description of the transaction |
+
+ > CAMS supports more optional keys and you can raise an [issue][issues] if
+ this is important to you.
+
+ [issues]: https://github.com/aviabird/gringotts/issues/new
- | Key | Remark | Status |
- | ---- | --- | ---- |
- | `billing_address` | | Not implemented |
- | `address` | | Not implemented |
- | `currency` | | **Implemented** |
- | `order_id` | | Not implemented |
- | `description` | | Not implemented |
-
- All these keys are being implemented, track progress in
- [issue #42](https://github.com/aviabird/gringotts/issues/42)!
-
- ## Configuration parameters for Cams:
-
- | Config parameter | Cams secret |
- | ------- | ---- |
- | `:username` | **Username** |
- | `:password` | **Password** |
+ ### Schema
+
+ * `billing_address` is a `map` from `atoms` to `String.t`, and can include any
+ of the keys from:
+ `:name, :address1, :address2, :company, :city, :state, :zip, :country, :phone, :fax]`
+ ## Registering your CAMS account at `Gringotts`
+
+ | Config parameter | CAMS secret |
+ | ------- | ---- |
+ | `:username` | **Username** |
+ | `:password` | **Password** |
+
> Your Application config **must include the `:username`, `:password`
- > fields** and would look something like this:
-
+ > fields** and would look something like this:
+
config :gringotts, Gringotts.Gateways.Cams,
- adapter: Gringotts.Gateways.Cams,
- username: "your_secret_user_name",
- password: "your_secret_password",
-
+ adapter: Gringotts.Gateways.Cams,
+ username: "your_secret_user_name",
+ password: "your_secret_password",
+
+ ## Scope of this module
- ## Scope of this module, and _quirks_
+ * CAMS **does not** process money in cents.
+ * Although CAMS supports payments from electronic check & various cards this module only
+ accepts payments via `VISA`, `MASTER`, `AMERICAN EXPRESS` and `DISCOVER`.
- * Cams process money in cents.
- * Although Cams supports payments from electronic check & various cards this library only
- accepts payments by cards like *visa*, *master*, *american_express* and *discover*.
+ ## Supported countries
+ **citation-needed**
+
+ ## Supported currencies
+ **citation-needed**
## Following the examples
- 1. First, set up a sample application and configure it to work with Cams.
- - You could do that from scratch by following our [Getting Started](#) guide.
- - To save you time, we recommend [cloning our example
- repo](https://github.com/aviabird/gringotts_example) that gives you a
- pre-configured sample app ready-to-go.
- + You could use the same config or update it the with your "secrets"
- that you get after registering with Cams.
+
+ 1. First, set up a sample application and configure it to work with CAMS.
+ - You could do that from scratch by following our [Getting Started][gs] guide.
+ - To save you time, we recommend [cloning our example][example-repo] that
+ gives you a pre-configured sample app ready-to-go.
+ + You could use the same config or update it the with your "secrets" that
+ you get after [registering with
+ CAMS](#module-registering-your-cams-account-at-gringotts).
2. Run an `iex` session with `iex -S mix` and add some variable bindings and
aliases to it (to save some time):
```
iex> alias Gringotts.{Response, CreditCard, Gateways.Cams}
- iex> opts = [currency: "USD"] # The default currency is USD, and this is just for an example.
- iex> payment = %CreditCard{number: "4111111111111111", month: 11, year: 2018,
- first_name: "Longbob", last_name: "Longsen",
- verification_code: "123", brand: "visa"}
+ iex> card = %CreditCard{first_name: "Harry",
+ last_name: "Potter",
+ number: "4111111111111111",
+ year: 2099,
+ month: 12,
+ verification_code: "999",
+ brand: "VISA"}
+ iex> money = %{value: Decimal.new(20), currency: "USD"}
```
-
We'll be using these in the examples below.
+ ## Integrating with phoenix
+
+ Refer the [GringottsPay][gpay-heroku-cams] website for an example of how to
+ integrate CAMS with phoenix. The source is available [here][gpay-repo].
+
+ [gpay-repo]: https://github.com/aviabird/gringotts_payment
+ [gpay-heroku-cams]: http://gringottspay.herokuapp.com/cams
+
## TODO
- * Credit Card Operations
+ * Operations using Credit Card
- Credit
- * Electronic Check
+ * Operations using electronic checks
- Sale
- Void
- Refund
+
+ [home]: http://www.centralams.com/
+ [docs]: https://secure.centralams.com/merchants/resources/integration/integration_portal.php?tid=d669ab54bb17e34c5ff2cfe504f033e7
+ [dashboard]: https://secure.centralams.com
+ [videos]: https://secure.centralams.com/merchants/video.php?tid=d669ab54bb17e34c5ff2cfe504f033e7
+ [gs]: #
+ [example-repo]: https://github.com/aviabird/gringotts_example
"""
- @live_url "https://secure.centralams.com/gw/api/transact.php"
- @default_currency "USD"
- @headers [{"Content-Type", "application/x-www-form-urlencoded"}]
+
use Gringotts.Gateways.Base
- use Gringotts.Adapter,
- required_config: [:username, :password, :default_currency]
- alias Gringotts.{CreditCard, Response}
+ use Gringotts.Adapter, required_config: [:username, :password]
+
+ alias Gringotts.{CreditCard, Response, Money}
alias Gringotts.Gateways.Cams.ResponseHandler, as: ResponseParser
- import Poison, only: [decode!: 1]
+ @live_url "https://secure.centralams.com/gw/api/transact.php"
+ @headers [{"Content-Type", "application/x-www-form-urlencoded"}]
+
@doc """
- Transfers `amount` from the customer to the merchant.
+ Performs a (pre) Authorize operation.
+
+ The authorization validates the `card` details with the banking network,
+ places a hold on the transaction `amount` in the customer’s issuing bank and
+ also triggers risk management. Funds are not transferred.
- Function to charge a user credit card for the specified amount. It performs authorize
- and capture at the same time.Purchase transaction are submitted and immediately sent for settlement.
-
- After successful purchase it returns an `authorization` which can be used later to:
- * `refund/3` an amount.
- * `void/2` a transaction(*if Not settled*).
+ When followed up with a `capture/3` transaction, funds will be transferred to
+ the merchant's account upon settlement.
+
+ CAMS returns a **Transaction ID** (available in the `Response.authorization`
+ field) which can be used later to:
+ * `capture/3` an amount.
+ * `void/2` an authorized transaction.
+
+ ## Optional Fields
+ options[
+ order_id: String,
+ description: String
+ ]
## Examples
- payment = %CreditCard{
- number: "4111111111111111", month: 11, year: 2018,
- first_name: "Longbob", last_name: "Longsen",
- verification_code: "123", brand: "visa"
- }
-
- options = [currency: "USD"]
- money = 100
-
- iex> Gringotts.purchase(Gringotts.Gateways.Cams, money, payment, options)
+
+ The following example shows how one would (pre) authorize a payment of $20 on
+ a sample `card`.
+ ```
+ iex> card = %CreditCard{first_name: "Harry",
+ last_name: "Potter",
+ number: "4111111111111111",
+ year: 2099,
+ month: 12,
+ verification_code: "999",
+ brand: "VISA"}
+ iex> money = %{value: Decimal.new(20), currency: "USD"}
+ iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Cams, money, card)
+ ```
"""
- @spec purchase(number, CreditCard.t, Keyword) :: Response
- def purchase(money, payment, options) do
- post = []
- |> add_invoice(money, options)
- |> add_payment(payment)
- |> add_address(payment, options)
- commit("sale", post, options)
+ @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def authorize(money, %CreditCard{} = card, options) do
+ params =
+ []
+ |> add_invoice(money)
+ |> add_payment(card)
+ |> add_address(card, options)
+
+ commit("auth", params, options)
end
@doc """
- Authorize a credit card transaction.
+ Captures a pre-authorized amount.
+
+ Captures can be submitted for an `amount` equal to or less than the originally
+ authorized `amount` in an `authorize/3`ation referenced by `transaction_id`.
- The authorization validates the `card` details with the banking network, places a hold on the
- transaction amount in the customer’s issuing bank and also triggers risk management.
- Funds are not transferred.It needs to be followed up with a capture transaction to transfer the funds
- to merchant account.After successful capture, transaction will be sent for settlement.
-
- Cams returns an `authorization` which can be used later to:
- * `capture/3` an amount.
- * `void/2` a authorized transaction.
+ Partial captures are allowed, and the remaining amount is released back to
+ the payment source [(video)][auth-and-capture].
+ > Multiple, partial captures on the same `authorization` token are **not supported**.
+
+ CAMS returns a **Transaction ID** (available in the `Response.authorization`
+ field) which can be used later to:
+ * `refund/3`
+ * `void/2` *(only before settlements!)*
+
+ [auth-and-capture]: https://vimeo.com/200903640
## Examples
- payment = %{
- number: "4111111111111111", month: 11, year: 2018,
- first_name: "Longbob", last_name: "Longsen",
- verification_code: "123", brand: "visa"
- }
-
- options = [currency: "USD"]
- money = 100
-
- iex> Gringotts.authorize(Gringotts.Gateways.Cams, money, payment, options)
+
+ The following example shows how one would (partially) capture a previously
+ authorized a payment worth $10 by referencing the obtained authorization `id`.
+ ```
+ iex> card = %CreditCard{first_name: "Harry",
+ last_name: "Potter",
+ number: "4111111111111111",
+ year: 2099,
+ month: 12,
+ verification_code: "999",
+ brand: "VISA"}
+ iex> money = %{value: Decimal.new(10), currency: "USD"}
+ iex> authorization = auth_result.authorization
+ # authorization = "some_authorization_transaction_id"
+ iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Cams, money, authorization)
+ ```
"""
- @spec authorize(number, CreditCard.t, Keyword) :: Response
- def authorize(money, payment, options) do
- post = []
- |> add_invoice(money, options)
- |> add_payment(payment)
- |> add_address(payment, options)
- commit("auth", post, options)
+ @spec capture(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
+ def capture(money, transaction_id, options) do
+ params =
+ [transactionid: transaction_id]
+ |> add_invoice(money)
+
+ commit("capture", params, options)
end
@doc """
- Captures a pre-authorized amount.
+ Transfers `amount` from the customer to the merchant.
+
+ CAMS attempts to process a purchase on behalf of the customer, by debiting
+ `amount` from the customer's account by charging the customer's `card`.
- It captures existing authorizations for settlement.Only authorizations can be captured.
- Captures can be submitted for an amount equal to or less than the original authorization.
- It allows partial captures like many other gateways and release the remaining amount back to
- the payment source **[citation-needed]**.Multiple captures can not be done using same `authorization`.
+ Returns a **Transaction ID** (available in the `Response.authorization`
+ field) which can be used later to:
+ * `refund/3`
+ * `void/2` *(only before settlements!)*
## Examples
- authorization = "3904093075"
- options = [currency: "USD"]
- money = 100
-
- iex> Gringotts.capture(Gringotts.Gateways.Cams, money, authorization, options)
+ The following example shows how one would process a payment worth $20 in
+ one-shot, without (pre) authorization.
+ ```
+ iex> card = %CreditCard{first_name: "Harry",
+ last_name: "Potter",
+ number: "4111111111111111",
+ year: 2099,
+ month: 12,
+ verification_code: "999",
+ brand: "VISA"}
+ iex> money = %{value: Decimal.new(20), currency: "USD"}
+ iex> Gringotts.purchase(Gringotts.Gateways.Cams, money, card)
+ ```
"""
- @spec capture(number, String.t, Keyword) :: Response
- def capture(money, authorization, options) do
- post = [transactionid: authorization]
- add_invoice(post, money, options)
- commit("capture", post, options)
+ @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def purchase(money, %CreditCard{} = card, options) do
+ params =
+ []
+ |> add_invoice(money)
+ |> add_payment(card)
+ |> add_address(card, options)
+
+ commit("sale", params, options)
end
@doc """
- Refunds the `amount` to the customer's account with reference to a prior transfer.
+ Refunds the `amount` to the customer's account with reference to a prior transfer.
- It will reverse a previously settled or pending settlement transaction.
- If the transaction has not been settled, a transaction `void/2` can also reverse it.
- It processes a full or partial refund worth `amount`, referencing a previous `purchase/3` or `capture/3`.
- Authorized transaction can not be reversed.
+ It's better to `void/2` a transaction if it has not been settled yet! Refunds
+ lead to to two entries on the customer's bank statement, one for the original
+ `purchase/3` or `capture/3` and another for the `refund/3`.
- `authorization` can be used to perform multiple refund, till:
- * all the pre-authorized amount is captured or,
- * the remaining amount is explicitly "reversed" via `void/2`. **[citation-needed]**
+ Multiple, partial refunds on the same **Transaction ID** are allowed till all
+ the captured amount is refunded.
## Examples
- authorization = "3904093078"
- options = [currency: "USD"]
- money = 100
-
- iex> Gringotts.refund(Gringotts.Gateways.Cams, money, authorization, options)
+ The following example shows how one would completely refund a previous capture
+ (and similarily for purchases).
+ ```
+ iex> capture_id = capture_result.authorization
+ # capture_id = "some_capture_transaction_id"
+ iex> money = %{value: Decimal.new(20), currency: "USD"}
+ iex> Gringotts.refund(Gringotts.Gateways.Cams, money, capture_id)
+ ```
"""
- @spec refund(number, String.t, Keyword) :: Response
- def refund(money, authorization, options) do
- post = [transactionid: authorization]
- add_invoice(post, money, options)
- commit("refund", post, options)
+ @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
+ def refund(money, transaction_id, options) do
+ params =
+ [transactionid: transaction_id]
+ |> add_invoice(money)
+
+ commit("refund", params, options)
end
@doc """
- Voids the referenced payment.
-
- Transaction voids will cancel an existing sale or captured authorization.
- In addition, non-captured authorizations can be voided to prevent any future capture.
- Voids can only occur if the transaction has not been settled.
+ Voids the referenced payment.
+
+ Cancel a transaction referenced by `transaction_id` that is not settled
+ yet. This will erase any entries from the customer's bank statement.
+
+ > `authorize/3` can be `void/2`ed to prevent captures.
## Examples
- authorization = "3904093075"
- options = []
-
- iex> Gringotts.void(Gringotts.Gateways.Cams, authorization, options)
+ The following example shows how one would void a previous (pre)
+ authorization.
+ ```
+ iex> auth_id = auth_result.id
+ # auth_id = "aome_authorisation_transaction_id"
+ iex> Gringotts.void(Gringotts.Gateways.Cams, auth_id)
+ ```
"""
- @spec void(String.t, Keyword) :: Response
- def void(authorization , options) do
- post = [transactionid: authorization]
- commit("void", post, options)
+ @spec void(String.t(), keyword) :: {:ok | :error, Response}
+ def void(transaction_id, options) do
+ params = [transactionid: transaction_id]
+ commit("void", params, options)
end
@doc """
- Validates the Account
+ Validates the `card`
- This action is used for doing an "Account Verification" on the cardholder's credit card
- without actually doing an authorization.
+ Verifies the credit `card` without authorizing any amount.
## Examples
- payment = %{
- number: "4111111111111111", month: 11, year: 2018,
- first_name: "Longbob", last_name: "Longsen",
- verification_code: "123", brand: "visa"
- }
-
- options = [currency: "USD"]
-
-
- iex> Gringotts.validate(Gringotts.Gateways.Cams, payment, options)
-
+ ```
+ iex> card = %CreditCard{first_name: "Harry",
+ last_name: "Potter",
+ number: "4111111111111111",
+ year: 2099,
+ month: 12,
+ verification_code: "999",
+ brand: "VISA"}
+ iex> Gringotts.validate(Gringotts.Gateways.Cams, card)
+ ```
"""
- @spec validate(CreditCard.t, Keyword):: Response
- def validate(payment, options) do
- post = []
- |> add_invoice(0, options)
- |> add_payment(payment)
- |> add_address(payment, options)
-
- commit("verify", post, options)
+ @spec validate(CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def validate(card, options) do
+ params =
+ []
+ |> add_invoice(%{value: Decimal.new(0), currency: "USD"})
+ |> add_payment(card)
+ |> add_address(card, options)
+
+ commit("verify", params, options)
end
# private methods
- defp add_invoice(post, money, options) do
- post
- |> Keyword.put(:amount, money)
- |> Keyword.put(:currency, (options[:config][:currency]) || @default_currency)
+ defp add_invoice(params, money) do
+ {currency, value} = Money.to_string(money)
+ [amount: value, currency: currency] ++ params
end
- defp add_payment(post, payment) do
- exp_month = join_month(payment)
- exp_year = payment.year
- |> to_string()
- |> String.slice(-2..-1)
-
- post
- |> Keyword.put(:ccnumber, payment.number)
- |> Keyword.put(:ccexp, "#{exp_month}#{exp_year}")
- |> Keyword.put(:cvv, payment.verification_code)
- end
+ defp add_payment(params, %CreditCard{} = card) do
+ exp_month = card.month |> to_string |> String.pad_leading(2, "0")
+ exp_year = card.year |> to_string |> String.slice(-2..-1)
- defp add_address(post, payment, options) do
- post = post
- |> Keyword.put(:firstname, payment.first_name)
- |> Keyword.put(:lastname, payment.last_name)
-
- if options[:billing_address] do
- address = options[:billing_address]
- post = post
- |> Keyword.put(:address1 , address[:address1])
- |> Keyword.put(:address2, address[:address2])
- |> Keyword.put(:city, address[:city])
- |> Keyword.put(:state, address[:state])
- |> Keyword.put(:zip, address[:zip])
- |> Keyword.put(:country, address[:country])
- |> Keyword.put(:phone, address[:phone])
- end
+ [ccnumber: card.number, ccexp: "#{exp_month}#{exp_year}", cvv: card.verification_code] ++
+ params
end
- defp join_month(payment) do
- payment.month
- |> to_string
- |> String.pad_leading(2, "0")
+ defp add_address(params, card, options) do
+ params ++
+ [firstname: card.first_name, lastname: card.last_name] ++
+ if options[:billing_address] != nil, do: Enum.into(options[:billing_address], []), else: []
end
defp commit(action, params, options) do
url = @live_url
- params = params
- |> Keyword.put(:type, action)
- |> Keyword.put(:password, options[:config][:password])
- |> Keyword.put(:username, options[:config][:username])
- |> params_to_string
-
+
+ auth = [
+ type: action,
+ password: options[:config][:password],
+ username: options[:config][:username]
+ ]
+
url
- |> HTTPoison.post(params, @headers)
- |> ResponseParser.parse
+ |> HTTPoison.post({:form, auth ++ params}, @headers)
+ |> ResponseParser.parse()
end
defmodule ResponseHandler do
@@ -341,7 +404,7 @@ defmodule Gringotts.Gateways.Cams do
def parse({:ok, %HTTPoison.Response{body: body, status_code: 404}}) do
body = URI.decode_query(body)
-
+
[status_code: 404]
|> handle_not_found(body)
|> handle_opts()
@@ -382,7 +445,7 @@ defmodule Gringotts.Gateways.Cams do
defp parse_html(body) do
error_message = List.to_string(Map.keys(body))
- [html_body | parse_message] = (Regex.run(~r|(.*)|, error_message))
+ [_ | parse_message] = Regex.run(~r|(.*)|, error_message)
List.to_string(parse_message)
end
diff --git a/test/gateways/cams_test.exs b/test/gateways/cams_test.exs
index 694695e8..3c20e545 100644
--- a/test/gateways/cams_test.exs
+++ b/test/gateways/cams_test.exs
@@ -1,201 +1,212 @@
defmodule Gringotts.Gateways.CamsTest do
-
- Code.require_file "../mocks/cams_mock.exs", __DIR__
+ Code.require_file("../mocks/cams_mock.exs", __DIR__)
use ExUnit.Case, async: false
+
alias Gringotts.{
- CreditCard, Response
+ CreditCard,
+ Response
}
+
alias Gringotts.Gateways.CamsMock, as: MockResponse
alias Gringotts.Gateways.Cams, as: Gateway
import Mock
- @payment %CreditCard{
+ @card %CreditCard{
number: "4111111111111111",
- month: 9,
- year: 2018,
- first_name: "Gopal",
- last_name: "Shimpi",
- verification_code: "123",
- brand: "visa"
+ month: 11,
+ year: 2099,
+ first_name: "Harry",
+ last_name: "Potter",
+ verification_code: "999",
+ brand: "VISA"
}
- @bad_payment %CreditCard {
- number: "411111111111111",
- month: 9,
- year: 2018,
- first_name: "Gopal",
- last_name: "Shimpi",
- verification_code: "123",
- brand: "visa"
+ @bad_card %CreditCard{
+ number: "42",
+ month: 11,
+ year: 2099,
+ first_name: "Harry",
+ last_name: "Potter",
+ verification_code: "999",
+ brand: "VISA"
}
@address %{
- name: "Jim Smith",
- address1: "456 My Street",
- address2: "Apt 1",
- company: "Widgets Inc",
- city: "Ottawa",
- state: "ON",
- zip: "K1C2N6",
- country: "US",
- phone: "(555)555-5555",
- fax: "(555)555-6666"
+ street1: "301, Gryffindor",
+ street2: "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
+ city: "Highlands",
+ state: "Scotland",
+ country: "GB",
+ company: "Ollivanders",
+ zip: "K1C2N6",
+ phone: "(555)555-5555",
+ fax: "(555)555-6666"
}
+ @auth %{username: "some_secret_user_name", password: "some_secret_password"}
@options [
- config: %{
- username: "testintegrationc",
- password: "password9"
- },
order_id: 0001,
billing_address: @address,
description: "Store Purchase"
]
- @money 100
- @bad_money "G"
- @authorization "3921111362"
- @bad_authorization "300000000"
+ @money Money.new(:USD, 100)
+ @money_more Money.new(:USD, 101)
+ @money_less Money.new(:USD, 99)
+ @bad_currency Money.new(:INR, 100)
+
+ @authorization "some_transaction_id"
+ @bad_authorization "some_fake_transaction_id"
+
+ setup_all do
+ Application.put_env(
+ :gringotts,
+ Gateway,
+ adapter: Gateway,
+ username: "some_secret_user_name",
+ password: "some_secret_password"
+ )
+ end
describe "purchase" do
- test "with all good" do
+ test "with correct params" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.successful_purchase end] do
- {:ok, %Response{success: result}} = Gateway.purchase(@money, @payment, @options)
+ post: fn _url, _body, _headers -> MockResponse.successful_purchase() end do
+ {:ok, %Response{success: result}} = Gringotts.purchase(Gateway, @money, @card, @options)
assert result
end
end
test "with bad card" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.failed_purchase_with_bad_credit_card end] do
- {:ok, %Response{message: result}} = Gateway.purchase(@money, @bad_payment, @options)
- assert String.contains?(result, "Invalid Credit Card Number")
- end
- end
+ post: fn _url, _body, _headers -> MockResponse.failed_purchase_with_bad_credit_card() end do
+ {:ok, %Response{message: result}} =
+ Gringotts.purchase(Gateway, @money, @bad_card, @options)
- test "with bad amount" do
- with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.failed_purchase_with_bad_money end] do
- {:ok, %Response{message: result}} = Gateway.purchase(@bad_money, @payment, @options)
- assert String.contains?(result, "Invalid amount")
+ assert String.contains?(result, "Invalid Credit Card Number")
end
end
test "with invalid currency" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.with_invalid_currency end] do
- {:ok, %Response{message: result}} = Gateway.purchase(@money, @payment, @options)
+ post: fn _url, _body, _headers -> MockResponse.with_invalid_currency() end do
+ {:ok, %Response{message: result}} = Gringotts.purchase(Gateway, @bad_currency, @card, @options)
assert String.contains?(result, "The cc payment type")
end
end
end
describe "authorize" do
- test "with all good" do
+ test "with correct params" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.successful_authorize end] do
- {:ok, %Response{success: result}} = Gateway.authorize(@money, @payment, @options)
+ post: fn _url, _body, _headers -> MockResponse.successful_authorize() end do
+ {:ok, %Response{success: result}} = Gringotts.authorize(Gateway, @money, @card, @options)
assert result
end
end
test "with bad card" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.failed_authorized_with_bad_card end] do
- {:ok, %Response{message: result}} = Gateway.authorize(@money, @bad_payment, @options)
+ post: fn _url, _body, _headers -> MockResponse.failed_authorized_with_bad_card() end do
+ {:ok, %Response{message: result}} =
+ Gringotts.authorize(Gateway, @money, @bad_card, @options)
+
assert String.contains?(result, "Invalid Credit Card Number")
end
end
- test "with bad amount" do
- with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.failed_purchase_with_bad_money end] do
- {:ok, %Response{message: result}} = Gateway.authorize(@bad_money, @payment, @options)
- assert String.contains?(result, "Invalid amount")
- end
- end
end
+
describe "capture" do
test "with full amount" do
- with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.successful_capture end] do
- {:ok, %Response{success: result}} = Gateway.capture(@money, @authorization, @options)
+ with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_capture() end do
+ {:ok, %Response{success: result}} =
+ Gringotts.capture(Gateway, @money, @authorization, @options)
+
assert result
end
end
test "with partial amount" do
- with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.successful_capture end] do
- {:ok, %Response{success: result}} = Gateway.capture(@money - 1, @authorization, @options)
+ with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_capture() end do
+ {:ok, %Response{success: result}} =
+ Gringotts.capture(Gateway, @money_less, @authorization, @options)
+
assert result
end
end
test "with invalid transaction_id" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.invalid_transaction_id end] do
- {:ok, %Response{message: result}} = Gateway.capture(@money, @bad_authorization, @options)
+ post: fn _url, _body, _headers -> MockResponse.invalid_transaction_id() end do
+ {:ok, %Response{message: result}} =
+ Gringotts.capture(Gateway, @money, @bad_authorization, @options)
+
assert String.contains?(result, "Transaction not found")
end
end
test "with more than authorization amount" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.more_than_authorization_amount end] do
- {:ok, %Response{message: result}} = Gateway.capture(@money + 1, @authorization, @options)
+ post: fn _url, _body, _headers -> MockResponse.more_than_authorization_amount() end do
+ {:ok, %Response{message: result}} =
+ Gringotts.capture(Gateway, @money_more, @authorization, @options)
+
assert String.contains?(result, "exceeds the authorization amount")
end
end
test "on already captured transaction" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.multiple_capture_on_same_transaction end] do
- {:ok, %Response{message: result}} = Gateway.capture(@money, @authorization, @options)
+ post: fn _url, _body, _headers -> MockResponse.multiple_capture_on_same_transaction() end do
+ {:ok, %Response{message: result}} =
+ Gringotts.capture(Gateway, @money, @authorization, @options)
+
assert String.contains?(result, "A capture requires that")
end
end
end
describe "refund" do
- test "with all good" do
- with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.successful_refund end] do
- {:ok, %Response{success: result}} = Gateway.refund(@money, @authorization, @options)
+ test "with correct params" do
+ with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_refund() end do
+ {:ok, %Response{success: result}} =
+ Gringotts.refund(Gateway, @money, @authorization, @options)
+
assert result
end
end
test "with more than purchased amount" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.more_than_purchase_amount end] do
- {:ok, %Response{message: result}} = Gateway.refund(@money + 1, @authorization, @options)
+ post: fn _url, _body, _headers -> MockResponse.more_than_purchase_amount() end do
+ {:ok, %Response{message: result}} =
+ Gringotts.refund(Gateway, @money_more, @authorization, @options)
+
assert String.contains?(result, "Refund amount may not exceed")
end
end
end
-
- describe "void" do
- test "with all good" do
- with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.successful_void end] do
- {:ok, %Response{message: result}} = Gateway.void(@authorization, @options)
+
+ describe "void" do
+ test "with correct params" do
+ with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_void() end do
+ {:ok, %Response{message: result}} = Gringotts.void(Gateway, @authorization, @options)
assert String.contains?(result, "Void Successful")
end
end
test "with invalid transaction_id" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.invalid_transaction_id end] do
- {:ok, %Response{message: result}} = Gateway.void(@bad_authorization, @options)
+ post: fn _url, _body, _headers -> MockResponse.invalid_transaction_id() end do
+ {:ok, %Response{message: result}} = Gringotts.void(Gateway, @bad_authorization, @options)
assert String.contains?(result, "Transaction not found")
end
end
end
describe "validate" do
- test "with all good" do
+ test "with correct params" do
with_mock HTTPoison,
- [post: fn(_url, _body, _headers) -> MockResponse.validate_creditcard end] do
- {:ok, %Response{success: result}} = Gateway.validate(@payment, @options)
+ post: fn _url, _body, _headers -> MockResponse.validate_creditcard() end do
+ {:ok, %Response{success: result}} = Gateway.validate(@card, @options ++ [config: @auth])
assert result
end
end
diff --git a/test/mocks/cams_mock.exs b/test/mocks/cams_mock.exs
index d7ee7808..e499b450 100644
--- a/test/mocks/cams_mock.exs
+++ b/test/mocks/cams_mock.exs
@@ -1,218 +1,211 @@
defmodule Gringotts.Gateways.CamsMock do
def successful_purchase do
- {:ok,
- %HTTPoison.Response{
- body: "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3916017714&avsresponse=N&cvvresponse=N&orderid=&type=sale&response_code=100",
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3916017714&avsresponse=N&cvvresponse=N&orderid=&type=sale&response_code=100",
headers: [
- {"Date", "Thu, 21 Dec 2017 12:45:16 GMT"},
+ {"Date", "Thu, 21 Dec 2017 12:45:16 GMT"},
{"Server", "Apache"},
- {"Content-Length", "137"},
+ {"Content-Length", "137"},
{"Content-Type", "text/html; charset=UTF-8"}
],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
-
+
def failed_purchase_with_bad_credit_card do
- {:ok,
- %HTTPoison.Response{
- body: "response=3&responsetext=Invalid Credit Card Number REFID:3502947912&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=sale&response_code=300",
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=3&responsetext=Invalid Credit Card Number REFID:3502947912&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=sale&response_code=300",
headers: [
{"Date", "Thu, 21 Dec 2017 13:20:08 GMT"},
{"Server", "Apache"},
- {"Content-Length", "155"},
+ {"Content-Length", "155"},
{"Content-Type", "text/html; charset=UTF-8"}
],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
- def failed_purchase_with_bad_money do
- {:ok,
- %HTTPoison.Response{
- body: "response=3&responsetext=Invalid amount REFID:3502949755&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=sale&response_code=300",
- headers: [
- {"Date", "Thu, 21 Dec 2017 13:50:20 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "143"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
- end
-
- def failed_purchase_with_bad_credit_card do
- {:ok,
- %HTTPoison.Response{
- body: "response=3&responsetext=Invalid Credit Card Number REFID:3502947912&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=sale&response_code=300",
- headers: [
- {"Date", "Thu, 21 Dec 2017 13:20:08 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "155"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
- end
+
def with_invalid_currency do
- {:ok,
- %HTTPoison.Response{
- body: "response=3&responsetext=The cc payment type [Visa] and/or currency [INR] is not accepted REFID:3503238709&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300",
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=3&responsetext=The cc payment type [Visa] and/or currency [INR] is not accepted REFID:3503238709&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300",
headers: [
{"Date", "Tue, 26 Dec 2017 10:37:42 GMT"},
{"Server", "Apache"},
- {"Content-Length", "193"},
+ {"Content-Length", "193"},
{"Content-Type", "text/html; charset=UTF-8"}
],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
-
+
def successful_capture do
- {:ok,
- %HTTPoison.Response{
- body: "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3921111362&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=100",
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3921111362&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=100",
headers: [
{"Date", "Tue, 26 Dec 2017 12:16:55 GMT"},
{"Server", "Apache"},
- {"Content-Length", "138"},
+ {"Content-Length", "138"},
{"Content-Type", "text/html; charset=UTF-8"}
],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
+
def successful_authorize do
- {:ok,
- %HTTPoison.Response{
- body: "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3921111362&avsresponse=N&cvvresponse=N&orderid=&type=auth&response_code=100",
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3921111362&avsresponse=N&cvvresponse=N&orderid=&type=auth&response_code=100",
headers: [
- {"Date", "Tue, 26 Dec 2017 12:16:11 GMT"},
+ {"Date", "Tue, 26 Dec 2017 12:16:11 GMT"},
{"Server", "Apache"},
- {"Content-Length", "137"},
+ {"Content-Length", "137"},
{"Content-Type", "text/html; charset=UTF-8"}
],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
+
def invalid_transaction_id do
- {:ok,
- %HTTPoison.Response{
- body: "response=3&responsetext=Transaction not found REFID:3503243979&authcode=&transactionid=3921118690&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=3&responsetext=Transaction not found REFID:3503243979&authcode=&transactionid=3921118690&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
headers: [
- {"Date", "Tue, 26 Dec 2017 12:39:05 GMT"},
+ {"Date", "Tue, 26 Dec 2017 12:39:05 GMT"},
{"Server", "Apache"},
- {"Content-Length", "163"},
+ {"Content-Length", "163"},
{"Content-Type", "text/html; charset=UTF-8"}
],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
- end
- def more_than_authorization_amount do
- {:ok,
- %HTTPoison.Response{
- body: "response=3&responsetext=The specified amount of 1001 exceeds the authorization amount of 1000.00 REFID:3503244462&authcode=&transactionid=3921127126&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
+ end
+
+ def more_than_authorization_amount do
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=3&responsetext=The specified amount of 1001 exceeds the authorization amount of 1000.00 REFID:3503244462&authcode=&transactionid=3921127126&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
headers: [
{"Date", "Tue, 26 Dec 2017 13:00:55 GMT"},
{"Server", "Apache"},
- {"Content-Length", "214"},
+ {"Content-Length", "214"},
{"Content-Type", "text/html; charset=UTF-8"}
],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
+
def successful_refund do
- {:ok,
- %HTTPoison.Response{
- body: "response=1&responsetext=SUCCESS&authcode=&transactionid=3921158933&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=100",
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=1&responsetext=SUCCESS&authcode=&transactionid=3921158933&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=100",
headers: [
{"Date", "Tue, 26 Dec 2017 14:00:08 GMT"},
{"Server", "Apache"},
- {"Content-Length", "131"},
+ {"Content-Length", "131"},
{"Content-Type", "text/html; charset=UTF-8"}
],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def more_than_purchase_amount do
- {:ok,
- %HTTPoison.Response{
- body: "response=3&responsetext=Refund amount may not exceed the transaction balance REFID:3503249728&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300",
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=3&responsetext=Refund amount may not exceed the transaction balance REFID:3503249728&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300",
headers: [
- {"Date", "Tue, 26 Dec 2017 14:05:31 GMT"},
+ {"Date", "Tue, 26 Dec 2017 14:05:31 GMT"},
{"Server", "Apache"},
- {"Content-Length", "183"},
+ {"Content-Length", "183"},
{"Content-Type", "text/html; charset=UTF-8"}
],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
- end
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
+ end
def successful_void do
- {:ok,
- %HTTPoison.Response{
- body: "response=1&responsetext=Transaction Void Successful&authcode=123456&transactionid=3921178863&avsresponse=&cvvresponse=&orderid=&type=void&response_code=100",
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=1&responsetext=Transaction Void Successful&authcode=123456&transactionid=3921178863&avsresponse=&cvvresponse=&orderid=&type=void&response_code=100",
headers: [
- {"Date", "Tue, 26 Dec 2017 14:26:05 GMT"},
+ {"Date", "Tue, 26 Dec 2017 14:26:05 GMT"},
{"Server", "Apache"},
- {"Content-Length", "155"},
+ {"Content-Length", "155"},
{"Content-Type", "text/html; charset=UTF-8"}
],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def failed_authorized_with_bad_card do
- {:ok,
- %HTTPoison.Response{
- body: "response=3&responsetext=Invalid Credit Card Number REFID:3503305883&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300",
- headers: [
- {"Date", "Wed, 27 Dec 2017 09:51:45 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "155"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=3&responsetext=Invalid Credit Card Number REFID:3503305883&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300",
+ headers: [
+ {"Date", "Wed, 27 Dec 2017 09:51:45 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "155"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def multiple_capture_on_same_transaction do
- {:ok,
- %HTTPoison.Response{
- body: "response=3&responsetext=A capture requires that the existing transaction be an AUTH REFID:3503316182&authcode=&transactionid=3922433984&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
- headers: [
- {"Date", "Wed, 27 Dec 2017 13:47:12 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "201"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=3&responsetext=A capture requires that the existing transaction be an AUTH REFID:3503316182&authcode=&transactionid=3922433984&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
+ headers: [
+ {"Date", "Wed, 27 Dec 2017 13:47:12 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "201"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def refund_the_authorised_transaction do
- {:ok,
- %HTTPoison.Response{
- body: "response=3&responsetext=Refund amount may not exceed the transaction balance REFID:3503316128&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300",
- headers: [{"Date", "Wed, 27 Dec 2017 13:45:19 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "183"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=3&responsetext=Refund amount may not exceed the transaction balance REFID:3503316128&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300",
+ headers: [
+ {"Date", "Wed, 27 Dec 2017 13:45:19 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "183"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def validate_creditcard do
- {:ok,
- %HTTPoison.Response{
- body: "response=1&responsetext=&authcode=&transactionid=3933708264&avsresponse=&cvvresponse=&orderid=&type=verify&response_code=100",
+ {:ok, %HTTPoison.Response{
+ body:
+ "response=1&responsetext=&authcode=&transactionid=3933708264&avsresponse=&cvvresponse=&orderid=&type=verify&response_code=100",
headers: [
- {"Date", "Thu, 04 Jan 2018 11:12:20 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "124"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200}}
+ {"Date", "Thu, 04 Jan 2018 11:12:20 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "124"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
end
From f2796b465aba6817687b4fd0af5e3306044aa39a Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Thu, 25 Jan 2018 18:44:38 +0530
Subject: [PATCH 21/60] Fix doc example typos, and mock tests (#93)
* Corrected capture args order
* Mock tests now use the worker
* Corrected capture args order
---
lib/gringotts/gateways/monei.ex | 118 +++++++++++------------
test/gateways/monei_test.exs | 106 +++++++++++---------
test/integration/gateways/monei_test.exs | 2 +-
3 files changed, 117 insertions(+), 109 deletions(-)
diff --git a/lib/gringotts/gateways/monei.ex b/lib/gringotts/gateways/monei.ex
index 124f1fbc..53ebb130 100644
--- a/lib/gringotts/gateways/monei.ex
+++ b/lib/gringotts/gateways/monei.ex
@@ -131,7 +131,7 @@ defmodule Gringotts.Gateways.Monei do
aliases to it (to save some time):
```
iex> alias Gringotts.{Response, CreditCard, Gateways.Monei}
- iex> amount = %{value: Decimal.new(42), currency: "EUR"}
+ iex> amount = %{value: Decimal.new(42), currency: "USD"}
iex> card = %CreditCard{first_name: "Harry",
last_name: "Potter",
number: "4200000000000000",
@@ -145,7 +145,7 @@ defmodule Gringotts.Gateways.Monei do
"birthDate": "1980-07-31",
"mobile": "+15252525252",
"email": "masterofdeath@ministryofmagic.gov",
- "ip": "1.1.1",
+ "ip": "127.0.0.1",
"status": "NEW"}
iex> merchant = %{"name": "Ollivanders",
"city": "South Side",
@@ -249,17 +249,17 @@ defmodule Gringotts.Gateways.Monei do
## Example
- The following session shows how one would (pre) authorize a payment of $40 on
+ The following example shows how one would (pre) authorize a payment of $42 on
a sample `card`.
- iex> amount = %{value: Decimal.new(42), currency: "EUR"}
+ iex> amount = %{value: Decimal.new(42), currency: "USD"}
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Monei, amount, card, opts)
iex> auth_result.id # This is the authorization ID
iex> auth_result.token # This is the registration ID/token
"""
@spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
- def authorize(amount, card = %CreditCard{}, opts) do
+ def authorize(amount, %CreditCard{} = card, opts) do
{currency, value} = Money.to_string(amount)
params =
@@ -287,16 +287,16 @@ defmodule Gringotts.Gateways.Monei do
## Example
- The following session shows how one would (partially) capture a previously
+ The following example shows how one would (partially) capture a previously
authorized a payment worth $35 by referencing the obtained authorization `id`.
- iex> amount = %{value: Decimal.new(42), currency: "EUR"}
- iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VIS iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Monei, 35, auth_result.id, opts)
+ iex> amount = %{value: Decimal.new(35), currency: "USD"}
+ iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Monei, amount, auth_result.id, opts)
"""
- @spec capture(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
- def capture(amount, payment_id, opts)
+ @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response}
+ def capture(payment_id, amount, opts)
- def capture(amount, <>, opts) do
+ def capture(<>, amount, opts) do
{currency, value} = Money.to_string(amount)
params = [
@@ -321,16 +321,16 @@ defmodule Gringotts.Gateways.Monei do
## Example
- The following session shows how one would process a payment in one-shot,
- without (pre) authorization.
+ The following example shows how one would process a payment worth $42 in
+ one-shot, without (pre) authorization.
- iex> amount = %{value: Decimal.new(42), currency: "EUR"}
+ iex> amount = %{value: Decimal.new(42), currency: "USD"}
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.Monei, amount, card, opts)
iex> purchase_result.token # This is the registration ID/token
"""
@spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
- def purchase(amount, card = %CreditCard{}, opts) do
+ def purchase(amount, %CreditCard{} = card, opts) do
{currency, value} = Money.to_string(amount)
params =
@@ -342,46 +342,6 @@ defmodule Gringotts.Gateways.Monei do
commit(:post, "payments", params, [{:currency, currency} | opts])
end
- @doc """
- Voids the referenced payment.
-
- This method attempts a reversal of the either a previous `purchase/3` or
- `authorize/3` referenced by `payment_id`.
-
- As a consequence, the customer will never see any booking on his
- statement. Refer MONEI's [Backoffice
- Operations](https://docs.monei.net/tutorials/manage-payments/backoffice)
- guide.
-
- ## Voiding a previous authorization
-
- MONEI will reverse the authorization by sending a "reversal request" to the
- payment source (card issuer) to clear the funds held against the
- authorization. If some of the authorized amount was captured, only the
- remaining amount is cleared. **[citation-needed]**
-
- ## Voiding a previous purchase
-
- MONEI will reverse the payment, by sending all the amount back to the
- customer. Note that this is not the same as `refund/3`.
-
- ## Example
-
- The following session shows how one would void a previous (pre)
- authorization. Remember that our `capture/3` example only did a partial
- capture.
-
- iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.Monei, auth_result.id, opts)
- """
- @spec void(String.t(), keyword) :: {:ok | :error, Response}
- def void(payment_id, opts)
-
- def void(<>, opts) do
- params = [paymentType: "RV"]
- commit(:post, "payments/#{payment_id}", params, opts)
- end
-
@doc """
Refunds the `amount` to the customer's account with reference to a prior transfer.
@@ -395,11 +355,10 @@ defmodule Gringotts.Gateways.Monei do
## Example
- The following session shows how one would refund a previous purchase (and
- similarily for captures).
+ The following example shows how one would (completely) refund a previous
+ purchase (and similarily for captures).
- iex> amount = %{value: Decimal.new(42), currency: "EUR"}
- iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> amount = %{value: Decimal.new(42), currency: "USD"}
iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.Monei, purchase_result.id, amount)
"""
@spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
@@ -431,7 +390,7 @@ defmodule Gringotts.Gateways.Monei do
## Example
- The following session shows how one would store a card (a payment-source) for
+ The following example shows how one would store a card (a payment-source) for
future use.
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
@@ -457,6 +416,45 @@ defmodule Gringotts.Gateways.Monei do
commit(:delete, "registrations/#{registration_id}", [], opts)
end
+ @doc """
+ Voids the referenced payment.
+
+ This method attempts a reversal of the either a previous `purchase/3`,
+ `capture/3` or `authorize/3` referenced by `payment_id`.
+
+ As a consequence, the customer will never see any booking on his
+ statement. Refer MONEI's [Backoffice
+ Operations](https://docs.monei.net/tutorials/manage-payments/backoffice)
+ guide.
+
+ ## Voiding a previous authorization
+
+ MONEI will reverse the authorization by sending a "reversal request" to the
+ payment source (card issuer) to clear the funds held against the
+ authorization. If some of the authorized amount was captured, only the
+ remaining amount is cleared. **[citation-needed]**
+
+ ## Voiding a previous purchase
+
+ MONEI will reverse the payment, by sending all the amount back to the
+ customer. Note that this is not the same as `refund/3`.
+
+ ## Example
+
+ The following example shows how one would void a previous (pre)
+ authorization. Remember that our `capture/3` example only did a partial
+ capture.
+
+ iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.Monei, auth_result.id, opts)
+ """
+ @spec void(String.t(), keyword) :: {:ok | :error, Response}
+ def void(payment_id, opts)
+
+ def void(<>, opts) do
+ params = [paymentType: "RV"]
+ commit(:post, "payments/#{payment_id}", params, opts)
+ end
+
defp card_params(card) do
[
"card.number": card.number,
diff --git a/test/gateways/monei_test.exs b/test/gateways/monei_test.exs
index 51823a08..9fd72895 100644
--- a/test/gateways/monei_test.exs
+++ b/test/gateways/monei_test.exs
@@ -1,5 +1,5 @@
defmodule Gringotts.Gateways.MoneiTest do
- use ExUnit.Case, async: false
+ use ExUnit.Case, async: true
alias Gringotts.{
CreditCard
@@ -7,6 +7,10 @@ defmodule Gringotts.Gateways.MoneiTest do
alias Gringotts.Gateways.Monei, as: Gateway
+ @amount42 Money.new(42, :USD)
+ @amount3 Money.new(3, :USD)
+ @bad_currency Money.new(42, :INR)
+
@card %CreditCard{
first_name: "Harry",
last_name: "Potter",
@@ -27,37 +31,6 @@ defmodule Gringotts.Gateways.MoneiTest do
brand: "VISA"
}
- @bad_currency Money.new(42, :INR)
-
- @auth_success ~s[
- {"id": "8a82944a603b12d001603c1a1c2d5d90",
- "result": {
- "code": "000.100.110",
- "description": "Request successfully processed in 'Merchant in Integrator Test Mode'"}
- }]
-
- @register_success ~s[
- {"id": "8a82944960e073640160e92da2204743",
- "registrationId": "8a82944a60e09c550160e92da144491e",
- "result": {
- "code": "000.100.110",
- "description": "Request successfully processed in 'Merchant in Integrator Test Mode'"}
- }]
-
- @store_success ~s[
- {"result":{
- "code":"000.100.110",
- "description":"Request successfully processed in 'Merchant in Integrator Test Mode'"
- },
- "card":{
- "bin":"420000",
- "last4Digits":"0000",
- "holder":"Jo Doe",
- "expiryMonth":"12",
- "expiryYear":"2099"
- }
- }]
-
@customer %{
givenName: "Harry",
surname: "Potter",
@@ -100,14 +73,43 @@ defmodule Gringotts.Gateways.MoneiTest do
custom: %{"voldemort" => "he who must not be named"}
]
+ @auth_success ~s[
+ {"id": "8a82944a603b12d001603c1a1c2d5d90",
+ "result": {
+ "code": "000.100.110",
+ "description": "Request successfully processed in 'Merchant in Integrator Test Mode'"}
+ }]
+
+ @register_success ~s[
+ {"id": "8a82944960e073640160e92da2204743",
+ "registrationId": "8a82944a60e09c550160e92da144491e",
+ "result": {
+ "code": "000.100.110",
+ "description": "Request successfully processed in 'Merchant in Integrator Test Mode'"}
+ }]
+
+ @store_success ~s[
+ {"result":{
+ "code":"000.100.110",
+ "description":"Request successfully processed in 'Merchant in Integrator Test Mode'"
+ },
+ "card":{
+ "bin":"420000",
+ "last4Digits":"0000",
+ "holder":"Jo Doe",
+ "expiryMonth":"12",
+ "expiryYear":"2099"
+ }
+ }]
+
# A new Bypass instance is needed per test, so that we can do parallel tests
setup do
bypass = Bypass.open()
auth = %{
- userId: "8a829417539edb400153c1eae83932ac",
- password: "6XqRtMGS2N",
- entityId: "8a829417539edb400153c1eae6de325e",
+ userId: "some_secret_user_id",
+ password: "some_secret_password",
+ entityId: "some_secret_entity_id",
test_url: "http://localhost:#{bypass.port}"
}
@@ -126,11 +128,11 @@ defmodule Gringotts.Gateways.MoneiTest do
end)
Bypass.down(bypass)
- {:error, response} = Gateway.authorize(Money.new(42, :USD), @card, config: auth)
+ {:error, response} = Gateway.authorize(@amount42, @card, config: auth)
assert response.reason == "network related failure"
Bypass.up(bypass)
- {:ok, _} = Gateway.authorize(Money.new(42, :USD), @card, config: auth)
+ {:ok, _} = Gateway.authorize(@amount42, @card, config: auth)
end
test "with all extra_params.", %{bypass: bypass, auth: auth} do
@@ -147,15 +149,18 @@ defmodule Gringotts.Gateways.MoneiTest do
assert conn_.body_params["merchantTransactionId"] == randoms[:transaction_id]
assert conn_.body_params["transactionCategory"] == @extra_opts[:category]
assert conn_.body_params["customer.merchantCustomerId"] == @customer[:merchantCustomerId]
- assert conn_.body_params["shipping.customer.merchantCustomerId"] == @customer[:merchantCustomerId]
+
+ assert conn_.body_params["shipping.customer.merchantCustomerId"] ==
+ @customer[:merchantCustomerId]
+
assert conn_.body_params["merchant.submerchantId"] == @merchant[:submerchantId]
assert conn_.body_params["billing.city"] == @billing[:city]
assert conn_.body_params["shipping.method"] == @shipping[:method]
Plug.Conn.resp(conn, 200, @register_success)
end)
- opts = [{:config, auth} | randoms] ++ @extra_opts
- {:ok, response} = Gateway.purchase(Money.new(42, :USD), @card, opts)
+ opts = randoms ++ @extra_opts ++ [config: auth]
+ {:ok, response} = Gateway.purchase(@amount42, @card, opts)
assert response.code == "000.100.110"
assert response.token == "8a82944a60e09c550160e92da144491e"
end
@@ -165,7 +170,7 @@ defmodule Gringotts.Gateways.MoneiTest do
Plug.Conn.resp(conn, 400, "")
end)
- {:error, _} = Gateway.authorize(Money.new(42, :USD), @bad_card, config: auth)
+ {:error, _} = Gateway.authorize(@amount42, @bad_card, config: auth)
end
end
@@ -175,7 +180,7 @@ defmodule Gringotts.Gateways.MoneiTest do
Plug.Conn.resp(conn, 200, @auth_success)
end)
- {:ok, response} = Gateway.authorize(Money.new(42, :USD), @card, config: auth)
+ {:ok, response} = Gateway.authorize(@amount42, @card, config: auth)
assert response.code == "000.100.110"
end
end
@@ -186,7 +191,7 @@ defmodule Gringotts.Gateways.MoneiTest do
Plug.Conn.resp(conn, 200, @auth_success)
end)
- {:ok, response} = Gateway.purchase(Money.new(42, :USD), @card, config: auth)
+ {:ok, response} = Gateway.purchase(@amount42, @card, config: auth)
assert response.code == "000.100.110"
end
@@ -197,7 +202,7 @@ defmodule Gringotts.Gateways.MoneiTest do
Plug.Conn.resp(conn, 200, @register_success)
end)
- {:ok, response} = Gateway.purchase(Money.new(42, :USD), @card, config: auth, register: true)
+ {:ok, response} = Gateway.purchase(@amount42, @card, register: true, config: auth)
assert response.code == "000.100.110"
assert response.token == "8a82944a60e09c550160e92da144491e"
end
@@ -227,7 +232,7 @@ defmodule Gringotts.Gateways.MoneiTest do
)
{:ok, response} =
- Gateway.capture(Money.new(42, :USD), "7214344242e11af79c0b9e7b4f3f6234", config: auth)
+ Gateway.capture("7214344242e11af79c0b9e7b4f3f6234", @amount42, config: auth)
assert response.code == "000.100.110"
end
@@ -244,7 +249,13 @@ defmodule Gringotts.Gateways.MoneiTest do
end
)
- {:ok, response} = Gateway.capture(Money.new(42, :USD), "7214344242e11af79c0b9e7b4f3f6234", config: auth, register: true)
+ {:ok, response} =
+ Gateway.capture(
+ "7214344242e11af79c0b9e7b4f3f6234",
+ @amount42,
+ register: true,
+ config: auth
+ )
assert response.code == "000.100.110"
end
@@ -261,8 +272,7 @@ defmodule Gringotts.Gateways.MoneiTest do
end
)
- {:ok, response} =
- Gateway.refund(Money.new(3, :USD), "7214344242e11af79c0b9e7b4f3f6234", config: auth)
+ {:ok, response} = Gateway.refund(@amount3, "7214344242e11af79c0b9e7b4f3f6234", config: auth)
assert response.code == "000.100.110"
end
diff --git a/test/integration/gateways/monei_test.exs b/test/integration/gateways/monei_test.exs
index da24cc3e..6619a43f 100644
--- a/test/integration/gateways/monei_test.exs
+++ b/test/integration/gateways/monei_test.exs
@@ -99,7 +99,7 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
@tag :skip
test "capture", %{opts: _opts} do
- case Gringotts.capture(Gateway, @amount, "s") do
+ case Gringotts.capture(Gateway, "s", @amount) do
{:ok, response} ->
assert response.code == "000.100.110"
From fa1cd11352184520516a63a19e658473676c0a24 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Thu, 25 Jan 2018 18:47:33 +0530
Subject: [PATCH 22/60] [mix-task] Fixed arg order in capture (#94)
* Fixed arg order in capture
* fixes some patterns in function clauses
* Prompts for filename
* Added a missing comma in integration template
---
lib/mix/new.ex | 57 ++++++++++++++++++++++++-------------
templates/gateway.eex | 8 +++---
templates/integration.eex | 2 +-
templates/mock_response.eex | 2 +-
templates/test.eex | 4 +--
5 files changed, 45 insertions(+), 28 deletions(-)
diff --git a/lib/mix/new.ex b/lib/mix/new.ex
index 4f2bb609..c37fd7d0 100644
--- a/lib/mix/new.ex
+++ b/lib/mix/new.ex
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Gringotts.New do
@shortdoc """
Generates a barebones implementation for a gateway.
"""
-
+
@moduledoc """
Generates a barebones implementation for a gateway.
@@ -14,24 +14,26 @@ defmodule Mix.Tasks.Gringotts.New do
A barebones implementation of the gateway will be created along with skeleton
mock and integration tests in `lib/gringotts/gateways/`. The command will
prompt for the module name, and other metadata.
-
+
## Options
> ***Tip!***
> You can supply the extra arguments to `gringotts.new` to skip (some of) the
> prompts.
-
+
* `-m` `--module` - The module name for the Gateway.
* `--url` - The homepage of the gateway.
## Examples
- mix gringotts.new foobar
+ mix gringotts.new FooBar
The prompts for this will be:
+ ```
MODULE = `Foobar`
URL = `https://www.foobar.com`
- REQUIRED_KEYS = []
+ ```
+ and the filename will be `foo_bar.ex`
"""
use Mix.Task
@@ -41,7 +43,7 @@ defmodule Mix.Tasks.Gringotts.New do
Comma separated list of required configuration keys:
(This can be skipped by hitting `Enter`)
> }
-
+
def run(args) do
{key_list, [name], []} =
OptionParser.parse(
@@ -58,39 +60,54 @@ Comma separated list of required configuration keys:
:error -> prompt_with_suggestion("\nModule name", String.capitalize(name))
{:ok, mod_name} -> mod_name
end
-
+
url =
case Keyword.fetch(key_list, :url) do
- :error -> prompt_with_suggestion("\nHomepage URL", "https://www.#{String.Casing.downcase(name)}.com")
- {:ok, url} -> url
+ :error ->
+ prompt_with_suggestion(
+ "\nHomepage URL",
+ "https://www.#{String.Casing.downcase(name)}.com"
+ )
+
+ {:ok, url} ->
+ url
end
-
+
+ file_name = prompt_with_suggestion("\nFilename", Macro.underscore(name))
+
required_keys =
- case Mix.Shell.IO.prompt(@long_msg) |> String.trim do
+ case Mix.Shell.IO.prompt(@long_msg) |> String.trim() do
"" -> []
- keys -> String.split(keys, ",") |> Enum.map(&(String.trim(&1))) |> Enum.map(&(String.to_atom(&1)))
+
+ keys ->
+ String.split(keys, ",") |> Enum.map(&String.trim(&1)) |> Enum.map(&String.to_atom(&1))
end
bindings = [
gateway: name,
gateway_module: module_name,
- gateway_underscore: Macro.underscore(name),
+ gateway_underscore: file_name,
required_config_keys: required_keys,
gateway_url: url,
- gateway_mock_test: Macro.underscore(name) <> "_test",
- gateway_mock_response: Macro.underscore(name) <> "_mock",
+ mock_test_filename: file_name <> "_test",
+ mock_response_filename: file_name <> "_mock"
]
- if (Mix.Shell.IO.yes? "\nDoes this look good?\n#{inspect(bindings, pretty: true)}\n>") do
+ if Mix.Shell.IO.yes?(
+ "\nDoes this look good?\n#{inspect(bindings, pretty: true, width: 40)}\n>"
+ ) do
gateway = EEx.eval_file("templates/gateway.eex", bindings)
mock = EEx.eval_file("templates/test.eex", bindings)
mock_response = EEx.eval_file("templates/mock_response.eex", bindings)
integration = EEx.eval_file("templates/integration.eex", bindings)
create_file("lib/gringotts/gateways/#{bindings[:gateway_underscore]}.ex", gateway)
- create_file("test/gateways/#{bindings[:gateway_mock_test]}.exs", mock)
- create_file("test/mocks/#{bindings[:gateway_mock_response]}.exs", mock_response)
- create_file("test/integration/gateways/#{bindings[:gateway_mock_test]}.exs", integration)
+ create_file("test/integration/gateways/#{bindings[:mock_test_filename]}.exs", integration)
+
+ if Mix.Shell.IO.yes?("\nAlso create empty mock test suite?\n>") do
+ create_file("test/gateways/#{bindings[:mock_test_filename]}.exs", mock)
+ create_file("test/mocks/#{bindings[:mock_response_filename]}.exs", mock_response)
+ end
else
Mix.Shell.IO.info("Doing nothing, bye!")
end
@@ -98,7 +115,7 @@ Comma separated list of required configuration keys:
defp prompt_with_suggestion(message, suggestion) do
decorated_message = "#{message} [#{suggestion}]"
- response = Mix.Shell.IO.prompt(decorated_message) |> String.trim
+ response = Mix.Shell.IO.prompt(decorated_message) |> String.trim()
if response == "", do: suggestion, else: response
end
end
diff --git a/templates/gateway.eex b/templates/gateway.eex
index 78165a5e..b68e0a31 100644
--- a/templates/gateway.eex
+++ b/templates/gateway.eex
@@ -148,8 +148,8 @@ defmodule Gringotts.Gateways.<%= gateway_module %> do
> A barebones example using the bindings you've suggested in the `moduledoc`.
"""
- @spec capture(Money.t, String.t(), keyword) :: {:ok | :error, Response}
- def capture(amount, payment_id, opts) do
+ @spec capture(String.t(), Money.t, keyword) :: {:ok | :error, Response}
+ def capture(payment_id, amount, opts) do
# commit(args, ...)
end
@@ -211,7 +211,7 @@ defmodule Gringotts.Gateways.<%= gateway_module %> do
> A barebones example using the bindings you've suggested in the `moduledoc`.
"""
@spec refund(Money.t, String.t(), keyword) :: {:ok | :error, Response}
- def refund(amount, <>, opts) do
+ def refund(amount, payment_id, opts) do
# commit(args, ...)
end
@@ -247,7 +247,7 @@ defmodule Gringotts.Gateways.<%= gateway_module %> do
> A barebones example using the bindings you've suggested in the `moduledoc`.
"""
@spec unstore(String.t(), keyword) :: {:ok | :error, Response}
- def unstore(<>, opts) do
+ def unstore(registration_id, opts) do
# commit(args, ...)
end
diff --git a/templates/integration.eex b/templates/integration.eex
index 65fc4bff..f57b4b86 100644
--- a/templates/integration.eex
+++ b/templates/integration.eex
@@ -9,7 +9,7 @@ defmodule Gringotts.Integration.Gateways.<%= gateway_module <> "Test"%> do
setup_all do
Application.put_env(:gringotts, Gringotts.Gateways.<%= gateway_module%>,
[
- adapter: Gringotts.Gateways.<%= gateway_module%><%= if required_config_keys != [] do %><%= for key <- Enum.intersperse(required_config_keys, ",") do %><%= if key === "," do %><%= "#{key}" %><%= else %>
+ adapter: Gringotts.Gateways.<%= gateway_module%><%= if required_config_keys != [] do %>,<%= for key <- Enum.intersperse(required_config_keys, ",") do %><%= if key === "," do %><%= "#{key}" %><% else %>
<%= "#{key}" %>: "your_secret_<%= "#{key}" %>"<% end %><% end %><% end %>
]
)
diff --git a/templates/mock_response.eex b/templates/mock_response.eex
index 1b0e5b63..d4ad1f5b 100644
--- a/templates/mock_response.eex
+++ b/templates/mock_response.eex
@@ -1,6 +1,6 @@
defmodule Gringotts.Gateways.<%= gateway_module <> "Mock"%> do
- # The module should include mock responses for test cases in <%= gateway_mock_test <> ".exs"%>.
+ # The module should include mock responses for test cases in <%= mock_test_filename <> ".exs"%>.
# e.g.
# def successful_purchase do
# {:ok, %HTTPoison.Response{body: ~s[{data: "successful_purchase"}]}
diff --git a/templates/test.eex b/templates/test.eex
index db4bb72f..2fc65a79 100644
--- a/templates/test.eex
+++ b/templates/test.eex
@@ -2,13 +2,13 @@ defmodule Gringotts.Gateways.<%= gateway_module <> "Test" %> do
# The file contains mocked tests for <%= gateway_module%>
# We recommend using [mock][1] for this, you can place the mock responses from
- # the Gateway in `test/mocks/<%= gateway_mock_response%>.exs` file, which has also been
+ # the Gateway in `test/mocks/<%= mock_response_filename%>.exs` file, which has also been
# generated for you.
#
# [1]: https://github.com/jjh42/mock
# Load the mock response file before running the tests.
- Code.require_file "../mocks/<%= gateway_mock_response <> ".exs"%>", __DIR__
+ Code.require_file "../mocks/<%= mock_response_filename <> ".exs"%>", __DIR__
use ExUnit.Case, async: false
alias Gringotts.Gateways.<%= gateway_module%>
From 5b15d30705dcfea8a1bec05433c02e9063e2f88a Mon Sep 17 00:00:00 2001
From: Jyoti Gautam
Date: Thu, 25 Jan 2018 18:53:53 +0530
Subject: [PATCH 23/60] Global Collect payment gateway integration. (#95)
---
lib/gringotts/gateways/global_collect.ex | 461 +++++++++++++++++++++++
mix.exs | 21 +-
test/gateways/global_collect_test.exs | 207 ++++++++++
test/mocks/global_collect_mock.exs | 182 +++++++++
4 files changed, 861 insertions(+), 10 deletions(-)
create mode 100644 lib/gringotts/gateways/global_collect.ex
create mode 100644 test/gateways/global_collect_test.exs
create mode 100644 test/mocks/global_collect_mock.exs
diff --git a/lib/gringotts/gateways/global_collect.ex b/lib/gringotts/gateways/global_collect.ex
new file mode 100644
index 00000000..0a2010e6
--- /dev/null
+++ b/lib/gringotts/gateways/global_collect.ex
@@ -0,0 +1,461 @@
+defmodule Gringotts.Gateways.GlobalCollect do
+ @moduledoc """
+ [GlobalCollect][home] gateway implementation.
+
+ For further details, please refer [GlobalCollect API documentation](https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/index.html).
+
+ Following are the features that have been implemented for the GlobalCollect Gateway:
+
+ | Action | Method |
+ | ------ | ------ |
+ | Authorize | `authorize/3` |
+ | Purchase | `purchase/3` |
+ | Capture | `capture/3` |
+ | Refund | `refund/3` |
+ | Void | `void/2` |
+
+ ## Optional or extra parameters
+
+ Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply
+ optional arguments for transactions with the gateway.
+
+ | Key | Status |
+ | ---- | --- |
+ | `merchantCustomerId` | implemented |
+ | `description` | implemented |
+ | `customer_name` | implemented |
+ | `dob` | implemented |
+ | `company` | implemented |
+ | `email` | implemented |
+ | `phone` | implemented |
+ | `order_id` | implemented |
+ | `invoice` | implemented |
+ | `billingAddress` | implemented |
+ | `shippingAddress` | implemented |
+ | `name` | implemented |
+ | `skipAuthentication` | implemented |
+
+ ## Registering your GlobalCollect account at `Gringotts`
+
+ After creating your account successfully on [GlobalCollect](http://www.globalcollect.com/) follow the [dashboard link](https://sandbox.account.ingenico.com/#/account/apikey) to fetch the secret_api_key, api_key_id and [here](https://sandbox.account.ingenico.com/#/account/merchantid) for merchant_id.
+
+ Here's how the secrets map to the required configuration parameters for GlobalCollect:
+
+ | Config parameter | GlobalCollect secret |
+ | ------- | ---- |
+ | `:secret_api_key`| **SecretApiKey** |
+ | `:api_key_id` | **ApiKeyId** |
+ | `:merchant_id` | **MerchantId** |
+
+ Your Application config **must include the `[:secret_api_key, :api_key_id, :merchant_id]` field(s)** and would look
+ something like this:
+
+ config :gringotts, Gringotts.Gateways.GlobalCollect,
+ adapter: Gringotts.Gateways.GlobalCollect,
+ secret_api_key: "your_secret_secret_api_key"
+ api_key_id: "your_secret_api_key_id"
+ merchant_id: "your_secret_merchant_id"
+
+ ## Supported currencies and countries
+
+ The GlobalCollect platform is able to support payments in [over 150 currencies][currencies]
+
+ [currencies]: https://epayments.developer-ingenico.com/best-practices/services/currency-conversion
+ ## Following the examples
+
+ 1. First, set up a sample application and configure it to work with GlobalCollect.
+ - You could do that from scratch by following our [Getting Started][gs] guide.
+ - To save you time, we recommend [cloning our example
+ repo][example] that gives you a pre-configured sample app ready-to-go.
+ + You could use the same config or update it the with your "secrets"
+ as described [above](#module-registering-your-globalcollect-account-at-GlobalCollect).
+
+ 2. Run an `iex` session with `iex -S mix` and add some variable bindings and
+ aliases to it (to save some time):
+ ```
+ iex> alias Gringotts.{Response, CreditCard, Gateways.GlobalCollect}
+
+ iex> shippingAddress = %{
+ street: "Desertroad",
+ houseNumber: "1",
+ additionalInfo: "Suite II",
+ zip: "84536",
+ city: "Monument Valley",
+ state: "Utah",
+ countryCode: "US"
+ }
+
+ iex> billingAddress = %{
+ street: "Desertroad",
+ houseNumber: "13",
+ additionalInfo: "b",
+ zip: "84536",
+ city: "Monument Valley",
+ state: "Utah",
+ countryCode: "US"
+ }
+
+ iex> invoice = %{
+ invoiceNumber: "000000123",
+ invoiceDate: "20140306191500"
+ }
+
+ iex> name = %{
+ title: "Miss",
+ firstName: "Road",
+ surname: "Runner"
+ }
+
+ iex> opts = [ description: "Store Purchase 1437598192", merchantCustomerId: "234", customer_name: "John Doe", dob: "19490917", company: "asma", email: "johndoe@gmail.com", phone: "7765746563", order_id: "2323", invoice: invoice, billingAddress: billingAddress, shippingAddress: shippingAddress, name: name, skipAuthentication: "true" ]
+
+ ```
+
+ We'll be using these in the examples below.
+
+ [example]: https://github.com/aviabird/gringotts_example
+ """
+ @base_url "https://api-sandbox.globalcollect.com/v1/"
+
+ use Gringotts.Gateways.Base
+
+ # The Adapter module provides the `validate_config/1`
+ # Add the keys that must be present in the Application config in the
+ # `required_config` list
+ use Gringotts.Adapter, required_config: [:secret_api_key, :api_key_id, :merchant_id]
+
+ import Poison, only: [decode: 1]
+
+ alias Gringotts.{Money,
+ CreditCard,
+ Response}
+
+ @brand_map %{
+ "visa": "1",
+ "american_express": "2",
+ "master": "3",
+ "discover": "128",
+ "jcb": "125",
+ "diners_club": "132"
+ }
+
+ @doc """
+ Performs a (pre) Authorize operation.
+
+ The authorization validates the `card` details with the banking network,
+ places a hold on the transaction `amount` in the customer’s issuing bank and
+ also triggers risk management. Funds are not transferred.
+
+ GlobalCollect returns a payment id which can be further used to:
+ * `capture/3` _an_ amount.
+ * `refund/3` _an_amount
+ * `void/2` a pre_authorization
+
+ ## Example
+
+ > The following session shows how one would (pre) authorize a payment of $100 on
+ a sample `card`.
+ ```
+ iex> card = %CreditCard{
+ number: "4567350000427977",
+ month: 12,
+ year: 18,
+ first_name: "John",
+ last_name: "Doe",
+ verification_code: "123",
+ brand: "visa"
+ }
+
+ iex> amount = %{value: Decimal.new(100), currency: "USD"}
+
+ iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.GlobalCollect, amount, card, opts)
+ ```
+ """
+ @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def authorize(amount, card = %CreditCard{}, opts) do
+ params = create_params_for_auth_or_purchase(amount, card, opts)
+ commit(:post, "payments", params, opts)
+ end
+
+ @doc """
+ Captures a pre-authorized `amount`.
+
+ `amount` is transferred to the merchant account by GlobalCollect used in the
+ pre-authorization referenced by `payment_id`.
+
+ ## Note
+
+ > Authorized payment with PENDING_APPROVAL status only allow a single capture whereas the one with PENDING_CAPTURE status is used for payments that allow multiple captures.
+ > PENDING_APPROVAL is a common status only with card and direct debit transactions.
+
+ ## Example
+
+ The following session shows how one would (partially) capture a previously
+ authorized a payment worth $100 by referencing the obtained authorization `id`.
+
+ ```
+ iex> card = %CreditCard{
+ number: "4567350000427977",
+ month: 12,
+ year: 18,
+ first_name: "John",
+ last_name: "Doe",
+ verification_code: "123",
+ brand: "visa"
+ }
+
+ iex> amount = %{value: Decimal.new(100), currency: "USD"}
+
+ iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.GlobalCollect, amount, card, opts)
+
+ ```
+
+ """
+ @spec capture(String.t(), Money.t, keyword) :: {:ok | :error, Response}
+ def capture(payment_id, amount, opts) do
+ params = create_params_for_capture(amount, opts)
+ commit(:post, "payments/#{payment_id}/approve", params, opts)
+ end
+
+ @doc """
+ Transfers `amount` from the customer to the merchant.
+
+ GlobalCollect attempts to process a purchase on behalf of the customer, by
+ debiting `amount` from the customer's account by charging the customer's
+ `card`.
+
+ ## Example
+
+ > The following session shows how one would process a payment in one-shot,
+ without (pre) authorization.
+
+ ```
+ iex> card = %CreditCard{
+ number: "4567350000427977",
+ month: 12,
+ year: 18,
+ first_name: "John",
+ last_name: "Doe",
+ verification_code: "123",
+ brand: "visa"
+ }
+
+ iex> amount = %{value: Decimal.new(100), currency: "USD"}
+
+ iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.GlobalCollect, amount, card, opts)
+
+ ```
+ """
+ @spec purchase(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def purchase(amount, card = %CreditCard{}, opts) do
+ case authorize(amount, card, opts) do
+ {:ok, results} ->
+ payment_id = results.raw["payment"]["id"]
+ capture(payment_id, amount, opts)
+
+ {:error, results} ->
+ {:error, results}
+ end
+ end
+
+ @doc """
+ Voids the referenced payment.
+
+ This makes it impossible to process the payment any further and will also try to reverse an authorization on a card.
+ Reversing an authorization that you will not be utilizing will prevent you from having to pay a fee/penalty for unused authorization requests.
+
+ ## Example
+
+ > The following session shows how one would void a previous (pre)
+ authorization. Remember that our `capture/3` example only did a complete
+ capture.
+
+ ```
+ iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.GlobalCollect, auth_result.payment.id, opts)
+
+ ```
+ """
+ @spec void(String.t(), keyword) :: {:ok | :error, Response}
+ def void(payment_id, opts) do
+ params = nil
+ commit(:post, "payments/#{payment_id}/cancel", params, opts)
+ end
+
+ @doc """
+ Refunds the `amount` to the customer's account with reference to a prior transfer.
+
+ > You can refund any transaction by just calling this API
+
+ ## Note
+
+ You always have the option to refund just a portion of the payment amount.
+ It is also possible to submit multiple refund requests on one payment as long as the total amount to be refunded does not exceed the total amount that was paid.
+
+ ## Example
+
+ > The following session shows how one would refund a previous purchase (and
+ similarily for captures).
+
+ ```
+ iex> amount = %{value: Decimal.new(100), currency: "USD"}
+
+ iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.GlobalCollect, auth_result.payment.id, amount)
+ ```
+ """
+ @spec refund(Money.t, String.t(), keyword) :: {:ok | :error, Response}
+ def refund(amount, payment_id, opts) do
+ params = create_params_for_refund(amount, opts)
+ commit(:post, "payments/#{payment_id}/refund", params, opts)
+ end
+
+ ###############################################################################
+ # PRIVATE METHODS #
+ ###############################################################################
+
+ # Makes the request to GlobalCollect's network.
+ # For consistency with other gateway implementations, make your (final)
+ # network request in here, and parse it using another private method called
+ # `respond`.
+
+ defp create_params_for_refund(amount, opts) do
+ %{
+ amountOfMoney: add_money(amount, opts),
+ customer: add_customer(opts)
+ }
+ end
+
+ defp create_params_for_auth_or_purchase(amount, payment, opts) do
+ %{
+ order: add_order(amount, opts),
+ cardPaymentMethodSpecificInput: add_payment(payment, @brand_map, opts)
+ }
+ end
+
+ defp create_params_for_capture(amount, opts) do
+ %{
+ order: add_order(amount, opts)
+ }
+ end
+
+ defp add_order(money, options) do
+ %{
+ amountOfMoney: add_money(money, options),
+ customer: add_customer(options),
+ references: add_references(options)
+ }
+ end
+
+ defp add_money(amount, options) do
+ {currency, amount, _} = Money.to_integer(amount)
+ %{
+ amount: amount,
+ currencyCode: currency
+ }
+ end
+
+ defp add_customer(options) do
+ %{
+ merchantCustomerId: options[:merchantCustomerId],
+ personalInformation: personal_info(options),
+ dateOfBirth: options[:dob],
+ companyInformation: company_info(options),
+ billingAddress: options[:billingAddress],
+ shippingAddress: options[:shippingAddress],
+ contactDetails: contact(options)
+ }
+ end
+
+ defp add_references(options) do
+ %{
+ descriptor: options[:description],
+ invoiceData: options[:invoice]
+ }
+ end
+
+ defp personal_info(options) do
+ %{
+ name: options[:name]
+ }
+ end
+
+ defp company_info(options) do
+ %{
+ name: options[:company]
+ }
+ end
+
+ defp contact(options) do
+ %{
+ emailAddress: options[:email],
+ phoneNumber: options[:phone]
+ }
+ end
+
+ def add_card(%CreditCard{} = payment) do
+ %{
+ cvv: payment.verification_code,
+ cardNumber: payment.number,
+ expiryDate: "#{payment.month}"<>"#{payment.year}",
+ cardholderName: CreditCard.full_name(payment)
+ }
+ end
+
+ defp add_payment(payment, brand_map, opts) do
+ brand = payment.brand
+ %{
+ paymentProductId: Map.fetch!(brand_map, String.to_atom(brand)),
+ skipAuthentication: opts[:skipAuthentication],
+ card: add_card(payment)
+ }
+ end
+
+ defp auth_digest(path, secret_api_key, time, opts) do
+ data = "POST\napplication/json\n#{time}\n/v1/#{opts[:config][:merchant_id]}/#{path}\n"
+ :crypto.hmac(:sha256, secret_api_key, data)
+ end
+
+ defp commit(method, path, params, opts) do
+ headers = create_headers(path, opts)
+ data = Poison.encode!(params)
+ url = "#{@base_url}#{opts[:config][:merchant_id]}/#{path}"
+ response = HTTPoison.request(method, url, data, headers)
+ response |> respond
+ end
+
+ defp create_headers(path, opts) do
+ time = date
+ sha_signature = auth_digest(path, opts[:config][:secret_api_key], time, opts) |> Base.encode64
+ auth_token = "GCS v1HMAC:#{opts[:config][:api_key_id]}:#{sha_signature}"
+ headers = [{"Content-Type", "application/json"}, {"Authorization", auth_token}, {"Date", time}]
+ end
+
+ defp date() do
+ use Timex
+ datetime = Timex.now |> Timex.local
+ strftime_str = Timex.format!(datetime, "%a, %d %b %Y %H:%M:%S ", :strftime)
+ time_zone = Timex.timezone(:local, datetime)
+ time = strftime_str <>"#{time_zone.abbreviation}"
+ end
+
+ # Parses GlobalCollect's response and returns a `Gringotts.Response` struct
+ # in a `:ok`, `:error` tuple.
+ @spec respond(term) :: {:ok | :error, Response}
+ defp respond(global_collect_response)
+
+ defp respond({:ok, %{status_code: code, body: body}}) when code in [200, 201] do
+ case decode(body) do
+ {:ok, results} -> {:ok, Response.success(raw: results, status_code: code)}
+ end
+ end
+
+ defp respond({:ok, %{status_code: status_code, body: body}}) do
+ {:ok, results} = decode(body)
+ message = Enum.map(results["errors"],fn (x) -> x["message"] end)
+ detail = List.to_string(message)
+ {:error, Response.error(status_code: status_code, message: detail, raw: results)}
+ end
+
+ defp respond({:error, %HTTPoison.Error{} = error}) do
+ {:error, Response.error(code: error.id, reason: :network_fail?, description: "HTTPoison says '#{error.reason}'")}
+ end
+
+end
diff --git a/mix.exs b/mix.exs
index 63dcfbe7..7947413c 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,6 +1,6 @@
defmodule Gringotts.Mixfile do
use Mix.Project
-
+
def project do
[
app: :gringotts,
@@ -17,9 +17,9 @@ defmodule Gringotts.Mixfile do
tool: ExCoveralls
],
preferred_cli_env: [
- "coveralls": :test,
- "coveralls.detail": :test,
- "coveralls.post": :test,
+ "coveralls": :test,
+ "coveralls.detail": :test,
+ "coveralls.post": :test,
"coveralls.html": :test,
"coveralls.travis": :test
],
@@ -32,7 +32,7 @@ defmodule Gringotts.Mixfile do
# Type `mix help compile.app` for more information
def application do
[
- applications: [:httpoison, :hackney, :elixir_xml_to_map],
+ applications: [:httpoison, :hackney, :elixir_xml_to_map, :timex],
mod: {Gringotts.Application, []}
]
end
@@ -50,7 +50,7 @@ defmodule Gringotts.Mixfile do
[
{:poison, "~> 3.1.0"},
{:httpoison, "~> 0.13"},
- {:xml_builder, "~> 0.1.1"},
+ {:xml_builder, "~> 0.1.1"},
{:elixir_xml_to_map, "~> 0.1"},
# Money related
@@ -67,14 +67,15 @@ defmodule Gringotts.Mixfile do
# various analyses tools
{:credo, "~> 0.3", only: [:dev, :test]},
{:inch_ex, "~> 0.5", only: :docs},
- {:dialyxir, "~> 0.3", only: :dev}
+ {:dialyxir, "~> 0.3", only: :dev},
+ {:timex, "~> 3.1"}
]
end
defp description do
"""
- Gringotts is a payment processing library in Elixir integrating
- various payment gateways, this draws motivation for shopify's
+ Gringotts is a payment processing library in Elixir integrating
+ various payment gateways, this draws motivation for shopify's
activemerchant ruby gem.
"""
end
@@ -92,5 +93,5 @@ defmodule Gringotts.Mixfile do
[
"Gateways": ~r/^Gringotts.Gateways.?/,
]
- end
+ end
end
diff --git a/test/gateways/global_collect_test.exs b/test/gateways/global_collect_test.exs
new file mode 100644
index 00000000..87b0d702
--- /dev/null
+++ b/test/gateways/global_collect_test.exs
@@ -0,0 +1,207 @@
+defmodule Gringotts.Gateways.GlobalCollectTest do
+
+ Code.require_file "../mocks/global_collect_mock.exs", __DIR__
+ use ExUnit.Case, async: false
+ alias Gringotts.Gateways.GlobalCollectMock, as: MockResponse
+ alias Gringotts.Gateways.GlobalCollect
+ alias Gringotts.{
+ CreditCard
+ }
+
+ import Mock
+
+ @amount Money.new("500", :USD)
+
+ @bad_amount Money.new("50.3", :USD)
+
+ @shippingAddress %{
+ street: "Desertroad",
+ houseNumber: "1",
+ additionalInfo: "Suite II",
+ zip: "84536",
+ city: "Monument Valley",
+ state: "Utah",
+ countryCode: "US"
+ }
+
+ @valid_card %CreditCard{
+ number: "4567350000427977",
+ month: 12,
+ year: 18,
+ first_name: "John",
+ last_name: "Doe",
+ verification_code: "123",
+ brand: "visa"
+ }
+
+ @invalid_card %CreditCard{
+ number: "4567350000427977",
+ month: 12,
+ year: 10,
+ first_name: "John",
+ last_name: "Doe",
+ verification_code: "123",
+ brand: "visa"
+ }
+
+ @billingAddress %{
+ street: "Desertroad",
+ houseNumber: "13",
+ additionalInfo: "b",
+ zip: "84536",
+ city: "Monument Valley",
+ state: "Utah",
+ countryCode: "US"
+ }
+
+ @invoice %{
+ invoiceNumber: "000000123",
+ invoiceDate: "20140306191500"
+ }
+
+ @name %{
+ title: "Miss",
+ firstName: "Road",
+ surname: "Runner"
+ }
+
+ @valid_token "charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b"
+
+ @invalid_token 30
+
+ @invalid_config [config: %{secret_api_key: "Qtg9v4Q0G13sLRNcClWhHnvN1kVYWDcy4w9rG8T86XU=", api_key_id: "e5743abfc360ed12"}]
+
+ @options [
+ config: %{secret_api_key: "Qtg9v4Q0G13sLRNcClWhHnvN1kVYWDcy4w9rG8T86XU=", api_key_id: "e5743abfc360ed12", merchant_id: "1226"},
+ description: "Store Purchase 1437598192",
+ merchantCustomerId: "234",
+ customer_name: "John Doe",
+ dob: "19490917", company: "asma",
+ email: "johndoe@gmail.com",
+ phone: "7468474533",
+ order_id: "2323",
+ invoice: @invoice,
+ billingAddress: @billingAddress,
+ shippingAddress: @shippingAddress,
+ name: @name, skipAuthentication: "true"
+ ]
+
+ describe "validation arguments check" do
+ test "with no merchant id passed in config" do
+ assert_raise ArgumentError, fn ->
+ GlobalCollect.validate_config(@invalid_config)
+ end
+ end
+ end
+
+ describe "purchase" do
+ test "with valid card" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_purchase_with_valid_card end] do
+ {:ok, response} = GlobalCollect.purchase(@amount, @valid_card, @options)
+ assert response.status_code == 201
+ assert response.success == true
+ assert response.raw["payment"]["statusOutput"]["isAuthorized"] == true
+ end
+ end
+
+
+ test "with invalid amount" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_purchase_with_invalid_amount end] do
+ {:error, response} = GlobalCollect.purchase(@bad_amount, @valid_card, @options)
+ assert response.status_code == 400
+ assert response.success == false
+ assert response.message == "INVALID_VALUE: '50.3' is not a valid value for field 'amount'"
+ end
+ end
+ end
+
+ describe "authorize" do
+ test "with valid card" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_authorize_with_valid_card end] do
+ {:ok, response} = GlobalCollect.authorize(@amount, @valid_card, @options)
+ assert response.status_code == 201
+ assert response.success == true
+ assert response.raw["payment"]["statusOutput"]["isAuthorized"] == true
+ end
+ end
+
+ test "with invalid card" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_authorize_with_invalid_card end] do
+ {:error, response} = GlobalCollect.authorize(@amount, @invalid_card, @options)
+ assert response.status_code == 400
+ assert response.success == false
+ assert response.message == "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT"
+ end
+ end
+
+ test "with invalid amount" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_authorize_with_invalid_amount end] do
+ {:error, response} = GlobalCollect.authorize(@bad_amount, @valid_card, @options)
+ assert response.status_code == 400
+ assert response.success == false
+ assert response.message == "INVALID_VALUE: '50.3' is not a valid value for field 'amount'"
+ end
+ end
+ end
+
+ describe "refund" do
+ test "with refund not enabled for the respective account" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_refund end] do
+ {:error, response} = GlobalCollect.refund(@amount, @valid_token, @options)
+ assert response.status_code == 400
+ assert response.success == false
+ assert response.message == "ORDER WITHOUT REFUNDABLE PAYMENTS"
+ end
+ end
+ end
+
+ describe "capture" do
+ test "with valid payment id" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_capture_with_valid_paymentid end] do
+ {:ok, response} = GlobalCollect.capture(@valid_token, @amount, @options)
+ assert response.status_code == 200
+ assert response.success == true
+ assert response.raw["payment"]["status"] == "CAPTURE_REQUESTED"
+ end
+ end
+
+ test "with invalid payment id" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_capture_with_invalid_paymentid end] do
+ {:error, response} = GlobalCollect.capture(@invalid_token, @amount, @options)
+ assert response.status_code == 404
+ assert response.success == false
+ assert response.message == "UNKNOWN_PAYMENT_ID"
+ end
+ end
+ end
+
+ describe "void" do
+ test "with valid card" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_void_with_valid_card end] do
+ {:ok, response} = GlobalCollect.void(@valid_token, @options)
+ assert response.status_code == 200
+ assert response.raw["payment"]["status"] == "CANCELLED"
+ end
+ end
+ end
+
+ describe "network failure" do
+ test "with authorization" do
+ with_mock HTTPoison,
+ [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_network_failure end] do
+ {:error, response} = GlobalCollect.authorize(@amount, @valid_card, @options)
+ assert response.success == false
+ assert response.reason == :network_fail?
+ end
+ end
+ end
+end
diff --git a/test/mocks/global_collect_mock.exs b/test/mocks/global_collect_mock.exs
new file mode 100644
index 00000000..8bb9d553
--- /dev/null
+++ b/test/mocks/global_collect_mock.exs
@@ -0,0 +1,182 @@
+defmodule Gringotts.Gateways.GlobalCollectMock do
+
+ def test_for_purchase_with_valid_card do
+ {:ok,
+ %HTTPoison.Response{
+ body: "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000074\",\n \"externalReference\" : \"000000122600000000740000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000740000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20180118135349\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
+ headers: [{"Date", "Thu, 18 Jan 2018 12:53:49 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"Location",
+ "https://api-sandbox.globalcollect.com:443/v1/1226/payments/000000122600000000740000100001"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 201
+ }
+ }
+ end
+
+ def test_for_purchase_with_invalid_card do
+ {:ok,
+ %HTTPoison.Response{
+ body: "{\n \"errorId\" : \"363899bd-acfb-4452-bbb0-741c0df6b4b8\",\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"980825\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000075\",\n \"externalReference\" : \"000000122600000000750000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000750000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"546247\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20180118135651\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n }\n}",
+ headers: [
+ {"Date", "Thu, 18 Jan 2018 12:56:51 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Connection", "close"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 400
+ }
+ }
+ end
+
+ def test_for_purchase_with_invalid_amount do
+ {:ok,
+ %HTTPoison.Response{
+ body: "{\n \"errorId\" : \"8c34dc0b-776c-44e3-8cd4-b36222960153\",\n \"errors\" : [ {\n \"code\" : \"1099\",\n \"id\" : \"INVALID_VALUE\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"message\" : \"INVALID_VALUE: '50.3' is not a valid value for field 'amount'\",\n \"httpStatusCode\" : 400\n } ]\n}",
+ headers: [
+ {"Date", "Wed, 24 Jan 2018 07:16:06 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Connection", "close"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 400
+ }
+ }
+ end
+
+ def test_for_authorize_with_valid_card do
+ {:ok,
+ %HTTPoison.Response{
+ body: "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000065\",\n \"externalReference\" : \"000000122600000000650000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000650000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20180118110419\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
+ headers: [
+ {"Date", "Thu, 18 Jan 2018 10:04:19 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"Location",
+ "https://api-sandbox.globalcollect.com:443/v1/1226/payments/000000122600000000650000100001"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 201
+ }
+ }
+ end
+
+ def test_for_authorize_with_invalid_card do
+ {:ok,
+ %HTTPoison.Response{
+ body: "{\n \"errorId\" : \"dcdf5c8d-e475-4fbc-ac57-76123c1640a2\",\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"978754\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000066\",\n \"externalReference\" : \"000000122600000000660000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000660000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"978755\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20180118111508\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n }\n}",
+ headers: [
+ {"Date", "Thu, 18 Jan 2018 10:15:08 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Connection", "close"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 400
+ }
+ }
+ end
+
+ def test_for_authorize_with_invalid_amount do
+ {:ok,
+ %HTTPoison.Response{body: "{\n \"errorId\" : \"1dbef568-ed86-4c8d-a3c3-74ced258d5a2\",\n \"errors\" : [ {\n \"code\" : \"1099\",\n \"id\" : \"INVALID_VALUE\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"message\" : \"INVALID_VALUE: '50.3' is not a valid value for field 'amount'\",\n \"httpStatusCode\" : 400\n } ]\n}",
+ headers: [
+ {"Date", "Tue, 23 Jan 2018 11:18:11 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Connection", "close"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 400
+ }
+ }
+ end
+
+ def test_for_refund do
+ {:ok,
+ %HTTPoison.Response{
+ body: "{\n \"errorId\" : \"b6ba00d2-8f11-4822-8f32-c6d0a4d8793b\",\n \"errors\" : [ {\n \"code\" : \"300450\",\n \"message\" : \"ORDER WITHOUT REFUNDABLE PAYMENTS\",\n \"httpStatusCode\" : 400\n } ]\n}",
+ headers: [
+ {"Date", "Wed, 24 Jan 2018 05:33:56 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Connection", "close"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments/000000122600000000870000100001/refund",
+ status_code: 400
+ }
+ }
+ end
+
+ def test_for_capture_with_valid_paymentid do
+ {:ok,
+ %HTTPoison.Response{
+ body: "{\n \"payment\" : {\n \"id\" : \"000000122600000000650000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 50,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"CAPTURE_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_CONNECT_OR_3RD_PARTY\",\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20180123140826\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
+ headers: [
+ {"Date", "Tue, 23 Jan 2018 13:08:26 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments/000000122600000000650000100001/approve",
+ status_code: 200
+ }
+ }
+ end
+
+ def test_for_capture_with_invalid_paymentid do
+ {:ok,
+ %HTTPoison.Response{
+ body: "{\n \"errorId\" : \"ccb99804-0240-45b6-bb28-52aaae59d71b\",\n \"errors\" : [ {\n \"code\" : \"1002\",\n \"id\" : \"UNKNOWN_PAYMENT_ID\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"propertyName\" : \"paymentId\",\n \"message\" : \"UNKNOWN_PAYMENT_ID\",\n \"httpStatusCode\" : 404\n } ]\n}",
+ headers: [
+ {"Date", "Tue, 23 Jan 2018 12:25:59 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments/30/approve",
+ status_code: 404
+ }
+ }
+ end
+
+ def test_for_void_with_valid_card do
+ {:ok,
+ %HTTPoison.Response{
+ body: "{\n \"payment\" : {\n \"id\" : \"000000122600000000870000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 50,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20180124064204\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n}",
+ headers: [
+ {"Date", "Wed, 24 Jan 2018 05:42:04 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments/000000122600000000870000100001/cancel",
+ status_code: 200
+ }
+ }
+ end
+
+ def test_for_network_failure do
+ {:error, %HTTPoison.Error{id: nil, reason: :nxdomain}}
+ end
+end
From 99850ab1172e9b2304feaa522607f908e2fe5ce9 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Tue, 6 Feb 2018 17:27:33 +0530
Subject: [PATCH 24/60] Introduces `Response.t` with docs (#119)
* Fixes #1: Introducing `Response.t`
with docs
* [monei] Adapted for new `Response.t`
* Refactored `commit`, `respond` for readability
* [monei] Updated test cases
* Corrected specs
* [bogus] Adapted for Response.t
---
lib/gringotts/credit_card.ex | 2 +-
lib/gringotts/gateways/bogus.ex | 4 +-
lib/gringotts/gateways/monei.ex | 128 ++++++++++++-----------
lib/gringotts/response.ex | 83 +++++++++++----
mix.lock | 4 +
test/gateways/bogus_test.exs | 16 +--
test/gateways/monei_test.exs | 23 ++--
test/integration/gateways/monei_test.exs | 12 +--
8 files changed, 163 insertions(+), 109 deletions(-)
diff --git a/lib/gringotts/credit_card.ex b/lib/gringotts/credit_card.ex
index 32a2a50c..01811e00 100644
--- a/lib/gringotts/credit_card.ex
+++ b/lib/gringotts/credit_card.ex
@@ -1,6 +1,6 @@
defmodule Gringotts.CreditCard do
@moduledoc """
- Defines a `Struct` for (credit) cards and some utilities.
+ Defines a `struct` for (credit) cards and some utilities.
"""
defstruct [:number, :month, :year, :first_name, :last_name, :verification_code, :brand]
diff --git a/lib/gringotts/gateways/bogus.ex b/lib/gringotts/gateways/bogus.ex
index 14140f39..de903744 100644
--- a/lib/gringotts/gateways/bogus.ex
+++ b/lib/gringotts/gateways/bogus.ex
@@ -28,10 +28,10 @@ defmodule Gringotts.Gateways.Bogus do
do: success(customer_id)
defp success,
- do: {:ok, Response.success(authorization: random_string())}
+ do: {:ok, Response.success(id: random_string())}
defp success(id),
- do: {:ok, Response.success(authorization: id)}
+ do: {:ok, Response.success(id: id)}
defp random_string(length \\ 10),
do: 1..length |> Enum.map(&random_char/1) |> Enum.join
diff --git a/lib/gringotts/gateways/monei.ex b/lib/gringotts/gateways/monei.ex
index 53ebb130..7eeca41b 100644
--- a/lib/gringotts/gateways/monei.ex
+++ b/lib/gringotts/gateways/monei.ex
@@ -258,7 +258,7 @@ defmodule Gringotts.Gateways.Monei do
iex> auth_result.id # This is the authorization ID
iex> auth_result.token # This is the registration ID/token
"""
- @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response.t()}
def authorize(amount, %CreditCard{} = card, opts) do
{currency, value} = Money.to_string(amount)
@@ -293,7 +293,7 @@ defmodule Gringotts.Gateways.Monei do
iex> amount = %{value: Decimal.new(35), currency: "USD"}
iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Monei, amount, auth_result.id, opts)
"""
- @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response}
+ @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response.t()}
def capture(payment_id, amount, opts)
def capture(<>, amount, opts) do
@@ -329,7 +329,7 @@ defmodule Gringotts.Gateways.Monei do
iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.Monei, amount, card, opts)
iex> purchase_result.token # This is the registration ID/token
"""
- @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response.t()}
def purchase(amount, %CreditCard{} = card, opts) do
{currency, value} = Money.to_string(amount)
@@ -361,7 +361,7 @@ defmodule Gringotts.Gateways.Monei do
iex> amount = %{value: Decimal.new(42), currency: "USD"}
iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.Monei, purchase_result.id, amount)
"""
- @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
+ @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response.t()}
def refund(amount, <>, opts) do
{currency, value} = Money.to_string(amount)
@@ -396,7 +396,7 @@ defmodule Gringotts.Gateways.Monei do
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> {:ok, store_result} = Gringotts.store(Gringotts.Gateways.Monei, card, [])
"""
- @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response}
+ @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response.t()}
def store(%CreditCard{} = card, opts) do
params = card_params(card)
commit(:post, "registrations", params, opts)
@@ -409,7 +409,7 @@ defmodule Gringotts.Gateways.Monei do
Deletes previously stored payment-source data.
"""
- @spec unstore(String.t(), keyword) :: {:ok | :error, Response}
+ @spec unstore(String.t(), keyword) :: {:ok | :error, Response.t()}
def unstore(registration_id, opts)
def unstore(<>, opts) do
@@ -447,7 +447,7 @@ defmodule Gringotts.Gateways.Monei do
iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.Monei, auth_result.id, opts)
"""
- @spec void(String.t(), keyword) :: {:ok | :error, Response}
+ @spec void(String.t(), keyword) :: {:ok | :error, Response.t()}
def void(payment_id, opts)
def void(<>, opts) do
@@ -466,72 +466,101 @@ defmodule Gringotts.Gateways.Monei do
]
end
- # Makes the request to MONEI's network.
- @spec commit(atom, String.t(), keyword, keyword) :: {:ok | :error, Response}
- defp commit(method, endpoint, params, opts) do
- auth_params = [
+ defp auth_params(opts) do
+ [
"authentication.userId": opts[:config][:userId],
"authentication.password": opts[:config][:password],
"authentication.entityId": opts[:config][:entityId]
]
+ end
+
+ # Makes the request to MONEI's network.
+ @spec commit(atom, String.t(), keyword, keyword) :: {:ok | :error, Response.t()}
+ defp commit(:post, endpoint, params, opts) do
url = "#{base_url(opts)}/#{version(opts)}/#{endpoint}"
case expand_params(opts, params[:paymentType]) do
{:error, reason} ->
- {:error, Response.error(description: reason)}
+ {:error, Response.error(reason: reason)}
validated_params ->
- network_response =
- case method do
- :post ->
- HTTPoison.post(
- url,
- {:form, params ++ validated_params ++ auth_params},
- @default_headers
- )
-
- :delete ->
- HTTPoison.delete(url <> "?" <> URI.encode_query(auth_params))
- end
-
- respond(network_response)
+ url
+ |> HTTPoison.post({:form, params ++ validated_params ++ auth_params(opts)}, @default_headers)
+ |> respond
end
end
+ # This clause is only used by `unstore/2`
+ defp commit(:delete, endpoint, _params, opts) do
+ base_url = "#{base_url(opts)}/#{version(opts)}/#{endpoint}"
+ auth_params = auth_params(opts)
+ query_string = auth_params |> URI.encode_query()
+
+ base_url <> "?" <> query_string
+ |> HTTPoison.delete()
+ |> respond
+ end
+
# Parses MONEI's response and returns a `Gringotts.Response` struct in a
# `:ok`, `:error` tuple.
- @spec respond(term) :: {:ok | :error, Response}
+ @spec respond(term) :: {:ok | :error, Response.t()}
defp respond(monei_response)
defp respond({:ok, %{status_code: 200, body: body}}) do
- case decode(body) do
- {:ok, decoded_json} ->
- case parse_response(decoded_json) do
- {:ok, results} -> {:ok, Response.success([{:id, decoded_json["id"]} | results])}
- {:error, errors} -> {:ok, Response.error([{:id, decoded_json["id"]} | errors])}
- end
+ common = [raw: body, status_code: 200]
+
+ with {:ok, decoded_json} <- decode(body),
+ {:ok, results} <- parse_response(decoded_json) do
+ {:ok, Response.success(common ++ results)}
+ else
+ {:not_ok, errors} ->
+ {:ok, Response.error(common ++ errors)}
{:error, _} ->
- {:error, Response.error(raw: body, code: :undefined_response_from_monei)}
+ {:error, Response.error([reason: "undefined response from monei"] ++ common)}
end
end
defp respond({:ok, %{status_code: status_code, body: body}}) do
- {:error, Response.error(code: status_code, raw: body)}
+ {:error, Response.error(status_code: status_code, raw: body)}
end
defp respond({:error, %HTTPoison.Error{} = error}) do
{
:error,
Response.error(
- code: error.id,
reason: "network related failure",
- description: "HTTPoison says '#{error.reason}'"
+ message: "HTTPoison says '#{error.reason}' [ID: #{error.id || "nil"}]"
)
}
end
+ defp parse_response(%{"result" => result} = data) do
+ {address, zip_code} = @avs_code_translator[result["avsResponse"]]
+
+ results = [
+ id: data["id"],
+ token: data["registrationId"],
+ gateway_code: result["code"],
+ message: result["description"],
+ fraud_review: data["risk"],
+ cvc_result: @cvc_code_translator[result["cvvResponse"]],
+ avs_result: %{address: address, zip_code: zip_code}
+ ]
+
+ non_nil_params = Enum.filter(results, fn {_, v} -> v != nil end)
+ verify(non_nil_params)
+ end
+
+ defp verify(results) do
+ if String.match?(results[:gateway_code], ~r{^(000\.000\.|000\.100\.1|000\.[36])}) do
+ {:ok, results}
+ else
+ {:not_ok, [{:reason, results[:message]} | results]}
+ end
+ end
+
defp expand_params(params, action_type) do
Enum.reduce_while(params, [], fn {k, v}, acc ->
case k do
@@ -587,31 +616,6 @@ defmodule Gringotts.Gateways.Monei do
currency in @supported_currencies
end
- defp parse_response(%{"result" => result} = data) do
- {address, zip_code} = @avs_code_translator[result["avsResponse"]]
-
- results = [
- code: result["code"],
- description: result["description"],
- risk: data["risk"]["score"],
- cvc_result: @cvc_code_translator[result["cvvResponse"]],
- avs_result: [address: address, zip_code: zip_code],
- raw: data,
- token: data["registrationId"]
- ]
-
- filtered = Enum.filter(results, fn {_, v} -> v != nil end)
- verify(filtered)
- end
-
- defp verify(results) do
- if String.match?(results[:code], ~r{^(000\.000\.|000\.100\.1|000\.[36])}) do
- {:ok, results}
- else
- {:error, [{:reason, results[:description]} | results]}
- end
- end
-
defp make(prefix, param) do
Enum.into(param, [], fn {k, v} -> {"#{prefix}.#{k}", v} end)
end
diff --git a/lib/gringotts/response.ex b/lib/gringotts/response.ex
index f9097490..ac369f89 100644
--- a/lib/gringotts/response.ex
+++ b/lib/gringotts/response.ex
@@ -1,26 +1,73 @@
defmodule Gringotts.Response do
- @moduledoc ~S"""
- Module which defines the struct for response struct.
-
- Response struct is a standard response from public API to the application.
-
- It mostly has such as:-
- * `success`: boolean indicating the status of the transaction
- * `authorization`: token which is used to issue requests without the card info
- * `status_code`: response code
- * `error_code`: error code if there is error else nil
- * `message`: message related to the status of the response
- * `avs_result`: result for address verfication
- * `cvc_result`: result for cvc verification
- * `params`: original raw response from the gateway
- * `fraud_review`: information related to fraudulent transactions
+ @moduledoc """
+ Defines the Response `struct` and some utilities.
+
+ All `Gringotts` public API calls will return a `Response.t` wrapped in an
+ `:ok` or `:error` `tuple`. It is guaranteed that an `:ok` will be returned
+ only when the request succeeds at the gateway, ie, no error occurs.
"""
-
+
defstruct [
- :success, :authorization, :status_code, :error_code, :message,
- :avs_result, :cvc_result, :params, :fraud_review
+ :success, :id, :token, :status_code, :gateway_code, :reason, :message,
+ :avs_result, :cvc_result, :raw, :fraud_review
]
+ @typedoc """
+ The standard Response from `Gringotts`.
+
+ | Field | Type | Description |
+ |----------------|-------------------|---------------------------------------|
+ | `success` | `boolean` | Indicates the status of the\
+ transaction. |
+ | `id` | `String.t` | Gateway supplied identifier of the\
+ transaction. |
+ | `token` | `String.t` | Gateway supplied `token`. _This is\
+ different from `Response.id`_. |
+ | `status_code` | `non_neg_integer` | `HTTP` response code. |
+ | `gateway_code` | `String.t` | Gateway's response code "as-is". |
+ | `message` | `String.t` | String describing the response status.|
+ | `avs_result` | `map` | Address Verification Result.\
+ Schema: `%{street: String.t,\
+ zip_code: String.t}` |
+ | `cvc_result` | `String.t` | Result of the [CVC][cvc] validation. |
+ | `reason` | `String.t` | Explain the `reason` of error, in\
+ case of error. `nil` otherwise. |
+ | `raw` | `String.t` | Raw response from the gateway. |
+ | `fraud_review` | `term` | Gateway's risk assessment of the\
+ transaction. |
+
+ ## Notes
+
+ 1. It is not guaranteed that all fields will be populated for all calls, and
+ some gateways might insert non-standard fields. Please refer the Gateways'
+ docs for that information.
+
+ 2. `success` is deprecated in `v1.1.0` and will be removed in `v1.2.0`.
+
+ 3. For some actions the Gateway returns an additional token, say as reponse to
+ a customer tokenization/registration. In such cases the `id` is not
+ useable because it refers to the transaction, the `token` is.
+
+ > On the other hand for authorizations or captures, there's no `token`.
+
+ 4. The schema of `fraud_review` is Gateway specific.
+
+ [cvc]: https://en.wikipedia.org/wiki/Card_security_code
+ """
+ @type t:: %__MODULE__{
+ success: boolean,
+ id: String.t,
+ token: String.t,
+ status_code: non_neg_integer,
+ gateway_code: String.t,
+ reason: String.t,
+ message: String.t,
+ avs_result: %{street: String.t, zip_code: String.t},
+ cvc_result: String.t,
+ raw: String.t,
+ fraud_review: term
+ }
+
def success(opts \\ []) do
new(true, opts)
end
diff --git a/mix.lock b/mix.lock
index df5aabfe..e3441847 100644
--- a/mix.lock
+++ b/mix.lock
@@ -2,6 +2,7 @@
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"bypass": {:hex, :bypass, "0.8.1", "16d409e05530ece4a72fabcf021a3e5c7e15dcc77f911423196a0c551f2a15ca", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"},
+ "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
"credo": {:hex, :credo, "0.8.10", "261862bb7363247762e1063713bb85df2bbd84af8d8610d1272cd9c1943bba63", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"},
@@ -16,6 +17,7 @@
"ex_money": {:hex, :ex_money, "1.1.2", "4336192f1ac263900dfb4f63c1f71bc36a7cdee5d900e81937d3213be3360f9f", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}, {:ex_cldr, "~> 1.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 1.0", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.8.0", "99d2691d3edf8612f128be3f9869c4d44b91c67cec92186ce49470ae7a7404cf", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
+ "gettext": {:hex, :gettext, "0.14.0", "1a019a2e51d5ad3d126efe166dcdf6563768e5d06c32a99ad2281a1fa94b4c72", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.10.1", "c38d0ca52ea80254936a32c45bb7eb414e7a96a521b4ce76d00a69753b157f21", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
@@ -30,5 +32,7 @@
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
+ "timex": {:hex, :timex, "3.1.25", "6002dae5432f749d1c93e2cd103eb73cecb53e50d2c885349e8e4146fc96bd44", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
+ "tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
"xml_builder": {:hex, :xml_builder, "0.1.2", "b48ab9ed0a24f43a6061e0c21deda88b966a2121af5c445d4fc550dd822e23dc", [:mix], [], "hexpm"}}
diff --git a/test/gateways/bogus_test.exs b/test/gateways/bogus_test.exs
index b7c10ebd..5810dfc0 100644
--- a/test/gateways/bogus_test.exs
+++ b/test/gateways/bogus_test.exs
@@ -5,35 +5,35 @@ defmodule Gringotts.Gateways.BogusTest do
alias Gringotts.Gateways.Bogus, as: Gateway
test "authorize" do
- {:ok, %Response{authorization: authorization, success: success}} =
+ {:ok, %Response{id: id, success: success}} =
Gateway.authorize(10.95, :card, [])
assert success
- assert authorization != nil
+ assert id != nil
end
test "purchase" do
- {:ok, %Response{authorization: authorization, success: success}} =
+ {:ok, %Response{id: id, success: success}} =
Gateway.purchase(10.95, :card, [])
assert success
- assert authorization != nil
+ assert id != nil
end
test "capture" do
- {:ok, %Response{authorization: authorization, success: success}} =
+ {:ok, %Response{id: id, success: success}} =
Gateway.capture(1234, 5, [])
assert success
- assert authorization != nil
+ assert id != nil
end
test "void" do
- {:ok, %Response{authorization: authorization, success: success}} =
+ {:ok, %Response{id: id, success: success}} =
Gateway.void(1234, [])
assert success
- assert authorization != nil
+ assert id != nil
end
test "store" do
diff --git a/test/gateways/monei_test.exs b/test/gateways/monei_test.exs
index 9fd72895..8d3a11ec 100644
--- a/test/gateways/monei_test.exs
+++ b/test/gateways/monei_test.exs
@@ -119,7 +119,7 @@ defmodule Gringotts.Gateways.MoneiTest do
describe "core" do
test "with unsupported currency.", %{auth: auth} do
{:error, response} = Gateway.authorize(@bad_currency, @card, config: auth)
- assert response.description == "Invalid currency"
+ assert response.reason == "Invalid currency"
end
test "when MONEI is down or unreachable.", %{bypass: bypass, auth: auth} do
@@ -161,7 +161,7 @@ defmodule Gringotts.Gateways.MoneiTest do
opts = randoms ++ @extra_opts ++ [config: auth]
{:ok, response} = Gateway.purchase(@amount42, @card, opts)
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
assert response.token == "8a82944a60e09c550160e92da144491e"
end
@@ -181,7 +181,7 @@ defmodule Gringotts.Gateways.MoneiTest do
end)
{:ok, response} = Gateway.authorize(@amount42, @card, config: auth)
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
end
end
@@ -192,7 +192,7 @@ defmodule Gringotts.Gateways.MoneiTest do
end)
{:ok, response} = Gateway.purchase(@amount42, @card, config: auth)
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
end
test "with createRegistration.", %{bypass: bypass, auth: auth} do
@@ -203,7 +203,7 @@ defmodule Gringotts.Gateways.MoneiTest do
end)
{:ok, response} = Gateway.purchase(@amount42, @card, register: true, config: auth)
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
assert response.token == "8a82944a60e09c550160e92da144491e"
end
end
@@ -215,8 +215,7 @@ defmodule Gringotts.Gateways.MoneiTest do
end)
{:ok, response} = Gateway.store(@card, config: auth)
- assert response.code == "000.100.110"
- assert response.raw["card"]["holder"] == "Jo Doe"
+ assert response.gateway_code == "000.100.110"
end
end
@@ -234,7 +233,7 @@ defmodule Gringotts.Gateways.MoneiTest do
{:ok, response} =
Gateway.capture("7214344242e11af79c0b9e7b4f3f6234", @amount42, config: auth)
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
end
test "with createRegistration that is ignored", %{bypass: bypass, auth: auth} do
@@ -257,7 +256,7 @@ defmodule Gringotts.Gateways.MoneiTest do
config: auth
)
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
end
end
@@ -274,7 +273,7 @@ defmodule Gringotts.Gateways.MoneiTest do
{:ok, response} = Gateway.refund(@amount3, "7214344242e11af79c0b9e7b4f3f6234", config: auth)
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
end
end
@@ -290,7 +289,7 @@ defmodule Gringotts.Gateways.MoneiTest do
)
{:error, response} = Gateway.unstore("7214344242e11af79c0b9e7b4f3f6234", config: auth)
- assert response.code == :undefined_response_from_monei
+ assert response.reason == "undefined response from monei"
end
end
@@ -306,7 +305,7 @@ defmodule Gringotts.Gateways.MoneiTest do
)
{:ok, response} = Gateway.void("7214344242e11af79c0b9e7b4f3f6234", config: auth)
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
end
end
diff --git a/test/integration/gateways/monei_test.exs b/test/integration/gateways/monei_test.exs
index 6619a43f..3066ff62 100644
--- a/test/integration/gateways/monei_test.exs
+++ b/test/integration/gateways/monei_test.exs
@@ -85,9 +85,9 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
test "authorize", %{opts: opts} do
case Gringotts.authorize(Gateway, @amount, @card, opts) do
{:ok, response} ->
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
- assert response.description ==
+ assert response.message ==
"Request successfully processed in 'Merchant in Integrator Test Mode'"
assert String.length(response.id) == 32
@@ -101,9 +101,9 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
test "capture", %{opts: _opts} do
case Gringotts.capture(Gateway, "s", @amount) do
{:ok, response} ->
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
- assert response.description ==
+ assert response.message ==
"Request successfully processed in 'Merchant in Integrator Test Mode'"
assert String.length(response.id) == 32
@@ -116,9 +116,9 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
test "purchase", %{opts: opts} do
case Gringotts.purchase(Gateway, @amount, @card, opts) do
{:ok, response} ->
- assert response.code == "000.100.110"
+ assert response.gateway_code == "000.100.110"
- assert response.description ==
+ assert response.message ==
"Request successfully processed in 'Merchant in Integrator Test Mode'"
assert String.length(response.id) == 32
From 1920ced84bd481f7e7e2ee62d5478c73b8ee814d Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Mon, 5 Feb 2018 14:42:10 +0530
Subject: [PATCH 25/60] Removes payment worker.
* Gringotts does not start any process now
* Removed `adapter` key from config as it was redundant.
* Updated docs and mix task.
---
README.md | 10 ---
lib/gringotts.ex | 38 +++++-----
lib/gringotts/adapter.ex | 2 +-
lib/gringotts/application.ex | 33 ---------
lib/gringotts/gateways/authorize_net.ex | 1 -
lib/gringotts/gateways/cams.ex | 1 -
lib/gringotts/gateways/global_collect.ex | 1 -
lib/gringotts/gateways/monei.ex | 1 -
lib/gringotts/gateways/paymill.ex | 1 -
lib/gringotts/gateways/stripe.ex | 1 -
lib/gringotts/gateways/trexle.ex | 1 -
lib/gringotts/worker.ex | 89 ------------------------
mix.exs | 1 -
templates/gateway.eex | 1 -
templates/integration.eex | 3 +-
test/gateways/cams_test.exs | 5 +-
test/gringotts_test.exs | 5 +-
test/integration/gateways/monei_test.exs | 1 -
18 files changed, 23 insertions(+), 172 deletions(-)
delete mode 100644 lib/gringotts/application.ex
delete mode 100644 lib/gringotts/worker.ex
diff --git a/README.md b/README.md
index 42e56c2f..6b71deb4 100644
--- a/README.md
+++ b/README.md
@@ -33,15 +33,6 @@ def deps do
end
```
-Add gringotts to the list of applications to be started.
-```elixir
-def application do
- [
- extra_applications: [:gringotts]
- ]
-end
-```
-
## Usage
This simple example demonstrates how a purchase can be made using a person's credit card details.
@@ -50,7 +41,6 @@ Add configs in `config/config.exs` file.
```elixir
config :gringotts, Gringotts.Gateways.Monei,
- adapter: Gringotts.Gateways.Monei,
userId: "your_secret_user_id",
password: "your_secret_password",
entityId: "your_secret_channel_id"
diff --git a/lib/gringotts.ex b/lib/gringotts.ex
index 13d0eb07..d4f9e65e 100644
--- a/lib/gringotts.ex
+++ b/lib/gringotts.ex
@@ -133,13 +133,8 @@ defmodule Gringotts do
following format:
config :gringotts, Gringotts.Gateways.XYZ,
- adapter: Gringotts.Gateways.XYZ,
# some_documented_key: associated_value
# some_other_key: another_value
-
- > ***Note!***
- > The config key matches the `:adapter`! Both ***must*** be the Gateway module
- > name!
"""
import GenServer, only: [call: 2]
@@ -166,8 +161,8 @@ defmodule Gringotts do
{:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.XYZ, amount, card, opts)
"""
def authorize(gateway, amount, card, opts \\ []) do
- validate_config(gateway)
- call(:payment_worker, {:authorize, gateway, amount, card, opts})
+ config = get_and_validate_config(gateway)
+ gateway.authorize(amount, card, [{:config, config} | opts])
end
@doc """
@@ -189,8 +184,8 @@ defmodule Gringotts do
Gringotts.capture(Gringotts.Gateways.XYZ, amount, auth_result.id, opts)
"""
def capture(gateway, id, amount, opts \\ []) do
- validate_config(gateway)
- call(:payment_worker, {:capture, gateway, id, amount, opts})
+ config = get_and_validate_config(gateway)
+ gateway.capture(id, amount, [{:config, config} | opts])
end
@doc """
@@ -217,8 +212,8 @@ defmodule Gringotts do
Gringotts.purchase(Gringotts.Gateways.XYZ, amount, card, opts)
"""
def purchase(gateway, amount, card, opts \\ []) do
- validate_config(gateway)
- call(:payment_worker, {:purchase, gateway, amount, card, opts})
+ config = get_and_validate_config(gateway)
+ gateway.purchase(amount, card, [{:config, config} | opts])
end
@doc """
@@ -237,8 +232,8 @@ defmodule Gringotts do
Gringotts.purchase(Gringotts.Gateways.XYZ, amount, id, opts)
"""
def refund(gateway, amount, id, opts \\ []) do
- validate_config(gateway)
- call(:payment_worker, {:refund, gateway, amount, id, opts})
+ config = get_and_validate_config(gateway)
+ gateway.refund(amount, id, [{:config, config} | opts])
end
@doc """
@@ -258,8 +253,8 @@ defmodule Gringotts do
Gringotts.store(Gringotts.Gateways.XYZ, card, opts)
"""
def store(gateway, card, opts \\ []) do
- validate_config(gateway)
- call(:payment_worker, {:store, gateway, card, opts})
+ config = get_and_validate_config(gateway)
+ gateway.store(card, [{:config, config} | opts])
end
@doc """
@@ -276,8 +271,8 @@ defmodule Gringotts do
Gringotts.unstore(Gringotts.Gateways.XYZ, token)
"""
def unstore(gateway, token, opts \\ []) do
- validate_config(gateway)
- call(:payment_worker, {:unstore, gateway, token, opts})
+ config = get_and_validate_config(gateway)
+ gateway.unstore(token, [{:config, config} | opts])
end
@doc """
@@ -297,13 +292,16 @@ defmodule Gringotts do
Gringotts.void(Gringotts.Gateways.XYZ, id, opts)
"""
def void(gateway, id, opts \\ []) do
- validate_config(gateway)
- call(:payment_worker, {:void, gateway, id, opts})
+ config = get_and_validate_config(gateway)
+ gateway.void(id, [{:config, config} | opts])
end
- defp validate_config(gateway) do
+ defp get_and_validate_config(gateway) do
# Keep the key name and adapter the same in the config in application
config = Application.get_env(:gringotts, gateway)
+ # The following call to validate_config might raise an error
gateway.validate_config(config)
+ global_config = Application.get_env(:gringotts, :global_config) || [mode: :test]
+ Keyword.merge(global_config, config)
end
end
diff --git a/lib/gringotts/adapter.ex b/lib/gringotts/adapter.ex
index d6e250b6..8d2a24f8 100644
--- a/lib/gringotts/adapter.ex
+++ b/lib/gringotts/adapter.ex
@@ -31,4 +31,4 @@ defmodule Gringotts.Adapter do
end
end
end
-end
\ No newline at end of file
+end
diff --git a/lib/gringotts/application.ex b/lib/gringotts/application.ex
deleted file mode 100644
index f852de3b..00000000
--- a/lib/gringotts/application.ex
+++ /dev/null
@@ -1,33 +0,0 @@
-defmodule Gringotts.Application do
- @moduledoc ~S"""
- Has the supervision tree which monitors all the workers
- that are handling the payments.
- """
- use Application
-
- def start(_type, _args) do
- import Supervisor.Spec, warn: false
- app_config = Application.get_all_env(:gringotts)
- adapters = Enum.filter(app_config, fn({_, klist}) -> klist != [] end)
- |> Enum.map(fn({_, klist}) -> Keyword.get(klist, :adapter) end)
-
- children = [
- # Define workers and child supervisors to be supervised
- # worker(Gringotts.Worker, [arg1, arg2, arg3])
- worker(
- Gringotts.Worker,
- [
- adapters, # gateways
- app_config, # options(config from application)
- # Since we just have one worker handling all the incoming
- # requests so this name remains fixed
- [name: :payment_worker]
- ])
- ]
-
- # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
- # for other strategies and supported options
- opts = [strategy: :one_for_one, name: Gringotts.Supervisor]
- Supervisor.start_link(children, opts)
- end
-end
diff --git a/lib/gringotts/gateways/authorize_net.ex b/lib/gringotts/gateways/authorize_net.ex
index 04762db3..5e2b57fd 100644
--- a/lib/gringotts/gateways/authorize_net.ex
+++ b/lib/gringotts/gateways/authorize_net.ex
@@ -65,7 +65,6 @@ defmodule Gringotts.Gateways.AuthorizeNet do
fields** and would look something like this:
config :gringotts, Gringotts.Gateways.AuthorizeNet,
- adapter: Gringotts.Gateways.AuthorizeNet,
name: "name_provided_by_authorize_net",
transaction_key: "transactionKey_provided_by_authorize_net"
diff --git a/lib/gringotts/gateways/cams.ex b/lib/gringotts/gateways/cams.ex
index 7e37dd05..1787e947 100644
--- a/lib/gringotts/gateways/cams.ex
+++ b/lib/gringotts/gateways/cams.ex
@@ -56,7 +56,6 @@ defmodule Gringotts.Gateways.Cams do
> fields** and would look something like this:
config :gringotts, Gringotts.Gateways.Cams,
- adapter: Gringotts.Gateways.Cams,
username: "your_secret_user_name",
password: "your_secret_password",
diff --git a/lib/gringotts/gateways/global_collect.ex b/lib/gringotts/gateways/global_collect.ex
index 0a2010e6..0c6d143e 100644
--- a/lib/gringotts/gateways/global_collect.ex
+++ b/lib/gringotts/gateways/global_collect.ex
@@ -51,7 +51,6 @@ defmodule Gringotts.Gateways.GlobalCollect do
something like this:
config :gringotts, Gringotts.Gateways.GlobalCollect,
- adapter: Gringotts.Gateways.GlobalCollect,
secret_api_key: "your_secret_secret_api_key"
api_key_id: "your_secret_api_key_id"
merchant_id: "your_secret_merchant_id"
diff --git a/lib/gringotts/gateways/monei.ex b/lib/gringotts/gateways/monei.ex
index 7eeca41b..c297a1a3 100644
--- a/lib/gringotts/gateways/monei.ex
+++ b/lib/gringotts/gateways/monei.ex
@@ -73,7 +73,6 @@ defmodule Gringotts.Gateways.Monei do
fields** and would look something like this:
config :gringotts, Gringotts.Gateways.Monei,
- adapter: Gringotts.Gateways.Monei,
userId: "your_secret_user_id",
password: "your_secret_password",
entityId: "your_secret_channel_id"
diff --git a/lib/gringotts/gateways/paymill.ex b/lib/gringotts/gateways/paymill.ex
index 7a05bca1..8b00ba3e 100644
--- a/lib/gringotts/gateways/paymill.ex
+++ b/lib/gringotts/gateways/paymill.ex
@@ -22,7 +22,6 @@ defmodule Gringotts.Gateways.Paymill do
Your application config must include 'private_key', 'public_key'
config :gringotts, Gringotts.Gateways.Paymill,
- adapter: Gringotts.Gateways.Paymill,
private_key: "your_privat_key",
public_key: "your_public_key"
"""
diff --git a/lib/gringotts/gateways/stripe.ex b/lib/gringotts/gateways/stripe.ex
index 1a0d2b89..7c1befd2 100644
--- a/lib/gringotts/gateways/stripe.ex
+++ b/lib/gringotts/gateways/stripe.ex
@@ -51,7 +51,6 @@ defmodule Gringotts.Gateways.Stripe do
Your Application config must look something like this:
config :gringotts, Gringotts.Gateways.Stripe,
- adapter: Gringotts.Gateways.Stripe,
secret_key: "your_secret_key",
default_currency: "usd"
"""
diff --git a/lib/gringotts/gateways/trexle.ex b/lib/gringotts/gateways/trexle.ex
index d89d7767..fba77838 100644
--- a/lib/gringotts/gateways/trexle.ex
+++ b/lib/gringotts/gateways/trexle.ex
@@ -41,7 +41,6 @@ defmodule Gringotts.Gateways.Trexle do
Your Application config must look something like this:
config :gringotts, Gringotts.Gateways.Trexle,
- adapter: Gringotts.Gateways.Trexle,
api_key: "your-secret-API-key"
[dashboard]: https://trexle.com/dashboard/
diff --git a/lib/gringotts/worker.ex b/lib/gringotts/worker.ex
deleted file mode 100644
index 786ad6ed..00000000
--- a/lib/gringotts/worker.ex
+++ /dev/null
@@ -1,89 +0,0 @@
-defmodule Gringotts.Worker do
- @moduledoc ~S"""
- A central supervised worker handling all the calls for different gateways
-
- It's main task is to re-route the requests to the respective gateway methods.
-
- State for this worker currently is:-
- * `gateways`:- a list of all the gateways configured in the application.
- * `all_configs`:- All the configurations for all the gateways that are configured.
- """
- use GenServer
-
- def start_link(gateways, all_config, opts \\ []) do
- GenServer.start_link(__MODULE__, [gateways, all_config], opts)
- end
-
- def init([gateways, all_config]) do
- {:ok, %{configs: all_config, gateways: gateways}}
- end
-
- @doc """
- Handles call for `authorize` method
- """
- def handle_call({:authorize, gateway, amount, card, opts}, _from, state) do
- {gateway, config} = set_gateway_and_config(gateway)
- response = gateway.authorize(amount, card, [{:config, config} | opts])
- {:reply, response, state}
- end
-
- @doc """
- Handles call for `purchase` method
- """
- def handle_call({:purchase, gateway, amount, card, opts}, _from, state) do
- {gateway, config} = set_gateway_and_config(gateway)
- response = gateway.purchase(amount, card, [{:config, config} | opts])
- {:reply, response, state}
- end
-
- @doc """
- Handles call for `capture` method
- """
- def handle_call({:capture, gateway, id, amount, opts}, _from, state) do
- {gateway, config} = set_gateway_and_config(gateway)
- response = gateway.capture(id, amount, [{:config, config} | opts])
- {:reply, response, state}
- end
-
- @doc """
- Handles call for `void` method
- """
- def handle_call({:void, gateway, id, opts}, _from, state) do
- {gateway, config} = set_gateway_and_config(gateway)
- response = gateway.void(id, [{:config, config} | opts])
- {:reply, response, state}
- end
-
- @doc """
- Handles call for 'refund' method
- """
- def handle_call({:refund, gateway, amount, id, opts}, _from, state) do
- {gateway, config} = set_gateway_and_config(gateway)
- response = gateway.refund(amount, id, [{:config, config} | opts])
- {:reply, response, state}
- end
-
- @doc """
- Handles call for `store` method
- """
- def handle_call({:store, gateway, card, opts}, _from, state) do
- {gateway, config} = set_gateway_and_config(gateway)
- response = gateway.store(card, [{:config, config} | opts])
- {:reply, response, state}
- end
-
- @doc """
- Handles call for 'unstore' method
- """
- def handle_call({:unstore, gateway, customer_id, opts}, _from, state) do
- {gateway, config} = set_gateway_and_config(gateway)
- response = gateway.unstore(customer_id, [{:config, config} | opts])
- {:reply, response, state}
- end
-
- defp set_gateway_and_config(request_gateway) do
- global_config = Application.get_env(:gringotts, :global_config) || [mode: :test]
- gateway_config = Application.get_env(:gringotts, request_gateway)
- {request_gateway, Keyword.merge(global_config, gateway_config)}
- end
-end
diff --git a/mix.exs b/mix.exs
index 7947413c..36d86864 100644
--- a/mix.exs
+++ b/mix.exs
@@ -33,7 +33,6 @@ defmodule Gringotts.Mixfile do
def application do
[
applications: [:httpoison, :hackney, :elixir_xml_to_map, :timex],
- mod: {Gringotts.Application, []}
]
end
diff --git a/templates/gateway.eex b/templates/gateway.eex
index b68e0a31..6169bae4 100644
--- a/templates/gateway.eex
+++ b/templates/gateway.eex
@@ -50,7 +50,6 @@ defmodule Gringotts.Gateways.<%= gateway_module %> do
> something like this:
>
> config :gringotts, Gringotts.Gateways.<%= gateway_module %>,
- > adapter: Gringotts.Gateways.<%= gateway_module %><%= if required_config_keys != [] do %>,<% end %>
<%= for key <- required_config_keys do %>> <%= "#{key}" %>: "your_secret_<%= "#{key}" %>"
<% end %>
diff --git a/templates/integration.eex b/templates/integration.eex
index f57b4b86..74bc5fc7 100644
--- a/templates/integration.eex
+++ b/templates/integration.eex
@@ -8,8 +8,7 @@ defmodule Gringotts.Integration.Gateways.<%= gateway_module <> "Test"%> do
setup_all do
Application.put_env(:gringotts, Gringotts.Gateways.<%= gateway_module%>,
- [
- adapter: Gringotts.Gateways.<%= gateway_module%><%= if required_config_keys != [] do %>,<%= for key <- Enum.intersperse(required_config_keys, ",") do %><%= if key === "," do %><%= "#{key}" %><% else %>
+ [ <%= if required_config_keys == [] do %># some_key: "some_secret_key"<% else %><%= for key <- Enum.intersperse(required_config_keys, ",") do %><%= if key === "," do %><%= "#{key}" %><% else %>
<%= "#{key}" %>: "your_secret_<%= "#{key}" %>"<% end %><% end %><% end %>
]
)
diff --git a/test/gateways/cams_test.exs b/test/gateways/cams_test.exs
index 3c20e545..6faf56b2 100644
--- a/test/gateways/cams_test.exs
+++ b/test/gateways/cams_test.exs
@@ -57,10 +57,7 @@ defmodule Gringotts.Gateways.CamsTest do
@bad_authorization "some_fake_transaction_id"
setup_all do
- Application.put_env(
- :gringotts,
- Gateway,
- adapter: Gateway,
+ Application.put_env(:gringotts, Gateway,
username: "some_secret_user_name",
password: "some_secret_password"
)
diff --git a/test/gringotts_test.exs b/test/gringotts_test.exs
index d4634e14..7d9f7681 100644
--- a/test/gringotts_test.exs
+++ b/test/gringotts_test.exs
@@ -4,12 +4,11 @@ defmodule GringottsTest do
import Gringotts
@test_config [
- adapter: GringottsTest.FakeGateway,
some_auth_info: :merchant_secret_key,
other_secret: :sun_rises_in_the_east
]
- @bad_config [adapter: GringottsTest.FakeGateway, some_auth_info: :merchant_secret_key]
+ @bad_config [some_auth_info: :merchant_secret_key]
defmodule FakeGateway do
use Gringotts.Adapter, required_config: [:some_auth_info, :other_secret]
@@ -83,7 +82,7 @@ defmodule GringottsTest do
assert_raise(
ArgumentError,
- "expected [:other_secret] to be set, got: [adapter: GringottsTest.FakeGateway, some_auth_info: :merchant_secret_key]\n",
+ "expected [:other_secret] to be set, got: [some_auth_info: :merchant_secret_key]\n",
fn -> authorize(GringottsTest.FakeGateway, 100, :card, []) end
)
end
diff --git a/test/integration/gateways/monei_test.exs b/test/integration/gateways/monei_test.exs
index 3066ff62..8ed929e4 100644
--- a/test/integration/gateways/monei_test.exs
+++ b/test/integration/gateways/monei_test.exs
@@ -66,7 +66,6 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
Application.put_env(
:gringotts,
Gringotts.Gateways.Monei,
- adapter: Gringotts.Gateways.Monei,
userId: "8a8294186003c900016010a285582e0a",
password: "hMkqf2qbWf",
entityId: "8a82941760036820016010a28a8337f6"
From 8622ded4b44a7990c0787939f5fd30d633b83e14 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Mon, 5 Feb 2018 17:38:25 +0530
Subject: [PATCH 26/60] Removed offending (now useless) test case
---
test/integration/gateways/monei_test.exs | 5 -----
1 file changed, 5 deletions(-)
diff --git a/test/integration/gateways/monei_test.exs b/test/integration/gateways/monei_test.exs
index 8ed929e4..c9b67aad 100644
--- a/test/integration/gateways/monei_test.exs
+++ b/test/integration/gateways/monei_test.exs
@@ -126,9 +126,4 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
flunk()
end
end
-
- test "Environment setup" do
- config = Application.get_env(:gringotts, Gringotts.Gateways.Monei)
- assert config[:adapter] == Gringotts.Gateways.Monei
- end
end
From 6aa6a868ecaf2bd8e459bef4a0daf9fdeb49f878 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Thu, 15 Feb 2018 13:00:33 +0530
Subject: [PATCH 27/60] Removed GenServer import. Fixes #8.
---
lib/gringotts.ex | 2 --
1 file changed, 2 deletions(-)
diff --git a/lib/gringotts.ex b/lib/gringotts.ex
index d4f9e65e..11bda944 100644
--- a/lib/gringotts.ex
+++ b/lib/gringotts.ex
@@ -137,8 +137,6 @@ defmodule Gringotts do
# some_other_key: another_value
"""
- import GenServer, only: [call: 2]
-
@doc """
Performs a (pre) Authorize operation.
From 3768649e58f03f5d395e205bd3e440d821f36907 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Fri, 9 Feb 2018 17:21:41 +0530
Subject: [PATCH 28/60] Refactored ResponseHandler, for new Response.t
- Updated dependency `xml_builder`. The new `generate/2` provides a
`format: :none | :indented` option.
- `:format` is set to `:none` to produce "minified" network requests.
* This is almost a complete rewrite to reduce code duplication.
- check_response_type() was acting as guard that matched only against
some response types. It did not handle the scenario when a non-supported
response would be obtained. It really served no purpose
- check_response_type -> extract_gateway_response
+ This guards as well as fetches, previously the fetch was being
done multiple times.
* Moved all response handling inside the `ResponseHandler`.
* Since we now have a struct, and want to deprecate `:success`, `Response.success/1` and `Response.error/1`, helpers now act on structs.
* `errorCode` and `errorText` are used to build `:reason`.
+ Removed pointless asserts from tests.
---
lib/gringotts/gateways/authorize_net.ex | 187 ++++++++++++------------
mix.exs | 2 +-
mix.lock | 2 +-
test/gateways/authorize_net_test.exs | 19 +--
4 files changed, 96 insertions(+), 114 deletions(-)
diff --git a/lib/gringotts/gateways/authorize_net.ex b/lib/gringotts/gateways/authorize_net.ex
index 5e2b57fd..1504eea2 100644
--- a/lib/gringotts/gateways/authorize_net.ex
+++ b/lib/gringotts/gateways/authorize_net.ex
@@ -101,7 +101,6 @@ defmodule Gringotts.Gateways.AuthorizeNet do
"""
import XmlBuilder
- import XmlToMap
use Gringotts.Gateways.Base
use Gringotts.Adapter, required_config: [:name, :transaction_key]
@@ -397,9 +396,9 @@ defmodule Gringotts.Gateways.AuthorizeNet do
def store(card, opts) do
request_data =
if opts[:customer_profile_id] do
- card |> create_customer_payment_profile(opts) |> generate
+ card |> create_customer_payment_profile(opts) |> generate(format: :none)
else
- card |> create_customer_profile(opts) |> generate
+ card |> create_customer_profile(opts) |> generate(format: :none)
end
response_data = commit(:post, request_data, opts)
@@ -420,42 +419,32 @@ defmodule Gringotts.Gateways.AuthorizeNet do
@spec unstore(String.t(), Keyword.t()) :: {:ok | :error, Response}
def unstore(customer_profile_id, opts) do
- request_data = customer_profile_id |> delete_customer_profile(opts) |> generate
+ request_data = customer_profile_id |> delete_customer_profile(opts) |> generate(format: :none)
response_data = commit(:post, request_data, opts)
respond(response_data)
end
- # method to make the api request with params
+ # method to make the API request with params
defp commit(method, payload, opts) do
path = base_url(opts)
headers = @header
HTTPoison.request(method, path, payload, headers)
end
- # Function to return a response
- defp respond({:ok, %{body: body, status_code: 200}}) do
- raw_response = naive_map(body)
- response_type = ResponseHandler.check_response_type(raw_response)
- response_check(raw_response[response_type], raw_response)
- end
+ defp respond({:ok, %{body: body, status_code: 200}}), do: ResponseHandler.respond(body)
defp respond({:ok, %{body: body, status_code: code}}) do
- {:error, Response.error(params: body, error_code: code)}
+ {:error, %Response{raw: body, status_code: code}}
end
defp respond({:error, %HTTPoison.Error{} = error}) do
- {:error, Response.error(error_code: error.id, message: "HTTPoison says '#{error.reason}'")}
- end
-
- # Functions to send successful and error responses depending on message received
- # from gateway.
-
- defp response_check(%{"messages" => %{"resultCode" => "Ok"}}, raw_response) do
- {:ok, ResponseHandler.parse_gateway_success(raw_response)}
- end
-
- defp response_check(%{"messages" => %{"resultCode" => "Error"}}, raw_response) do
- {:error, ResponseHandler.parse_gateway_error(raw_response)}
+ {
+ :error,
+ %Response{
+ reason: "network related failure",
+ message: "HTTPoison says '#{error.reason}' [ID: #{error.id || "nil"}]"
+ }
+ }
end
##############################################################################
@@ -470,7 +459,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
add_order_id(opts),
add_purchase_transaction_request(amount, transaction_type, payment, opts)
])
- |> generate
+ |> generate(format: :none)
end
# function for formatting the request for normal capture
@@ -481,7 +470,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
add_order_id(opts),
add_capture_transaction_request(amount, id, transaction_type, opts)
])
- |> generate
+ |> generate(format: :none)
end
# function to format the request for normal refund
@@ -492,7 +481,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
add_order_id(opts),
add_refund_transaction_request(amount, id, opts, transaction_type)
])
- |> generate
+ |> generate(format: :none)
end
# function to format the request for normal void operation
@@ -506,7 +495,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
add_ref_trans_id(id)
])
])
- |> generate
+ |> generate(format: :none)
end
defp create_customer_payment_profile(card, opts) do
@@ -746,88 +735,98 @@ defmodule Gringotts.Gateways.AuthorizeNet do
end
end
+ ##################################################################################
+ # RESPONSE_HANDLER MODULE #
+ # #
+ ##################################################################################
+
defmodule ResponseHandler do
@moduledoc false
alias Gringotts.Response
- @response_type %{
- auth_response: "authenticateTestResponse",
- transaction_response: "createTransactionResponse",
- error_response: "ErrorResponse",
- customer_profile_response: "createCustomerProfileResponse",
- customer_payment_profile_response: "createCustomerPaymentProfileResponse",
- delete_customer_profile: "deleteCustomerProfileResponse"
- }
+ @supported_response_types [
+ "authenticateTestResponse",
+ "createTransactionResponse",
+ "ErrorResponse",
+ "createCustomerProfileResponse",
+ "createCustomerPaymentProfileResponse",
+ "deleteCustomerProfileResponse"
+ ]
+
+ def respond(body) do
+ response_map = XmlToMap.naive_map(body)
+ case extract_gateway_response(response_map) do
+ :undefined_response ->
+ {
+ :error,
+ %Response{
+ reason: "Undefined response from AunthorizeNet",
+ raw: body,
+ message: "You might wish to open an issue with Gringotts."
+ }
+ }
- def parse_gateway_success(raw_response) do
- response_type = check_response_type(raw_response)
- token = raw_response[response_type]["transactionResponse"]["transId"]
- message = raw_response[response_type]["messages"]["message"]["text"]
- avs_result = raw_response[response_type]["transactionResponse"]["avsResultCode"]
- cvc_result = raw_response[response_type]["transactionResponse"]["cavvResultCode"]
+ result ->
+ build_response(result, %Response{raw: body, status_code: 200})
+ end
+ end
+
+ def extract_gateway_response(response_map) do
+ # The type of the response should be supported
+ @supported_response_types
+ |> Stream.map(&Map.get(response_map, &1, nil))
+ # Find the first non-nil from the above, if all are `nil`...
+ # We are in trouble!
+ |> Enum.find(:undefined_response, &(&1))
+ end
- []
- |> status_code(200)
- |> set_token(token)
+ defp build_response(%{"messages" => %{"resultCode" => "Ok"}} = result, base_response) do
+ {:ok, ResponseHandler.parse_gateway_success(result, base_response)}
+ end
+
+ defp build_response(%{"messages" => %{"resultCode" => "Error"}} = result, base_response) do
+ {:error, ResponseHandler.parse_gateway_error(result, base_response)}
+ end
+
+ def parse_gateway_success(result, base_response) do
+ id = result["transactionResponse"]["transId"]
+ message = result["messages"]["message"]["text"]
+ avs_result = result["transactionResponse"]["avsResultCode"]
+ cvc_result = result["transactionResponse"]["cavvResultCode"]
+ gateway_code = result["messages"]["message"]["code"]
+
+ base_response
+ |> set_id(id)
|> set_message(message)
+ |> set_gateway_code(gateway_code)
|> set_avs_result(avs_result)
|> set_cvc_result(cvc_result)
- |> set_params(raw_response)
- |> set_success(true)
- |> handle_opts
end
- def parse_gateway_error(raw_response) do
- response_type = check_response_type(raw_response)
+ def parse_gateway_error(result, base_response) do
+ message = result["messages"]["message"]["text"]
+ gateway_code = result["messages"]["message"]["code"]
- {message, error_code} =
- if raw_response[response_type]["transactionResponse"]["errors"] do
- {
- raw_response[response_type]["messages"]["message"]["text"] <>
- " " <>
- raw_response[response_type]["transactionResponse"]["errors"]["error"]["errorText"],
- raw_response[response_type]["transactionResponse"]["errors"]["error"]["errorCode"]
- }
- else
- {
- raw_response[response_type]["messages"]["message"]["text"],
- raw_response[response_type]["messages"]["message"]["code"]
- }
- end
+ error_text = result["transactionResponse"]["errors"]["error"]["errorText"]
+ error_code = result["transactionResponse"]["errors"]["error"]["errorCode"]
+ reason = "#{error_text} [Error code (#{error_code})]"
- []
- |> status_code(200)
+ base_response
|> set_message(message)
- |> set_error_code(error_code)
- |> set_params(raw_response)
- |> set_success(false)
- |> handle_opts
+ |> set_gateway_code(gateway_code)
+ |> set_reason(reason)
end
- def check_response_type(raw_response) do
- cond do
- raw_response[@response_type[:transaction_response]] -> "createTransactionResponse"
- raw_response[@response_type[:error_response]] -> "ErrorResponse"
- raw_response[@response_type[:customer_profile_response]] -> "createCustomerProfileResponse"
- raw_response[@response_type[:customer_payment_profile_response]] -> "createCustomerPaymentProfileResponse"
- raw_response[@response_type[:delete_customer_profile]] -> "deleteCustomerProfileResponse"
- end
- end
+ ############################################################################
+ # HELPERS #
+ ############################################################################
- defp set_token(opts, token), do: [{:authorization, token} | opts]
- defp set_success(opts, value), do: [{:success, value} | opts]
- defp status_code(opts, code), do: [{:status, code} | opts]
- defp set_message(opts, message), do: [{:message, message} | opts]
- defp set_avs_result(opts, result), do: [{:avs, result} | opts]
- defp set_cvc_result(opts, result), do: [{:cvc, result} | opts]
- defp set_params(opts, raw_response), do: [{:params, raw_response} | opts]
- defp set_error_code(opts, code), do: [{:error, code} | opts]
-
- defp handle_opts(opts) do
- case Keyword.fetch(opts, :success) do
- {:ok, true} -> Response.success(opts)
- {:ok, false} -> Response.error(opts)
- end
- end
+ defp set_id(response, id), do: %{response | id: id}
+ defp set_message(response, message), do: %{response | message: message}
+ defp set_gateway_code(response, code), do: %{response | gateway_code: code}
+ defp set_reason(response, body), do: %{response | reason: body}
+
+ defp set_avs_result(response, result), do: %{response | avs_result: result}
+ defp set_cvc_result(response, result), do: %{response | cvc_result: result}
end
end
diff --git a/mix.exs b/mix.exs
index 36d86864..f63edfc6 100644
--- a/mix.exs
+++ b/mix.exs
@@ -49,7 +49,7 @@ defmodule Gringotts.Mixfile do
[
{:poison, "~> 3.1.0"},
{:httpoison, "~> 0.13"},
- {:xml_builder, "~> 0.1.1"},
+ {:xml_builder, "~> 2.1"},
{:elixir_xml_to_map, "~> 0.1"},
# Money related
diff --git a/mix.lock b/mix.lock
index e3441847..da4109e3 100644
--- a/mix.lock
+++ b/mix.lock
@@ -35,4 +35,4 @@
"timex": {:hex, :timex, "3.1.25", "6002dae5432f749d1c93e2cd103eb73cecb53e50d2c885349e8e4146fc96bd44", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
- "xml_builder": {:hex, :xml_builder, "0.1.2", "b48ab9ed0a24f43a6061e0c21deda88b966a2121af5c445d4fc550dd822e23dc", [:mix], [], "hexpm"}}
+ "xml_builder": {:hex, :xml_builder, "2.1.0", "c249d5339427c13cae11e9d9d0e8b40d25d228b9ecc54029f24017385e60280b", [:mix], [], "hexpm"}}
diff --git a/test/gateways/authorize_net_test.exs b/test/gateways/authorize_net_test.exs
index b4906339..d57e3522 100644
--- a/test/gateways/authorize_net_test.exs
+++ b/test/gateways/authorize_net_test.exs
@@ -125,7 +125,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
MockResponse.successful_purchase_response()
end do
assert {:ok, response} = ANet.purchase(@amount, @card, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
end
end
@@ -135,7 +134,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
MockResponse.bad_card_purchase_response()
end do
assert {:error, response} = ANet.purchase(@amount, @bad_card, @opts)
- assert response.params["ErrorResponse"]["messages"]["resultCode"] == "Error"
end
end
end
@@ -147,7 +145,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
MockResponse.successful_authorize_response()
end do
assert {:ok, response} = ANet.authorize(@amount, @card, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
end
end
@@ -157,7 +154,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
MockResponse.bad_card_purchase_response()
end do
assert {:error, response} = ANet.authorize(@amount, @bad_card, @opts)
- assert response.params["ErrorResponse"]["messages"]["resultCode"] == "Error"
end
end
end
@@ -169,7 +165,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
MockResponse.successful_capture_response()
end do
assert {:ok, response} = ANet.capture(@capture_id, @amount, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
end
end
@@ -177,7 +172,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
with_mock HTTPoison,
request: fn _method, _url, _body, _headers -> MockResponse.bad_id_capture() end do
assert {:error, response} = ANet.capture(@capture_invalid_id, @amount, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Error"
end
end
end
@@ -189,7 +183,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
MockResponse.successful_refund_response()
end do
assert {:ok, response} = ANet.refund(@amount, @refund_id, @opts_refund)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
end
end
@@ -197,7 +190,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
with_mock HTTPoison,
request: fn _method, _url, _body, _headers -> MockResponse.bad_card_refund() end do
assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund_bad_payment)
- assert response.params["ErrorResponse"]["messages"]["resultCode"] == "Error"
end
end
@@ -205,7 +197,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
with_mock HTTPoison,
request: fn _method, _url, _body, _headers -> MockResponse.debit_less_than_refund() end do
assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Error"
end
end
end
@@ -215,7 +206,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
with_mock HTTPoison,
request: fn _method, _url, _body, _headers -> MockResponse.successful_void() end do
assert {:ok, response} = ANet.void(@void_id, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Ok"
end
end
@@ -223,7 +213,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
with_mock HTTPoison,
request: fn _method, _url, _body, _headers -> MockResponse.void_non_existent_id() end do
assert {:error, response} = ANet.void(@void_invalid_id, @opts)
- assert response.params["createTransactionResponse"]["messages"]["resultCode"] == "Error"
end
end
end
@@ -233,7 +222,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
with_mock HTTPoison,
request: fn _method, _url, _body, _headers -> MockResponse.successful_store_response() end do
assert {:ok, response} = ANet.store(@card, @opts_store)
- assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
end
end
@@ -241,7 +229,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
with_mock HTTPoison,
request: fn _method, _url, _body, _headers -> MockResponse.successful_store_response() end do
assert {:ok, response} = ANet.store(@card, @opts_store_without_validation)
- assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
end
end
@@ -252,7 +239,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
end do
assert {:error, response} = ANet.store(@card, @opts_store_no_profile)
- assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] ==
"Error"
end
end
@@ -264,7 +250,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
end do
assert {:ok, response} = ANet.store(@card, @opts_customer_profile)
- assert response.params["createCustomerPaymentProfileResponse"]["messages"]["resultCode"] ==
"Ok"
end
end
@@ -273,7 +258,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
with_mock HTTPoison,
request: fn _method, _url, _body, _headers -> MockResponse.successful_store_response() end do
assert {:ok, response} = ANet.store(@card, @opts_customer_profile_args)
- assert response.params["createCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
end
end
end
@@ -285,7 +269,6 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
MockResponse.successful_unstore_response()
end do
assert {:ok, response} = ANet.unstore(@unstore_id, @opts)
- assert response.params["deleteCustomerProfileResponse"]["messages"]["resultCode"] == "Ok"
end
end
end
@@ -296,7 +279,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
MockResponse.netwok_error_non_existent_domain()
end do
assert {:error, response} = ANet.purchase(@amount, @card, @opts)
- assert response.message == "HTTPoison says 'nxdomain'"
+ assert response.message == "HTTPoison says 'nxdomain' [ID: nil]"
end
end
end
From e16fe7237d7ec58c83a8fa876cc4b314c10927ae Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Fri, 9 Feb 2018 17:48:04 +0530
Subject: [PATCH 29/60] Refactored `commit` and corrected avs, cvc result
Also added CAVV result filed to the response which is nil for
MaasterCards (as per ANet docs).
---
lib/gringotts/gateways/authorize_net.ex | 109 ++++++++++++++++++------
test/gateways/authorize_net_test.exs | 36 ++++----
2 files changed, 100 insertions(+), 45 deletions(-)
diff --git a/lib/gringotts/gateways/authorize_net.ex b/lib/gringotts/gateways/authorize_net.ex
index 1504eea2..b036ce52 100644
--- a/lib/gringotts/gateways/authorize_net.ex
+++ b/lib/gringotts/gateways/authorize_net.ex
@@ -47,11 +47,15 @@ defmodule Gringotts.Gateways.AuthorizeNet do
## Notes
- Authorize.Net supports [multiple currencies][currencies] however, multiple
- currencies in one account are not supported. A merchant would need multiple
- Authorize.Net accounts, one for each chosen currency.
-
- > Currently, `Gringotts` supports single Authorize.Net account configuration.
+ 1. Though Authorize.Net supports [multiple currencies][currencies] however,
+ multiple currencies in one account are not supported in _this_ module. A
+ merchant would need multiple Authorize.Net accounts, one for each chosen
+ currency.
+ 2. The responses of this module include a non-standard field: `:cavv_result`.
+ - `:cavv_result` is the "cardholder authentication verification response
+ code". In case of Mastercard transactions, this field will always be
+ `nil`. Please refer the "Response Format" section in the [docs][docs] for
+ more details.
[currencies]: https://community.developer.authorize.net/t5/The-Authorize-Net-Developer-Blog/Authorize-Net-UK-Europe-Update/ba-p/35957
@@ -108,7 +112,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
@test_url "https://apitest.authorize.net/xml/v1/request.api"
@production_url "https://api.authorize.net/xml/v1/request.api"
- @header [{"Content-Type", "text/xml"}]
+ @headers [{"Content-Type", "text/xml"}]
@transaction_type %{
purchase: "authCaptureTransaction",
@@ -181,8 +185,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
@spec purchase(Money.t(), CreditCard.t(), Keyword.t()) :: {:ok | :error, Response}
def purchase(amount, payment, opts) do
request_data = add_auth_purchase(amount, payment, opts, @transaction_type[:purchase])
- response_data = commit(:post, request_data, opts)
- respond(response_data)
+ commit(request_data, opts)
end
@doc """
@@ -242,8 +245,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
@spec authorize(Money.t(), CreditCard.t(), Keyword.t()) :: {:ok | :error, Response}
def authorize(amount, payment, opts) do
request_data = add_auth_purchase(amount, payment, opts, @transaction_type[:authorize])
- response_data = commit(:post, request_data, opts)
- respond(response_data)
+ commit(request_data, opts)
end
@doc """
@@ -285,8 +287,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
@spec capture(String.t(), Money.t(), Keyword.t()) :: {:ok | :error, Response}
def capture(id, amount, opts) do
request_data = normal_capture(amount, id, opts, @transaction_type[:capture])
- response_data = commit(:post, request_data, opts)
- respond(response_data)
+ commit(request_data, opts)
end
@doc """
@@ -315,8 +316,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
@spec refund(Money.t(), String.t(), Keyword.t()) :: {:ok | :error, Response}
def refund(amount, id, opts) do
request_data = normal_refund(amount, id, opts, @transaction_type[:refund])
- response_data = commit(:post, request_data, opts)
- respond(response_data)
+ commit(request_data, opts)
end
@doc """
@@ -341,8 +341,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
@spec void(String.t(), Keyword.t()) :: {:ok | :error, Response}
def void(id, opts) do
request_data = normal_void(id, opts, @transaction_type[:void])
- response_data = commit(:post, request_data, opts)
- respond(response_data)
+ commit(request_data, opts)
end
@doc """
@@ -401,8 +400,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
card |> create_customer_profile(opts) |> generate(format: :none)
end
- response_data = commit(:post, request_data, opts)
- respond(response_data)
+ commit(request_data, opts)
end
@doc """
@@ -420,15 +418,15 @@ defmodule Gringotts.Gateways.AuthorizeNet do
@spec unstore(String.t(), Keyword.t()) :: {:ok | :error, Response}
def unstore(customer_profile_id, opts) do
request_data = customer_profile_id |> delete_customer_profile(opts) |> generate(format: :none)
- response_data = commit(:post, request_data, opts)
- respond(response_data)
+ commit(request_data, opts)
end
# method to make the API request with params
- defp commit(method, payload, opts) do
- path = base_url(opts)
- headers = @header
- HTTPoison.request(method, path, payload, headers)
+ defp commit(payload, opts) do
+ opts
+ |> base_url()
+ |> HTTPoison.post(payload, @headers)
+ |> respond()
end
defp respond({:ok, %{body: body, status_code: 200}}), do: ResponseHandler.respond(body)
@@ -753,6 +751,50 @@ defmodule Gringotts.Gateways.AuthorizeNet do
"deleteCustomerProfileResponse"
]
+ @avs_code_translator %{
+ "A" => {"pass", "fail"}, #The street address matched, but the postal code did not.
+ "B" => {nil, nil}, # No address information was provided.
+ "E" => {"fail", nil}, # The AVS check returned an error.
+ "G" => {nil, nil}, # The card was issued by a bank outside the U.S. and does not support AVS.
+ "N" => {"fail", "fail"}, # Neither the street address nor postal code matched.
+ "P" => {nil, nil}, # AVS is not applicable for this transaction.
+ "R" => {nil, nil}, # Retry — AVS was unavailable or timed out.
+ "S" => {nil, nil}, # AVS is not supported by card issuer.
+ "U" => {nil, nil}, # Address information is unavailable.
+ "W" => {"fail", "pass"}, # The US ZIP+4 code matches, but the street address does not.
+ "X" => {"pass", "pass"}, # Both the street address and the US ZIP+4 code matched.
+ "Y" => {"pass", "pass"}, # The street address and postal code matched.
+ "Z" => {"fail", "pass"}, # The postal code matched, but the street address did not.
+ "" => {nil, nil}, # fallback in-case of absence
+ nil => {nil, nil} # fallback in-case of absence
+ }
+
+ @cvc_code_translator %{
+ "M" => "CVV matched.",
+ "N" => "CVV did not match.",
+ "P" => "CVV was not processed.",
+ "S" => "CVV should have been present but was not indicated.",
+ "U" => "The issuer was unable to process the CVV check.",
+ nil => nil # fallback in-case of absence
+ }
+
+ @cavv_code_translator %{
+ "" => "CAVV not validated.",
+ "0" => "CAVV was not validated because erroneous data was submitted.",
+ "1" => "CAVV failed validation.",
+ "2" => "CAVV passed validation.",
+ "3" => "CAVV validation could not be performed; issuer attempt incomplete.",
+ "4" => "CAVV validation could not be performed; issuer system error.",
+ "5" => "Reserved for future use.",
+ "6" => "Reserved for future use.",
+ "7" => "CAVV failed validation, but the issuer is available. Valid for U.S.-issued card submitted to non-U.S acquirer.",
+ "8" => "CAVV passed validation and the issuer is available. Valid for U.S.-issued card submitted to non-U.S. acquirer.",
+ "9" => "CAVV failed validation and the issuer is unavailable. Valid for U.S.-issued card submitted to non-U.S acquirer.",
+ "A" => "CAVV passed validation but the issuer unavailable. Valid for U.S.-issued card submitted to non-U.S acquirer.",
+ "B" => "CAVV passed validation, information only, no liability shift.",
+ nil => nil # fallback in-case of absence
+ }
+
def respond(body) do
response_map = XmlToMap.naive_map(body)
case extract_gateway_response(response_map) do
@@ -792,7 +834,8 @@ defmodule Gringotts.Gateways.AuthorizeNet do
id = result["transactionResponse"]["transId"]
message = result["messages"]["message"]["text"]
avs_result = result["transactionResponse"]["avsResultCode"]
- cvc_result = result["transactionResponse"]["cavvResultCode"]
+ cvc_result = result["transactionResponse"]["cvvResultCode"]
+ cavv_result = result["transactionResponse"]["cavvResultCode"]
gateway_code = result["messages"]["message"]["code"]
base_response
@@ -801,6 +844,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
|> set_gateway_code(gateway_code)
|> set_avs_result(avs_result)
|> set_cvc_result(cvc_result)
+ |> set_cavv_result(cavv_result)
end
def parse_gateway_error(result, base_response) do
@@ -826,7 +870,18 @@ defmodule Gringotts.Gateways.AuthorizeNet do
defp set_gateway_code(response, code), do: %{response | gateway_code: code}
defp set_reason(response, body), do: %{response | reason: body}
- defp set_avs_result(response, result), do: %{response | avs_result: result}
- defp set_cvc_result(response, result), do: %{response | cvc_result: result}
+ defp set_avs_result(response, avs_code) do
+ {street, zip_code} = @avs_code_translator[avs_code]
+ %{response | avs_result: %{street: street, zip_code: zip_code}}
+ end
+
+ defp set_cvc_result(response, cvv_code) do
+ %{response | cvc_result: @cvc_code_translator[cvv_code]}
+ end
+
+ defp set_cavv_result(response, cavv_code) do
+ Map.put(response, :cavv_result, @cavv_code_translator[cavv_code])
+ end
+
end
end
diff --git a/test/gateways/authorize_net_test.exs b/test/gateways/authorize_net_test.exs
index d57e3522..2ce52278 100644
--- a/test/gateways/authorize_net_test.exs
+++ b/test/gateways/authorize_net_test.exs
@@ -121,7 +121,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "purchase" do
test "successful response with right params" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers ->
+ post: fn _url, _body, _headers ->
MockResponse.successful_purchase_response()
end do
assert {:ok, response} = ANet.purchase(@amount, @card, @opts)
@@ -130,7 +130,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
test "with bad card" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers ->
+ post: fn _url, _body, _headers ->
MockResponse.bad_card_purchase_response()
end do
assert {:error, response} = ANet.purchase(@amount, @bad_card, @opts)
@@ -141,7 +141,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "authorize" do
test "successful response with right params" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers ->
+ post: fn _url, _body, _headers ->
MockResponse.successful_authorize_response()
end do
assert {:ok, response} = ANet.authorize(@amount, @card, @opts)
@@ -150,7 +150,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
test "with bad card" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers ->
+ post: fn _url, _body, _headers ->
MockResponse.bad_card_purchase_response()
end do
assert {:error, response} = ANet.authorize(@amount, @bad_card, @opts)
@@ -161,7 +161,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "capture" do
test "successful response with right params" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers ->
+ post: fn _url, _body, _headers ->
MockResponse.successful_capture_response()
end do
assert {:ok, response} = ANet.capture(@capture_id, @amount, @opts)
@@ -170,7 +170,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
test "with bad transaction id" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers -> MockResponse.bad_id_capture() end do
+ post: fn _url, _body, _headers -> MockResponse.bad_id_capture() end do
assert {:error, response} = ANet.capture(@capture_invalid_id, @amount, @opts)
end
end
@@ -179,7 +179,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "refund" do
test "successful response with right params" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers ->
+ post: fn _url, _body, _headers ->
MockResponse.successful_refund_response()
end do
assert {:ok, response} = ANet.refund(@amount, @refund_id, @opts_refund)
@@ -188,14 +188,14 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
test "bad payment params" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers -> MockResponse.bad_card_refund() end do
+ post: fn _url, _body, _headers -> MockResponse.bad_card_refund() end do
assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund_bad_payment)
end
end
test "debit less than refund amount" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers -> MockResponse.debit_less_than_refund() end do
+ post: fn _url, _body, _headers -> MockResponse.debit_less_than_refund() end do
assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund)
end
end
@@ -204,14 +204,14 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "void" do
test "successful response with right params" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers -> MockResponse.successful_void() end do
+ post: fn _url, _body, _headers -> MockResponse.successful_void() end do
assert {:ok, response} = ANet.void(@void_id, @opts)
end
end
test "with bad transaction id" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers -> MockResponse.void_non_existent_id() end do
+ post: fn _url, _body, _headers -> MockResponse.void_non_existent_id() end do
assert {:error, response} = ANet.void(@void_invalid_id, @opts)
end
end
@@ -220,21 +220,21 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "store" do
test "successful response with right params" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers -> MockResponse.successful_store_response() end do
+ post: fn _url, _body, _headers -> MockResponse.successful_store_response() end do
assert {:ok, response} = ANet.store(@card, @opts_store)
end
end
test "successful response without validation and customer type" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers -> MockResponse.successful_store_response() end do
+ post: fn _url, _body, _headers -> MockResponse.successful_store_response() end do
assert {:ok, response} = ANet.store(@card, @opts_store_without_validation)
end
end
test "without any profile" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers ->
+ post: fn _url, _body, _headers ->
MockResponse.store_without_profile_fields()
end do
assert {:error, response} = ANet.store(@card, @opts_store_no_profile)
@@ -245,7 +245,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
test "with customer profile id" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers ->
+ post: fn _url, _body, _headers ->
MockResponse.customer_payment_profile_success_response()
end do
assert {:ok, response} = ANet.store(@card, @opts_customer_profile)
@@ -256,7 +256,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
test "successful response without valiadtion mode and customer type" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers -> MockResponse.successful_store_response() end do
+ post: fn _url, _body, _headers -> MockResponse.successful_store_response() end do
assert {:ok, response} = ANet.store(@card, @opts_customer_profile_args)
end
end
@@ -265,7 +265,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "unstore" do
test "successful response with right params" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers ->
+ post: fn _url, _body, _headers ->
MockResponse.successful_unstore_response()
end do
assert {:ok, response} = ANet.unstore(@unstore_id, @opts)
@@ -275,7 +275,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
test "network error type non existent domain" do
with_mock HTTPoison,
- request: fn _method, _url, _body, _headers ->
+ post: fn _url, _body, _headers ->
MockResponse.netwok_error_non_existent_domain()
end do
assert {:error, response} = ANet.purchase(@amount, @card, @opts)
From 6eef8ef0bdb10ba78fb6e30f61220da7b889c09b Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Fri, 9 Feb 2018 21:59:32 +0530
Subject: [PATCH 30/60] Correct a few doc examples
---
lib/gringotts/gateways/authorize_net.ex | 39 ++++++++++++-------------
1 file changed, 19 insertions(+), 20 deletions(-)
diff --git a/lib/gringotts/gateways/authorize_net.ex b/lib/gringotts/gateways/authorize_net.ex
index b036ce52..14950eb9 100644
--- a/lib/gringotts/gateways/authorize_net.ex
+++ b/lib/gringotts/gateways/authorize_net.ex
@@ -94,7 +94,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
```
iex> alias Gringotts.{Response, CreditCard, Gateways.AuthorizeNet}
- iex> amount = %{value: Decimal.new(20.0), currency: "USD"}
+ iex> amount = Money.new(20, :USD}
iex> card = %CreditCard{number: "5424000000000015", year: 2099, month: 12, verification_code: "999"}
```
@@ -136,8 +136,8 @@ defmodule Gringotts.Gateways.AuthorizeNet do
Charges a credit `card` for the specified `amount`. It performs `authorize`
and `capture` at the [same time][auth-cap-same-time].
- Authorize.Net returns `transId` (available in the `Response.authorization`
- field) which can be used to:
+ Authorize.Net returns `transId` (available in the `Response.id` field) which
+ can be used to:
* `refund/3` a settled transaction.
* `void/2` a transaction.
@@ -170,7 +170,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
]
## Example
- iex> amount = %{value: Decimal.new(20.0), currency: "USD"}
+ iex> amount = Money.new(20, :USD}
iex> opts = [
ref_id: "123456",
order: %{invoice_number: "INV-12345", description: "Product Description"},
@@ -182,7 +182,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
iex> card = %CreditCard{number: "5424000000000015", year: 2099, month: 12, verification_code: "999"}
iex> result = Gringotts.purchase(Gringotts.Gateways.AuthorizeNet, amount, card, opts)
"""
- @spec purchase(Money.t(), CreditCard.t(), Keyword.t()) :: {:ok | :error, Response}
+ @spec purchase(Money.t(), CreditCard.t(), Keyword.t()) :: {:ok | :error, Response.t()}
def purchase(amount, payment, opts) do
request_data = add_auth_purchase(amount, payment, opts, @transaction_type[:purchase])
commit(request_data, opts)
@@ -197,8 +197,8 @@ defmodule Gringotts.Gateways.AuthorizeNet do
To transfer the funds to merchant's account follow this up with a `capture/3`.
- Authorize.Net returns a `transId` (available in the `Response.authorization`
- field) which can be used for:
+ Authorize.Net returns a `transId` (available in the `Response.id` field) which
+ can be used for:
* `capture/3` an authorized transaction.
* `void/2` a transaction.
@@ -230,7 +230,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
## Example
- iex> amount = %{value: Decimal.new(20.0), currency: "USD"}
+ iex> amount = Money.new(20, :USD}
iex> opts = [
ref_id: "123456",
order: %{invoice_number: "INV-12345", description: "Product Description"},
@@ -242,7 +242,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
iex> card = %CreditCard{number: "5424000000000015", year: 2099, month: 12, verification_code: "999"}
iex> result = Gringotts.authorize(Gringotts.Gateways.AuthorizeNet, amount, card, opts)
"""
- @spec authorize(Money.t(), CreditCard.t(), Keyword.t()) :: {:ok | :error, Response}
+ @spec authorize(Money.t(), CreditCard.t(), Keyword.t()) :: {:ok | :error, Response.t()}
def authorize(amount, payment, opts) do
request_data = add_auth_purchase(amount, payment, opts, @transaction_type[:authorize])
commit(request_data, opts)
@@ -254,8 +254,8 @@ defmodule Gringotts.Gateways.AuthorizeNet do
`amount` is transferred to the merchant account by Authorize.Net when it is smaller or
equal to the amount used in the pre-authorization referenced by `id`.
- Authorize.Net returns a `transId` (available in the `Response.authorization`
- field) which can be used to:
+ Authorize.Net returns a `transId` (available in the `Response.id` field) which
+ can be used to:
* `refund/3` a settled transaction.
* `void/2` a transaction.
@@ -280,11 +280,11 @@ defmodule Gringotts.Gateways.AuthorizeNet do
iex> opts = [
ref_id: "123456"
]
- iex> amount = %{value: Decimal.new(20.0), currency: "USD"}
+ iex> amount = Money.new(20, :USD}
iex> id = "123456"
iex> result = Gringotts.capture(Gringotts.Gateways.AuthorizeNet, id, amount, opts)
"""
- @spec capture(String.t(), Money.t(), Keyword.t()) :: {:ok | :error, Response}
+ @spec capture(String.t(), Money.t(), Keyword.t()) :: {:ok | :error, Response.t()}
def capture(id, amount, opts) do
request_data = normal_capture(amount, id, opts, @transaction_type[:capture])
commit(request_data, opts)
@@ -310,10 +310,10 @@ defmodule Gringotts.Gateways.AuthorizeNet do
ref_id: "123456"
]
iex> id = "123456"
- iex> amount = %{value: Decimal.new(20.0), currency: "USD"}
+ iex> amount = Money.new(20, :USD}
iex> result = Gringotts.refund(Gringotts.Gateways.AuthorizeNet, amount, id, opts)
"""
- @spec refund(Money.t(), String.t(), Keyword.t()) :: {:ok | :error, Response}
+ @spec refund(Money.t(), String.t(), Keyword.t()) :: {:ok | :error, Response.t()}
def refund(amount, id, opts) do
request_data = normal_refund(amount, id, opts, @transaction_type[:refund])
commit(request_data, opts)
@@ -338,7 +338,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
iex> id = "123456"
iex> result = Gringotts.void(Gringotts.Gateways.AuthorizeNet, id, opts)
"""
- @spec void(String.t(), Keyword.t()) :: {:ok | :error, Response}
+ @spec void(String.t(), Keyword.t()) :: {:ok | :error, Response.t()}
def void(id, opts) do
request_data = normal_void(id, opts, @transaction_type[:void])
commit(request_data, opts)
@@ -391,7 +391,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
iex> card = %CreditCard{number: "5424000000000015", year: 2099, month: 12, verification_code: "999"}
iex> result = Gringotts.store(Gringotts.Gateways.AuthorizeNet, card, opts)
"""
- @spec store(CreditCard.t(), Keyword.t()) :: {:ok | :error, Response}
+ @spec store(CreditCard.t(), Keyword.t()) :: {:ok | :error, Response.t()}
def store(card, opts) do
request_data =
if opts[:customer_profile_id] do
@@ -411,11 +411,10 @@ defmodule Gringotts.Gateways.AuthorizeNet do
## Example
iex> id = "123456"
- iex> opts = []
- iex> result = Gringotts.store(Gringotts.Gateways.AuthorizeNet, id, opts)
+ iex> result = Gringotts.store(Gringotts.Gateways.AuthorizeNet, id)
"""
- @spec unstore(String.t(), Keyword.t()) :: {:ok | :error, Response}
+ @spec unstore(String.t(), Keyword.t()) :: {:ok | :error, Response.t()}
def unstore(customer_profile_id, opts) do
request_data = customer_profile_id |> delete_customer_profile(opts) |> generate(format: :none)
commit(request_data, opts)
From e3c86cef2e8a34db7635d2466a949771fab5f272 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Thu, 15 Mar 2018 20:58:47 +0530
Subject: [PATCH 31/60] Adds changelog, contributing guide and improves mix
task (#117)
* Fix Adapter moduledoc and bogus gateway
* Fixes #24
* bogus test also uses Money protocol now
* Changed validate_config docs
* Improved mix task docs (filename)
* Better module name suggestion
* Now, Filename can be specified on the CLI with the `-f` flag
* Added changelog and contributing guide.
* Also reworded README slightly
* Correct "amount" in examples to ex_money
* Replaced example bindings with links to `.iex.exs`
* Removed unused params from functions.
* Fix call to `downcase`
---
CHANGELOG.md | 52 ++++++++++++++
CONTRIBUTING.md | 91 +++++++++++++++++++++++++
README.md | 58 ++++++++++++----
lib/gringotts/adapter.ex | 49 ++++++++++---
lib/gringotts/gateways/authorize_net.ex | 32 ++++-----
lib/gringotts/gateways/bogus.ex | 33 ++++-----
lib/gringotts/gateways/cams.ex | 37 +++++-----
lib/gringotts/gateways/monei.ex | 63 +++--------------
lib/gringotts/gateways/trexle.ex | 41 +++--------
lib/mix/new.ex | 61 +++++++++++------
mix.exs | 6 +-
mix.lock | 12 ++--
templates/mock_response.eex | 2 +-
templates/test.eex | 4 +-
test/gateways/bogus_test.exs | 13 ++--
15 files changed, 358 insertions(+), 196 deletions(-)
create mode 100644 CHANGELOG.md
create mode 100644 CONTRIBUTING.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..5a1f16ad
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,52 @@
+# [`v1.1.0-alpha`][tag-1_1_0_alpha]
+
+## Added
+
+* [`ISS`][iss#80] [`PR`][pr#78]
+Add a `Mix` task that generates a barebones gateway implementation and test suite.
+
+## Changed
+
+* [`ISS`][iss#62] [`PR`][pr#71] [`PR`][pr#86]
+Deprecate use of `floats` for money amounts, introduce the `Gringotts.Money` protocol.
+
+[iss#62]: https://github.com/aviabird/gringotts/issues/62
+[iss#80]: https://github.com/aviabird/gringotts/issues/80
+
+[pr#71]: https://github.com/aviabird/gringotts/pulls/71
+[pr#78]:https://github.com/aviabird/gringotts/pulls/78
+[pr#86]:https://github.com/aviabird/gringotts/pulls/86
+
+# [`v1.0.2`][tag-1_0_2]
+
+## Added
+
+* New Gateway: **Trexle**
+
+## Changed
+
+* Reduced arity of public API calls by 1
+ - No need to pass the name of the `worker` as argument.
+
+# [`v1.0.1`][tag-1_0_1]
+
+## Added
+
+* Improved documentation - made consistent accross gateways
+* Improved test coverage
+
+# [`v1.0.0`][tag-1_0_0]
+
+* Initial public API release.
+* Single worker architecture, config fetched from `config.exs`
+* Supported Gateways:
+ - Stripe
+ - MONEI
+ - Paymill
+ - WireCard
+ - CAMS
+
+[tag-1_1_0_alpha]: https://github.com/aviabird/gringotts/releases/tag/v1.1.0-alpha
+[tag-1_0_2]: https://github.com/aviabird/gringotts/releases/tag/v1.0.2
+[tag-1_0_1]: https://github.com/aviabird/gringotts/releases/tag/v1.0.1
+[tag-1_0_0]: https://github.com/aviabird/gringotts/releases/tag/v1.0.0
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..6eeb7033
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,91 @@
+# Contributing to [`gringotts`][gringotts]
+
+There are many ways to contribute to `gringotts`,
+
+1. [Integrate a new Payment Gateway][wiki-new-gateway].
+2. Expanding the feature coverage of (partially) supported gateways.
+3. Moving forward on the [roadmap][roadmap] or on tasks being tracked in the
+ [milestones][milestones].
+
+We manage our development using [milestones][milestones] and issues so if you're
+a first time contributor, look out for the [`good first issue`][first-issues]
+and the [`hotlist: community-help`][ch-issues] labels on the [issues][issues]
+page.
+
+The docs are hosted on [hexdocs.pm][hexdocs] and are updated for each
+release. **You must build the docs locally using `mix docs` to get the bleeding
+edge developer docs.**
+
+The article on [Gringott's Architecture][wiki-arch] explains how API calls are
+processed.
+
+:exclamation: ***Please base your work on the `dev` branch.***
+
+[roadmap]: https://github.com/aviabird/gringotts/wiki/Roadmap
+[wiki-arch]: https://github.com/aviabird/gringotts/wiki/Architecture
+
+### PR submission checklist
+
+Each PR should introduce a *focussed set of changes*, and ideally not span over
+unrelated modules.
+
+* [ ] Run the edited files through [credo][credo] and the Elixir
+ [formatter][hashrocket-formatter] (new in `v1.6`).
+* [ ] Check the test coverage by running `mix coveralls`. 100% coverage is not
+ strictly required.
+* [ ] If the PR introduces a new Gateway or just Gateway specific changes,
+ please format the title like so,\
+ `[] `
+
+[gringotts]: https://github.com/aviabird/gringotts
+[milestones]: https://github.com/aviabird/gringotts/milestones
+[issues]: https://github.com/aviabird/gringotts/issues
+[first-issues]: https://github.com/aviabird/gringotts/issues?q=is%3Aissue+is%3Aopen+label%3A"good+first+issue"
+[ch-issues]: https://github.com/aviabird/gringotts/issues?q=is%3Aissue+is%3Aopen+label%3A"hotfix%3A+community-help"
+[hexdocs]: https://hexdocs.pm/gringotts
+[credo]: https://github.com/rrrene/credo
+[hashrocket-formatter]: https://hashrocket.com/blog/posts/format-your-elixir-code-now
+
+# Style Guidelines
+
+We use [`credo`][credo] and the elixir formatter for consistent style, so please
+use them!
+
+## General Rules
+
+* Keep line length below 120 characters.
+* Complex anonymous functions should be extracted into named functions.
+* One line functions, should only take up one line!
+* Pipes are great, but don't use them, if they are less readable than brackets
+ then drop the pipe!
+
+## Writing documentation
+
+All our docs are inline and built using [`ExDocs`][exdocs]. Please take a look
+at how the docs are structured for the [MONEI gateway][src-monei] for
+inspiration.
+
+[exdocs]: https://github.com/elixir-lang/ex_doc
+[src-monei]: https://github.com/aviabird/gringotts/blob/dev/lib/gringotts/gateways/monei.ex
+
+## Writing test cases
+
+> This is WIP.
+
+`gringotts` has mock and integration tests. We have currently used
+[`bypass`][bypass] and [`mock`][mock] for mock tests, but we don't recommed
+using `mock` as it constrains tests to run serially. Use [`mox`][mox] instead.\
+Take a look at [MONEI's mock tests][src-monei-tests] for inspiration.
+
+--------------------------------------------------------------------------------
+
+> **Where to next?**
+> Wanna add a new gateway? Head to our [guide][wiki-new-gateway] for that.
+
+[wiki-new-gateway]: https://github.com/aviabird/gringotts/wiki/Adding-a-new-Gateway
+[bypass]: https://github.com/pspdfkit-labs/bypass
+[mock]: https://github.com/jjh42/mock
+[mox]: https://github.com/plataformatec/mox
+[src-monei-tests]: https://github.com/aviabird/gringotts/blob/dev/test/gateways/monei_test.exs
+[gringotts]: https://github.com/aviabird/gringotts
+[docs]: https://hexdocs.pm/gringotts/Gringotts.html
diff --git a/README.md b/README.md
index 6b71deb4..ce98c205 100644
--- a/README.md
+++ b/README.md
@@ -5,24 +5,24 @@
- Gringotts is a payment processing library in Elixir integrating various payment gateways, this draws motivation for shopify's activemerchant gem. Checkout the Demo here.
+ Gringotts is a payment processing library in Elixir integrating various payment gateways, drawing motivation for Shopify's activemerchant
gem. Checkout the Demo here.
-A simple and unified API to access dozens of different payment
-gateways with very different internal APIs is what Gringotts has to offer you.
+Gringotts offers a **simple and unified API** to access dozens of different payment
+gateways with very different APIs, response schemas, documentation and jargon.
## Installation
-### From hex.pm
+### From [`hex.pm`][hexpm]
-Make the following changes to the `mix.exs` file.
-
-Add gringotts to the list of dependencies.
+Add `gringotts` to the list of dependencies of your application.
```elixir
+# your mix.exs
+
def deps do
[
{:gringotts, "~> 1.0"},
@@ -35,23 +35,31 @@ end
## Usage
-This simple example demonstrates how a purchase can be made using a person's credit card details.
+This simple example demonstrates how a `purchase` can be made using a sample
+credit card using the [MONEI][monei] gateway.
-Add configs in `config/config.exs` file.
+One must "register" their account with `gringotts` ie, put all the
+authentication details in the Application config. Usually via
+`config/config.exs`
```elixir
+# config/config.exs
+
config :gringotts, Gringotts.Gateways.Monei,
userId: "your_secret_user_id",
password: "your_secret_password",
entityId: "your_secret_channel_id"
```
-Copy and paste this code in your module
+Copy and paste this code in a module or an `IEx` session
```elixir
alias Gringotts.Gateways.Monei
alias Gringotts.{CreditCard}
+# a fake sample card that will work now because the Gateway is by default
+# in "test" mode.
+
card = %CreditCard{
first_name: "Harry",
last_name: "Potter",
@@ -61,9 +69,10 @@ card = %CreditCard{
brand: "VISA"
}
+# a sum of $42
amount = Money.new(42, :USD)
-case Gringotts.purchase(Monei, amount, card, opts) do
+case Gringotts.purchase(Monei, amount, card) do
{:ok, %{id: id}} ->
IO.puts("Payment authorized, reference token: '#{id}'")
@@ -72,6 +81,9 @@ case Gringotts.purchase(Monei, amount, card, opts) do
end
```
+[hexpm]: https://hex.pm/packages/gringotts
+[monei]: http://www.monei.net
+
## Supported Gateways
| Gateway | Supported countries |
@@ -95,9 +107,29 @@ end
## Road Map
-- Support more gateways on an on-going basis.
-- Each gateway request is hosted in a worker process and supervised.
+Apart from supporting more and more gateways, we also keep a somewhat detailed
+plan for the future on our [wiki][roadmap].
+
+## FAQ
+
+#### 1. What's with the name? "Gringotts"?
+
+Gringotts has a nice ring to it. Also [this][reason].
+
+#### 2. What is the worker doing in the middle?
+
+We wanted to "supervise" our payments, and power utilities to process recurring
+payments, subscriptions with it. But yes, as of now, it is a bottle neck and
+unnecessary.
+
+It's slated to be removed in [`v2.0.0`][milestone-2_0_0_alpha] and any supervised / async /
+parallel work can be explicitly managed via native elixir constructs.
+
+[milestone-2_0_0_alpha]: https://github.com/aviabird/gringotts/milestone/3
+[reason]: http://harrypotter.wikia.com/wiki/Gringotts
## License
MIT
+
+[roadmap]: https://github.com/aviabird/gringotts/wiki/Roadmap
diff --git a/lib/gringotts/adapter.ex b/lib/gringotts/adapter.ex
index 8d2a24f8..978cd1d1 100644
--- a/lib/gringotts/adapter.ex
+++ b/lib/gringotts/adapter.ex
@@ -1,20 +1,53 @@
defmodule Gringotts.Adapter do
- @moduledoc ~S"""
- Adapter module is currently holding the validation part.
+ @moduledoc """
+ Validates the "required" configuration.
- This modules is being `used` by all the payment gateways and raises a run-time
- error for the missing configurations which are passed by the gateways to
- `validate_config` method.
+ All gateway modules must `use` this module, which provides a run-time
+ configuration validator.
- Raises an exception `ArgumentError` if the config is not as per the `@required_config`
- """
+ Gringotts picks up the merchant's Gateway authentication secrets from the
+ Application config. The configuration validator can be customized by providing
+ a list of `required_config` keys. The validator will check if these keys are
+ available at run-time, before each call to the Gateway.
+
+ ## Example
+
+ Say a merchant must provide his `secret_user_name` and `secret_password` to
+ some Gateway `XYZ`. Then, `Gringotts` expects that the `GatewayXYZ` module
+ would use `Adapter` in the following manner:
+
+ ```
+ defmodule Gringotts.Gateways.GatewayXYZ do
+
+ use Gringotts.Adapter, required_config: [:secret_user_name, :secret_password]
+ use Gringotts.Gateways.Base
+
+ # the rest of the implentation
+ end
+ ```
+ And, the merchant woud provide these secrets in the Application config,
+ possibly via `config/config.exs` like so,
+ ```
+ # config/config.exs
+
+ config :gringotts, Gringotts.Gateways.GatewayXYZ,
+ adapter: Gringotts.Gateways.GatewayXYZ,
+ secret_user_name: "some_really_secret_user_name",
+ secret_password: "some_really_secret_password"
+
+ ```
+ """
+
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
@required_config opts[:required_config] || []
@doc """
- Validates the config dynamically depending on what is the value of `required_config`
+ Catches gateway configuration errors.
+
+ Raises a run-time `ArgumentError` if any of the `required_config` values
+ is not available or missing from the Application config.
"""
def validate_config(config) do
missing_keys = Enum.reduce(@required_config, [], fn(key, missing_keys) ->
diff --git a/lib/gringotts/gateways/authorize_net.ex b/lib/gringotts/gateways/authorize_net.ex
index 14950eb9..59281314 100644
--- a/lib/gringotts/gateways/authorize_net.ex
+++ b/lib/gringotts/gateways/authorize_net.ex
@@ -89,18 +89,16 @@ defmodule Gringotts.Gateways.AuthorizeNet do
that gives you a pre-configured sample app ready-to-go.
+ You could use the same config or update it the with your "secrets"
[above](#module-configuring-your-authorizenet-account-at-gringotts).
- 2. Run an `iex` session with `iex -S mix` and add some variable bindings and
- aliases to it (to save some time):
+
+ 2. To save a lot of time, create a [`.iex.exs`][iex-docs] file as shown in
+ [this gist][authorize_net.iex.exs] to introduce a set of handy bindings and
+ aliases.
- ```
- iex> alias Gringotts.{Response, CreditCard, Gateways.AuthorizeNet}
- iex> amount = Money.new(20, :USD}
- iex> card = %CreditCard{number: "5424000000000015", year: 2099, month: 12, verification_code: "999"}
- ```
-
- We'll be using these in the examples below.
+ We'll be using these bindings in the examples below.
[example-repo]: https://github.com/aviabird/gringotts_example
+ [iex-docs]: https://hexdocs.pm/iex/IEx.html#module-the-iex-exs-file
+ [authorize_net.iex.exs]: https://gist.github.com/oyeb/b1030058bda1fa9a3d81f1cf30723695
[gs]: https://github.com/aviabird/gringotts/wiki
"""
@@ -170,7 +168,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
]
## Example
- iex> amount = Money.new(20, :USD}
+ iex> amount = Money.new(20, :USD)
iex> opts = [
ref_id: "123456",
order: %{invoice_number: "INV-12345", description: "Product Description"},
@@ -230,7 +228,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
## Example
- iex> amount = Money.new(20, :USD}
+ iex> amount = Money.new(20, :USD)
iex> opts = [
ref_id: "123456",
order: %{invoice_number: "INV-12345", description: "Product Description"},
@@ -280,7 +278,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
iex> opts = [
ref_id: "123456"
]
- iex> amount = Money.new(20, :USD}
+ iex> amount = Money.new(20, :USD)
iex> id = "123456"
iex> result = Gringotts.capture(Gringotts.Gateways.AuthorizeNet, id, amount, opts)
"""
@@ -310,7 +308,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
ref_id: "123456"
]
iex> id = "123456"
- iex> amount = Money.new(20, :USD}
+ iex> amount = Money.new(20, :USD)
iex> result = Gringotts.refund(Gringotts.Gateways.AuthorizeNet, amount, id, opts)
"""
@spec refund(Money.t(), String.t(), Keyword.t()) :: {:ok | :error, Response.t()}
@@ -465,7 +463,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
|> element(%{xmlns: @aut_net_namespace}, [
add_merchant_auth(opts[:config]),
add_order_id(opts),
- add_capture_transaction_request(amount, id, transaction_type, opts)
+ add_capture_transaction_request(amount, id, transaction_type)
])
|> generate(format: :none)
end
@@ -561,7 +559,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
add_transaction_type(transaction_type),
add_amount(amount),
add_payment_source(payment),
- add_invoice(transaction_type, opts),
+ add_invoice(opts),
add_tax_fields(opts),
add_duty_fields(opts),
add_shipping_fields(opts),
@@ -570,7 +568,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
])
end
- defp add_capture_transaction_request(amount, id, transaction_type, opts) do
+ defp add_capture_transaction_request(amount, id, transaction_type) do
element(:transactionRequest, [
add_transaction_type(transaction_type),
add_amount(amount),
@@ -626,7 +624,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
])
end
- defp add_invoice(transactionType, opts) do
+ defp add_invoice(opts) do
element([
element(:order, [
element(:invoiceNumber, opts[:order][:invoice_number]),
diff --git a/lib/gringotts/gateways/bogus.ex b/lib/gringotts/gateways/bogus.ex
index de903744..d6bdaa02 100644
--- a/lib/gringotts/gateways/bogus.ex
+++ b/lib/gringotts/gateways/bogus.ex
@@ -1,4 +1,6 @@
defmodule Gringotts.Gateways.Bogus do
+ @moduledoc false
+
use Gringotts.Gateways.Base
alias Gringotts.{
@@ -6,36 +8,29 @@ defmodule Gringotts.Gateways.Bogus do
Response
}
+ @some_authorization_id "14a62fff80f24a25f775eeb33624bbb3"
+
def authorize(_amount, _card_or_id, _opts),
do: success()
def purchase(_amount, _card_or_id, _opts),
do: success()
- def capture(id, amount, _opts),
- do: success(id)
+ def capture(_id, _amount, _opts),
+ do: success()
- def void(id, _opts),
- do: success(id)
+ def void(_id, _opts),
+ do: success()
- def refund(_amount, id, _opts),
- do: success(id)
+ def refund(_amount, _id, _opts),
+ do: success()
- def store(_card = %CreditCard{}, _opts),
+ def store(%CreditCard{} = _card, _opts),
do: success()
- def unstore(customer_id, _opts),
- do: success(customer_id)
+ def unstore(_customer_id, _opts),
+ do: success()
defp success,
- do: {:ok, Response.success(id: random_string())}
-
- defp success(id),
- do: {:ok, Response.success(id: id)}
-
- defp random_string(length \\ 10),
- do: 1..length |> Enum.map(&random_char/1) |> Enum.join
-
- defp random_char(_),
- do: to_string(:rand.uniform(9))
+ do: {:ok, Response.success(id: @some_authorization_id)}
end
diff --git a/lib/gringotts/gateways/cams.ex b/lib/gringotts/gateways/cams.ex
index 1787e947..0cb0c4e8 100644
--- a/lib/gringotts/gateways/cams.ex
+++ b/lib/gringotts/gateways/cams.ex
@@ -38,13 +38,13 @@ defmodule Gringotts.Gateways.Cams do
this is important to you.
[issues]: https://github.com/aviabird/gringotts/issues/new
-
+
### Schema
* `billing_address` is a `map` from `atoms` to `String.t`, and can include any
of the keys from:
`:name, :address1, :address2, :company, :city, :state, :zip, :country, :phone, :fax]`
-
+
## Registering your CAMS account at `Gringotts`
| Config parameter | CAMS secret |
@@ -81,26 +81,21 @@ defmodule Gringotts.Gateways.Cams do
you get after [registering with
CAMS](#module-registering-your-cams-account-at-gringotts).
- 2. Run an `iex` session with `iex -S mix` and add some variable bindings and
- aliases to it (to save some time):
- ```
- iex> alias Gringotts.{Response, CreditCard, Gateways.Cams}
- iex> card = %CreditCard{first_name: "Harry",
- last_name: "Potter",
- number: "4111111111111111",
- year: 2099,
- month: 12,
- verification_code: "999",
- brand: "VISA"}
- iex> money = %{value: Decimal.new(20), currency: "USD"}
- ```
- We'll be using these in the examples below.
+ 2. To save a lot of time, create a [`.iex.exs`][iex-docs] file as shown in
+ [this gist][cams.iex.exs] to introduce a set of handy bindings and
+ aliases.
+
+ We'll be using these bindings in the examples below.
+
+ [example-repo]: https://github.com/aviabird/gringotts_example
+ [iex-docs]: https://hexdocs.pm/iex/IEx.html#module-the-iex-exs-file
+ [cams.iex.exs]: https://gist.github.com/oyeb/9a299df95cc13a87324e321faca5c9b8
## Integrating with phoenix
Refer the [GringottsPay][gpay-heroku-cams] website for an example of how to
integrate CAMS with phoenix. The source is available [here][gpay-repo].
-
+
[gpay-repo]: https://github.com/aviabird/gringotts_payment
[gpay-heroku-cams]: http://gringottspay.herokuapp.com/cams
@@ -164,7 +159,7 @@ defmodule Gringotts.Gateways.Cams do
month: 12,
verification_code: "999",
brand: "VISA"}
- iex> money = %{value: Decimal.new(20), currency: "USD"}
+ iex> money = Money.new(20, :USD)
iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Cams, money, card)
```
"""
@@ -209,7 +204,7 @@ defmodule Gringotts.Gateways.Cams do
month: 12,
verification_code: "999",
brand: "VISA"}
- iex> money = %{value: Decimal.new(10), currency: "USD"}
+ iex> money = Money.new(10, :USD)
iex> authorization = auth_result.authorization
# authorization = "some_authorization_transaction_id"
iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Cams, money, authorization)
@@ -247,7 +242,7 @@ defmodule Gringotts.Gateways.Cams do
month: 12,
verification_code: "999",
brand: "VISA"}
- iex> money = %{value: Decimal.new(20), currency: "USD"}
+ iex> money = Money.new(20, :USD)
iex> Gringotts.purchase(Gringotts.Gateways.Cams, money, card)
```
"""
@@ -279,7 +274,7 @@ defmodule Gringotts.Gateways.Cams do
```
iex> capture_id = capture_result.authorization
# capture_id = "some_capture_transaction_id"
- iex> money = %{value: Decimal.new(20), currency: "USD"}
+ iex> money = Money.new(20, :USD)
iex> Gringotts.refund(Gringotts.Gateways.Cams, money, capture_id)
```
"""
diff --git a/lib/gringotts/gateways/monei.ex b/lib/gringotts/gateways/monei.ex
index c297a1a3..eeef6ec6 100644
--- a/lib/gringotts/gateways/monei.ex
+++ b/lib/gringotts/gateways/monei.ex
@@ -126,56 +126,15 @@ defmodule Gringotts.Gateways.Monei do
that you see in `Dashboard > Sub-accounts` as described
[above](#module-registering-your-monei-account-at-gringotts).
- 2. Run an `iex` session with `iex -S mix` and add some variable bindings and
- aliases to it (to save some time):
- ```
- iex> alias Gringotts.{Response, CreditCard, Gateways.Monei}
- iex> amount = %{value: Decimal.new(42), currency: "USD"}
- iex> card = %CreditCard{first_name: "Harry",
- last_name: "Potter",
- number: "4200000000000000",
- year: 2099, month: 12,
- verification_code: "123",
- brand: "VISA"}
- iex> customer = %{"givenName": "Harry",
- "surname": "Potter",
- "merchantCustomerId": "the_boy_who_lived",
- "sex": "M",
- "birthDate": "1980-07-31",
- "mobile": "+15252525252",
- "email": "masterofdeath@ministryofmagic.gov",
- "ip": "127.0.0.1",
- "status": "NEW"}
- iex> merchant = %{"name": "Ollivanders",
- "city": "South Side",
- "street": "Diagon Alley",
- "state": "London",
- "country": "GB",
- "submerchantId": "Makers of Fine Wands since 382 B.C."}
- iex> billing = %{"street1": "301, Gryffindor",
- "street2": "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
- "city": "Highlands",
- "state": "Scotland",
- "country": "GB"}
- iex> shipping = %{"street1": "301, Gryffindor",
- "street2": "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
- "city": "Highlands",
- "state": "Scotland",
- "country": "GB",
- "method": "SAME_DAY_SERVICE",
- "comment": "For our valued customer, Mr. Potter"}
- iex> opts = [customer: customer,
- merchant: merchant,
- billing: billing,
- shipping: shipping,
- category: "EC",
- custom: %{"voldemort": "he who must not be named"},
- register: true]
- ```
-
- We'll be using these in the examples below.
+ 2. To save a lot of time, create a [`.iex.exs`][iex-docs] file as shown in
+ [this gist][monei.iex.exs] to introduce a set of handy bindings and
+ aliases.
+
+ We'll be using these bindings in the examples below.
[example-repo]: https://github.com/aviabird/gringotts_example
+ [iex-docs]: https://hexdocs.pm/iex/IEx.html#module-the-iex-exs-file
+ [monei.iex.exs]: https://gist.github.com/oyeb/a2e2ac5986cc90a12a6136f6bf1357e5
## TODO
@@ -251,7 +210,7 @@ defmodule Gringotts.Gateways.Monei do
The following example shows how one would (pre) authorize a payment of $42 on
a sample `card`.
- iex> amount = %{value: Decimal.new(42), currency: "USD"}
+ iex> amount = Money.new(42, :USD)
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Monei, amount, card, opts)
iex> auth_result.id # This is the authorization ID
@@ -289,7 +248,7 @@ defmodule Gringotts.Gateways.Monei do
The following example shows how one would (partially) capture a previously
authorized a payment worth $35 by referencing the obtained authorization `id`.
- iex> amount = %{value: Decimal.new(35), currency: "USD"}
+ iex> amount = Money.new(35, :USD)
iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Monei, amount, auth_result.id, opts)
"""
@spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response.t()}
@@ -323,7 +282,7 @@ defmodule Gringotts.Gateways.Monei do
The following example shows how one would process a payment worth $42 in
one-shot, without (pre) authorization.
- iex> amount = %{value: Decimal.new(42), currency: "USD"}
+ iex> amount = Money.new(42, :USD)
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.Monei, amount, card, opts)
iex> purchase_result.token # This is the registration ID/token
@@ -357,7 +316,7 @@ defmodule Gringotts.Gateways.Monei do
The following example shows how one would (completely) refund a previous
purchase (and similarily for captures).
- iex> amount = %{value: Decimal.new(42), currency: "USD"}
+ iex> amount = Money.new(42, :USD)
iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.Monei, purchase_result.id, amount)
"""
@spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response.t()}
diff --git a/lib/gringotts/gateways/trexle.ex b/lib/gringotts/gateways/trexle.ex
index fba77838..523d4c2f 100644
--- a/lib/gringotts/gateways/trexle.ex
+++ b/lib/gringotts/gateways/trexle.ex
@@ -66,35 +66,16 @@ defmodule Gringotts.Gateways.Trexle do
that as described
[above](#module-registering-your-trexle-account-at-gringotts).
- 2. Run an `iex` session with `iex -S mix` and add some variable bindings and
- aliases to it (to save some time):
- ```
- iex> alias Gringotts.{Response, CreditCard, Gateways.Trexle}
- iex> card = %CreditCard{
- first_name: "Harry",
- last_name: "Potter",
- number: "4200000000000000",
- year: 2099, month: 12,
- verification_code: "123",
- brand: "VISA"}
- iex> address = %Address{
- street1: "301, Gryffindor",
- street2: "Hogwarts School of Witchcraft and Wizardry, Hogwarts Castle",
- city: "Highlands",
- region: "SL",
- country: "GB",
- postal_code: "11111",
- phone: "(555)555-5555"}
- iex> options = [email: "masterofdeath@ministryofmagic.gov",
- ip_address: "127.0.0.1",
- billing_address: address,
- description: "For our valued customer, Mr. Potter"]
- ```
+ 2. To save a lot of time, create a [`.iex.exs`][iex-docs] file as shown in
+ [this gist][trexle.iex.exs] to introduce a set of handy bindings and
+ aliases.
- We'll be using these in the examples below.
+ We'll be using these bindings in the examples below.
[example-repo]: https://github.com/aviabird/gringotts_example
- [gs]: #
+ [iex-docs]: https://hexdocs.pm/iex/IEx.html#module-the-iex-exs-file
+ [trexle.iex.exs]: https://gist.github.com/oyeb/055f40e9ad4102f5480febd2cfa00787
+ [gs]: https://github.com/aviabird/gringotts/wiki
"""
@base_url "https://core.trexle.com/api/v1/"
@@ -120,7 +101,7 @@ defmodule Gringotts.Gateways.Trexle do
a sample `card`.
```
- iex> amount = %{value: Decimal.new(100),currency: "USD")
+ iex> amount = Money.new(10, :USD)
iex> card = %CreditCard{
first_name: "Harry",
last_name: "Potter",
@@ -170,7 +151,7 @@ defmodule Gringotts.Gateways.Trexle do
authorized a payment worth $10 by referencing the obtained `charge_token`.
```
- iex> amount = %{value: Decimal.new(100),currency: "USD")
+ iex> amount = Money.new(10, :USD)
iex> token = "some-real-token"
iex> Gringotts.capture(Gringotts.Gateways.Trexle, token, amount)
```
@@ -194,7 +175,7 @@ defmodule Gringotts.Gateways.Trexle do
one-shot, without (pre) authorization.
```
- iex> amount = %{value: Decimal.new(100),currency: "USD")
+ iex> amount = Money.new(10, :USD)
iex> card = %CreditCard{
first_name: "Harry",
last_name: "Potter",
@@ -242,7 +223,7 @@ defmodule Gringotts.Gateways.Trexle do
`purchase/3` (and similarily for `capture/3`s).
```
- iex> amount = %{value: Decimal.new(100),currency: "USD")
+ iex> amount = Money.new(10, :USD)
iex> token = "some-real-token"
iex> Gringotts.refund(Gringotts.Gateways.Trexle, amount, token)
```
diff --git a/lib/mix/new.ex b/lib/mix/new.ex
index c37fd7d0..02f4058b 100644
--- a/lib/mix/new.ex
+++ b/lib/mix/new.ex
@@ -6,10 +6,12 @@ defmodule Mix.Tasks.Gringotts.New do
@moduledoc """
Generates a barebones implementation for a gateway.
- It expects the (brand) name of the gateway as argument. This will not
- necessarily be the module name, but we recommend the name be capitalized.
+ It expects the (brand) name of the gateway as argument and we recommend that
+ it be capitalized. *This will not necessarily be the module name*.
- mix gringotts.new NAME [-m, --module MODULE] [--url URL]
+ ```
+ mix gringotts.new NAME [-m, --module MODULE] [-f, --file FILENAME] [--url URL]
+ ```
A barebones implementation of the gateway will be created along with skeleton
mock and integration tests in `lib/gringotts/gateways/`. The command will
@@ -22,6 +24,7 @@ defmodule Mix.Tasks.Gringotts.New do
> prompts.
* `-m` `--module` - The module name for the Gateway.
+ * `-f` `--file` - The filename.
* `--url` - The homepage of the gateway.
## Examples
@@ -30,10 +33,10 @@ defmodule Mix.Tasks.Gringotts.New do
The prompts for this will be:
```
- MODULE = `Foobar`
- URL = `https://www.foobar.com`
+ MODULE = "Foobar"
+ URL = "https://www.foobar.com"
+ FILENAME = "foo_bar.ex"
```
- and the filename will be `foo_bar.ex`
"""
use Mix.Task
@@ -45,20 +48,26 @@ Comma separated list of required configuration keys:
> }
def run(args) do
- {key_list, [name], []} =
+ {key_list, name, []} =
OptionParser.parse(
args,
- switches: [module: :string, url: :string],
- aliases: [m: :module]
+ switches: [module: :string, url: :string, file: :string],
+ aliases: [m: :module, f: :file]
)
Mix.Shell.IO.info("Generating barebones implementation for #{name}.")
Mix.Shell.IO.info("Hit enter to select the suggestion.")
+ module_suggestion =
+ name |> String.split() |> Enum.map(&String.capitalize(&1)) |> Enum.join("")
+
module_name =
case Keyword.fetch(key_list, :module) do
- :error -> prompt_with_suggestion("\nModule name", String.capitalize(name))
- {:ok, mod_name} -> mod_name
+ :error ->
+ prompt_with_suggestion("\nModule name", module_suggestion)
+
+ {:ok, mod_name} ->
+ mod_name
end
url =
@@ -66,18 +75,28 @@ Comma separated list of required configuration keys:
:error ->
prompt_with_suggestion(
"\nHomepage URL",
- "https://www.#{String.Casing.downcase(name)}.com"
+ "https://www.#{String.downcase(module_suggestion)}.com"
)
{:ok, url} ->
url
end
- file_name = prompt_with_suggestion("\nFilename", Macro.underscore(name))
+ file_name =
+ case Keyword.fetch(key_list, :file) do
+ :error ->
+ prompt_with_suggestion("\nFilename", Macro.underscore(module_name) <> ".ex")
+
+ {:ok, filename} ->
+ filename
+ end
+
+ file_base_name = String.slice(file_name, 0..-4)
required_keys =
case Mix.Shell.IO.prompt(@long_msg) |> String.trim() do
- "" -> []
+ "" ->
+ []
keys ->
String.split(keys, ",") |> Enum.map(&String.trim(&1)) |> Enum.map(&String.to_atom(&1))
@@ -87,10 +106,12 @@ Comma separated list of required configuration keys:
gateway: name,
gateway_module: module_name,
gateway_underscore: file_name,
+ # The key :gateway_filename is not used in any template as of now.
+ gateway_filename: "#{file_name}",
required_config_keys: required_keys,
gateway_url: url,
- mock_test_filename: file_name <> "_test",
- mock_response_filename: file_name <> "_mock"
+ mock_test_filename: "#{file_base_name}_test.exs",
+ mock_response_filename: "#{file_base_name}_mock.exs"
]
if Mix.Shell.IO.yes?(
@@ -101,12 +122,12 @@ Comma separated list of required configuration keys:
mock_response = EEx.eval_file("templates/mock_response.eex", bindings)
integration = EEx.eval_file("templates/integration.eex", bindings)
- create_file("lib/gringotts/gateways/#{bindings[:gateway_underscore]}.ex", gateway)
- create_file("test/integration/gateways/#{bindings[:mock_test_filename]}.exs", integration)
+ create_file("lib/gringotts/gateways/#{bindings[:gateway_filename]}", gateway)
+ create_file("test/integration/gateways/#{bindings[:mock_test_filename]}", integration)
if Mix.Shell.IO.yes?("\nAlso create empty mock test suite?\n>") do
- create_file("test/gateways/#{bindings[:mock_test_filename]}.exs", mock)
- create_file("test/mocks/#{bindings[:mock_response_filename]}.exs", mock_response)
+ create_file("test/gateways/#{bindings[:mock_test_filename]}", mock)
+ create_file("test/mocks/#{bindings[:mock_response_filename]}", mock_response)
end
else
Mix.Shell.IO.info("Doing nothing, bye!")
diff --git a/mix.exs b/mix.exs
index f63edfc6..ef54cc7f 100644
--- a/mix.exs
+++ b/mix.exs
@@ -58,16 +58,16 @@ defmodule Gringotts.Mixfile do
{:ex_money, "~> 1.1.0", only: [:dev, :test], optional: true},
# docs and tests
- {:ex_doc, "~> 0.16", only: :dev, runtime: false},
+ {:ex_doc, "~> 0.18", only: :dev, runtime: false},
{:mock, "~> 0.3.0", only: :test},
{:bypass, "~> 0.8", only: :test},
- {:excoveralls, "~> 0.7", only: :test},
+ {:excoveralls, "~> 0.8", only: :test},
# various analyses tools
{:credo, "~> 0.3", only: [:dev, :test]},
{:inch_ex, "~> 0.5", only: :docs},
{:dialyxir, "~> 0.3", only: :dev},
- {:timex, "~> 3.1"}
+ {:timex, "~> 3.2"}
]
end
diff --git a/mix.lock b/mix.lock
index da4109e3..4e785b03 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,4 +1,5 @@
-%{"abnf2": {:hex, :abnf2, "0.1.2", "6f8792b8ac3288dba5fc889c2bceae9fe78f74e1a7b36bea9726ffaa9d7bef95", [:mix], [], "hexpm"},
+%{
+ "abnf2": {:hex, :abnf2, "0.1.2", "6f8792b8ac3288dba5fc889c2bceae9fe78f74e1a7b36bea9726ffaa9d7bef95", [:mix], [], "hexpm"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"bypass": {:hex, :bypass, "0.8.1", "16d409e05530ece4a72fabcf021a3e5c7e15dcc77f911423196a0c551f2a15ca", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"},
@@ -17,8 +18,8 @@
"ex_money": {:hex, :ex_money, "1.1.2", "4336192f1ac263900dfb4f63c1f71bc36a7cdee5d900e81937d3213be3360f9f", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}, {:ex_cldr, "~> 1.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 1.0", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.8.0", "99d2691d3edf8612f128be3f9869c4d44b91c67cec92186ce49470ae7a7404cf", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
- "gettext": {:hex, :gettext, "0.14.0", "1a019a2e51d5ad3d126efe166dcdf6563768e5d06c32a99ad2281a1fa94b4c72", [:mix], [], "hexpm"},
- "hackney": {:hex, :hackney, "1.10.1", "c38d0ca52ea80254936a32c45bb7eb414e7a96a521b4ce76d00a69753b157f21", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
+ "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
+ "hackney": {:hex, :hackney, "1.11.0", "4951ee019df102492dabba66a09e305f61919a8a183a7860236c0fde586134b6", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
@@ -32,7 +33,8 @@
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
- "timex": {:hex, :timex, "3.1.25", "6002dae5432f749d1c93e2cd103eb73cecb53e50d2c885349e8e4146fc96bd44", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
+ "timex": {:hex, :timex, "3.2.1", "639975eac45c4c08c2dbf7fc53033c313ff1f94fad9282af03619a3826493612", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
- "xml_builder": {:hex, :xml_builder, "2.1.0", "c249d5339427c13cae11e9d9d0e8b40d25d228b9ecc54029f24017385e60280b", [:mix], [], "hexpm"}}
+ "xml_builder": {:hex, :xml_builder, "2.1.0", "c249d5339427c13cae11e9d9d0e8b40d25d228b9ecc54029f24017385e60280b", [:mix], [], "hexpm"},
+}
diff --git a/templates/mock_response.eex b/templates/mock_response.eex
index d4ad1f5b..b50bc667 100644
--- a/templates/mock_response.eex
+++ b/templates/mock_response.eex
@@ -1,6 +1,6 @@
defmodule Gringotts.Gateways.<%= gateway_module <> "Mock"%> do
- # The module should include mock responses for test cases in <%= mock_test_filename <> ".exs"%>.
+ # The module should include mock responses for test cases in <%= mock_test_filename %>.
# e.g.
# def successful_purchase do
# {:ok, %HTTPoison.Response{body: ~s[{data: "successful_purchase"}]}
diff --git a/templates/test.eex b/templates/test.eex
index 2fc65a79..93c0b62d 100644
--- a/templates/test.eex
+++ b/templates/test.eex
@@ -2,13 +2,13 @@ defmodule Gringotts.Gateways.<%= gateway_module <> "Test" %> do
# The file contains mocked tests for <%= gateway_module%>
# We recommend using [mock][1] for this, you can place the mock responses from
- # the Gateway in `test/mocks/<%= mock_response_filename%>.exs` file, which has also been
+ # the Gateway in `test/mocks/<%= mock_response_filename %>` file, which has also been
# generated for you.
#
# [1]: https://github.com/jjh42/mock
# Load the mock response file before running the tests.
- Code.require_file "../mocks/<%= mock_response_filename <> ".exs"%>", __DIR__
+ Code.require_file "../mocks/<%= mock_response_filename %>", __DIR__
use ExUnit.Case, async: false
alias Gringotts.Gateways.<%= gateway_module%>
diff --git a/test/gateways/bogus_test.exs b/test/gateways/bogus_test.exs
index 5810dfc0..041c1469 100644
--- a/test/gateways/bogus_test.exs
+++ b/test/gateways/bogus_test.exs
@@ -4,9 +4,12 @@ defmodule Gringotts.Gateways.BogusTest do
alias Gringotts.Response
alias Gringotts.Gateways.Bogus, as: Gateway
+ @some_id "some_arbitrary_id"
+ @amount Money.new(5, :USD)
+
test "authorize" do
{:ok, %Response{id: id, success: success}} =
- Gateway.authorize(10.95, :card, [])
+ Gateway.authorize(@amount, :card, [])
assert success
assert id != nil
@@ -14,7 +17,7 @@ defmodule Gringotts.Gateways.BogusTest do
test "purchase" do
{:ok, %Response{id: id, success: success}} =
- Gateway.purchase(10.95, :card, [])
+ Gateway.purchase(@amount, :card, [])
assert success
assert id != nil
@@ -22,7 +25,7 @@ defmodule Gringotts.Gateways.BogusTest do
test "capture" do
{:ok, %Response{id: id, success: success}} =
- Gateway.capture(1234, 5, [])
+ Gateway.capture(@some_id, @amount, [])
assert success
assert id != nil
@@ -30,7 +33,7 @@ defmodule Gringotts.Gateways.BogusTest do
test "void" do
{:ok, %Response{id: id, success: success}} =
- Gateway.void(1234, [])
+ Gateway.void(@some_id, [])
assert success
assert id != nil
@@ -45,7 +48,7 @@ defmodule Gringotts.Gateways.BogusTest do
test "unstore with customer" do
{:ok, %Response{success: success}} =
- Gateway.unstore(1234, [])
+ Gateway.unstore(@some_id, [])
assert success
end
From 71a5bc397fbc8914995bf8dc10e50f5a1b5cbe29 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Thu, 15 Mar 2018 20:59:37 +0530
Subject: [PATCH 32/60] [monei] Test fixes (#116)
* Ignore some optional params for RF, RV, CP
Some optional params like billing, customer, merchant must not be
expanded in case of capture, refund and void.
* Improved mock tests, fixes #98
Mock tests now mostly check if the request is correctly built.
Since most requests have common parameters, they are not checked
everywhere.
* Improve integration tests (more cases), fixes #108
* Integration tests no longer use the worker as a workaround for #8
* Added more test cases, can possibly be improved using describe blocks with
local setup.
* There are almost no assertions and it is expected that errors will
bubble up to the pattern matches.
---
lib/gringotts/gateways/monei.ex | 67 +++++++++-----
mix.lock | 12 +--
test/gateways/monei_test.exs | 110 +++++++++++++++--------
test/integration/gateways/monei_test.exs | 104 +++++++++++++--------
4 files changed, 195 insertions(+), 98 deletions(-)
diff --git a/lib/gringotts/gateways/monei.ex b/lib/gringotts/gateways/monei.ex
index eeef6ec6..2d08080c 100644
--- a/lib/gringotts/gateways/monei.ex
+++ b/lib/gringotts/gateways/monei.ex
@@ -338,6 +338,8 @@ defmodule Gringotts.Gateways.Monei do
which can be used to effectively process _One-Click_ and _Recurring_ payments,
and return a registration token for reference.
+ The registration token is available in the `Response.id` field.
+
It is recommended to associate these details with a "Customer" by passing
customer details in the `opts`.
@@ -352,7 +354,8 @@ defmodule Gringotts.Gateways.Monei do
future use.
iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> {:ok, store_result} = Gringotts.store(Gringotts.Gateways.Monei, card, [])
+ iex> {:ok, store_result} = Gringotts.store(Gringotts.Gateways.Monei, card)
+ iex> store_result.id # This is the registration token
"""
@spec store(CreditCard.t(), keyword) :: {:ok | :error, Response.t()}
def store(%CreditCard{} = card, opts) do
@@ -438,7 +441,7 @@ defmodule Gringotts.Gateways.Monei do
defp commit(:post, endpoint, params, opts) do
url = "#{base_url(opts)}/#{version(opts)}/#{endpoint}"
- case expand_params(opts, params[:paymentType]) do
+ case expand_params(Keyword.delete(opts, :config), params[:paymentType]) do
{:error, reason} ->
{:error, Response.error(reason: reason)}
@@ -528,16 +531,16 @@ defmodule Gringotts.Gateways.Monei do
else: {:halt, {:error, "Invalid currency"}}
:customer ->
- {:cont, acc ++ make("customer", v)}
+ {:cont, acc ++ make(action_type, "customer", v)}
:merchant ->
- {:cont, acc ++ make("merchant", v)}
+ {:cont, acc ++ make(action_type, "merchant", v)}
:billing ->
- {:cont, acc ++ make("billing", v)}
+ {:cont, acc ++ make(action_type, "billing", v)}
:shipping ->
- {:cont, acc ++ make("shipping", v)}
+ {:cont, acc ++ make(action_type, "shipping", v)}
:invoice_id ->
{:cont, [{"merchantInvoiceId", v} | acc]}
@@ -549,23 +552,16 @@ defmodule Gringotts.Gateways.Monei do
{:cont, [{"transactionCategory", v} | acc]}
:shipping_customer ->
- {:cont, acc ++ make("shipping.customer", v)}
+ {:cont, acc ++ make(action_type, "shipping.customer", v)}
:custom ->
{:cont, acc ++ make_custom(v)}
:register ->
- {
- :cont,
- if action_type in ["PA", "DB"] do
- [{"createRegistration", true} | acc]
- else
- acc
- end
- }
-
- _ ->
- {:cont, acc}
+ {:cont, acc ++ make(action_type, :register, v)}
+
+ unsupported ->
+ {:halt, {:error, "Unsupported optional param '#{unsupported}'"}}
end
end)
end
@@ -574,8 +570,39 @@ defmodule Gringotts.Gateways.Monei do
currency in @supported_currencies
end
- defp make(prefix, param) do
- Enum.into(param, [], fn {k, v} -> {"#{prefix}.#{k}", v} end)
+ defp parse_response(%{"result" => result} = data) do
+ {address, zip_code} = @avs_code_translator[result["avsResponse"]]
+
+ results = [
+ code: result["code"],
+ description: result["description"],
+ risk: data["risk"]["score"],
+ cvc_result: @cvc_code_translator[result["cvvResponse"]],
+ avs_result: [address: address, zip_code: zip_code],
+ raw: data,
+ token: data["registrationId"]
+ ]
+
+ filtered = Enum.filter(results, fn {_, v} -> v != nil end)
+ verify(filtered)
+ end
+
+ defp verify(results) do
+ if String.match?(results[:code], ~r{^(000\.000\.|000\.100\.1|000\.[36])}) do
+ {:ok, results}
+ else
+ {:error, [{:reason, results[:description]} | results]}
+ end
+ end
+
+ defp make(action_type, _prefix, _param) when action_type in ["CP", "RF", "RV"], do: []
+ defp make(action_type, prefix, param) do
+ case prefix do
+ :register ->
+ if action_type in ["PA", "DB"], do: [createRegistration: true], else: []
+
+ _ -> Enum.into(param, [], fn {k, v} -> {"#{prefix}.#{k}", v} end)
+ end
end
defp make_custom(custom_map) do
diff --git a/mix.lock b/mix.lock
index 4e785b03..aaaa5cc1 100644
--- a/mix.lock
+++ b/mix.lock
@@ -12,11 +12,11 @@
"earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"},
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "0.1.1", "57e924cd11731947bfd245ce57d0b8dd8b7168bf8edb20cd156a2982ca96fdfa", [:mix], [{:erlsom, "~>1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm"},
"erlsom": {:hex, :erlsom, "1.4.1", "53dbacf35adfea6f0714fd0e4a7b0720d495e88c5e24e12c5dc88c7b62bd3e49", [:rebar3], [], "hexpm"},
- "ex_cldr": {:hex, :ex_cldr, "1.1.0", "26f4a206307770b70139214ab820c5ed1f6241eb3394dd0db216ff95bf7e213a", [:mix], [{:abnf2, "~> 0.1", [hex: :abnf2, repo: "hexpm", optional: false]}, {:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:poison, "~> 2.1 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
- "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "1.1.0", "75904f202ca602eca5f3af572d56ed3d4a51543fecd08c9ab626ae2d876f44da", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 1.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:poison, "~> 2.1 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
- "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
- "ex_money": {:hex, :ex_money, "1.1.2", "4336192f1ac263900dfb4f63c1f71bc36a7cdee5d900e81937d3213be3360f9f", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}, {:ex_cldr, "~> 1.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 1.0", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}], "hexpm"},
- "excoveralls": {:hex, :excoveralls, "0.8.0", "99d2691d3edf8612f128be3f9869c4d44b91c67cec92186ce49470ae7a7404cf", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
+ "ex_cldr": {:hex, :ex_cldr, "1.4.4", "654966e8724d607e5cf9ecd5509ffcf66868b17e479bbd22ab2e9123595f9103", [:mix], [{:abnf2, "~> 0.1", [hex: :abnf2, repo: "hexpm", optional: false]}, {:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 2.1 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
+ "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "1.3.1", "50a117654dff8f8ee6958e68a65d0c2835a7e2f1aff94c1ea8f582c04fdf0bd4", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 1.4.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.1 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
+ "ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
+ "ex_money": {:hex, :ex_money, "1.1.3", "843eed0a5673206de33be47cdc06574401abc3e2d33cbcf6d74e160226791ae4", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}, {:ex_cldr, "~> 1.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 1.0", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}], "hexpm"},
+ "excoveralls": {:hex, :excoveralls, "0.8.1", "0bbf67f22c7dbf7503981d21a5eef5db8bbc3cb86e70d3798e8c802c74fa5e27", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.11.0", "4951ee019df102492dabba66a09e305f61919a8a183a7860236c0fde586134b6", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
@@ -29,7 +29,7 @@
"mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
- "plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
+ "plug": {:hex, :plug, "1.5.0", "224b25b4039bedc1eac149fb52ed456770b9678bbf0349cdd810460e1e09195b", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
diff --git a/test/gateways/monei_test.exs b/test/gateways/monei_test.exs
index 8d3a11ec..3aaa88a1 100644
--- a/test/gateways/monei_test.exs
+++ b/test/gateways/monei_test.exs
@@ -39,7 +39,7 @@ defmodule Gringotts.Gateways.MoneiTest do
birthDate: "1980-07-31",
mobile: "+15252525252",
email: "masterofdeath@ministryofmagic.gov",
- ip: "1.1.1",
+ ip: "127.0.0.1",
status: "NEW"
}
@merchant %{
@@ -96,7 +96,7 @@ defmodule Gringotts.Gateways.MoneiTest do
"card":{
"bin":"420000",
"last4Digits":"0000",
- "holder":"Jo Doe",
+ "holder":"Harry Potter",
"expiryMonth":"12",
"expiryYear":"2099"
}
@@ -123,16 +123,24 @@ defmodule Gringotts.Gateways.MoneiTest do
end
test "when MONEI is down or unreachable.", %{bypass: bypass, auth: auth} do
- Bypass.expect_once(bypass, fn conn ->
- Plug.Conn.resp(conn, 200, @auth_success)
- end)
-
Bypass.down(bypass)
{:error, response} = Gateway.authorize(@amount42, @card, config: auth)
assert response.reason == "network related failure"
-
Bypass.up(bypass)
- {:ok, _} = Gateway.authorize(@amount42, @card, config: auth)
+ end
+
+ test "that all auth info is picked.", %{bypass: bypass, auth: auth} do
+ Bypass.expect_once(bypass, "POST", "/v1/payments", fn conn ->
+ p_conn = parse(conn)
+ params = p_conn.body_params
+ assert params["authentication.entityId"] == "some_secret_entity_id"
+ assert params["authentication.password"] == "some_secret_password"
+ assert params["authentication.userId"] == "some_secret_user_id"
+ Plug.Conn.resp(conn, 200, @auth_success)
+ end)
+
+ {:ok, response} = Gateway.purchase(@amount42, @card, config: auth)
+ assert response.gateway_code == "000.100.110"
end
test "with all extra_params.", %{bypass: bypass, auth: auth} do
@@ -142,20 +150,21 @@ defmodule Gringotts.Gateways.MoneiTest do
]
Bypass.expect_once(bypass, "POST", "/v1/payments", fn conn ->
- conn_ = parse(conn)
- assert conn_.body_params["createRegistration"] == "true"
- assert conn_.body_params["customParameters"] == @extra_opts[:custom]
- assert conn_.body_params["merchantInvoiceId"] == randoms[:invoice_id]
- assert conn_.body_params["merchantTransactionId"] == randoms[:transaction_id]
- assert conn_.body_params["transactionCategory"] == @extra_opts[:category]
- assert conn_.body_params["customer.merchantCustomerId"] == @customer[:merchantCustomerId]
-
- assert conn_.body_params["shipping.customer.merchantCustomerId"] ==
+ p_conn = parse(conn)
+ params = p_conn.body_params
+ assert params["createRegistration"] == "true"
+ assert params["customParameters"] == @extra_opts[:custom]
+ assert params["merchantInvoiceId"] == randoms[:invoice_id]
+ assert params["merchantTransactionId"] == randoms[:transaction_id]
+ assert params["transactionCategory"] == @extra_opts[:category]
+ assert params["customer.merchantCustomerId"] == @customer[:merchantCustomerId]
+
+ assert params["shipping.customer.merchantCustomerId"] ==
@customer[:merchantCustomerId]
- assert conn_.body_params["merchant.submerchantId"] == @merchant[:submerchantId]
- assert conn_.body_params["billing.city"] == @billing[:city]
- assert conn_.body_params["shipping.method"] == @shipping[:method]
+ assert params["merchant.submerchantId"] == @merchant[:submerchantId]
+ assert params["billing.city"] == @billing[:city]
+ assert params["shipping.method"] == @shipping[:method]
Plug.Conn.resp(conn, 200, @register_success)
end)
@@ -165,7 +174,7 @@ defmodule Gringotts.Gateways.MoneiTest do
assert response.token == "8a82944a60e09c550160e92da144491e"
end
- test "when card has expired.", %{bypass: bypass, auth: auth} do
+ test "when we get non-json.", %{bypass: bypass, auth: auth} do
Bypass.expect_once(bypass, "POST", "/v1/payments", fn conn ->
Plug.Conn.resp(conn, 400, "")
end)
@@ -177,6 +186,11 @@ defmodule Gringotts.Gateways.MoneiTest do
describe "authorize" do
test "when all is good.", %{bypass: bypass, auth: auth} do
Bypass.expect(bypass, "POST", "/v1/payments", fn conn ->
+ p_conn = parse(conn)
+ params = p_conn.body_params
+ assert params["amount"] == "42.00"
+ assert params["currency"] == "USD"
+ assert params["paymentType"] == "PA"
Plug.Conn.resp(conn, 200, @auth_success)
end)
@@ -188,6 +202,11 @@ defmodule Gringotts.Gateways.MoneiTest do
describe "purchase" do
test "when all is good.", %{bypass: bypass, auth: auth} do
Bypass.expect_once(bypass, "POST", "/v1/payments", fn conn ->
+ p_conn = parse(conn)
+ params = p_conn.body_params
+ assert params["amount"] == "42.00"
+ assert params["currency"] == "USD"
+ assert params["paymentType"] == "DB"
Plug.Conn.resp(conn, 200, @auth_success)
end)
@@ -197,8 +216,9 @@ defmodule Gringotts.Gateways.MoneiTest do
test "with createRegistration.", %{bypass: bypass, auth: auth} do
Bypass.expect_once(bypass, "POST", "/v1/payments", fn conn ->
- conn_ = parse(conn)
- assert conn_.body_params["createRegistration"] == "true"
+ p_conn = parse(conn)
+ params = p_conn.body_params
+ assert params["createRegistration"] == "true"
Plug.Conn.resp(conn, 200, @register_success)
end)
@@ -211,6 +231,14 @@ defmodule Gringotts.Gateways.MoneiTest do
describe "store" do
test "when all is good.", %{bypass: bypass, auth: auth} do
Bypass.expect_once(bypass, "POST", "/v1/registrations", fn conn ->
+ p_conn = parse(conn)
+ params = p_conn.body_params
+ params["card.cvv"] == "123"
+ params["card.expiryMonth"] == "12"
+ params["card.expiryYear"] == "2099"
+ params["card.holder"] == "Harry Potter"
+ params["card.number"] == "4200000000000000"
+ params["paymentBrand"] == "VISA"
Plug.Conn.resp(conn, 200, @store_success)
end)
@@ -226,6 +254,11 @@ defmodule Gringotts.Gateways.MoneiTest do
"POST",
"/v1/payments/7214344242e11af79c0b9e7b4f3f6234",
fn conn ->
+ p_conn = parse(conn)
+ params = p_conn.body_params
+ assert params["amount"] == "42.00"
+ assert params["currency"] == "USD"
+ assert params["paymentType"] == "CP"
Plug.Conn.resp(conn, 200, @auth_success)
end
)
@@ -242,8 +275,9 @@ defmodule Gringotts.Gateways.MoneiTest do
"POST",
"/v1/payments/7214344242e11af79c0b9e7b4f3f6234",
fn conn ->
- conn_ = parse(conn)
- assert :error == Map.fetch(conn_.body_params, "createRegistration")
+ p_conn = parse(conn)
+ params = p_conn.body_params
+ assert :error == Map.fetch(params, "createRegistration")
Plug.Conn.resp(conn, 200, @auth_success)
end
)
@@ -267,6 +301,11 @@ defmodule Gringotts.Gateways.MoneiTest do
"POST",
"/v1/payments/7214344242e11af79c0b9e7b4f3f6234",
fn conn ->
+ p_conn = parse(conn)
+ params = p_conn.body_params
+ assert params["amount"] == "3.00"
+ assert params["currency"] == "USD"
+ assert params["paymentType"] == "RF"
Plug.Conn.resp(conn, 200, @auth_success)
end
)
@@ -284,6 +323,11 @@ defmodule Gringotts.Gateways.MoneiTest do
"DELETE",
"/v1/registrations/7214344242e11af79c0b9e7b4f3f6234",
fn conn ->
+ p_conn = parse(conn)
+ params = p_conn.query_params
+ assert params["authentication.entityId"] == "some_secret_entity_id"
+ assert params["authentication.password"] == "some_secret_password"
+ assert params["authentication.userId"] == "some_secret_user_id"
Plug.Conn.resp(conn, 200, "")
end
)
@@ -300,6 +344,11 @@ defmodule Gringotts.Gateways.MoneiTest do
"POST",
"/v1/payments/7214344242e11af79c0b9e7b4f3f6234",
fn conn ->
+ p_conn = parse(conn)
+ params = p_conn.body_params
+ assert :error == Map.fetch(params, :amount)
+ assert :error == Map.fetch(params, :currency)
+ assert params["paymentType"] == "RV"
Plug.Conn.resp(conn, 200, @auth_success)
end
)
@@ -309,17 +358,6 @@ defmodule Gringotts.Gateways.MoneiTest do
end
end
- @tag :skip
- test "respond various scenarios, can't test a private function." do
- json_200 = %HTTPoison.Response{body: @auth_success, status_code: 200}
- json_not_200 = %HTTPoison.Response{body: @auth_success, status_code: 300}
- html_200 = %HTTPoison.Response{body: ~s[\n], status_code: 200}
- html_not_200 = %HTTPoison.Response{body: ~s[
- assert response.gateway_code == "000.100.110"
-
- assert response.message ==
- "Request successfully processed in 'Merchant in Integrator Test Mode'"
-
- assert String.length(response.id) == 32
-
+ test "[authorize] without tokenisation", %{opts: opts} do
+ with {:ok, auth_result} <- Gateway.authorize(@amount, @card, opts),
+ {:ok, _capture_result} <- Gateway.capture(auth_result.id, @amount, opts) do
+ "yay!"
+ else
{:error, _err} ->
flunk()
end
end
- @tag :skip
- test "capture", %{opts: _opts} do
- case Gringotts.capture(Gateway, "s", @amount) do
- {:ok, response} ->
- assert response.gateway_code == "000.100.110"
-
- assert response.message ==
- "Request successfully processed in 'Merchant in Integrator Test Mode'"
-
- assert String.length(response.id) == 32
+ test "[authorize -> capture] with tokenisation", %{opts: opts} do
+ with {:ok, auth_result} <- Gateway.authorize(@amount, @card, opts ++ [register: true]),
+ {:ok, _registration_token} <- Map.fetch(auth_result, :token),
+ {:ok, _capture_result} <- Gateway.capture(auth_result.id, @amount, opts) do
+ "yay!"
+ else {:error, _err} ->
+ flunk()
+ end
+ end
- {:error, _err} ->
+ test "[authorize -> void]", %{opts: opts} do
+ with {:ok, auth_result} <- Gateway.authorize(@amount, @card, opts),
+ {:ok, _void_result} <- Gateway.void(auth_result.id, opts) do
+ "yay!"
+ else {:error, _err} ->
flunk()
end
end
- test "purchase", %{opts: opts} do
- case Gringotts.purchase(Gateway, @amount, @card, opts) do
- {:ok, response} ->
- assert response.gateway_code == "000.100.110"
+ test "[purchase/capture -> void]", %{opts: opts} do
+ with {:ok, purchase_result} <- Gateway.purchase(@amount, @card, opts),
+ {:ok, _void_result} <- Gateway.void(purchase_result.id, opts) do
+ "yay!"
+ else {:error, _err} ->
+ flunk()
+ end
+ end
- assert response.message ==
- "Request successfully processed in 'Merchant in Integrator Test Mode'"
+ test "[purchase/capture -> refund] (partial)", %{opts: opts} do
+ with {:ok, purchase_result} <- Gateway.purchase(@amount, @card, opts),
+ {:ok, _refund_result} <- Gateway.refund(@sub_amount, purchase_result.id, opts) do
+ "yay!"
+ else {:error, _err} ->
+ flunk()
+ end
+ end
- assert String.length(response.id) == 32
+ test "[store]", %{opts: opts} do
+ assert {:ok, _store_result} = Gateway.store(@card, opts)
+ end
- {:error, _err} ->
+ @tag :skip
+ test "[store -> unstore]", %{opts: opts} do
+ with {:ok, store_result} <- Gateway.store(@card, opts),
+ {:ok, _unstore_result} <- Gateway.unstore(store_result.id, opts) do
+ "yay!"
+ else {:error, _err} ->
flunk()
end
end
+
+ test "[purchase]", %{opts: opts} do
+ assert {:ok, _response} = Gateway.purchase(@amount, @card, opts)
+ end
+
+ test "Environment setup" do
+ config = Application.get_env(:gringotts, Gringotts.Gateways.Monei)
+ assert config[:adapter] == Gringotts.Gateways.Monei
+ end
end
From cf39d54d27e9a13cce97f9787f7bec606b82b83d Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Mon, 19 Mar 2018 11:45:40 +0530
Subject: [PATCH 33/60] Fix gringotts.new Option.parse call. (#125)
---
lib/mix/new.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/mix/new.ex b/lib/mix/new.ex
index 02f4058b..7b4fcd2e 100644
--- a/lib/mix/new.ex
+++ b/lib/mix/new.ex
@@ -48,7 +48,7 @@ Comma separated list of required configuration keys:
> }
def run(args) do
- {key_list, name, []} =
+ {key_list, [name], []} =
OptionParser.parse(
args,
switches: [module: :string, url: :string, file: :string],
From a9b76dbfc7c7c46accf00776ff6fbf1def25e122 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Mon, 29 Jan 2018 22:09:12 +0530
Subject: [PATCH 34/60] Adapts Stripe with the money protocol
* Fixes #5 and #109
* Moved the stripe_test to integration.
* Fixed credo issue in money integration test
Changes to MONEI
* Removed unnecessary clauses from MONEI
* Re-formatted source.
---
lib/gringotts/gateways/base.ex | 35 +++----
lib/gringotts/gateways/monei.ex | 53 +++--------
lib/gringotts/gateways/stripe.ex | 107 +++++++++++-----------
test/gateways/monei_test.exs | 12 +--
test/gateways/stripe_test.exs | 58 ------------
test/integration/gateways/stripe_test.exs | 44 +++++++++
test/integration/money.exs | 2 +-
7 files changed, 132 insertions(+), 179 deletions(-)
delete mode 100644 test/gateways/stripe_test.exs
create mode 100644 test/integration/gateways/stripe_test.exs
diff --git a/lib/gringotts/gateways/base.ex b/lib/gringotts/gateways/base.ex
index 92f8cfd2..be881992 100644
--- a/lib/gringotts/gateways/base.ex
+++ b/lib/gringotts/gateways/base.ex
@@ -1,4 +1,18 @@
defmodule Gringotts.Gateways.Base do
+ @moduledoc """
+ Dummy implementation of the Gringotts API
+
+ All gateway implementations must `use` this module as it provides (pseudo)
+ implementations for the all methods of the Gringotts API.
+
+ In case `GatewayXYZ` does not implement `unstore`, the following call would
+ not raise an error:
+ ```
+ Gringotts.unstore(GatewayXYZ, "some_registration_id")
+ ```
+ because this module provides an implementation.
+ """
+
alias Gringotts.Response
defmacro __using__(_) do
@@ -38,27 +52,6 @@ defmodule Gringotts.Gateways.Base do
not_implemented()
end
- defp http(method, path, params \\ [], opts \\ []) do
- credentials = Keyword.get(opts, :credentials)
- headers = [{"Content-Type", "application/x-www-form-urlencoded"}]
- data = params_to_string(params)
-
- HTTPoison.request(method, path, data, headers, [hackney: [basic_auth: credentials]])
- end
-
- defp money_to_cents(amount) when is_float(amount) do
- trunc(amount * 100)
- end
-
- defp money_to_cents(amount) do
- amount * 100
- end
-
- defp params_to_string(params) do
- params |> Enum.filter(fn {_k, v} -> v != nil end)
- |> URI.encode_query
- end
-
@doc false
defp not_implemented do
{:error, Response.error(code: :not_implemented)}
diff --git a/lib/gringotts/gateways/monei.ex b/lib/gringotts/gateways/monei.ex
index 2d08080c..4d659656 100644
--- a/lib/gringotts/gateways/monei.ex
+++ b/lib/gringotts/gateways/monei.ex
@@ -153,15 +153,11 @@ defmodule Gringotts.Gateways.Monei do
@base_url "https://test.monei-api.net"
@default_headers ["Content-Type": "application/x-www-form-urlencoded", charset: "UTF-8"]
- @supported_currencies [
- "AED", "AFN", "ANG", "AOA", "AWG", "AZN", "BAM", "BGN", "BRL", "BYN", "CDF",
- "CHF", "CUC", "EGP", "EUR", "GBP", "GEL", "GHS", "MDL", "MGA", "MKD", "MWK",
- "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "PAB", "PEN", "PGK", "PHP",
- "PKR", "PLN", "PYG", "QAR", "RSD", "RUB", "RWF", "SAR", "SCR", "SDG", "SEK",
- "SGD", "SHP", "SLL", "SOS", "SRD", "STD", "SYP", "SZL", "THB", "TJS", "TOP",
- "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VND", "VUV",
- "WST", "XAF", "XCD", "XOF", "XPF", "YER", "ZAR", "ZMW", "ZWL"
- ]
+ @supported_currencies ~w(AED AFN ANG AOA AWG AZN BAM BGN BRL BYN CDF CHF CUC
+ EGP EUR GBP GEL GHS MDL MGA MKD MWK MZN NAD NGN NIO NOK NPR NZD PAB PEN PGK
+ PHP PKR PLN PYG QAR RSD RUB RWF SAR SCR SDG SEK SGD SHP SLL SOS SRD STD SYP
+ SZL THB TJS TOP TRY TTD TWD TZS UAH UGX USD UYU UZS VND VUV WST XAF XCD XOF
+ XPF YER ZAR ZMW ZWL)
@version "v1"
@@ -435,7 +431,6 @@ defmodule Gringotts.Gateways.Monei do
]
end
-
# Makes the request to MONEI's network.
@spec commit(atom, String.t(), keyword, keyword) :: {:ok | :error, Response.t()}
defp commit(:post, endpoint, params, opts) do
@@ -447,7 +442,10 @@ defmodule Gringotts.Gateways.Monei do
validated_params ->
url
- |> HTTPoison.post({:form, params ++ validated_params ++ auth_params(opts)}, @default_headers)
+ |> HTTPoison.post(
+ {:form, params ++ validated_params ++ auth_params(opts)},
+ @default_headers
+ )
|> respond
end
end
@@ -458,7 +456,7 @@ defmodule Gringotts.Gateways.Monei do
auth_params = auth_params(opts)
query_string = auth_params |> URI.encode_query()
- base_url <> "?" <> query_string
+ (base_url <> "?" <> query_string)
|> HTTPoison.delete()
|> respond
end
@@ -472,7 +470,7 @@ defmodule Gringotts.Gateways.Monei do
common = [raw: body, status_code: 200]
with {:ok, decoded_json} <- decode(body),
- {:ok, results} <- parse_response(decoded_json) do
+ {:ok, results} <- parse_response(decoded_json) do
{:ok, Response.success(common ++ results)}
else
{:not_ok, errors} ->
@@ -570,38 +568,15 @@ defmodule Gringotts.Gateways.Monei do
currency in @supported_currencies
end
- defp parse_response(%{"result" => result} = data) do
- {address, zip_code} = @avs_code_translator[result["avsResponse"]]
-
- results = [
- code: result["code"],
- description: result["description"],
- risk: data["risk"]["score"],
- cvc_result: @cvc_code_translator[result["cvvResponse"]],
- avs_result: [address: address, zip_code: zip_code],
- raw: data,
- token: data["registrationId"]
- ]
-
- filtered = Enum.filter(results, fn {_, v} -> v != nil end)
- verify(filtered)
- end
-
- defp verify(results) do
- if String.match?(results[:code], ~r{^(000\.000\.|000\.100\.1|000\.[36])}) do
- {:ok, results}
- else
- {:error, [{:reason, results[:description]} | results]}
- end
- end
-
defp make(action_type, _prefix, _param) when action_type in ["CP", "RF", "RV"], do: []
+
defp make(action_type, prefix, param) do
case prefix do
:register ->
if action_type in ["PA", "DB"], do: [createRegistration: true], else: []
- _ -> Enum.into(param, [], fn {k, v} -> {"#{prefix}.#{k}", v} end)
+ _ ->
+ Enum.into(param, [], fn {k, v} -> {"#{prefix}.#{k}", v} end)
end
end
diff --git a/lib/gringotts/gateways/stripe.ex b/lib/gringotts/gateways/stripe.ex
index 7c1befd2..94a8ce48 100644
--- a/lib/gringotts/gateways/stripe.ex
+++ b/lib/gringotts/gateways/stripe.ex
@@ -1,9 +1,8 @@
defmodule Gringotts.Gateways.Stripe do
-
@moduledoc """
Stripe gateway implementation. For reference see [Stripe's API documentation](https://stripe.com/docs/api).
The following features of Stripe are implemented:
-
+
| Action | Method |
| ------ | ------ |
| Pre-authorize | `authorize/3` |
@@ -18,7 +17,7 @@ defmodule Gringotts.Gateways.Stripe do
Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply
optional arguments for transactions with the Stripe gateway. The following keys
are supported:
-
+
| Key | Remark | Status |
| ---- | --- | ---- |
| `currency` | | **Implemented** |
@@ -38,18 +37,18 @@ defmodule Gringotts.Gateways.Stripe do
| `default_source` | | Not implemented |
| `email` | | Not implemented |
| `shipping` | | Not implemented |
-
+
## Registering your Stripe account at `Gringotts`
After [making an account on Stripe](https://stripe.com/), head
to the dashboard and find your account `secrets` in the `API` section.
-
+
## Here's how the secrets map to the required configuration parameters for Stripe:
| Config parameter | Stripe secret |
| ------- | ---- |
| `:secret_key` | **Secret key** |
-
+
Your Application config must look something like this:
-
+
config :gringotts, Gringotts.Gateways.Stripe,
secret_key: "your_secret_key",
default_currency: "usd"
@@ -58,11 +57,12 @@ defmodule Gringotts.Gateways.Stripe do
@base_url "https://api.stripe.com/v1"
use Gringotts.Gateways.Base
- use Gringotts.Adapter, required_config: [:secret_key, :default_currency]
+ use Gringotts.Adapter, required_config: [:secret_key]
alias Gringotts.{
CreditCard,
- Address
+ Address,
+ Money
}
@doc """
@@ -71,17 +71,17 @@ defmodule Gringotts.Gateways.Stripe do
The authorization validates the card details with the banking network,
places a hold on the transaction amount in the customer’s issuing bank and
also triggers risk management. Funds are not transferred.
-
+
Stripe returns an `charge_id` which should be stored at your side and can be used later to:
* `capture/3` an amount.
* `void/2` a pre-authorization.
-
+
## Note
Uncaptured charges expire in 7 days. For more information, [see authorizing charges and settling later](https://support.stripe.com/questions/can-i-authorize-a-charge-and-then-wait-to-settle-it-later).
## Example
The following session shows how one would (pre) authorize a payment of $10 on a sample `card`.
-
+
iex> card = %CreditCard{
first_name: "John",
last_name: "Smith",
@@ -104,7 +104,7 @@ defmodule Gringotts.Gateways.Stripe do
iex> Gringotts.authorize(Gringotts.Gateways.Stripe, amount, card, opts)
"""
- @spec authorize(number, CreditCard.t() | String.t(), keyword) :: map
+ @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: map
def authorize(amount, payment, opts \\ []) do
params = create_params_for_auth_or_purchase(amount, payment, opts, false)
commit(:post, "charges", params, opts)
@@ -112,10 +112,10 @@ defmodule Gringotts.Gateways.Stripe do
@doc """
Transfers amount from the customer to the merchant.
-
+
Stripe attempts to process a purchase on behalf of the customer, by debiting
amount from the customer's account by charging the customer's card.
-
+
## Example
The following session shows how one would process a payment in one-shot,
without (pre) authorization.
@@ -142,7 +142,7 @@ defmodule Gringotts.Gateways.Stripe do
iex> Gringotts.purchase(Gringotts.Gateways.Stripe, amount, card, opts)
"""
- @spec purchase(number, CreditCard.t() | String.t(), keyword) :: map
+ @spec purchase(Money.t(), CreditCard.t() | String.t(), keyword) :: map
def purchase(amount, payment, opts \\ []) do
params = create_params_for_auth_or_purchase(amount, payment, opts)
commit(:post, "charges", params, opts)
@@ -168,7 +168,7 @@ defmodule Gringotts.Gateways.Stripe do
iex> Gringotts.capture(Gringotts.Gateways.Stripe, id, amount, opts)
"""
- @spec capture(String.t(), number, keyword) :: map
+ @spec capture(String.t(), Money.t(), keyword) :: map
def capture(id, amount, opts \\ []) do
params = optional_params(opts) ++ amount_params(amount)
commit(:post, "charges/#{id}/capture", params, opts)
@@ -176,7 +176,7 @@ defmodule Gringotts.Gateways.Stripe do
@doc """
Voids the referenced payment.
-
+
This method attempts a reversal of the either a previous `purchase/3` or
`authorize/3` referenced by `charge_id`.
As a consequence, the customer will never see any booking on his
@@ -190,7 +190,7 @@ defmodule Gringotts.Gateways.Stripe do
## Voiding a previous purchase
Stripe will reverse the payment, by sending all the amount back to the
customer. Note that this is not the same as `refund/3`.
-
+
## Example
The following session shows how one would void a previous (pre)
authorization. Remember that our `capture/3` example only did a partial
@@ -223,7 +223,7 @@ defmodule Gringotts.Gateways.Stripe do
iex> Gringotts.refund(Gringotts.Gateways.Stripe, amount, id, opts)
"""
- @spec refund(number, String.t(), keyword) :: map
+ @spec refund(Money.t(), String.t(), keyword) :: map
def refund(amount, id, opts \\ []) do
params = optional_params(opts) ++ amount_params(amount)
commit(:post, "charges/#{id}/refund", params, opts)
@@ -231,7 +231,7 @@ defmodule Gringotts.Gateways.Stripe do
@doc """
Stores the payment-source data for later use.
-
+
Stripe can store the payment-source details, for example card which can be used to effectively
to process One-Click and Recurring_ payments, and return a `customer_id` for reference.
@@ -285,27 +285,22 @@ defmodule Gringotts.Gateways.Stripe do
# Private methods
defp create_params_for_auth_or_purchase(amount, payment, opts, capture \\ true) do
- params = optional_params(opts)
- ++ [capture: capture]
- ++ amount_params(amount)
- ++ source_params(payment, opts)
-
- params
- |> Keyword.has_key?(:currency)
- |> with_currency(params, opts[:config])
+ [capture: capture] ++
+ optional_params(opts) ++ amount_params(amount) ++ source_params(payment, opts)
end
- def with_currency(true, params, _), do: params
- def with_currency(false, params, config), do: [{:currency, config[:default_currency]} | params]
-
defp create_card_token(params, opts) do
commit(:post, "tokens", params, opts)
end
- defp amount_params(amount), do: [amount: money_to_cents(amount)]
+ defp amount_params(amount) do
+ {currency, int_value, _} = Money.to_integer(amount)
+ [amount: int_value, currency: currency]
+ end
defp source_params(token_or_customer, _) when is_binary(token_or_customer) do
[head, _] = String.split(token_or_customer, "_")
+
case head do
"tok" -> [source: token_or_customer]
"cus" -> [customer: token_or_customer]
@@ -313,64 +308,68 @@ defmodule Gringotts.Gateways.Stripe do
end
defp source_params(%CreditCard{} = card, opts) do
- params =
- card_params(card) ++
- address_params(opts[:address])
+ params = card_params(card) ++ address_params(opts[:address])
response = create_card_token(params, opts)
- case Map.has_key?(response, "error") do
- true -> []
- false -> response
- |> Map.get("id")
- |> source_params(opts)
+ if Map.has_key?(response, "error") do
+ []
+ else
+ response
+ |> Map.get("id")
+ |> source_params(opts)
end
end
defp source_params(_, _), do: []
defp card_params(%CreditCard{} = card) do
- [ "card[name]": CreditCard.full_name(card),
+ [
+ "card[name]": CreditCard.full_name(card),
"card[number]": card.number,
"card[exp_year]": card.year,
"card[exp_month]": card.month,
"card[cvc]": card.verification_code
- ]
+ ]
end
defp card_params(_), do: []
defp address_params(%Address{} = address) do
- [ "card[address_line1]": address.street1,
+ [
+ "card[address_line1]": address.street1,
"card[address_line2]": address.street2,
- "card[address_city]": address.city,
+ "card[address_city]": address.city,
"card[address_state]": address.region,
- "card[address_zip]": address.postal_code,
+ "card[address_zip]": address.postal_code,
"card[address_country]": address.country
]
end
defp address_params(_), do: []
- defp commit(method, path, params \\ [], opts \\ []) do
+ defp commit(method, path, params, opts) do
auth_token = "Bearer " <> opts[:config][:secret_key]
- headers = [{"Content-Type", "application/x-www-form-urlencoded"}, {"Authorization", auth_token}]
- data = params_to_string(params)
- response = HTTPoison.request(method, "#{@base_url}/#{path}", data, headers)
+
+ headers = [
+ {"Content-Type", "application/x-www-form-urlencoded"},
+ {"Authorization", auth_token}
+ ]
+
+ response = HTTPoison.request(method, "#{@base_url}/#{path}", {:form, params}, headers)
format_response(response)
end
defp optional_params(opts) do
opts
- |> Keyword.delete(:config)
- |> Keyword.delete(:address)
+ |> Keyword.delete(:config)
+ |> Keyword.delete(:address)
end
defp format_response(response) do
case response do
- {:ok, %HTTPoison.Response{body: body}} -> body |> Poison.decode!
+ {:ok, %HTTPoison.Response{body: body}} -> body |> Poison.decode!()
_ -> %{"error" => "something went wrong, please try again later"}
end
end
-
end
diff --git a/test/gateways/monei_test.exs b/test/gateways/monei_test.exs
index 3aaa88a1..ae41e9a0 100644
--- a/test/gateways/monei_test.exs
+++ b/test/gateways/monei_test.exs
@@ -233,12 +233,12 @@ defmodule Gringotts.Gateways.MoneiTest do
Bypass.expect_once(bypass, "POST", "/v1/registrations", fn conn ->
p_conn = parse(conn)
params = p_conn.body_params
- params["card.cvv"] == "123"
- params["card.expiryMonth"] == "12"
- params["card.expiryYear"] == "2099"
- params["card.holder"] == "Harry Potter"
- params["card.number"] == "4200000000000000"
- params["paymentBrand"] == "VISA"
+ assert params["card.cvv"] == "123"
+ assert params["card.expiryMonth"] == "12"
+ assert params["card.expiryYear"] == "2099"
+ assert params["card.holder"] == "Harry Potter"
+ assert params["card.number"] == "4200000000000000"
+ assert params["paymentBrand"] == "VISA"
Plug.Conn.resp(conn, 200, @store_success)
end)
diff --git a/test/gateways/stripe_test.exs b/test/gateways/stripe_test.exs
deleted file mode 100644
index 73bc211a..00000000
--- a/test/gateways/stripe_test.exs
+++ /dev/null
@@ -1,58 +0,0 @@
-defmodule Gringotts.Gateways.StripeTest do
-
- use ExUnit.Case
-
- alias Gringotts.Gateways.Stripe
- alias Gringotts.{
- CreditCard,
- Address
- }
-
- @card %CreditCard{
- first_name: "John",
- last_name: "Smith",
- number: "4242424242424242",
- year: "2017",
- month: "12",
- verification_code: "123"
- }
-
- @address %Address{
- street1: "123 Main",
- street2: "Suite 100",
- city: "New York",
- region: "NY",
- country: "US",
- postal_code: "11111"
- }
-
- @required_opts [config: [api_key: "sk_test_vIX41hayC0BKrPWQerLuOMld"], currency: "usd"]
- @optional_opts [address: @address]
-
- describe "authorize/3" do
- # test "should authorize wth card and required opts attrs" do
- # amount = 5
- # response = Stripe.authorize(amount, @card, @required_opts ++ @optional_opts)
-
- # assert Map.has_key?(response, "id")
- # assert response["amount"] == 500
- # assert response["captured"] == false
- # assert response["currency"] == "usd"
- # end
-
- # test "should not authorize if card is not passed" do
- # amount = 5
- # response = Stripe.authorize(amount, %{}, @required_opts ++ @optional_opts)
-
- # assert Map.has_key?(response, "error")
- # end
-
- # test "should not authorize if required opts not present" do
- # amount = 5
- # response = Stripe.authorize(amount, @card, @optional_opts)
-
- # assert Map.has_key?(response, "error")
- # end
-
- end
-end
diff --git a/test/integration/gateways/stripe_test.exs b/test/integration/gateways/stripe_test.exs
new file mode 100644
index 00000000..b390f989
--- /dev/null
+++ b/test/integration/gateways/stripe_test.exs
@@ -0,0 +1,44 @@
+defmodule Gringotts.Gateways.StripeTest do
+
+ use ExUnit.Case
+
+ alias Gringotts.Gateways.Stripe
+ alias Gringotts.{
+ CreditCard,
+ Address
+ }
+
+ @moduletag integration: true
+
+ @amount Money.new(5, :USD)
+ @card %CreditCard{
+ first_name: "John",
+ last_name: "Smith",
+ number: "4242424242424242",
+ year: "2068", # Can't be more than 50 years in the future, Haha.
+ month: "12",
+ verification_code: "123"
+ }
+
+ @address %Address{
+ street1: "123 Main",
+ street2: "Suite 100",
+ city: "New York",
+ region: "NY",
+ country: "US",
+ postal_code: "11111"
+ }
+
+ @required_opts [config: [secret_key: "sk_test_vIX41hayC0BKrPWQerLuOMld"]]
+ @optional_opts [address: @address]
+
+ describe "authorize/3" do
+ test "with correct params" do
+ response = Stripe.authorize(@amount, @card, @required_opts ++ @optional_opts)
+ assert Map.has_key?(response, "id")
+ assert response["amount"] == 500
+ assert response["captured"] == false
+ assert response["currency"] == "usd"
+ end
+ end
+end
diff --git a/test/integration/money.exs b/test/integration/money.exs
index ca42febe..3f5691ba 100644
--- a/test/integration/money.exs
+++ b/test/integration/money.exs
@@ -26,7 +26,7 @@ defmodule Gringotts.Integration.Gateways.MoneyTest do
test "to_integer" do
assert match? {"EUR", 4200, -2}, MoneyProtocol.to_integer(@ex_money)
- assert match? {"BHD", 42000, -3}, MoneyProtocol.to_integer(@ex_money_bhd)
+ assert match? {"BHD", 42_000, -3}, MoneyProtocol.to_integer(@ex_money_bhd)
end
test "to_string" do
From f99f2586a3e5a968836474e67e48621b051110b1 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Fri, 9 Feb 2018 14:03:24 +0530
Subject: [PATCH 35/60] Adapted Trexle for new `Response.t`
* Trexle does not seem to provide fraud risk, AVS, CVV validation
results. There are no docs for this.
---
lib/gringotts/gateways/trexle.ex | 12 ++++++--
test/gateways/trexle_test.exs | 52 ++++++++++++--------------------
2 files changed, 28 insertions(+), 36 deletions(-)
diff --git a/lib/gringotts/gateways/trexle.ex b/lib/gringotts/gateways/trexle.ex
index 523d4c2f..a9c3d988 100644
--- a/lib/gringotts/gateways/trexle.ex
+++ b/lib/gringotts/gateways/trexle.ex
@@ -331,17 +331,23 @@ defmodule Gringotts.Gateways.Trexle do
{
:ok,
- Response.success(authorization: token, message: message, raw: results, status_code: code)
+ %Response{id: token, message: message, raw: body, status_code: code}
}
end
defp respond({:ok, %{status_code: status_code, body: body}}) do
{:ok, results} = decode(body)
detail = results["detail"]
- {:error, Response.error(status_code: status_code, message: detail, raw: results)}
+ {:error, %Response{status_code: status_code, message: detail, reason: detail, raw: body}}
end
defp respond({:error, %HTTPoison.Error{} = error}) do
- {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")}
+ {
+ :error,
+ %Response{
+ reason: "network related failure",
+ message: "HTTPoison says '#{error.reason}' [ID: #{error.id || "nil"}]"
+ }
+ }
end
end
diff --git a/test/gateways/trexle_test.exs b/test/gateways/trexle_test.exs
index f8a50562..7078b9f6 100644
--- a/test/gateways/trexle_test.exs
+++ b/test/gateways/trexle_test.exs
@@ -14,8 +14,8 @@ defmodule Gringotts.Gateways.TrexleTest do
@valid_card %CreditCard{
first_name: "Harry",
last_name: "Potter",
- number: "4200000000000000",
- year: 2099,
+ number: "4000056655665556",
+ year: 2068,
month: 12,
verification_code: "123",
brand: "VISA"
@@ -24,7 +24,7 @@ defmodule Gringotts.Gateways.TrexleTest do
@invalid_card %CreditCard{
first_name: "Harry",
last_name: "Potter",
- number: "4200000000000000",
+ number: "4000056655665556",
year: 2010,
month: 12,
verification_code: "123",
@@ -46,10 +46,10 @@ defmodule Gringotts.Gateways.TrexleTest do
# 50 US cents, trexle does not work with amount smaller than 50 cents.
@bad_amount Money.new("0.49", :USD)
- @valid_token "7214344252e11af79c0b9e7b4f3f6234"
- @invalid_token "14a62fff80f24a25f775eeb33624bbb3"
+ @valid_token "some_valid_token"
+ @invalid_token "some_invalid_token"
- @auth %{api_key: "7214344252e11af79c0b9e7b4f3f6234"}
+ @auth %{api_key: "some_api_key"}
@opts [
config: @auth,
email: "masterofdeath@ministryofmagic.gov",
@@ -64,10 +64,7 @@ defmodule Gringotts.Gateways.TrexleTest do
request: fn _method, _url, _body, _headers, _options ->
MockResponse.test_for_purchase_with_valid_card()
end do
- {:ok, response} = Trexle.purchase(@amount, @valid_card, @opts)
- assert response.status_code == 201
- assert response.raw["response"]["success"] == true
- assert response.raw["response"]["captured"] == false
+ assert {:ok, response} = Trexle.purchase(@amount, @valid_card, @opts)
end
end
@@ -76,10 +73,8 @@ defmodule Gringotts.Gateways.TrexleTest do
request: fn _method, _url, _body, _headers, _options ->
MockResponse.test_for_purchase_with_invalid_card()
end do
- {:error, response} = Trexle.purchase(@amount, @invalid_card, @opts)
- assert response.status_code == 400
- assert response.success == false
- assert response.message == "Your card's expiration year is invalid."
+ assert {:error, response} = Trexle.purchase(@amount, @invalid_card, @opts)
+ assert response.reason == "Your card's expiration year is invalid."
end
end
@@ -88,10 +83,9 @@ defmodule Gringotts.Gateways.TrexleTest do
request: fn _method, _url, _body, _headers, _options ->
MockResponse.test_for_purchase_with_invalid_amount()
end do
- {:error, response} = Trexle.purchase(@bad_amount, @valid_card, @opts)
+ assert {:error, response} = Trexle.purchase(@bad_amount, @valid_card, @opts)
assert response.status_code == 400
- assert response.success == false
- assert response.message == "Amount must be at least 50 cents"
+ assert response.reason == "Amount must be at least 50 cents"
end
end
end
@@ -102,10 +96,8 @@ defmodule Gringotts.Gateways.TrexleTest do
request: fn _method, _url, _body, _headers, _options ->
MockResponse.test_for_authorize_with_valid_card()
end do
- {:ok, response} = Trexle.authorize(@amount, @valid_card, @opts)
+ assert {:ok, response} = Trexle.authorize(@amount, @valid_card, @opts)
assert response.status_code == 201
- assert response.raw["response"]["success"] == true
- assert response.raw["response"]["captured"] == false
end
end
end
@@ -116,10 +108,8 @@ defmodule Gringotts.Gateways.TrexleTest do
request: fn _method, _url, _body, _headers, _options ->
MockResponse.test_for_authorize_with_valid_card()
end do
- {:ok, response} = Trexle.refund(@amount, @valid_token, @opts)
+ assert {:ok, response} = Trexle.refund(@amount, @valid_token, @opts)
assert response.status_code == 201
- assert response.raw["response"]["success"] == true
- assert response.raw["response"]["captured"] == false
end
end
end
@@ -130,11 +120,9 @@ defmodule Gringotts.Gateways.TrexleTest do
request: fn _method, _url, _body, _headers, _options ->
MockResponse.test_for_capture_with_valid_chargetoken()
end do
- {:ok, response} = Trexle.capture(@valid_token, @amount, @opts)
+ assert {:ok, response} = Trexle.capture(@valid_token, @amount, @opts)
+ # Why 200 here?? It's 201 everywhere lese. Check trexle docs.
assert response.status_code == 200
- assert response.raw["response"]["success"] == true
- assert response.raw["response"]["captured"] == true
- assert response.message == "Transaction approved"
end
end
@@ -143,10 +131,9 @@ defmodule Gringotts.Gateways.TrexleTest do
request: fn _method, _url, _body, _headers, _options ->
MockResponse.test_for_capture_with_invalid_chargetoken()
end do
- {:error, response} = Trexle.capture(@invalid_token, @amount, @opts)
+ assert {:error, response} = Trexle.capture(@invalid_token, @amount, @opts)
assert response.status_code == 400
- assert response.success == false
- assert response.message == "invalid token"
+ assert response.reason == "invalid token"
end
end
end
@@ -157,7 +144,7 @@ defmodule Gringotts.Gateways.TrexleTest do
request: fn _method, _url, _body, _headers, _options ->
MockResponse.test_for_store_with_valid_card()
end do
- {:ok, response} = Trexle.store(@valid_card, @opts)
+ assert {:ok, response} = Trexle.store(@valid_card, @opts)
assert response.status_code == 201
end
end
@@ -170,8 +157,7 @@ defmodule Gringotts.Gateways.TrexleTest do
MockResponse.test_for_network_failure()
end do
{:error, response} = Trexle.authorize(@amount, @valid_card, @opts)
- assert response.success == false
- assert response.message == "HTTPoison says 'some_hackney_error'"
+ assert response.message == "HTTPoison says 'some_hackney_error' [ID: some_hackney_error_id]"
end
end
end
From a91deec959e21aa3e61c726faf9c4ead08d46598 Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Wed, 21 Mar 2018 17:25:51 +0530
Subject: [PATCH 36/60] [CAMS] Adapt for new Response.t (#120)
* Refactored ResponseHandler, updated Response.t
* CAMS now parses AVS and CVV response - that was missing till now.
* Removes unnecessary `parse` clause.
* Mock tests shouldn't use gringotts.ex
---
lib/gringotts/gateways/cams.ex | 151 ++++++++++++++++++---------------
test/gateways/cams_test.exs | 93 +++++++++-----------
2 files changed, 123 insertions(+), 121 deletions(-)
diff --git a/lib/gringotts/gateways/cams.ex b/lib/gringotts/gateways/cams.ex
index 0cb0c4e8..12687f6b 100644
--- a/lib/gringotts/gateways/cams.ex
+++ b/lib/gringotts/gateways/cams.ex
@@ -144,7 +144,7 @@ defmodule Gringotts.Gateways.Cams do
## Optional Fields
options[
order_id: String,
- description: String
+ description: String
]
## Examples
@@ -163,7 +163,7 @@ defmodule Gringotts.Gateways.Cams do
iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Cams, money, card)
```
"""
- @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response.t()}
def authorize(money, %CreditCard{} = card, options) do
params =
[]
@@ -210,7 +210,7 @@ defmodule Gringotts.Gateways.Cams do
iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Cams, money, authorization)
```
"""
- @spec capture(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
+ @spec capture(Money.t(), String.t(), keyword) :: {:ok | :error, Response.t()}
def capture(money, transaction_id, options) do
params =
[transactionid: transaction_id]
@@ -246,7 +246,7 @@ defmodule Gringotts.Gateways.Cams do
iex> Gringotts.purchase(Gringotts.Gateways.Cams, money, card)
```
"""
- @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response.t()}
def purchase(money, %CreditCard{} = card, options) do
params =
[]
@@ -278,7 +278,7 @@ defmodule Gringotts.Gateways.Cams do
iex> Gringotts.refund(Gringotts.Gateways.Cams, money, capture_id)
```
"""
- @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
+ @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response.t()}
def refund(money, transaction_id, options) do
params =
[transactionid: transaction_id]
@@ -305,7 +305,7 @@ defmodule Gringotts.Gateways.Cams do
iex> Gringotts.void(Gringotts.Gateways.Cams, auth_id)
```
"""
- @spec void(String.t(), keyword) :: {:ok | :error, Response}
+ @spec void(String.t(), keyword) :: {:ok | :error, Response.t()}
def void(transaction_id, options) do
params = [transactionid: transaction_id]
commit("void", params, options)
@@ -328,7 +328,7 @@ defmodule Gringotts.Gateways.Cams do
iex> Gringotts.validate(Gringotts.Gateways.Cams, card)
```
"""
- @spec validate(CreditCard.t(), keyword) :: {:ok | :error, Response}
+ @spec validate(CreditCard.t(), keyword) :: {:ok | :error, Response.t()}
def validate(card, options) do
params =
[]
@@ -378,76 +378,93 @@ defmodule Gringotts.Gateways.Cams do
@moduledoc false
alias Gringotts.Response
+ # Fetched from CAMS POST API docs.
+ @avs_code_translator %{
+ "X" => {nil, "pass: 9-character numeric ZIP"},
+ "Y" => {nil, "pass: 5-character numeric ZIP"},
+ "D" => {nil, "pass: 5-character numeric ZIP"},
+ "M" => {nil, "pass: 5-character numeric ZIP"},
+ "2" => {"pass: customer name", "pass: 5-character numeric ZIP"},
+ "6" => {"pass: customer name", "pass: 5-character numeric ZIP"},
+ "A" => {"pass: only address", "fail"},
+ "B" => {"pass: only address", "fail"},
+ "3" => {"pass: address, customer name", "fail"},
+ "7" => {"pass: address, customer name", "fail"},
+ "W" => {"fail", "pass: 9-character numeric ZIP match"},
+ "Z" => {"fail", "pass: 5-character ZIP match"},
+ "P" => {"fail", "pass: 5-character ZIP match"},
+ "L" => {"fail", "pass: 5-character ZIP match"},
+ "1" => {"pass: only customer name", "pass: 5-character ZIP"},
+ "5" => {"pass: only customer name", "pass: 5-character ZIP"},
+ "N" => {"fail", "fail"},
+ "C" => {"fail", "fail"},
+ "4" => {"fail", "fail"},
+ "8" => {"fail", "fail"},
+ "U" => {nil, nil},
+ "G" => {nil, nil},
+ "I" => {nil, nil},
+ "R" => {nil, nil},
+ "E" => {nil, nil},
+ "S" => {nil, nil},
+ "0" => {nil, nil},
+ "O" => {nil, nil},
+ "" => {nil, nil}
+ }
+
+ # Fetched from CAMS POST API docs.
+ @cvc_code_translator %{
+ "M" => "pass",
+ "N" => "fail",
+ "P" => "not_processed",
+ "S" => "Merchant indicated that CVV2/CVC2 is not present on card",
+ "U" => "Issuer is not certified and/or has not provided Visa encryption key"
+ }
+
@doc false
def parse({:ok, %HTTPoison.Response{body: body, status_code: 200}}) do
- body = URI.decode_query(body)
-
- [status_code: 200]
- |> set_authorization(body)
- |> set_success(body)
- |> set_message(body)
- |> set_params(body)
- |> set_error_code(body)
- |> handle_opts()
- end
-
- def parse({:ok, %HTTPoison.Response{body: body, status_code: 400}}) do
- body = URI.decode_query(body)
- set_params([status_code: 400], body)
+ decoded_body = URI.decode_query(body)
+ {street, zip_code} = @avs_code_translator[decoded_body["avsresponse"]]
+ gateway_code = decoded_body["response_code"]
+ message = decoded_body["responsetext"]
+ response = %Response{
+ status_code: 200,
+ id: decoded_body["transactionid"],
+ gateway_code: gateway_code,
+ avs_result: %{street: street, zip_code: zip_code},
+ cvc_result: @cvc_code_translator[decoded_body["cvvresponse"]],
+ message: decoded_body["responsetext"],
+ raw: body
+ }
+
+ if successful?(gateway_code) do
+ {:ok, response}
+ else
+ {:error, %{response | reason: message}}
+ end
end
- def parse({:ok, %HTTPoison.Response{body: body, status_code: 404}}) do
- body = URI.decode_query(body)
+ def parse({:ok, %HTTPoison.Response{body: body, status_code: code}}) do
+ response = %Response{
+ status_code: code,
+ raw: body
+ }
- [status_code: 404]
- |> handle_not_found(body)
- |> handle_opts()
+ {:error, response}
end
def parse({:error, %HTTPoison.Error{} = error}) do
- [
- message: "HTTPoison says #{error.reason}",
- error_code: error.id,
- success: false
- ]
- end
-
- defp set_authorization(opts, %{"transactionid" => id}) do
- opts ++ [authorization: id]
- end
-
- defp set_message(opts, %{"responsetext" => message}) do
- opts ++ [message: message]
- end
-
- defp set_params(opts, body) do
- opts ++ [params: body]
- end
-
- defp set_error_code(opts, %{"response_code" => response_code}) do
- opts ++ [error_code: response_code]
- end
-
- defp set_success(opts, %{"response_code" => response_code}) do
- opts ++ [success: response_code == "100"]
+ {
+ :error,
+ %Response{
+ reason: "network related failure",
+ message: "HTTPoison says '#{error.reason}' [ID: #{error.id || "nil"}]",
+ success: false
+ }
+ }
end
- defp handle_not_found(opts, body) do
- error = parse_html(body)
- opts ++ [success: false, message: error]
- end
-
- defp parse_html(body) do
- error_message = List.to_string(Map.keys(body))
- [_ | parse_message] = Regex.run(~r|(.*)|, error_message)
- List.to_string(parse_message)
- end
-
- defp handle_opts(opts) do
- case Keyword.fetch(opts, :success) do
- {:ok, true} -> {:ok, Response.success(opts)}
- {:ok, false} -> {:ok, Response.error(opts)}
- end
+ defp successful?(gateway_code) do
+ gateway_code == "100"
end
end
end
diff --git a/test/gateways/cams_test.exs b/test/gateways/cams_test.exs
index 6faf56b2..edf0e5f4 100644
--- a/test/gateways/cams_test.exs
+++ b/test/gateways/cams_test.exs
@@ -43,9 +43,10 @@ defmodule Gringotts.Gateways.CamsTest do
}
@auth %{username: "some_secret_user_name", password: "some_secret_password"}
@options [
- order_id: 0001,
+ order_id: 1,
billing_address: @address,
- description: "Store Purchase"
+ description: "Store Purchase",
+ config: @auth
]
@money Money.new(:USD, 100)
@@ -53,40 +54,32 @@ defmodule Gringotts.Gateways.CamsTest do
@money_less Money.new(:USD, 99)
@bad_currency Money.new(:INR, 100)
- @authorization "some_transaction_id"
- @bad_authorization "some_fake_transaction_id"
-
- setup_all do
- Application.put_env(:gringotts, Gateway,
- username: "some_secret_user_name",
- password: "some_secret_password"
- )
- end
+ @id "some_transaction_id"
+ @bad_id "some_fake_transaction_id"
describe "purchase" do
test "with correct params" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.successful_purchase() end do
- {:ok, %Response{success: result}} = Gringotts.purchase(Gateway, @money, @card, @options)
- assert result
+ assert {:ok, %Response{}} = Gateway.purchase(@money, @card, @options)
end
end
test "with bad card" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.failed_purchase_with_bad_credit_card() end do
- {:ok, %Response{message: result}} =
- Gringotts.purchase(Gateway, @money, @bad_card, @options)
+ {:error, %Response{reason: reason}} =
+ Gateway.purchase(@money, @bad_card, @options)
- assert String.contains?(result, "Invalid Credit Card Number")
+ assert String.contains?(reason, "Invalid Credit Card Number")
end
end
test "with invalid currency" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.with_invalid_currency() end do
- {:ok, %Response{message: result}} = Gringotts.purchase(Gateway, @bad_currency, @card, @options)
- assert String.contains?(result, "The cc payment type")
+ {:error, %Response{reason: reason}} = Gateway.purchase(@bad_currency, @card, @options)
+ assert String.contains?(reason, "The cc payment type")
end
end
end
@@ -95,18 +88,17 @@ defmodule Gringotts.Gateways.CamsTest do
test "with correct params" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.successful_authorize() end do
- {:ok, %Response{success: result}} = Gringotts.authorize(Gateway, @money, @card, @options)
- assert result
+ assert {:ok, %Response{}} = Gateway.authorize(@money, @card, @options)
end
end
test "with bad card" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.failed_authorized_with_bad_card() end do
- {:ok, %Response{message: result}} =
- Gringotts.authorize(Gateway, @money, @bad_card, @options)
+ {:error, %Response{reason: reason}} =
+ Gateway.authorize(@money, @bad_card, @options)
- assert String.contains?(result, "Invalid Credit Card Number")
+ assert String.contains?(reason, "Invalid Credit Card Number")
end
end
end
@@ -114,49 +106,45 @@ defmodule Gringotts.Gateways.CamsTest do
describe "capture" do
test "with full amount" do
with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_capture() end do
- {:ok, %Response{success: result}} =
- Gringotts.capture(Gateway, @money, @authorization, @options)
-
- assert result
+ assert {:ok, %Response{}} =
+ Gateway.capture(@money, @id , @options)
end
end
test "with partial amount" do
with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_capture() end do
- {:ok, %Response{success: result}} =
- Gringotts.capture(Gateway, @money_less, @authorization, @options)
-
- assert result
+ assert {:ok, %Response{}} =
+ Gateway.capture(@money_less, @id , @options)
end
end
test "with invalid transaction_id" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.invalid_transaction_id() end do
- {:ok, %Response{message: result}} =
- Gringotts.capture(Gateway, @money, @bad_authorization, @options)
+ {:error, %Response{reason: reason}} =
+ Gateway.capture(@money, @bad_id, @options)
- assert String.contains?(result, "Transaction not found")
+ assert String.contains?(reason, "Transaction not found")
end
end
- test "with more than authorization amount" do
+ test "with more than authorized amount" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.more_than_authorization_amount() end do
- {:ok, %Response{message: result}} =
- Gringotts.capture(Gateway, @money_more, @authorization, @options)
+ {:error, %Response{reason: reason}} =
+ Gateway.capture(@money_more, @id , @options)
- assert String.contains?(result, "exceeds the authorization amount")
+ assert String.contains?(reason, "exceeds the authorization amount")
end
end
test "on already captured transaction" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.multiple_capture_on_same_transaction() end do
- {:ok, %Response{message: result}} =
- Gringotts.capture(Gateway, @money, @authorization, @options)
+ {:error, %Response{reason: reason}} =
+ Gateway.capture(@money, @id , @options)
- assert String.contains?(result, "A capture requires that")
+ assert String.contains?(reason, "A capture requires that")
end
end
end
@@ -164,20 +152,18 @@ defmodule Gringotts.Gateways.CamsTest do
describe "refund" do
test "with correct params" do
with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_refund() end do
- {:ok, %Response{success: result}} =
- Gringotts.refund(Gateway, @money, @authorization, @options)
-
- assert result
+ assert {:ok, %Response{}} =
+ Gateway.refund(@money, @id , @options)
end
end
test "with more than purchased amount" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.more_than_purchase_amount() end do
- {:ok, %Response{message: result}} =
- Gringotts.refund(Gateway, @money_more, @authorization, @options)
+ {:error, %Response{reason: reason}} =
+ Gateway.refund(@money_more, @id , @options)
- assert String.contains?(result, "Refund amount may not exceed")
+ assert String.contains?(reason, "Refund amount may not exceed")
end
end
end
@@ -185,16 +171,16 @@ defmodule Gringotts.Gateways.CamsTest do
describe "void" do
test "with correct params" do
with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_void() end do
- {:ok, %Response{message: result}} = Gringotts.void(Gateway, @authorization, @options)
- assert String.contains?(result, "Void Successful")
+ {:ok, %Response{message: message}} = Gateway.void(@id , @options)
+ assert String.contains?(message, "Void Successful")
end
end
test "with invalid transaction_id" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.invalid_transaction_id() end do
- {:ok, %Response{message: result}} = Gringotts.void(Gateway, @bad_authorization, @options)
- assert String.contains?(result, "Transaction not found")
+ {:error, %Response{reason: reason}} = Gateway.void(@bad_id, @options)
+ assert String.contains?(reason, "Transaction not found")
end
end
end
@@ -203,8 +189,7 @@ defmodule Gringotts.Gateways.CamsTest do
test "with correct params" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.validate_creditcard() end do
- {:ok, %Response{success: result}} = Gateway.validate(@card, @options ++ [config: @auth])
- assert result
+ assert {:ok, %Response{}} = Gateway.validate(@card, @options ++ [config: @auth])
end
end
end
From 7bf1857c0cd0c2cc48a76b8a77eb7e6335465b9c Mon Sep 17 00:00:00 2001
From: Ananya Bahadur
Date: Thu, 22 Mar 2018 12:42:27 +0530
Subject: [PATCH 37/60] Format project and migrate to CodeCov (#135)
* Update travis config, add .formater.exs
* Migrate to CodeCov
* Travis will run the formatter check
* Add git-hooks and update contribution guide
* Changed credo lin-length config from 80 to 100
* Ran the formatter on the project
* Fix credo warnings
---
.credo.exs | 2 +-
.formatter.exs | 7 +
.scripts/inch_report.sh | 20 +
.scripts/post-commit | 12 +
.scripts/pre-commit | 50 ++
.travis.yml | 26 +-
CONTRIBUTING.md | 83 ++-
lib/gringotts.ex | 54 +-
lib/gringotts/adapter.ex | 15 +-
lib/gringotts/address.ex | 2 +
lib/gringotts/credit_card.ex | 22 +-
lib/gringotts/gateways/authorize_net.ex | 80 ++-
lib/gringotts/gateways/base.ex | 14 +-
lib/gringotts/gateways/bogus.ex | 28 +-
lib/gringotts/gateways/cams.ex | 3 +-
lib/gringotts/gateways/global_collect.ex | 60 +-
lib/gringotts/gateways/paymill.ex | 263 ++++-----
lib/gringotts/gateways/wire_card.ex | 193 ++++---
lib/gringotts/response.ex | 39 +-
mix.exs | 18 +-
test/gateways/authorize_net_test.exs | 37 +-
test/gateways/bogus_test.exs | 21 +-
test/gateways/cams_test.exs | 29 +-
test/gateways/global_collect_test.exs | 84 ++-
test/gateways/monei_test.exs | 32 +-
test/gateways/trexle_test.exs | 4 +-
test/gateways/wire_card_test.exs | 12 +-
test/gringotts_test.exs | 6 +-
test/integration/gateways/monei_test.exs | 17 +-
test/integration/gateways/stripe_test.exs | 7 +-
test/integration/money.exs | 32 +-
test/mocks/authorize_net_mock.exs | 673 +++++++++++++---------
test/mocks/cams_mock.exs | 350 +++++------
test/mocks/global_collect_mock.exs | 255 ++++----
test/mocks/trexle_mock.exs | 340 +++++------
35 files changed, 1623 insertions(+), 1267 deletions(-)
create mode 100644 .formatter.exs
create mode 100644 .scripts/inch_report.sh
create mode 100755 .scripts/post-commit
create mode 100755 .scripts/pre-commit
diff --git a/.credo.exs b/.credo.exs
index df92ae85..9381d3f7 100644
--- a/.credo.exs
+++ b/.credo.exs
@@ -77,7 +77,7 @@
{Credo.Check.Readability.FunctionNames},
{Credo.Check.Readability.LargeNumbers},
- {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80},
+ {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 100},
{Credo.Check.Readability.ModuleAttributeNames},
{Credo.Check.Readability.ModuleDoc},
{Credo.Check.Readability.ModuleNames},
diff --git a/.formatter.exs b/.formatter.exs
new file mode 100644
index 00000000..aa758aff
--- /dev/null
+++ b/.formatter.exs
@@ -0,0 +1,7 @@
+[
+ inputs: [
+ "{lib,config}/**/*.{ex,exs}", # lib and config
+ "test/**/*.{ex,exs}", # tests
+ "mix.exs"
+ ]
+]
diff --git a/.scripts/inch_report.sh b/.scripts/inch_report.sh
new file mode 100644
index 00000000..a352261d
--- /dev/null
+++ b/.scripts/inch_report.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+set -e
+bold=$(tput bold)
+purple='\e[106m'
+normal=$(tput sgr0)
+allowed_branches="^(master)|(develop)$"
+
+echo -e "${bold}${purple}"
+if [ $TRAVIS_PULL_REQUEST = false ]; then
+ if [[ $TRAVIS_BRANCH =~ $allowed_branches ]]; then
+ env MIX_ENV=docs mix deps.get
+ env MIX_ENV=docs mix inch.report
+ else
+ echo "Skipping Inch CI report because this branch does not match on /$allowed_branches/"
+ fi
+else
+ echo "Skipping Inch CI report because this is a PR build"
+fi
+echo -e "${normal}"
diff --git a/.scripts/post-commit b/.scripts/post-commit
new file mode 100755
index 00000000..ada60194
--- /dev/null
+++ b/.scripts/post-commit
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# Runs credo and the formatter on the staged files, after the commit is made
+# This is purely for notification and will not halt/change your commit.
+
+RED='\033[1;31m'
+LGRAY='\033[1;30m'
+NC='\033[0m' # No Color
+
+printf "${RED}Running 'mix credo --strict --format=oneline' on project...${NC}\n"
+mix credo --strict --format=oneline
+echo
diff --git a/.scripts/pre-commit b/.scripts/pre-commit
new file mode 100755
index 00000000..1f473c8c
--- /dev/null
+++ b/.scripts/pre-commit
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git commit" with no arguments. The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-commit".
+
+if git rev-parse --verify HEAD >/dev/null 2>&1
+then
+ against=HEAD
+else
+ # Initial commit: diff against an empty tree object
+ against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
+# If you want to allow non-ASCII filenames set this variable to true.
+allownonascii=$(git config --bool hooks.allownonascii)
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Cross platform projects tend to avoid non-ASCII filenames; prevent
+# them from being added to the repository. We exploit the fact that the
+# printable range starts at the space character and ends with tilde.
+if [ "$allownonascii" != "true" ] &&
+ # Note that the use of brackets around a tr range is ok here, (it's
+ # even required, for portability to Solaris 10's /usr/bin/tr), since
+ # the square bracket bytes happen to fall in the designated range.
+ test $(git diff --cached --name-only --diff-filter=A -z $against |
+ LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
+then
+ cat <<\EOF
+Error: Attempt to add a non-ASCII file name.
+
+This can cause problems if you want to work with people on other platforms.
+
+To be portable it is advisable to rename the file.
+
+If you know what you are doing you can disable this check using:
+
+ git config hooks.allownonascii true
+EOF
+ exit 1
+fi
+
+# Also run the mix format task, just check though.
+exec mix format --check-formatted
+
diff --git a/.travis.yml b/.travis.yml
index 82c3f5a9..a238ae93 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,28 @@
language: elixir
-elixir:
- - 1.5.2
+
otp_release:
- - 20.1
+ - 20.2
before_install:
- mix local.hex --force
- mix local.rebar --force
- mix deps.get
script:
- - mix coveralls.travis --include integration
+ - set -e
+ - MIX_ENV=test mix format --check-formatted
+ - set +e
+ - mix coveralls.json --include=integration
after_script:
- - MIX_ENV=docs mix deps.get
- - MIX_ENV=docs mix inch.report
+ - bash <(curl -s https://codecov.io/bash)
+ - bash .scripts/inch_report.sh
+
+matrix:
+ include:
+ - elixir: "1.5.3"
+ script:
+ - mix coveralls.json --include=integration
+ - elixir: "1.6.2"
+
+notifications:
+ email:
+ recipients:
+ - ananya95+travis@gmail.com
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6eeb7033..f096ca9d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -24,40 +24,42 @@ processed.
[roadmap]: https://github.com/aviabird/gringotts/wiki/Roadmap
[wiki-arch]: https://github.com/aviabird/gringotts/wiki/Architecture
-### PR submission checklist
+# Style Guidelines
-Each PR should introduce a *focussed set of changes*, and ideally not span over
-unrelated modules.
+We follow
+[lexmag/elixir-style-guide](https://github.com/lexmag/elixir-style-guide) and
+[rrrene/elixir-style-guide](https://github.com/rrrene/elixir-style-guide) (both
+overlap a lot), and use the elixir formatter.
-* [ ] Run the edited files through [credo][credo] and the Elixir
- [formatter][hashrocket-formatter] (new in `v1.6`).
-* [ ] Check the test coverage by running `mix coveralls`. 100% coverage is not
- strictly required.
-* [ ] If the PR introduces a new Gateway or just Gateway specific changes,
- please format the title like so,\
- `[] `
+To enforce these, and also to make it easier for new contributors to adhere to
+our style, we've provided a collection of handy `git-hooks` under the `.scripts/`
+directory.
-[gringotts]: https://github.com/aviabird/gringotts
-[milestones]: https://github.com/aviabird/gringotts/milestones
-[issues]: https://github.com/aviabird/gringotts/issues
-[first-issues]: https://github.com/aviabird/gringotts/issues?q=is%3Aissue+is%3Aopen+label%3A"good+first+issue"
-[ch-issues]: https://github.com/aviabird/gringotts/issues?q=is%3Aissue+is%3Aopen+label%3A"hotfix%3A+community-help"
-[hexdocs]: https://hexdocs.pm/gringotts
-[credo]: https://github.com/rrrene/credo
-[hashrocket-formatter]: https://hashrocket.com/blog/posts/format-your-elixir-code-now
+* `.scripts/pre-commit` Runs the `format --check-formatted` task.
+* `.scripts/post-commit` Runs a customised `credo` check.
-# Style Guidelines
+While we do not force you to use these hooks, you could write your
+very own by taking inspiration from ours :smile:
-We use [`credo`][credo] and the elixir formatter for consistent style, so please
-use them!
+To set the `git-hooks` as provided, go to the repo root,
+```sh
+cd path/to/gringotts/
+```
+and make these symbolic links:
+```sh
+ln -s .scripts/pre-commit .git/hooks/pre-commit
+ln -s .scripts/post-commit .git/hooks/post-commit
+```
+
+> Note that our CI will fail your PR if you dont run `mix format` in the project
+> root.
## General Rules
-* Keep line length below 120 characters.
+* Keep line length below 100 characters.
* Complex anonymous functions should be extracted into named functions.
* One line functions, should only take up one line!
-* Pipes are great, but don't use them, if they are less readable than brackets
- then drop the pipe!
+* Pipes are great, but don't use them if they are less readable than brackets!
## Writing documentation
@@ -77,6 +79,39 @@ inspiration.
using `mock` as it constrains tests to run serially. Use [`mox`][mox] instead.\
Take a look at [MONEI's mock tests][src-monei-tests] for inspiration.
+# PR submission checklist
+
+Each PR should introduce a *focussed set of changes*, and ideally not span over
+unrelated modules.
+
+* [ ] Format the project with the Elixir formatter.
+ ```sh
+ cd path/to/gringotts/
+ mix format
+ ```
+* [ ] Run the edited files through [credo][credo] with the `--strict` flag.
+ ```sh
+ cd path/to/gringotts/
+ mix credo --strict
+ ```
+* [ ] Check the test coverage by running `mix coveralls`. 100% coverage is not
+ strictly required.
+* [ ] If the PR introduces a new Gateway or just Gateway specific changes,
+ please format the title like so,\
+ `[] `
+
+> **Note**
+> You can skip the first two steps if you have set up `git-hooks` as we have
+> provided!
+
+[gringotts]: https://github.com/aviabird/gringotts
+[milestones]: https://github.com/aviabird/gringotts/milestones
+[issues]: https://github.com/aviabird/gringotts/issues
+[first-issues]: https://github.com/aviabird/gringotts/issues?q=is%3Aissue+is%3Aopen+label%3A"good+first+issue"
+[ch-issues]: https://github.com/aviabird/gringotts/issues?q=is%3Aissue+is%3Aopen+label%3A"hotfix%3A+community-help"
+[hexdocs]: https://hexdocs.pm/gringotts
+[credo]: https://github.com/rrrene/credo
+
--------------------------------------------------------------------------------
> **Where to next?**
diff --git a/lib/gringotts.ex b/lib/gringotts.ex
index 11bda944..003c6aba 100644
--- a/lib/gringotts.ex
+++ b/lib/gringotts.ex
@@ -6,21 +6,21 @@ defmodule Gringotts do
easy for merchants to use multiple gateways.
All gateways must conform to the API as described in this module, but can also
support more gateway features than those required by Gringotts.
-
+
## Standard API arguments
All requests to Gringotts are served by a supervised worker, this might be
made optional in future releases.
-
+
### `gateway` (Module) Name
-
+
The `gateway` to which this request is made. This is required in all API calls
because Gringotts supports multiple Gateways.
#### Example
If you've configured Gringotts to work with Stripe, you'll do this
to make an `authorization` request:
-
+
Gringotts.authorize(Gingotts.Gateways.Stripe, other args ...)
### `amount` _and currency_
@@ -39,7 +39,7 @@ defmodule Gringotts do
Otherwise, just wrap your `amount` with the `currency` together in a `Map` like so,
money = %{value: Decimal.new("100.50"), currency: "USD"}
-
+
> When this highly precise `amount` is serialized into the network request, we
> use a potentially lossy `Gringotts.Money.to_string/1` or
> `Gringotts.Money.to_integer/1` to perform rounding (if required) using the
@@ -49,14 +49,14 @@ defmodule Gringotts do
> STRONGLY RECOMMEND that merchants perform any required rounding and handle
> remainders in their application logic -- before passing the `amount` to
> Gringotts's API.**
-
+
#### Example
If you use `ex_money` in your project, and want to make an authorization for
$2.99 to the `XYZ` Gateway, you'll do the following:
# the money lib is aliased as "MoneyLib"
-
+
amount = MoneyLib.new("2.99", :USD)
Gringotts.authorize(Gringotts.Gateways.XYZ, amount, some_card, extra_options)
@@ -65,7 +65,7 @@ defmodule Gringotts do
[money]: https://hexdocs.pm/money/Money.html
[iss-money-lib-support]: https://github.com/aviabird/gringotts/projects/3#card-6801146
[wiki-half-even]: https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
-
+
### `card`, a payment source
Gringotts provides a `Gringotts.CreditCard` type to hold card parameters
@@ -78,7 +78,7 @@ defmodule Gringotts do
gateways might support payment via other instruments such as e-wallets,
vouchers, bitcoins or banks. Support for these instruments is planned in
future releases.
-
+
%CreditCard {
first_name: "Harry",
last_name: "Potter",
@@ -93,18 +93,18 @@ defmodule Gringotts do
`opts` is a `keyword` list of other options/information accepted by the
gateway. The format, use and structure is gateway specific and documented in
the Gateway's docs.
-
+
## Configuration
-
+
Merchants must provide Gateway specific configuration in their application
config in the usual elixir style. The required and optional fields are
documented in every Gateway.
-
+
> The required config keys are validated at runtime, as they include
> authentication information. See `Gringotts.Adapter.validate_config/2`.
-
+
### Global config
-
+
This is set using the `:global_config` key once in your application.
#### `:mode`
@@ -120,9 +120,9 @@ defmodule Gringotts do
environments.
* `:prod` -- for live environment, all requests will reach the financial and
banking networks. Switch to this in your application's `:prod` environment.
-
+
**Example**
-
+
config :gringotts, :global_config,
# for live environment
mode: :prod
@@ -136,7 +136,7 @@ defmodule Gringotts do
# some_documented_key: associated_value
# some_other_key: another_value
"""
-
+
@doc """
Performs a (pre) Authorize operation.
@@ -171,7 +171,7 @@ defmodule Gringotts do
* multiple captures, per authorization
## Example
-
+
To capture $4.20 on a previously authorized payment worth $4.20 by referencing
the obtained authorization `id` with the `XYZ` gateway,
@@ -181,7 +181,7 @@ defmodule Gringotts do
card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
Gringotts.capture(Gringotts.Gateways.XYZ, amount, auth_result.id, opts)
"""
- def capture(gateway, id, amount, opts \\ []) do
+ def capture(gateway, id, amount, opts \\ []) do
config = get_and_validate_config(gateway)
gateway.capture(id, amount, [{:config, config} | opts])
end
@@ -195,14 +195,14 @@ defmodule Gringotts do
This method _can_ be implemented as a chained call to `authorize/3` and
`capture/3`. But it must be implemented as a single call to the Gateway if it
provides a specific endpoint or action for this.
-
+
> ***Note!**
> All gateways must implement (atleast) this method.
## Example
To process a purchase worth $4.2, with the `XYZ` gateway,
-
+
amount = Money.new("4.2", :USD)
# IF YOU DON'T USE ex_money
# amount = %{value: Decimal.new("4.2"), currency: "EUR"}
@@ -229,7 +229,7 @@ defmodule Gringotts do
# amount = %{value: Decimal.new("4.2"), currency: "EUR"}
Gringotts.purchase(Gringotts.Gateways.XYZ, amount, id, opts)
"""
- def refund(gateway, amount, id, opts \\ []) do
+ def refund(gateway, amount, id, opts \\ []) do
config = get_and_validate_config(gateway)
gateway.refund(amount, id, [{:config, config} | opts])
end
@@ -238,7 +238,7 @@ defmodule Gringotts do
Stores the payment-source data for later use, returns a `token`.
> The token must be returned in the `Response.authorization` field.
-
+
## Note
This usually enables _One-Click_ and _Recurring_ payments.
@@ -250,7 +250,7 @@ defmodule Gringotts do
card = %Gringotts.CreditCard{first_name: "Jo", last_name: "Doe", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
Gringotts.store(Gringotts.Gateways.XYZ, card, opts)
"""
- def store(gateway, card, opts \\ []) do
+ def store(gateway, card, opts \\ []) do
config = get_and_validate_config(gateway)
gateway.store(card, [{:config, config} | opts])
end
@@ -264,11 +264,11 @@ defmodule Gringotts do
## Example
To unstore with the `XYZ` gateway,
-
+
token = "some_privileged_customer"
Gringotts.unstore(Gringotts.Gateways.XYZ, token)
"""
- def unstore(gateway, token, opts \\ []) do
+ def unstore(gateway, token, opts \\ []) do
config = get_and_validate_config(gateway)
gateway.unstore(token, [{:config, config} | opts])
end
@@ -289,7 +289,7 @@ defmodule Gringotts do
id = "some_previously_obtained_token"
Gringotts.void(Gringotts.Gateways.XYZ, id, opts)
"""
- def void(gateway, id, opts \\ []) do
+ def void(gateway, id, opts \\ []) do
config = get_and_validate_config(gateway)
gateway.void(id, [{:config, config} | opts])
end
diff --git a/lib/gringotts/adapter.ex b/lib/gringotts/adapter.ex
index 978cd1d1..5edd06f2 100644
--- a/lib/gringotts/adapter.ex
+++ b/lib/gringotts/adapter.ex
@@ -15,7 +15,7 @@ defmodule Gringotts.Adapter do
Say a merchant must provide his `secret_user_name` and `secret_password` to
some Gateway `XYZ`. Then, `Gringotts` expects that the `GatewayXYZ` module
would use `Adapter` in the following manner:
-
+
```
defmodule Gringotts.Gateways.GatewayXYZ do
@@ -38,7 +38,7 @@ defmodule Gringotts.Adapter do
```
"""
-
+
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
@required_config opts[:required_config] || []
@@ -50,16 +50,19 @@ defmodule Gringotts.Adapter do
is not available or missing from the Application config.
"""
def validate_config(config) do
- missing_keys = Enum.reduce(@required_config, [], fn(key, missing_keys) ->
- if config[key] in [nil, ""], do: [key | missing_keys], else: missing_keys
- end)
+ missing_keys =
+ Enum.reduce(@required_config, [], fn key, missing_keys ->
+ if config[key] in [nil, ""], do: [key | missing_keys], else: missing_keys
+ end)
+
raise_on_missing_config(missing_keys, config)
end
defp raise_on_missing_config([], _config), do: :ok
+
defp raise_on_missing_config(key, config) do
raise ArgumentError, """
- expected #{inspect key} to be set, got: #{inspect config}
+ expected #{inspect(key)} to be set, got: #{inspect(config)}
"""
end
end
diff --git a/lib/gringotts/address.ex b/lib/gringotts/address.ex
index e1c50c95..57c8dd5c 100644
--- a/lib/gringotts/address.ex
+++ b/lib/gringotts/address.ex
@@ -1,3 +1,5 @@
defmodule Gringotts.Address do
+ @moduledoc false
+
defstruct [:street1, :street2, :city, :region, :country, :postal_code, :phone]
end
diff --git a/lib/gringotts/credit_card.ex b/lib/gringotts/credit_card.ex
index 01811e00..e98481a0 100644
--- a/lib/gringotts/credit_card.ex
+++ b/lib/gringotts/credit_card.ex
@@ -4,6 +4,7 @@ defmodule Gringotts.CreditCard do
"""
defstruct [:number, :month, :year, :first_name, :last_name, :verification_code, :brand]
+
@typedoc """
Represents a Credit Card.
@@ -29,23 +30,24 @@ defmodule Gringotts.CreditCard do
[mo]: http://www.maestrocard.com/gateway/index.html
[dc]: http://www.dinersclub.com/
"""
- @type t :: %__MODULE__{number: String.t,
- month: 1..12,
- year: non_neg_integer,
- first_name: String.t,
- last_name: String.t,
- verification_code: String.t,
- brand: String.t}
+ @type t :: %__MODULE__{
+ number: String.t(),
+ month: 1..12,
+ year: non_neg_integer,
+ first_name: String.t(),
+ last_name: String.t(),
+ verification_code: String.t(),
+ brand: String.t()
+ }
@doc """
Returns the full name of the card holder.
Joins `first_name` and `last_name` with a space in between.
"""
- @spec full_name(t) :: String.t
+ @spec full_name(t) :: String.t()
def full_name(card) do
name = "#{card.first_name} #{card.last_name}"
- String.trim(name)
+ String.trim(name)
end
-
end
diff --git a/lib/gringotts/gateways/authorize_net.ex b/lib/gringotts/gateways/authorize_net.ex
index 59281314..2612bb7a 100644
--- a/lib/gringotts/gateways/authorize_net.ex
+++ b/lib/gringotts/gateways/authorize_net.ex
@@ -89,7 +89,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do
that gives you a pre-configured sample app ready-to-go.
+ You could use the same config or update it the with your "secrets"
[above](#module-configuring-your-authorizenet-account-at-gringotts).
-
+
2. To save a lot of time, create a [`.iex.exs`][iex-docs] file as shown in
[this gist][authorize_net.iex.exs] to introduce a set of handy bindings and
aliases.
@@ -226,7 +226,6 @@ defmodule Gringotts.Gateways.AuthorizeNet do
customer_ip: String
]
-
## Example
iex> amount = Money.new(20, :USD)
iex> opts = [
@@ -749,21 +748,36 @@ defmodule Gringotts.Gateways.AuthorizeNet do
]
@avs_code_translator %{
- "A" => {"pass", "fail"}, #The street address matched, but the postal code did not.
- "B" => {nil, nil}, # No address information was provided.
- "E" => {"fail", nil}, # The AVS check returned an error.
- "G" => {nil, nil}, # The card was issued by a bank outside the U.S. and does not support AVS.
- "N" => {"fail", "fail"}, # Neither the street address nor postal code matched.
- "P" => {nil, nil}, # AVS is not applicable for this transaction.
- "R" => {nil, nil}, # Retry — AVS was unavailable or timed out.
- "S" => {nil, nil}, # AVS is not supported by card issuer.
- "U" => {nil, nil}, # Address information is unavailable.
- "W" => {"fail", "pass"}, # The US ZIP+4 code matches, but the street address does not.
- "X" => {"pass", "pass"}, # Both the street address and the US ZIP+4 code matched.
- "Y" => {"pass", "pass"}, # The street address and postal code matched.
- "Z" => {"fail", "pass"}, # The postal code matched, but the street address did not.
- "" => {nil, nil}, # fallback in-case of absence
- nil => {nil, nil} # fallback in-case of absence
+ # The street address matched, but the postal code did not.
+ "A" => {"pass", "fail"},
+ # No address information was provided.
+ "B" => {nil, nil},
+ # The AVS check returned an error.
+ "E" => {"fail", nil},
+ # The card was issued by a bank outside the U.S. and does not support AVS.
+ "G" => {nil, nil},
+ # Neither the street address nor postal code matched.
+ "N" => {"fail", "fail"},
+ # AVS is not applicable for this transaction.
+ "P" => {nil, nil},
+ # Retry — AVS was unavailable or timed out.
+ "R" => {nil, nil},
+ # AVS is not supported by card issuer.
+ "S" => {nil, nil},
+ # Address information is unavailable.
+ "U" => {nil, nil},
+ # The US ZIP+4 code matches, but the street address does not.
+ "W" => {"fail", "pass"},
+ # Both the street address and the US ZIP+4 code matched.
+ "X" => {"pass", "pass"},
+ # The street address and postal code matched.
+ "Y" => {"pass", "pass"},
+ # The postal code matched, but the street address did not.
+ "Z" => {"fail", "pass"},
+ # fallback in-case of absence
+ "" => {nil, nil},
+ # fallback in-case of absence
+ nil => {nil, nil}
}
@cvc_code_translator %{
@@ -772,7 +786,8 @@ defmodule Gringotts.Gateways.AuthorizeNet do
"P" => "CVV was not processed.",
"S" => "CVV should have been present but was not indicated.",
"U" => "The issuer was unable to process the CVV check.",
- nil => nil # fallback in-case of absence
+ # fallback in-case of absence
+ nil => nil
}
@cavv_code_translator %{
@@ -784,16 +799,22 @@ defmodule Gringotts.Gateways.AuthorizeNet do
"4" => "CAVV validation could not be performed; issuer system error.",
"5" => "Reserved for future use.",
"6" => "Reserved for future use.",
- "7" => "CAVV failed validation, but the issuer is available. Valid for U.S.-issued card submitted to non-U.S acquirer.",
- "8" => "CAVV passed validation and the issuer is available. Valid for U.S.-issued card submitted to non-U.S. acquirer.",
- "9" => "CAVV failed validation and the issuer is unavailable. Valid for U.S.-issued card submitted to non-U.S acquirer.",
- "A" => "CAVV passed validation but the issuer unavailable. Valid for U.S.-issued card submitted to non-U.S acquirer.",
+ "7" =>
+ "CAVV failed validation, but the issuer is available. Valid for U.S.-issued card submitted to non-U.S acquirer.",
+ "8" =>
+ "CAVV passed validation and the issuer is available. Valid for U.S.-issued card submitted to non-U.S. acquirer.",
+ "9" =>
+ "CAVV failed validation and the issuer is unavailable. Valid for U.S.-issued card submitted to non-U.S acquirer.",
+ "A" =>
+ "CAVV passed validation but the issuer unavailable. Valid for U.S.-issued card submitted to non-U.S acquirer.",
"B" => "CAVV passed validation, information only, no liability shift.",
- nil => nil # fallback in-case of absence
+ # fallback in-case of absence
+ nil => nil
}
def respond(body) do
response_map = XmlToMap.naive_map(body)
+
case extract_gateway_response(response_map) do
:undefined_response ->
{
@@ -812,11 +833,11 @@ defmodule Gringotts.Gateways.AuthorizeNet do
def extract_gateway_response(response_map) do
# The type of the response should be supported
- @supported_response_types
- |> Stream.map(&Map.get(response_map, &1, nil))
# Find the first non-nil from the above, if all are `nil`...
# We are in trouble!
- |> Enum.find(:undefined_response, &(&1))
+ @supported_response_types
+ |> Stream.map(&Map.get(response_map, &1, nil))
+ |> Enum.find(:undefined_response, & &1)
end
defp build_response(%{"messages" => %{"resultCode" => "Ok"}} = result, base_response) do
@@ -862,10 +883,10 @@ defmodule Gringotts.Gateways.AuthorizeNet do
# HELPERS #
############################################################################
- defp set_id(response, id), do: %{response | id: id}
- defp set_message(response, message), do: %{response | message: message}
+ defp set_id(response, id), do: %{response | id: id}
+ defp set_message(response, message), do: %{response | message: message}
defp set_gateway_code(response, code), do: %{response | gateway_code: code}
- defp set_reason(response, body), do: %{response | reason: body}
+ defp set_reason(response, body), do: %{response | reason: body}
defp set_avs_result(response, avs_code) do
{street, zip_code} = @avs_code_translator[avs_code]
@@ -879,6 +900,5 @@ defmodule Gringotts.Gateways.AuthorizeNet do
defp set_cavv_result(response, cavv_code) do
Map.put(response, :cavv_result, @cavv_code_translator[cavv_code])
end
-
end
end
diff --git a/lib/gringotts/gateways/base.ex b/lib/gringotts/gateways/base.ex
index be881992..145b4b7a 100644
--- a/lib/gringotts/gateways/base.ex
+++ b/lib/gringotts/gateways/base.ex
@@ -12,18 +12,18 @@ defmodule Gringotts.Gateways.Base do
```
because this module provides an implementation.
"""
-
+
alias Gringotts.Response
defmacro __using__(_) do
quote location: :keep do
@doc false
- def purchase(_amount, _card_or_id, _opts) do
+ def purchase(_amount, _card_or_id, _opts) do
not_implemented()
end
@doc false
- def authorize(_amount, _card_or_id, _opts) do
+ def authorize(_amount, _card_or_id, _opts) do
not_implemented()
end
@@ -57,7 +57,13 @@ defmodule Gringotts.Gateways.Base do
{:error, Response.error(code: :not_implemented)}
end
- defoverridable [purchase: 3, authorize: 3, capture: 3, void: 2, refund: 3, store: 2, unstore: 2]
+ defoverridable purchase: 3,
+ authorize: 3,
+ capture: 3,
+ void: 2,
+ refund: 3,
+ store: 2,
+ unstore: 2
end
end
end
diff --git a/lib/gringotts/gateways/bogus.ex b/lib/gringotts/gateways/bogus.ex
index d6bdaa02..ada7575d 100644
--- a/lib/gringotts/gateways/bogus.ex
+++ b/lib/gringotts/gateways/bogus.ex
@@ -1,6 +1,6 @@
defmodule Gringotts.Gateways.Bogus do
@moduledoc false
-
+
use Gringotts.Gateways.Base
alias Gringotts.{
@@ -9,28 +9,20 @@ defmodule Gringotts.Gateways.Bogus do
}
@some_authorization_id "14a62fff80f24a25f775eeb33624bbb3"
-
- def authorize(_amount, _card_or_id, _opts),
- do: success()
- def purchase(_amount, _card_or_id, _opts),
- do: success()
+ def authorize(_amount, _card_or_id, _opts), do: success()
+
+ def purchase(_amount, _card_or_id, _opts), do: success()
- def capture(_id, _amount, _opts),
- do: success()
+ def capture(_id, _amount, _opts), do: success()
- def void(_id, _opts),
- do: success()
+ def void(_id, _opts), do: success()
- def refund(_amount, _id, _opts),
- do: success()
+ def refund(_amount, _id, _opts), do: success()
- def store(%CreditCard{} = _card, _opts),
- do: success()
+ def store(%CreditCard{} = _card, _opts), do: success()
- def unstore(_customer_id, _opts),
- do: success()
+ def unstore(_customer_id, _opts), do: success()
- defp success,
- do: {:ok, Response.success(id: @some_authorization_id)}
+ defp success, do: {:ok, Response.success(id: @some_authorization_id)}
end
diff --git a/lib/gringotts/gateways/cams.ex b/lib/gringotts/gateways/cams.ex
index 12687f6b..2f1d5805 100644
--- a/lib/gringotts/gateways/cams.ex
+++ b/lib/gringotts/gateways/cams.ex
@@ -408,7 +408,7 @@ defmodule Gringotts.Gateways.Cams do
"S" => {nil, nil},
"0" => {nil, nil},
"O" => {nil, nil},
- "" => {nil, nil}
+ "" => {nil, nil}
}
# Fetched from CAMS POST API docs.
@@ -426,6 +426,7 @@ defmodule Gringotts.Gateways.Cams do
{street, zip_code} = @avs_code_translator[decoded_body["avsresponse"]]
gateway_code = decoded_body["response_code"]
message = decoded_body["responsetext"]
+
response = %Response{
status_code: 200,
id: decoded_body["transactionid"],
diff --git a/lib/gringotts/gateways/global_collect.ex b/lib/gringotts/gateways/global_collect.ex
index 0c6d143e..624a8950 100644
--- a/lib/gringotts/gateways/global_collect.ex
+++ b/lib/gringotts/gateways/global_collect.ex
@@ -124,17 +124,15 @@ defmodule Gringotts.Gateways.GlobalCollect do
import Poison, only: [decode: 1]
- alias Gringotts.{Money,
- CreditCard,
- Response}
-
- @brand_map %{
- "visa": "1",
- "american_express": "2",
- "master": "3",
- "discover": "128",
- "jcb": "125",
- "diners_club": "132"
+ alias Gringotts.{Money, CreditCard, Response}
+
+ @brand_map %{
+ visa: "1",
+ american_express: "2",
+ master: "3",
+ discover: "128",
+ jcb: "125",
+ diners_club: "132"
}
@doc """
@@ -209,7 +207,7 @@ defmodule Gringotts.Gateways.GlobalCollect do
```
"""
- @spec capture(String.t(), Money.t, keyword) :: {:ok | :error, Response}
+ @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response}
def capture(payment_id, amount, opts) do
params = create_params_for_capture(amount, opts)
commit(:post, "payments/#{payment_id}/approve", params, opts)
@@ -244,7 +242,7 @@ defmodule Gringotts.Gateways.GlobalCollect do
```
"""
- @spec purchase(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response}
+ @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
def purchase(amount, card = %CreditCard{}, opts) do
case authorize(amount, card, opts) do
{:ok, results} ->
@@ -300,7 +298,7 @@ defmodule Gringotts.Gateways.GlobalCollect do
iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.GlobalCollect, auth_result.payment.id, amount)
```
"""
- @spec refund(Money.t, String.t(), keyword) :: {:ok | :error, Response}
+ @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
def refund(amount, payment_id, opts) do
params = create_params_for_refund(amount, opts)
commit(:post, "payments/#{payment_id}/refund", params, opts)
@@ -329,7 +327,7 @@ defmodule Gringotts.Gateways.GlobalCollect do
}
end
- defp create_params_for_capture(amount, opts) do
+ defp create_params_for_capture(amount, opts) do
%{
order: add_order(amount, opts)
}
@@ -345,6 +343,7 @@ defmodule Gringotts.Gateways.GlobalCollect do
defp add_money(amount, options) do
{currency, amount, _} = Money.to_integer(amount)
+
%{
amount: amount,
currencyCode: currency
@@ -393,15 +392,16 @@ defmodule Gringotts.Gateways.GlobalCollect do
%{
cvv: payment.verification_code,
cardNumber: payment.number,
- expiryDate: "#{payment.month}"<>"#{payment.year}",
+ expiryDate: "#{payment.month}" <> "#{payment.year}",
cardholderName: CreditCard.full_name(payment)
}
end
defp add_payment(payment, brand_map, opts) do
brand = payment.brand
+
%{
- paymentProductId: Map.fetch!(brand_map, String.to_atom(brand)),
+ paymentProductId: Map.fetch!(brand_map, String.to_atom(brand)),
skipAuthentication: opts[:skipAuthentication],
card: add_card(payment)
}
@@ -422,17 +422,25 @@ defmodule Gringotts.Gateways.GlobalCollect do
defp create_headers(path, opts) do
time = date
- sha_signature = auth_digest(path, opts[:config][:secret_api_key], time, opts) |> Base.encode64
+
+ sha_signature =
+ auth_digest(path, opts[:config][:secret_api_key], time, opts) |> Base.encode64()
+
auth_token = "GCS v1HMAC:#{opts[:config][:api_key_id]}:#{sha_signature}"
- headers = [{"Content-Type", "application/json"}, {"Authorization", auth_token}, {"Date", time}]
+
+ headers = [
+ {"Content-Type", "application/json"},
+ {"Authorization", auth_token},
+ {"Date", time}
+ ]
end
defp date() do
use Timex
- datetime = Timex.now |> Timex.local
+ datetime = Timex.now() |> Timex.local()
strftime_str = Timex.format!(datetime, "%a, %d %b %Y %H:%M:%S ", :strftime)
time_zone = Timex.timezone(:local, datetime)
- time = strftime_str <>"#{time_zone.abbreviation}"
+ time = strftime_str <> "#{time_zone.abbreviation}"
end
# Parses GlobalCollect's response and returns a `Gringotts.Response` struct
@@ -448,13 +456,17 @@ defmodule Gringotts.Gateways.GlobalCollect do
defp respond({:ok, %{status_code: status_code, body: body}}) do
{:ok, results} = decode(body)
- message = Enum.map(results["errors"],fn (x) -> x["message"] end)
+ message = Enum.map(results["errors"], fn x -> x["message"] end)
detail = List.to_string(message)
{:error, Response.error(status_code: status_code, message: detail, raw: results)}
end
defp respond({:error, %HTTPoison.Error{} = error}) do
- {:error, Response.error(code: error.id, reason: :network_fail?, description: "HTTPoison says '#{error.reason}'")}
+ {:error,
+ Response.error(
+ code: error.id,
+ reason: :network_fail?,
+ description: "HTTPoison says '#{error.reason}'"
+ )}
end
-
end
diff --git a/lib/gringotts/gateways/paymill.ex b/lib/gringotts/gateways/paymill.ex
index 8b00ba3e..5cf177e2 100644
--- a/lib/gringotts/gateways/paymill.ex
+++ b/lib/gringotts/gateways/paymill.ex
@@ -56,7 +56,7 @@ defmodule Gringotts.Gateways.Paymill do
iex> Gringotts.authorize(Gringotts.Gateways.Paymill, amount, card, options)
"""
- @spec authorize(number, String.t | CreditCard.t, Keyword) :: {:ok | :error, Response}
+ @spec authorize(number, String.t() | CreditCard.t(), Keyword) :: {:ok | :error, Response}
def authorize(amount, card_or_token, options) do
Keyword.put(options, :money, amount)
action_with_token(:authorize, amount, card_or_token, options)
@@ -81,7 +81,7 @@ defmodule Gringotts.Gateways.Paymill do
iex> Gringotts.purchase(Gringotts.Gateways.Paymill, amount, card, options)
"""
- @spec purchase(number, CreditCard.t, Keyword) :: {:ok | :error, Response}
+ @spec purchase(number, CreditCard.t(), Keyword) :: {:ok | :error, Response}
def purchase(amount, card, options) do
Keyword.put(options, :money, amount)
action_with_token(:purchase, amount, card, options)
@@ -99,7 +99,7 @@ defmodule Gringotts.Gateways.Paymill do
iex> Gringotts.capture(Gringotts.Gateways.Paymill, token, amount, options)
"""
- @spec capture(String.t, number, Keyword) :: {:ok | :error, Response}
+ @spec capture(String.t(), number, Keyword) :: {:ok | :error, Response}
def capture(authorization, amount, options) do
post = add_amount([], amount, options) ++ [{"preauthorization", authorization}]
@@ -116,13 +116,13 @@ defmodule Gringotts.Gateways.Paymill do
iex> Gringotts.void(Gringotts.Gateways.Paymill, token, options)
"""
- @spec void(String.t, Keyword) :: {:ok | :error, Response}
+ @spec void(String.t(), Keyword) :: {:ok | :error, Response}
def void(authorization, options) do
commit(:delete, "preauthorizations/#{authorization}", [], options)
end
@doc false
- @spec authorize_with_token(number, String.t, Keyword) :: term
+ @spec authorize_with_token(number, String.t(), Keyword) :: term
def authorize_with_token(money, card_token, options) do
post = add_amount([], money, options) ++ [{"token", card_token}]
@@ -130,51 +130,53 @@ defmodule Gringotts.Gateways.Paymill do
end
@doc false
- @spec purchase_with_token(number, String.t, Keyword) :: term
+ @spec purchase_with_token(number, String.t(), Keyword) :: term
def purchase_with_token(money, card_token, options) do
post = add_amount([], money, options) ++ [{"token", card_token}]
commit(:post, "transactions", post, options)
end
- @spec save_card(CreditCard.t, Keyword) :: Response
+ @spec save_card(CreditCard.t(), Keyword) :: Response
defp save_card(card, options) do
- {:ok, %HTTPoison.Response{body: response}} = HTTPoison.get(
+ {:ok, %HTTPoison.Response{body: response}} =
+ HTTPoison.get(
get_save_card_url(),
get_headers(options),
- params: get_save_card_params(card, options))
+ params: get_save_card_params(card, options)
+ )
- parse_card_response(response)
+ parse_card_response(response)
end
- @spec save(CreditCard.t, Keyword) :: Response
+ @spec save(CreditCard.t(), Keyword) :: Response
defp save(card, options) do
save_card(card, options)
end
defp action_with_token(action, amount, "tok_" <> id = card_token, options) do
- apply(__MODULE__, String.to_atom("#{action}_with_token"), [amount, card_token , options])
+ apply(__MODULE__, String.to_atom("#{action}_with_token"), [amount, card_token, options])
end
defp action_with_token(action, amount, %CreditCard{} = card, options) do
{:ok, response} = save_card(card, options)
card_token = get_token(response)
- apply(__MODULE__, String.to_atom("#{action}_with_token"), [amount, card_token , options])
+ apply(__MODULE__, String.to_atom("#{action}_with_token"), [amount, card_token, options])
end
defp get_save_card_params(card, options) do
- [
- {"transaction.mode" , "CONNECTOR_TEST"},
- {"channel.id" , get_config(:public_key, options)},
- {"jsonPFunction" , "jsonPFunction"},
- {"account.number" , card.number},
- {"account.expiry.month" , card.month},
- {"account.expiry.year" , card.year},
- {"account.verification" , card.verification_code},
- {"account.holder" , "#{card.first_name} #{card.last_name}"},
- {"presentation.amount3D" , get_amount(options)},
- {"presentation.currency3D" , get_currency(options)}
+ [
+ {"transaction.mode", "CONNECTOR_TEST"},
+ {"channel.id", get_config(:public_key, options)},
+ {"jsonPFunction", "jsonPFunction"},
+ {"account.number", card.number},
+ {"account.expiry.month", card.month},
+ {"account.expiry.year", card.year},
+ {"account.verification", card.verification_code},
+ {"account.holder", CreditCard.full_name(card)},
+ {"presentation.amount3D", get_amount(options)},
+ {"presentation.currency3D", get_currency(options)}
]
end
@@ -196,7 +198,7 @@ defmodule Gringotts.Gateways.Paymill do
response
|> String.replace(~r/jsonPFunction\(/, "")
|> String.replace(~r/\)/, "")
- |> Poison.decode
+ |> Poison.decode()
end
defp get_currency(options), do: options[:currency] || @default_currency
@@ -210,7 +212,7 @@ defmodule Gringotts.Gateways.Paymill do
defp commit(method, action, parameters \\ nil, options) do
method
|> HTTPoison.request(@live_url <> action, {:form, parameters}, get_headers(options), [])
- |> ResponseParser.parse
+ |> ResponseParser.parse()
end
defp get_config(key, options) do
@@ -222,118 +224,118 @@ defmodule Gringotts.Gateways.Paymill do
alias Gringotts.Response
@response_code %{
- 10_001 => "Undefined response",
- 10_002 => "Waiting for something",
- 11_000 => "Retry request at a later time",
-
- 20_000 => "Operation successful",
- 20_100 => "Funds held by acquirer",
- 20_101 => "Funds held by acquirer because merchant is new",
- 20_200 => "Transaction reversed",
- 20_201 => "Reversed due to chargeback",
- 20_202 => "Reversed due to money-back guarantee",
- 20_203 => "Reversed due to complaint by buyer",
- 20_204 => "Payment has been refunded",
- 20_300 => "Reversal has been canceled",
- 22_000 => "Initiation of transaction successful",
-
- 30_000 => "Transaction still in progress",
- 30_100 => "Transaction has been accepted",
- 31_000 => "Transaction pending",
- 31_100 => "Pending due to address",
- 31_101 => "Pending due to uncleared eCheck",
- 31_102 => "Pending due to risk review",
- 31_103 => "Pending due regulatory review",
- 31_104 => "Pending due to unregistered/unconfirmed receiver",
- 31_200 => "Pending due to unverified account",
- 31_201 => "Pending due to non-captured funds",
- 31_202 => "Pending due to international account (accept manually)",
- 31_203 => "Pending due to currency conflict (accept manually)",
- 31_204 => "Pending due to fraud filters (accept manually)",
-
- 40_000 => "Problem with transaction data",
- 40_001 => "Problem with payment data",
- 40_002 => "Invalid checksum",
- 40_100 => "Problem with credit card data",
- 40_101 => "Problem with CVV",
- 40_102 => "Card expired or not yet valid",
- 40_103 => "Card limit exceeded",
- 40_104 => "Card is not valid",
- 40_105 => "Expiry date not valid",
- 40_106 => "Credit card brand required",
- 40_200 => "Problem with bank account data",
- 40_201 => "Bank account data combination mismatch",
- 40_202 => "User authentication failed",
- 40_300 => "Problem with 3-D Secure data",
- 40_301 => "Currency/amount mismatch",
- 40_400 => "Problem with input data",
- 40_401 => "Amount too low or zero",
- 40_402 => "Usage field too long",
- 40_403 => "Currency not allowed",
- 40_410 => "Problem with shopping cart data",
- 40_420 => "Problem with address data",
- 40_500 => "Permission error with acquirer API",
- 40_510 => "Rate limit reached for acquirer API",
- 42_000 => "Initiation of transaction failed",
- 42_410 => "Initiation of transaction expired",
-
- 50_000 => "Problem with back end",
- 50_001 => "Country blacklisted",
- 50_002 => "IP address blacklisted",
- 50_004 => "Live mode not allowed",
- 50_005 => "Insufficient permissions (API key)",
- 50_100 => "Technical error with credit card",
- 50_101 => "Error limit exceeded",
- 50_102 => "Card declined",
- 50_103 => "Manipulation or stolen card",
- 50_104 => "Card restricted",
- 50_105 => "Invalid configuration data",
- 50_200 => "Technical error with bank account",
- 50_201 => "Account blacklisted",
- 50_300 => "Technical error with 3-D Secure",
- 50_400 => "Declined because of risk issues",
- 50_401 => "Checksum was wrong",
- 50_402 => "Bank account number was invalid (formal check)",
- 50_403 => "Technical error with risk check",
- 50_404 => "Unknown error with risk check",
- 50_405 => "Unknown bank code",
- 50_406 => "Open chargeback",
- 50_407 => "Historical chargeback",
- 50_408 => "Institution / public bank account (NCA)",
- 50_409 => "KUNO/Fraud",
- 50_410 => "Personal Account Protection (PAP)",
- 50_420 => "Rejected due to acquirer fraud settings",
- 50_430 => "Rejected due to acquirer risk settings",
- 50_440 => "Failed due to restrictions with acquirer account",
- 50_450 => "Failed due to restrictions with user account",
- 50_500 => "General timeout",
- 50_501 => "Timeout on side of the acquirer",
- 50_502 => "Risk management transaction timeout",
- 50_600 => "Duplicate operation",
- 50_700 => "Cancelled by user",
- 50_710 => "Failed due to funding source",
- 50_711 => "Payment method not usable, use other payment method",
- 50_712 => "Limit of funding source was exceeded",
- 50_713 => "Means of payment not reusable (canceled by user)",
- 50_714 => "Means of payment not reusable (expired)",
- 50_720 => "Rejected by acquirer",
- 50_730 => "Transaction denied by merchant",
- 50_800 => "Preauthorisation failed",
- 50_810 => "Authorisation has been voided",
- 50_820 => "Authorisation period expired"
- }
+ 10_001 => "Undefined response",
+ 10_002 => "Waiting for something",
+ 11_000 => "Retry request at a later time",
+ 20_000 => "Operation successful",
+ 20_100 => "Funds held by acquirer",
+ 20_101 => "Funds held by acquirer because merchant is new",
+ 20_200 => "Transaction reversed",
+ 20_201 => "Reversed due to chargeback",
+ 20_202 => "Reversed due to money-back guarantee",
+ 20_203 => "Reversed due to complaint by buyer",
+ 20_204 => "Payment has been refunded",
+ 20_300 => "Reversal has been canceled",
+ 22_000 => "Initiation of transaction successful",
+ 30_000 => "Transaction still in progress",
+ 30_100 => "Transaction has been accepted",
+ 31_000 => "Transaction pending",
+ 31_100 => "Pending due to address",
+ 31_101 => "Pending due to uncleared eCheck",
+ 31_102 => "Pending due to risk review",
+ 31_103 => "Pending due regulatory review",
+ 31_104 => "Pending due to unregistered/unconfirmed receiver",
+ 31_200 => "Pending due to unverified account",
+ 31_201 => "Pending due to non-captured funds",
+ 31_202 => "Pending due to international account (accept manually)",
+ 31_203 => "Pending due to currency conflict (accept manually)",
+ 31_204 => "Pending due to fraud filters (accept manually)",
+ 40_000 => "Problem with transaction data",
+ 40_001 => "Problem with payment data",
+ 40_002 => "Invalid checksum",
+ 40_100 => "Problem with credit card data",
+ 40_101 => "Problem with CVV",
+ 40_102 => "Card expired or not yet valid",
+ 40_103 => "Card limit exceeded",
+ 40_104 => "Card is not valid",
+ 40_105 => "Expiry date not valid",
+ 40_106 => "Credit card brand required",
+ 40_200 => "Problem with bank account data",
+ 40_201 => "Bank account data combination mismatch",
+ 40_202 => "User authentication failed",
+ 40_300 => "Problem with 3-D Secure data",
+ 40_301 => "Currency/amount mismatch",
+ 40_400 => "Problem with input data",
+ 40_401 => "Amount too low or zero",
+ 40_402 => "Usage field too long",
+ 40_403 => "Currency not allowed",
+ 40_410 => "Problem with shopping cart data",
+ 40_420 => "Problem with address data",
+ 40_500 => "Permission error with acquirer API",
+ 40_510 => "Rate limit reached for acquirer API",
+ 42_000 => "Initiation of transaction failed",
+ 42_410 => "Initiation of transaction expired",
+ 50_000 => "Problem with back end",
+ 50_001 => "Country blacklisted",
+ 50_002 => "IP address blacklisted",
+ 50_004 => "Live mode not allowed",
+ 50_005 => "Insufficient permissions (API key)",
+ 50_100 => "Technical error with credit card",
+ 50_101 => "Error limit exceeded",
+ 50_102 => "Card declined",
+ 50_103 => "Manipulation or stolen card",
+ 50_104 => "Card restricted",
+ 50_105 => "Invalid configuration data",
+ 50_200 => "Technical error with bank account",
+ 50_201 => "Account blacklisted",
+ 50_300 => "Technical error with 3-D Secure",
+ 50_400 => "Declined because of risk issues",
+ 50_401 => "Checksum was wrong",
+ 50_402 => "Bank account number was invalid (formal check)",
+ 50_403 => "Technical error with risk check",
+ 50_404 => "Unknown error with risk check",
+ 50_405 => "Unknown bank code",
+ 50_406 => "Open chargeback",
+ 50_407 => "Historical chargeback",
+ 50_408 => "Institution / public bank account (NCA)",
+ 50_409 => "KUNO/Fraud",
+ 50_410 => "Personal Account Protection (PAP)",
+ 50_420 => "Rejected due to acquirer fraud settings",
+ 50_430 => "Rejected due to acquirer risk settings",
+ 50_440 => "Failed due to restrictions with acquirer account",
+ 50_450 => "Failed due to restrictions with user account",
+ 50_500 => "General timeout",
+ 50_501 => "Timeout on side of the acquirer",
+ 50_502 => "Risk management transaction timeout",
+ 50_600 => "Duplicate operation",
+ 50_700 => "Cancelled by user",
+ 50_710 => "Failed due to funding source",
+ 50_711 => "Payment method not usable, use other payment method",
+ 50_712 => "Limit of funding source was exceeded",
+ 50_713 => "Means of payment not reusable (canceled by user)",
+ 50_714 => "Means of payment not reusable (expired)",
+ 50_720 => "Rejected by acquirer",
+ 50_730 => "Transaction denied by merchant",
+ 50_800 => "Preauthorisation failed",
+ 50_810 => "Authorisation has been voided",
+ 50_820 => "Authorisation period expired"
+ }
def parse({:ok, %HTTPoison.Response{body: body, status_code: 200}}) do
body = Poison.decode!(body)
parse_body(body)
end
+
def parse({:ok, %HTTPoison.Response{body: body, status_code: 400}}) do
body = Poison.decode!(body)
+
[]
|> set_params(body)
end
+
def parse({:ok, %HTTPoison.Response{body: body, status_code: 404}}) do
body = Poison.decode!(body)
+
[]
|> set_success(body)
|> set_params(body)
@@ -343,6 +345,7 @@ defmodule Gringotts.Gateways.Paymill do
defp set_success(opts, %{"error" => error}) do
opts ++ [message: error, success: false]
end
+
defp set_success(opts, %{"transaction" => %{"response_code" => 20_000}}) do
opts ++ [success: true]
end
@@ -363,31 +366,33 @@ defmodule Gringotts.Gateways.Paymill do
end
end
- #Status code
+ # Status code
defp parse_status_code(opts, %{"status" => "failed"} = body) do
response_code = get_in(body, ["transaction", "response_code"])
response_msg = Map.get(@response_code, response_code, -1)
opts ++ [message: response_msg]
end
+
defp parse_status_code(opts, %{"transaction" => transaction}) do
response_code = Map.get(transaction, "response_code", -1)
response_msg = Map.get(@response_code, response_code, -1)
opts ++ [status_code: response_code, message: response_msg]
end
+
defp parse_status_code(opts, %{"response_code" => code}) do
response_msg = Map.get(@response_code, code, -1)
opts ++ [status_code: code, message: response_msg]
end
- #Authorization
+ # Authorization
defp parse_authorization(opts, %{"status" => "failed"}) do
opts ++ [success: false]
end
+
defp parse_authorization(opts, %{"id" => id} = auth) do
opts ++ [authorization: id]
end
defp set_params(opts, body), do: opts ++ [params: body]
end
-
end
diff --git a/lib/gringotts/gateways/wire_card.ex b/lib/gringotts/gateways/wire_card.ex
index 5aafeb37..939a0fef 100644
--- a/lib/gringotts/gateways/wire_card.ex
+++ b/lib/gringotts/gateways/wire_card.ex
@@ -1,5 +1,6 @@
# call => Gringotts.Gateways.WireCard.authorize(100, creditcard, options)
import XmlBuilder
+
defmodule Gringotts.Gateways.WireCard do
@moduledoc """
WireCard System Plugins
@@ -7,7 +8,7 @@ defmodule Gringotts.Gateways.WireCard do
@test_url "https://c3-test.wirecard.com/secure/ssl-gateway"
@live_url "https://c3.wirecard.com/secure/ssl-gateway"
@homepage_url "http://www.wirecard.com"
-
+
@doc """
Wirecard only allows phone numbers with a format like this: +xxx(yyy)zzz-zzzz-ppp, where:
xxx = Country code
@@ -18,8 +19,8 @@ defmodule Gringotts.Gateways.WireCard do
number 5551234 within area code 202 (country code 1).
"""
@valid_phone_format ~r/\+\d{1,3}(\(?\d{3}\)?)?\d{3}-\d{4}-\d{3}/
- @default_currency "EUR"
- @default_amount 100
+ @default_currency "EUR"
+ @default_amount 100
use Gringotts.Gateways.Base
use Gringotts.Adapter, required_config: [:login, :password, :signature]
@@ -38,10 +39,10 @@ defmodule Gringotts.Gateways.WireCard do
then then the :recurring option will be forced to "Repeated"
===========================================================
TODO: Mandatorily check for :login,:password, :signature in options
- Note: payment_menthod for now is only credit_card and
+ Note: payment_menthod for now is only credit_card and
TODO: change it so it can also have GuWID
================================================
- E.g: =>
+ E.g: =>
creditcard = %CreditCard{
number: "4200000000000000",
month: 12,
@@ -65,19 +66,19 @@ defmodule Gringotts.Gateways.WireCard do
}
options = [
config: %{
- login: "00000031629CA9FA",
+ login: "00000031629CA9FA",
password: "TestXAPTER",
signature: "00000031629CAFD5",
- },
+ },
order_id: 1,
billing_address: address,
description: 'Wirecard remote test purchase',
email: "soleone@example.com",
ip: "127.0.0.1",
test: true
- ]
+ ]
"""
- @spec authorize(Integer | Float, CreditCard.t | String.t, Keyword) :: {:ok, Map}
+ @spec authorize(Integer | Float, CreditCard.t() | String.t(), Keyword) :: {:ok, Map}
def authorize(money, payment_method, options \\ [])
def authorize(money, %CreditCard{} = creditcard, options) do
@@ -92,9 +93,9 @@ defmodule Gringotts.Gateways.WireCard do
@doc """
Capture - the first paramter here should be a GuWid/authorization.
- Authorization is obtained by authorizing the creditcard.
+ Authorization is obtained by authorizing the creditcard.
"""
- @spec capture(String.t, Float, Keyword) :: {:ok, Map}
+ @spec capture(String.t(), Float, Keyword) :: {:ok, Map}
def capture(authorization, money, options \\ []) when is_binary(authorization) do
options = Keyword.put(options, :preauthorization, authorization)
commit(:post, :capture, money, options)
@@ -106,7 +107,7 @@ defmodule Gringotts.Gateways.WireCard do
transaction. If a GuWID is given, rather than a CreditCard,
then then the :recurring option will be forced to "Repeated"
"""
- @spec purchase(Float | Integer, CreditCard| String.t, Keyword) :: {:ok, Map}
+ @spec purchase(Float | Integer, CreditCard | String.t(), Keyword) :: {:ok, Map}
def purchase(money, payment_method, options \\ [])
def purchase(money, %CreditCard{} = creditcard, options) do
@@ -120,33 +121,33 @@ defmodule Gringotts.Gateways.WireCard do
end
@doc """
- Void - A credit card purchase that a seller cancels after it has
- been authorized but before it has been settled.
- A void transaction does not appear on the customer's
+ Void - A credit card purchase that a seller cancels after it has
+ been authorized but before it has been settled.
+ A void transaction does not appear on the customer's
credit card statement, though it might appear in a list
- of pending transactions when the customer checks their
+ of pending transactions when the customer checks their
account online.
==== Parameters ======
identification - The authorization string returned from the
initial authorization or purchase.
"""
- @spec void(String.t, Keyword) :: {:ok, Map}
+ @spec void(String.t(), Keyword) :: {:ok, Map}
def void(identification, options \\ []) when is_binary(identification) do
options = Keyword.put(options, :preauthorization, identification)
commit(:post, :reversal, nil, options)
end
-
+
@doc """
Performs a credit.
-
- This transaction indicates that money
- should flow from the merchant to the customer.
+
+ This transaction indicates that money
+ should flow from the merchant to the customer.
==== Parameters ====
- money -- The amount to be credited to the customer
+ money -- The amount to be credited to the customer
as an Integer value in cents.
identification -- GuWID
"""
- @spec refund(Float, String.t, Keyword) :: {:ok, Map}
+ @spec refund(Float, String.t(), Keyword) :: {:ok, Map}
def refund(money, identification, options \\ []) when is_binary(identification) do
options = Keyword.put(options, :preauthorization, identification)
commit(:post, :bookback, money, options)
@@ -161,54 +162,61 @@ defmodule Gringotts.Gateways.WireCard do
"RECURRING_TRANSACTION/Type" set to "Initial". Subsequent
transactions can then use the GuWID in place of a credit
card by setting "RECURRING_TRANSACTION/Type" to "Repeated".
-
+
This implementation of card store utilizes a Wirecard
"Authorization Check" (a Preauthorization that is automatically
reversed). It defaults to a check amount of "100" (i.e.
$1.00) but this can be overriden (see below).
-
+
IMPORTANT: In order to reuse the stored reference, the
+authorization+ from the response should be saved by
your application code.
-
+
==== Options specific to +store+
-
+
* :amount -- The amount, in cents, that should be
"validated" by the Authorization Check. This amount will
be reserved and then reversed. Default is 100.
-
+
Note: This is not the only way to achieve a card store
operation at Wirecard. Any +purchase+ or +authorize+
can be sent with +options[:recurring] = 'Initial'+ to make
the returned authorization/GuWID usable in later transactions
with +options[:recurring] = 'Repeated'+.
"""
- @spec store(CreditCard.t, Keyword) :: {:ok, Map}
+ @spec store(CreditCard.t(), Keyword) :: {:ok, Map}
def store(%CreditCard{} = creditcard, options \\ []) do
- options = options
- |> Keyword.put(:credit_card, creditcard)
- |> Keyword.put(:recurring, "Initial")
+ options =
+ options
+ |> Keyword.put(:credit_card, creditcard)
+ |> Keyword.put(:recurring, "Initial")
+
money = options[:amount] || @default_amount
# Amex does not support authorization_check
case creditcard.brand do
"american_express" -> commit(:post, :preauthorization, money, options)
- _ -> commit(:post, :authorization_check, money, options)
+ _ -> commit(:post, :authorization_check, money, options)
end
end
-
- # =================== Private Methods ===================
-
+
+ # =================== Private Methods ===================
+
# Contact WireCard, make the XML request, and parse the
# reply into a Response object.
defp commit(method, action, money, options) do
# TODO: validate and setup address hash as per AM
request = build_request(action, money, options)
- headers = %{"Content-Type" => "text/xml",
- "Authorization" => encoded_credentials(
- options[:config][:login], options[:config][:password]
- )
- }
- method |> HTTPoison.request(base_url(options) , request, headers) |> respond
+
+ headers = %{
+ "Content-Type" => "text/xml",
+ "Authorization" =>
+ encoded_credentials(
+ options[:config][:login],
+ options[:config][:password]
+ )
+ }
+
+ method |> HTTPoison.request(base_url(options), request, headers) |> respond
end
defp respond({:ok, %{status_code: 200, body: body}}) do
@@ -217,13 +225,13 @@ defmodule Gringotts.Gateways.WireCard do
end
defp respond({:ok, %{body: body, status_code: status_code}}) do
- {:error, "Some Error Occurred: \n #{ inspect body }"}
+ {:error, "Some Error Occurred: \n #{inspect(body)}"}
end
# Read the XML message from the gateway and check if it was successful,
# and also extract required return values from the response
# TODO: parse XML Response
- defp parse(data) do
+ defp parse(data) do
XmlToMap.naive_map(data)
end
@@ -231,15 +239,18 @@ defmodule Gringotts.Gateways.WireCard do
defp build_request(action, money, options) do
options = Keyword.put(options, :action, action)
- request = doc(element(:WIRECARD_BXML, [
- element(:W_REQUEST, [
- element(:W_JOB, [
- element(:JobID, ""),
- element(:BusinessCaseSignature, options[:config][:signature]),
- add_transaction_data(action, money, options)
+ request =
+ doc(
+ element(:WIRECARD_BXML, [
+ element(:W_REQUEST, [
+ element(:W_JOB, [
+ element(:JobID, ""),
+ element(:BusinessCaseSignature, options[:config][:signature]),
+ add_transaction_data(action, money, options)
+ ])
])
])
- ]))
+ )
request
end
@@ -250,11 +261,14 @@ defmodule Gringotts.Gateways.WireCard do
defp add_transaction_data(action, money, options) do
element("FNC_CC_#{atom_to_upcase_string(options[:action])}", [
element(:FunctionID, "dummy_description"),
- element(:CC_TRANSACTION, [
- element(:TransactionID, options[:order_id]),
- element(:CommerceType, (if options[:commerce_type], do: options[:commerce_type]))
- ] ++ add_action_data(action, money, options) ++ add_customer_data(options)
- )])
+ element(
+ :CC_TRANSACTION,
+ [
+ element(:TransactionID, options[:order_id]),
+ element(:CommerceType, if(options[:commerce_type], do: options[:commerce_type]))
+ ] ++ add_action_data(action, money, options) ++ add_customer_data(options)
+ )
+ ])
end
# Includes the IP address of the customer to the transaction-xml
@@ -269,9 +283,14 @@ defmodule Gringotts.Gateways.WireCard do
def add_action_data(action, money, options) do
case options[:action] do
# returns array of elements
- action when(action in [:preauthorization, :purchase, :authorization_check]) -> create_elems_for_preauth_or_purchase_or_auth_check(money, options)
- action when(action in [:capture, :bookback]) -> create_elems_for_capture_or_bookback(money, options)
- action when(action == :reversal) -> add_guwid(options[:preauthorization])
+ action when action in [:preauthorization, :purchase, :authorization_check] ->
+ create_elems_for_preauth_or_purchase_or_auth_check(money, options)
+
+ action when action in [:capture, :bookback] ->
+ create_elems_for_capture_or_bookback(money, options)
+
+ action when action == :reversal ->
+ add_guwid(options[:preauthorization])
end
end
@@ -280,25 +299,29 @@ defmodule Gringotts.Gateways.WireCard do
add_guwid(options[:preauthorization]) ++ [add_amount(money, options)]
end
- # Creates xml request elements if action is preauth, purchase ir auth_check
+ # Creates xml request elements if action is preauth, purchase ir auth_check
# TODO: handle nil values if array not generated
defp create_elems_for_preauth_or_purchase_or_auth_check(money, options) do
# TODO: setup_recurring_flag
- add_invoice(money, options) ++ element_for_credit_card_or_guwid(options) ++ add_address(options[:billing_address])
+ add_invoice(money, options) ++
+ element_for_credit_card_or_guwid(options) ++ add_address(options[:billing_address])
end
-
+
defp add_address(address) do
if address do
[
element(:CORPTRUSTCENTER_DATA, [
element(:ADDRESS, [
element(:Address1, address[:address1]),
- element(:Address2, (if address[:address2], do: address[:address2])),
+ element(:Address2, if(address[:address2], do: address[:address2])),
element(:City, address[:city]),
- element(:Zip, address[:zip]),
+ element(:Zip, address[:zip]),
add_state(address),
element(:Country, address[:country]),
- element(:Phone, (if regex_match(@valid_phone_format, address[:phone]), do: address[:phone])),
+ element(
+ :Phone,
+ if(regex_match(@valid_phone_format, address[:phone]), do: address[:phone])
+ ),
element(:Email, address[:email])
])
])
@@ -307,9 +330,9 @@ defmodule Gringotts.Gateways.WireCard do
end
defp add_state(address) do
- if (regex_match(~r/[A-Za-z]{2}/, address[:state]) && regex_match(~r/^(us|ca)$/i, address[:country])
- ) do
- element(:State, (String.upcase(address[:state])))
+ if regex_match(~r/[A-Za-z]{2}/, address[:state]) &&
+ regex_match(~r/^(us|ca)$/i, address[:country]) do
+ element(:State, String.upcase(address[:state]))
end
end
@@ -320,7 +343,7 @@ defmodule Gringotts.Gateways.WireCard do
add_guwid(options[:preauthorization])
end
end
-
+
# Includes Guwid data to transaction-xml
defp add_guwid(preauth) do
[element(:GuWID, preauth)]
@@ -329,13 +352,15 @@ defmodule Gringotts.Gateways.WireCard do
# Includes the credit-card data to the transaction-xml
# TODO: Format Credit Card month, ref AM
defp add_creditcard(creditcard) do
- [element(:CREDIT_CARD_DATA, [
- element(:CreditCardNumber, creditcard.number),
- element(:CVC2, creditcard.verification_code),
- element(:ExpirationYear, creditcard.year),
- element(:ExpirationMonth, creditcard.month),
- element(:CardHolderName, join_string([creditcard.first_name, creditcard.last_name], " "))
- ])]
+ [
+ element(:CREDIT_CARD_DATA, [
+ element(:CreditCardNumber, creditcard.number),
+ element(:CVC2, creditcard.verification_code),
+ element(:ExpirationYear, creditcard.year),
+ element(:ExpirationMonth, creditcard.month),
+ element(:CardHolderName, join_string([creditcard.first_name, creditcard.last_name], " "))
+ ])
+ ]
end
# Includes the payment (amount, currency, country) to the transaction-xml
@@ -345,11 +370,11 @@ defmodule Gringotts.Gateways.WireCard do
element(:Currency, currency(options)),
element(:CountryCode, options[:billing_address][:country]),
element(:RECURRING_TRANSACTION, [
- element(:Type, (options[:recurring] || "Single"))
+ element(:Type, options[:recurring] || "Single")
])
]
end
-
+
# Include the amount in the transaction-xml
# TODO: check for localized currency or currency
# localized_amount(money, options[:currency] || currency(money))
@@ -357,24 +382,24 @@ defmodule Gringotts.Gateways.WireCard do
defp atom_to_upcase_string(atom) do
atom
- |> to_string
- |> String.upcase
+ |> to_string
+ |> String.upcase()
end
# Encode login and password in Base64 to supply as HTTP header
# (for http basic authentication)
defp encoded_credentials(login, password) do
[login, password]
- |> join_string(":")
- |> Base.encode64
- |> (&("Basic "<> &1)).()
+ |> join_string(":")
+ |> Base.encode64()
+ |> (&("Basic " <> &1)).()
end
defp join_string(list_of_words, joiner), do: Enum.join(list_of_words, joiner)
defp regex_match(regex, string), do: Regex.match?(regex, string)
- defp base_url(opts), do: if opts[:test], do: @test_url, else: @live_url
+ defp base_url(opts), do: if(opts[:test], do: @test_url, else: @live_url)
defp currency(opts), do: opts[:currency] || @default_currency
end
diff --git a/lib/gringotts/response.ex b/lib/gringotts/response.ex
index ac369f89..c64ec0a2 100644
--- a/lib/gringotts/response.ex
+++ b/lib/gringotts/response.ex
@@ -8,8 +8,17 @@ defmodule Gringotts.Response do
"""
defstruct [
- :success, :id, :token, :status_code, :gateway_code, :reason, :message,
- :avs_result, :cvc_result, :raw, :fraud_review
+ :success,
+ :id,
+ :token,
+ :status_code,
+ :gateway_code,
+ :reason,
+ :message,
+ :avs_result,
+ :cvc_result,
+ :raw,
+ :fraud_review
]
@typedoc """
@@ -54,19 +63,19 @@ defmodule Gringotts.Response do
[cvc]: https://en.wikipedia.org/wiki/Card_security_code
"""
- @type t:: %__MODULE__{
- success: boolean,
- id: String.t,
- token: String.t,
- status_code: non_neg_integer,
- gateway_code: String.t,
- reason: String.t,
- message: String.t,
- avs_result: %{street: String.t, zip_code: String.t},
- cvc_result: String.t,
- raw: String.t,
- fraud_review: term
- }
+ @type t :: %__MODULE__{
+ success: boolean,
+ id: String.t(),
+ token: String.t(),
+ status_code: non_neg_integer,
+ gateway_code: String.t(),
+ reason: String.t(),
+ message: String.t(),
+ avs_result: %{street: String.t(), zip_code: String.t()},
+ cvc_result: String.t(),
+ raw: String.t(),
+ fraud_review: term
+ }
def success(opts \\ []) do
new(true, opts)
diff --git a/mix.exs b/mix.exs
index ef54cc7f..244a6405 100644
--- a/mix.exs
+++ b/mix.exs
@@ -17,14 +17,14 @@ defmodule Gringotts.Mixfile do
tool: ExCoveralls
],
preferred_cli_env: [
- "coveralls": :test,
+ coveralls: :test,
"coveralls.detail": :test,
- "coveralls.post": :test,
- "coveralls.html": :test,
- "coveralls.travis": :test
+ "coveralls.json": :test,
+ "coveralls.html": :test
],
deps: deps(),
- docs: docs()]
+ docs: docs()
+ ]
end
# Configuration for the OTP application
@@ -32,7 +32,7 @@ defmodule Gringotts.Mixfile do
# Type `mix help compile.app` for more information
def application do
[
- applications: [:httpoison, :hackney, :elixir_xml_to_map, :timex],
+ applications: [:httpoison, :hackney, :elixir_xml_to_map, :timex]
]
end
@@ -89,8 +89,8 @@ defmodule Gringotts.Mixfile do
end
defp groups_for_modules do
- [
- "Gateways": ~r/^Gringotts.Gateways.?/,
- ]
+ [
+ Gateways: ~r/^Gringotts.Gateways.?/
+ ]
end
end
diff --git a/test/gateways/authorize_net_test.exs b/test/gateways/authorize_net_test.exs
index 2ce52278..cec0b0cd 100644
--- a/test/gateways/authorize_net_test.exs
+++ b/test/gateways/authorize_net_test.exs
@@ -103,8 +103,8 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
customer_type: "individual"
]
@opts_customer_profile_args [
- config: @auth,
- customer_profile_id: "1814012002"
+ config: @auth,
+ customer_profile_id: "1814012002"
]
@refund_id "60036752756"
@@ -124,7 +124,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
post: fn _url, _body, _headers ->
MockResponse.successful_purchase_response()
end do
- assert {:ok, response} = ANet.purchase(@amount, @card, @opts)
+ assert {:ok, _response} = ANet.purchase(@amount, @card, @opts)
end
end
@@ -144,7 +144,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
post: fn _url, _body, _headers ->
MockResponse.successful_authorize_response()
end do
- assert {:ok, response} = ANet.authorize(@amount, @card, @opts)
+ assert {:ok, _response} = ANet.authorize(@amount, @card, @opts)
end
end
@@ -164,13 +164,12 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
post: fn _url, _body, _headers ->
MockResponse.successful_capture_response()
end do
- assert {:ok, response} = ANet.capture(@capture_id, @amount, @opts)
+ assert {:ok, _response} = ANet.capture(@capture_id, @amount, @opts)
end
end
test "with bad transaction id" do
- with_mock HTTPoison,
- post: fn _url, _body, _headers -> MockResponse.bad_id_capture() end do
+ with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.bad_id_capture() end do
assert {:error, response} = ANet.capture(@capture_invalid_id, @amount, @opts)
end
end
@@ -182,13 +181,12 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
post: fn _url, _body, _headers ->
MockResponse.successful_refund_response()
end do
- assert {:ok, response} = ANet.refund(@amount, @refund_id, @opts_refund)
+ assert {:ok, _response} = ANet.refund(@amount, @refund_id, @opts_refund)
end
end
test "bad payment params" do
- with_mock HTTPoison,
- post: fn _url, _body, _headers -> MockResponse.bad_card_refund() end do
+ with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.bad_card_refund() end do
assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund_bad_payment)
end
end
@@ -203,9 +201,8 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
describe "void" do
test "successful response with right params" do
- with_mock HTTPoison,
- post: fn _url, _body, _headers -> MockResponse.successful_void() end do
- assert {:ok, response} = ANet.void(@void_id, @opts)
+ with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_void() end do
+ assert {:ok, _response} = ANet.void(@void_id, @opts)
end
end
@@ -221,14 +218,14 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
test "successful response with right params" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.successful_store_response() end do
- assert {:ok, response} = ANet.store(@card, @opts_store)
+ assert {:ok, _response} = ANet.store(@card, @opts_store)
end
end
test "successful response without validation and customer type" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.successful_store_response() end do
- assert {:ok, response} = ANet.store(@card, @opts_store_without_validation)
+ assert {:ok, _response} = ANet.store(@card, @opts_store_without_validation)
end
end
@@ -239,7 +236,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
end do
assert {:error, response} = ANet.store(@card, @opts_store_no_profile)
- "Error"
+ "Error"
end
end
@@ -248,16 +245,16 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
post: fn _url, _body, _headers ->
MockResponse.customer_payment_profile_success_response()
end do
- assert {:ok, response} = ANet.store(@card, @opts_customer_profile)
+ assert {:ok, _response} = ANet.store(@card, @opts_customer_profile)
- "Ok"
+ "Ok"
end
end
test "successful response without valiadtion mode and customer type" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.successful_store_response() end do
- assert {:ok, response} = ANet.store(@card, @opts_customer_profile_args)
+ assert {:ok, _response} = ANet.store(@card, @opts_customer_profile_args)
end
end
end
@@ -268,7 +265,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do
post: fn _url, _body, _headers ->
MockResponse.successful_unstore_response()
end do
- assert {:ok, response} = ANet.unstore(@unstore_id, @opts)
+ assert {:ok, _response} = ANet.unstore(@unstore_id, @opts)
end
end
end
diff --git a/test/gateways/bogus_test.exs b/test/gateways/bogus_test.exs
index 041c1469..9235bcf8 100644
--- a/test/gateways/bogus_test.exs
+++ b/test/gateways/bogus_test.exs
@@ -6,51 +6,44 @@ defmodule Gringotts.Gateways.BogusTest do
@some_id "some_arbitrary_id"
@amount Money.new(5, :USD)
-
+
test "authorize" do
- {:ok, %Response{id: id, success: success}} =
- Gateway.authorize(@amount, :card, [])
+ {:ok, %Response{id: id, success: success}} = Gateway.authorize(@amount, :card, [])
assert success
assert id != nil
end
test "purchase" do
- {:ok, %Response{id: id, success: success}} =
- Gateway.purchase(@amount, :card, [])
+ {:ok, %Response{id: id, success: success}} = Gateway.purchase(@amount, :card, [])
assert success
assert id != nil
end
test "capture" do
- {:ok, %Response{id: id, success: success}} =
- Gateway.capture(@some_id, @amount, [])
+ {:ok, %Response{id: id, success: success}} = Gateway.capture(@some_id, @amount, [])
assert success
assert id != nil
end
test "void" do
- {:ok, %Response{id: id, success: success}} =
- Gateway.void(@some_id, [])
+ {:ok, %Response{id: id, success: success}} = Gateway.void(@some_id, [])
assert success
assert id != nil
end
test "store" do
- {:ok, %Response{success: success}} =
- Gateway.store(%Gringotts.CreditCard{}, [])
+ {:ok, %Response{success: success}} = Gateway.store(%Gringotts.CreditCard{}, [])
assert success
end
test "unstore with customer" do
- {:ok, %Response{success: success}} =
- Gateway.unstore(@some_id, [])
+ {:ok, %Response{success: success}} = Gateway.unstore(@some_id, [])
assert success
end
-
end
diff --git a/test/gateways/cams_test.exs b/test/gateways/cams_test.exs
index edf0e5f4..a6117a78 100644
--- a/test/gateways/cams_test.exs
+++ b/test/gateways/cams_test.exs
@@ -68,8 +68,7 @@ defmodule Gringotts.Gateways.CamsTest do
test "with bad card" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.failed_purchase_with_bad_credit_card() end do
- {:error, %Response{reason: reason}} =
- Gateway.purchase(@money, @bad_card, @options)
+ {:error, %Response{reason: reason}} = Gateway.purchase(@money, @bad_card, @options)
assert String.contains?(reason, "Invalid Credit Card Number")
end
@@ -95,8 +94,7 @@ defmodule Gringotts.Gateways.CamsTest do
test "with bad card" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.failed_authorized_with_bad_card() end do
- {:error, %Response{reason: reason}} =
- Gateway.authorize(@money, @bad_card, @options)
+ {:error, %Response{reason: reason}} = Gateway.authorize(@money, @bad_card, @options)
assert String.contains?(reason, "Invalid Credit Card Number")
end
@@ -106,23 +104,20 @@ defmodule Gringotts.Gateways.CamsTest do
describe "capture" do
test "with full amount" do
with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_capture() end do
- assert {:ok, %Response{}} =
- Gateway.capture(@money, @id , @options)
+ assert {:ok, %Response{}} = Gateway.capture(@money, @id, @options)
end
end
test "with partial amount" do
with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_capture() end do
- assert {:ok, %Response{}} =
- Gateway.capture(@money_less, @id , @options)
+ assert {:ok, %Response{}} = Gateway.capture(@money_less, @id, @options)
end
end
test "with invalid transaction_id" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.invalid_transaction_id() end do
- {:error, %Response{reason: reason}} =
- Gateway.capture(@money, @bad_id, @options)
+ {:error, %Response{reason: reason}} = Gateway.capture(@money, @bad_id, @options)
assert String.contains?(reason, "Transaction not found")
end
@@ -131,8 +126,7 @@ defmodule Gringotts.Gateways.CamsTest do
test "with more than authorized amount" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.more_than_authorization_amount() end do
- {:error, %Response{reason: reason}} =
- Gateway.capture(@money_more, @id , @options)
+ {:error, %Response{reason: reason}} = Gateway.capture(@money_more, @id, @options)
assert String.contains?(reason, "exceeds the authorization amount")
end
@@ -141,8 +135,7 @@ defmodule Gringotts.Gateways.CamsTest do
test "on already captured transaction" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.multiple_capture_on_same_transaction() end do
- {:error, %Response{reason: reason}} =
- Gateway.capture(@money, @id , @options)
+ {:error, %Response{reason: reason}} = Gateway.capture(@money, @id, @options)
assert String.contains?(reason, "A capture requires that")
end
@@ -152,16 +145,14 @@ defmodule Gringotts.Gateways.CamsTest do
describe "refund" do
test "with correct params" do
with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_refund() end do
- assert {:ok, %Response{}} =
- Gateway.refund(@money, @id , @options)
+ assert {:ok, %Response{}} = Gateway.refund(@money, @id, @options)
end
end
test "with more than purchased amount" do
with_mock HTTPoison,
post: fn _url, _body, _headers -> MockResponse.more_than_purchase_amount() end do
- {:error, %Response{reason: reason}} =
- Gateway.refund(@money_more, @id , @options)
+ {:error, %Response{reason: reason}} = Gateway.refund(@money_more, @id, @options)
assert String.contains?(reason, "Refund amount may not exceed")
end
@@ -171,7 +162,7 @@ defmodule Gringotts.Gateways.CamsTest do
describe "void" do
test "with correct params" do
with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.successful_void() end do
- {:ok, %Response{message: message}} = Gateway.void(@id , @options)
+ {:ok, %Response{message: message}} = Gateway.void(@id, @options)
assert String.contains?(message, "Void Successful")
end
end
diff --git a/test/gateways/global_collect_test.exs b/test/gateways/global_collect_test.exs
index 87b0d702..d64489c2 100644
--- a/test/gateways/global_collect_test.exs
+++ b/test/gateways/global_collect_test.exs
@@ -1,9 +1,9 @@
defmodule Gringotts.Gateways.GlobalCollectTest do
-
- Code.require_file "../mocks/global_collect_mock.exs", __DIR__
+ Code.require_file("../mocks/global_collect_mock.exs", __DIR__)
use ExUnit.Case, async: false
alias Gringotts.Gateways.GlobalCollectMock, as: MockResponse
alias Gringotts.Gateways.GlobalCollect
+
alias Gringotts.{
CreditCard
}
@@ -69,21 +69,32 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
@invalid_token 30
- @invalid_config [config: %{secret_api_key: "Qtg9v4Q0G13sLRNcClWhHnvN1kVYWDcy4w9rG8T86XU=", api_key_id: "e5743abfc360ed12"}]
+ @invalid_config [
+ config: %{
+ secret_api_key: "Qtg9v4Q0G13sLRNcClWhHnvN1kVYWDcy4w9rG8T86XU=",
+ api_key_id: "e5743abfc360ed12"
+ }
+ ]
@options [
- config: %{secret_api_key: "Qtg9v4Q0G13sLRNcClWhHnvN1kVYWDcy4w9rG8T86XU=", api_key_id: "e5743abfc360ed12", merchant_id: "1226"},
+ config: %{
+ secret_api_key: "Qtg9v4Q0G13sLRNcClWhHnvN1kVYWDcy4w9rG8T86XU=",
+ api_key_id: "e5743abfc360ed12",
+ merchant_id: "1226"
+ },
description: "Store Purchase 1437598192",
merchantCustomerId: "234",
customer_name: "John Doe",
- dob: "19490917", company: "asma",
+ dob: "19490917",
+ company: "asma",
email: "johndoe@gmail.com",
phone: "7468474533",
order_id: "2323",
invoice: @invoice,
billingAddress: @billingAddress,
shippingAddress: @shippingAddress,
- name: @name, skipAuthentication: "true"
+ name: @name,
+ skipAuthentication: "true"
]
describe "validation arguments check" do
@@ -97,18 +108,21 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
describe "purchase" do
test "with valid card" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_purchase_with_valid_card end] do
- {:ok, response} = GlobalCollect.purchase(@amount, @valid_card, @options)
- assert response.status_code == 201
- assert response.success == true
- assert response.raw["payment"]["statusOutput"]["isAuthorized"] == true
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.test_for_purchase_with_valid_card()
+ end do
+ {:ok, response} = GlobalCollect.purchase(@amount, @valid_card, @options)
+ assert response.status_code == 201
+ assert response.success == true
+ assert response.raw["payment"]["statusOutput"]["isAuthorized"] == true
end
end
-
test "with invalid amount" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_purchase_with_invalid_amount end] do
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.test_for_purchase_with_invalid_amount()
+ end do
{:error, response} = GlobalCollect.purchase(@bad_amount, @valid_card, @options)
assert response.status_code == 400
assert response.success == false
@@ -120,7 +134,9 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
describe "authorize" do
test "with valid card" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_authorize_with_valid_card end] do
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.test_for_authorize_with_valid_card()
+ end do
{:ok, response} = GlobalCollect.authorize(@amount, @valid_card, @options)
assert response.status_code == 201
assert response.success == true
@@ -130,17 +146,23 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
test "with invalid card" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_authorize_with_invalid_card end] do
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.test_for_authorize_with_invalid_card()
+ end do
{:error, response} = GlobalCollect.authorize(@amount, @invalid_card, @options)
assert response.status_code == 400
assert response.success == false
- assert response.message == "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT"
+
+ assert response.message ==
+ "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT"
end
end
test "with invalid amount" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_authorize_with_invalid_amount end] do
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.test_for_authorize_with_invalid_amount()
+ end do
{:error, response} = GlobalCollect.authorize(@bad_amount, @valid_card, @options)
assert response.status_code == 400
assert response.success == false
@@ -152,7 +174,7 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
describe "refund" do
test "with refund not enabled for the respective account" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_refund end] do
+ request: fn _method, _url, _body, _headers -> MockResponse.test_for_refund() end do
{:error, response} = GlobalCollect.refund(@amount, @valid_token, @options)
assert response.status_code == 400
assert response.success == false
@@ -164,7 +186,9 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
describe "capture" do
test "with valid payment id" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_capture_with_valid_paymentid end] do
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.test_for_capture_with_valid_paymentid()
+ end do
{:ok, response} = GlobalCollect.capture(@valid_token, @amount, @options)
assert response.status_code == 200
assert response.success == true
@@ -173,20 +197,24 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
end
test "with invalid payment id" do
- with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_capture_with_invalid_paymentid end] do
- {:error, response} = GlobalCollect.capture(@invalid_token, @amount, @options)
- assert response.status_code == 404
- assert response.success == false
- assert response.message == "UNKNOWN_PAYMENT_ID"
- end
+ with_mock HTTPoison,
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.test_for_capture_with_invalid_paymentid()
+ end do
+ {:error, response} = GlobalCollect.capture(@invalid_token, @amount, @options)
+ assert response.status_code == 404
+ assert response.success == false
+ assert response.message == "UNKNOWN_PAYMENT_ID"
+ end
end
end
describe "void" do
test "with valid card" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_void_with_valid_card end] do
+ request: fn _method, _url, _body, _headers ->
+ MockResponse.test_for_void_with_valid_card()
+ end do
{:ok, response} = GlobalCollect.void(@valid_token, @options)
assert response.status_code == 200
assert response.raw["payment"]["status"] == "CANCELLED"
@@ -197,7 +225,7 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
describe "network failure" do
test "with authorization" do
with_mock HTTPoison,
- [request: fn(_method, _url, _body, _headers) -> MockResponse.test_for_network_failure end] do
+ request: fn _method, _url, _body, _headers -> MockResponse.test_for_network_failure() end do
{:error, response} = GlobalCollect.authorize(@amount, @valid_card, @options)
assert response.success == false
assert response.reason == :network_fail?
diff --git a/test/gateways/monei_test.exs b/test/gateways/monei_test.exs
index ae41e9a0..e641d91b 100644
--- a/test/gateways/monei_test.exs
+++ b/test/gateways/monei_test.exs
@@ -6,6 +6,7 @@ defmodule Gringotts.Gateways.MoneiTest do
}
alias Gringotts.Gateways.Monei, as: Gateway
+ alias Plug.{Conn, Parsers}
@amount42 Money.new(42, :USD)
@amount3 Money.new(3, :USD)
@@ -136,7 +137,7 @@ defmodule Gringotts.Gateways.MoneiTest do
assert params["authentication.entityId"] == "some_secret_entity_id"
assert params["authentication.password"] == "some_secret_password"
assert params["authentication.userId"] == "some_secret_user_id"
- Plug.Conn.resp(conn, 200, @auth_success)
+ Conn.resp(conn, 200, @auth_success)
end)
{:ok, response} = Gateway.purchase(@amount42, @card, config: auth)
@@ -159,13 +160,12 @@ defmodule Gringotts.Gateways.MoneiTest do
assert params["transactionCategory"] == @extra_opts[:category]
assert params["customer.merchantCustomerId"] == @customer[:merchantCustomerId]
- assert params["shipping.customer.merchantCustomerId"] ==
- @customer[:merchantCustomerId]
+ assert params["shipping.customer.merchantCustomerId"] == @customer[:merchantCustomerId]
assert params["merchant.submerchantId"] == @merchant[:submerchantId]
assert params["billing.city"] == @billing[:city]
assert params["shipping.method"] == @shipping[:method]
- Plug.Conn.resp(conn, 200, @register_success)
+ Conn.resp(conn, 200, @register_success)
end)
opts = randoms ++ @extra_opts ++ [config: auth]
@@ -176,7 +176,7 @@ defmodule Gringotts.Gateways.MoneiTest do
test "when we get non-json.", %{bypass: bypass, auth: auth} do
Bypass.expect_once(bypass, "POST", "/v1/payments", fn conn ->
- Plug.Conn.resp(conn, 400, "")
+ Conn.resp(conn, 400, "")
end)
{:error, _} = Gateway.authorize(@amount42, @bad_card, config: auth)
@@ -191,7 +191,7 @@ defmodule Gringotts.Gateways.MoneiTest do
assert params["amount"] == "42.00"
assert params["currency"] == "USD"
assert params["paymentType"] == "PA"
- Plug.Conn.resp(conn, 200, @auth_success)
+ Conn.resp(conn, 200, @auth_success)
end)
{:ok, response} = Gateway.authorize(@amount42, @card, config: auth)
@@ -207,7 +207,7 @@ defmodule Gringotts.Gateways.MoneiTest do
assert params["amount"] == "42.00"
assert params["currency"] == "USD"
assert params["paymentType"] == "DB"
- Plug.Conn.resp(conn, 200, @auth_success)
+ Conn.resp(conn, 200, @auth_success)
end)
{:ok, response} = Gateway.purchase(@amount42, @card, config: auth)
@@ -219,7 +219,7 @@ defmodule Gringotts.Gateways.MoneiTest do
p_conn = parse(conn)
params = p_conn.body_params
assert params["createRegistration"] == "true"
- Plug.Conn.resp(conn, 200, @register_success)
+ Conn.resp(conn, 200, @register_success)
end)
{:ok, response} = Gateway.purchase(@amount42, @card, register: true, config: auth)
@@ -239,7 +239,7 @@ defmodule Gringotts.Gateways.MoneiTest do
assert params["card.holder"] == "Harry Potter"
assert params["card.number"] == "4200000000000000"
assert params["paymentBrand"] == "VISA"
- Plug.Conn.resp(conn, 200, @store_success)
+ Conn.resp(conn, 200, @store_success)
end)
{:ok, response} = Gateway.store(@card, config: auth)
@@ -259,7 +259,7 @@ defmodule Gringotts.Gateways.MoneiTest do
assert params["amount"] == "42.00"
assert params["currency"] == "USD"
assert params["paymentType"] == "CP"
- Plug.Conn.resp(conn, 200, @auth_success)
+ Conn.resp(conn, 200, @auth_success)
end
)
@@ -278,7 +278,7 @@ defmodule Gringotts.Gateways.MoneiTest do
p_conn = parse(conn)
params = p_conn.body_params
assert :error == Map.fetch(params, "createRegistration")
- Plug.Conn.resp(conn, 200, @auth_success)
+ Conn.resp(conn, 200, @auth_success)
end
)
@@ -306,7 +306,7 @@ defmodule Gringotts.Gateways.MoneiTest do
assert params["amount"] == "3.00"
assert params["currency"] == "USD"
assert params["paymentType"] == "RF"
- Plug.Conn.resp(conn, 200, @auth_success)
+ Conn.resp(conn, 200, @auth_success)
end
)
@@ -328,7 +328,7 @@ defmodule Gringotts.Gateways.MoneiTest do
assert params["authentication.entityId"] == "some_secret_entity_id"
assert params["authentication.password"] == "some_secret_password"
assert params["authentication.userId"] == "some_secret_user_id"
- Plug.Conn.resp(conn, 200, "")
+ Conn.resp(conn, 200, "")
end
)
@@ -349,7 +349,7 @@ defmodule Gringotts.Gateways.MoneiTest do
assert :error == Map.fetch(params, :amount)
assert :error == Map.fetch(params, :currency)
assert params["paymentType"] == "RV"
- Plug.Conn.resp(conn, 200, @auth_success)
+ Conn.resp(conn, 200, @auth_success)
end
)
@@ -359,8 +359,8 @@ defmodule Gringotts.Gateways.MoneiTest do
end
def parse(conn, opts \\ []) do
- opts = Keyword.put_new(opts, :parsers, [Plug.Parsers.URLENCODED])
- Plug.Parsers.call(conn, Plug.Parsers.init(opts))
+ opts = Keyword.put_new(opts, :parsers, [Parsers.URLENCODED])
+ Parsers.call(conn, Parsers.init(opts))
end
end
diff --git a/test/gateways/trexle_test.exs b/test/gateways/trexle_test.exs
index 7078b9f6..00da54ca 100644
--- a/test/gateways/trexle_test.exs
+++ b/test/gateways/trexle_test.exs
@@ -157,7 +157,9 @@ defmodule Gringotts.Gateways.TrexleTest do
MockResponse.test_for_network_failure()
end do
{:error, response} = Trexle.authorize(@amount, @valid_card, @opts)
- assert response.message == "HTTPoison says 'some_hackney_error' [ID: some_hackney_error_id]"
+
+ assert response.message ==
+ "HTTPoison says 'some_hackney_error' [ID: some_hackney_error_id]"
end
end
end
diff --git a/test/gateways/wire_card_test.exs b/test/gateways/wire_card_test.exs
index d23d9090..59de0f03 100644
--- a/test/gateways/wire_card_test.exs
+++ b/test/gateways/wire_card_test.exs
@@ -3,18 +3,11 @@ defmodule Gringotts.Gateways.WireCardTest do
import Mock
- alias Gringotts.{
- CreditCard,
- Address,
- Response
- }
- alias Gringotts.Gateways.WireCard, as: Gateway
-
setup do
# TEST_AUTHORIZATION_GUWID = 'C822580121385121429927'
# TEST_PURCHASE_GUWID = 'C865402121385575982910'
# TEST_CAPTURE_GUWID = 'C833707121385268439116'
-
+
# credit_card = %CreditCard{name: "Longbob", number: "4200000000000000", cvc: "123", expiration: {2015, 11}}
# config = %{credentails: {'user', 'pass'}, default_currency: "EUR"}
@@ -24,7 +17,4 @@ defmodule Gringotts.Gateways.WireCardTest do
test "test_successful_authorization" do
assert 1 + 1 == 2
end
-
-
-
end
diff --git a/test/gringotts_test.exs b/test/gringotts_test.exs
index 7d9f7681..01d01824 100644
--- a/test/gringotts_test.exs
+++ b/test/gringotts_test.exs
@@ -48,13 +48,11 @@ defmodule GringottsTest do
end
test "authorization" do
- assert authorize(GringottsTest.FakeGateway, 100, :card, []) ==
- :authorization_response
+ assert authorize(GringottsTest.FakeGateway, 100, :card, []) == :authorization_response
end
test "purchase" do
- assert purchase(GringottsTest.FakeGateway, 100, :card, []) ==
- :purchase_response
+ assert purchase(GringottsTest.FakeGateway, 100, :card, []) == :purchase_response
end
test "capture" do
diff --git a/test/integration/gateways/monei_test.exs b/test/integration/gateways/monei_test.exs
index 71f48bd7..59bc9b88 100644
--- a/test/integration/gateways/monei_test.exs
+++ b/test/integration/gateways/monei_test.exs
@@ -68,7 +68,7 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
password: "hMkqf2qbWf",
entityId: "8a82941760036820016010a28a8337f6"
}
-
+
setup_all do
Application.put_env(
:gringotts,
@@ -104,7 +104,8 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
{:ok, _registration_token} <- Map.fetch(auth_result, :token),
{:ok, _capture_result} <- Gateway.capture(auth_result.id, @amount, opts) do
"yay!"
- else {:error, _err} ->
+ else
+ {:error, _err} ->
flunk()
end
end
@@ -113,7 +114,8 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
with {:ok, auth_result} <- Gateway.authorize(@amount, @card, opts),
{:ok, _void_result} <- Gateway.void(auth_result.id, opts) do
"yay!"
- else {:error, _err} ->
+ else
+ {:error, _err} ->
flunk()
end
end
@@ -122,7 +124,8 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
with {:ok, purchase_result} <- Gateway.purchase(@amount, @card, opts),
{:ok, _void_result} <- Gateway.void(purchase_result.id, opts) do
"yay!"
- else {:error, _err} ->
+ else
+ {:error, _err} ->
flunk()
end
end
@@ -131,7 +134,8 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
with {:ok, purchase_result} <- Gateway.purchase(@amount, @card, opts),
{:ok, _refund_result} <- Gateway.refund(@sub_amount, purchase_result.id, opts) do
"yay!"
- else {:error, _err} ->
+ else
+ {:error, _err} ->
flunk()
end
end
@@ -145,7 +149,8 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do
with {:ok, store_result} <- Gateway.store(@card, opts),
{:ok, _unstore_result} <- Gateway.unstore(store_result.id, opts) do
"yay!"
- else {:error, _err} ->
+ else
+ {:error, _err} ->
flunk()
end
end
diff --git a/test/integration/gateways/stripe_test.exs b/test/integration/gateways/stripe_test.exs
index b390f989..e4d3e070 100644
--- a/test/integration/gateways/stripe_test.exs
+++ b/test/integration/gateways/stripe_test.exs
@@ -1,21 +1,22 @@
defmodule Gringotts.Gateways.StripeTest do
-
use ExUnit.Case
alias Gringotts.Gateways.Stripe
+
alias Gringotts.{
CreditCard,
Address
}
@moduletag integration: true
-
+
@amount Money.new(5, :USD)
@card %CreditCard{
first_name: "John",
last_name: "Smith",
number: "4242424242424242",
- year: "2068", # Can't be more than 50 years in the future, Haha.
+ # Can't be more than 50 years in the future, Haha.
+ year: "2068",
month: "12",
verification_code: "123"
}
diff --git a/test/integration/money.exs b/test/integration/money.exs
index 3f5691ba..e4aaa331 100644
--- a/test/integration/money.exs
+++ b/test/integration/money.exs
@@ -4,7 +4,7 @@ defmodule Gringotts.Integration.Gateways.MoneyTest do
alias Gringotts.Money, as: MoneyProtocol
@moduletag :integration
-
+
@ex_money Money.new(42, :EUR)
@ex_money_long Money.new("42.126456", :EUR)
@ex_money_bhd Money.new(42, :BHD)
@@ -12,50 +12,50 @@ defmodule Gringotts.Integration.Gateways.MoneyTest do
@any %{value: Decimal.new(42), currency: "EUR"}
@any_long %{value: Decimal.new("42.126456"), currency: "EUR"}
@any_bhd %{value: Decimal.new("42"), currency: "BHD"}
-
+
describe "ex_money" do
test "value is a Decimal.t" do
- assert match? %Decimal{}, MoneyProtocol.value(@ex_money)
+ assert match?(%Decimal{}, MoneyProtocol.value(@ex_money))
end
test "currency is an upcase String.t" do
the_currency = MoneyProtocol.currency(@ex_money)
- assert match? currency when is_binary(currency), the_currency
+ assert match?(currency when is_binary(currency), the_currency)
assert the_currency == String.upcase(the_currency)
end
test "to_integer" do
- assert match? {"EUR", 4200, -2}, MoneyProtocol.to_integer(@ex_money)
- assert match? {"BHD", 42_000, -3}, MoneyProtocol.to_integer(@ex_money_bhd)
+ assert match?({"EUR", 4200, -2}, MoneyProtocol.to_integer(@ex_money))
+ assert match?({"BHD", 42_000, -3}, MoneyProtocol.to_integer(@ex_money_bhd))
end
test "to_string" do
- assert match? {"EUR", "42.00"}, MoneyProtocol.to_string(@ex_money)
- assert match? {"EUR", "42.13"}, MoneyProtocol.to_string(@ex_money_long)
- assert match? {"BHD", "42.000"}, MoneyProtocol.to_string(@ex_money_bhd)
+ assert match?({"EUR", "42.00"}, MoneyProtocol.to_string(@ex_money))
+ assert match?({"EUR", "42.13"}, MoneyProtocol.to_string(@ex_money_long))
+ assert match?({"BHD", "42.000"}, MoneyProtocol.to_string(@ex_money_bhd))
end
end
describe "Any" do
test "value is a Decimal.t" do
- assert match? %Decimal{}, MoneyProtocol.value(@any)
+ assert match?(%Decimal{}, MoneyProtocol.value(@any))
end
test "currency is an upcase String.t" do
the_currency = MoneyProtocol.currency(@any)
- assert match? currency when is_binary(currency), the_currency
+ assert match?(currency when is_binary(currency), the_currency)
assert the_currency == String.upcase(the_currency)
end
test "to_integer" do
- assert match? {"EUR", 4200, -2}, MoneyProtocol.to_integer(@any)
- assert match? {"BHD", 4200, -2}, MoneyProtocol.to_integer(@any_bhd)
+ assert match?({"EUR", 4200, -2}, MoneyProtocol.to_integer(@any))
+ assert match?({"BHD", 4200, -2}, MoneyProtocol.to_integer(@any_bhd))
end
test "to_string" do
- assert match? {"EUR", "42.00"}, MoneyProtocol.to_string(@any)
- assert match? {"EUR", "42.13"}, MoneyProtocol.to_string(@any_long)
- assert match? {"BHD", "42.00"}, MoneyProtocol.to_string(@any_bhd)
+ assert match?({"EUR", "42.00"}, MoneyProtocol.to_string(@any))
+ assert match?({"EUR", "42.13"}, MoneyProtocol.to_string(@any_long))
+ assert match?({"BHD", "42.00"}, MoneyProtocol.to_string(@any_bhd))
end
end
end
diff --git a/test/mocks/authorize_net_mock.exs b/test/mocks/authorize_net_mock.exs
index cb68df4a..712602cd 100644
--- a/test/mocks/authorize_net_mock.exs
+++ b/test/mocks/authorize_net_mock.exs
@@ -1,321 +1,422 @@
- defmodule Gringotts.Gateways.AuthorizeNetMock do
-
- # purchase mock response
- def successful_purchase_response do
- {:ok,
- %HTTPoison.Response{body: ~s{123456OkI00001
Successful.1C7HPT1YP2600365530965D6782A03246EE3BAFABE8006E32DE970XXXX0015MasterCard1
This transaction has been approved.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-13182173"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "908"}, {"Date", "Thu, 21 Dec 2017 09:29:12 GMT"},
- {"Connection", "keep-alive"}],
+defmodule Gringotts.Gateways.AuthorizeNetMock do
+ # purchase mock response
+ def successful_purchase_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{123456OkI00001
Successful.1C7HPT1YP2600365530965D6782A03246EE3BAFABE8006E32DE970XXXX0015MasterCard1
This transaction has been approved.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-13182173"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "908"},
+ {"Date", "Thu, 21 Dec 2017 09:29:12 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- def bad_card_purchase_response do
- {:ok,
- %HTTPoison.Response{body: ~s{ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XXXXX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_f2f80544-1a98-4ad7-989b-8d267ebf5043-56152-10066531"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "514"}, {"Date", "Thu, 21 Dec 2017 09:35:45 GMT"},
- {"Connection", "keep-alive"}],
+ def bad_card_purchase_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XXXXX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_f2f80544-1a98-4ad7-989b-8d267ebf5043-56152-10066531"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "514"},
+ {"Date", "Thu, 21 Dec 2017 09:35:45 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- def bad_amount_purchase_response do
- {:ok,
- %HTTPoison.Response{body: ~s{123456ErrorE00027
The transaction was unsuccessful.3P0C7C56F020A2AE2660A87637CD00B4D5C0XXXX0015MasterCard5A valid amount is required.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-13187900"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "867"}, {"Date", "Thu, 21 Dec 2017 09:44:33 GMT"},
- {"Connection", "keep-alive"}],
+ def bad_amount_purchase_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{123456ErrorE00027
The transaction was unsuccessful.3P0C7C56F020A2AE2660A87637CD00B4D5C0XXXX0015MasterCard5A valid amount is required.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-13187900"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "867"},
+ {"Date", "Thu, 21 Dec 2017 09:44:33 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- # authorize mock response
- def successful_authorize_response do
- {:ok,
- %HTTPoison.Response{body: ~s{123456OkI00001
Successful.1K6Z0ABYP260036854582A4AD079E22A271D92662CF093CED7A5D0XXXX0015MasterCard1
This transaction has been approved.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15778237"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "908"}, {"Date", "Mon, 25 Dec 2017 14:17:56 GMT"},
- {"Connection", "keep-alive"}],
+ # authorize mock response
+ def successful_authorize_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{123456OkI00001
Successful.1K6Z0ABYP260036854582A4AD079E22A271D92662CF093CED7A5D0XXXX0015MasterCard1
This transaction has been approved.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15778237"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "908"},
+ {"Date", "Mon, 25 Dec 2017 14:17:56 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- def bad_card_authorize_response do
- {:ok,
- %HTTPoison.Response{body: ~s{ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XXXXX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_f2f80544-1a98-4ad7-989b-8d267ebf5043-56152-12660528"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "514"}, {"Date", "Mon, 25 Dec 2017 14:19:29 GMT"},
- {"Connection", "keep-alive"}],
+ def bad_card_authorize_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XXXXX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_f2f80544-1a98-4ad7-989b-8d267ebf5043-56152-12660528"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "514"},
+ {"Date", "Mon, 25 Dec 2017 14:19:29 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- def bad_amount_authorize_response do
- {:ok,
- %HTTPoison.Response{body: ~s{123456ErrorE00027
The transaction was unsuccessful.3P0C7C56F020A2AE2660A87637CD00B4D5C0XXXX0015MasterCard290There is one or more missing or invalid required fields.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15779095"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "898"}, {"Date", "Mon, 25 Dec 2017 14:22:02 GMT"},
- {"Connection", "keep-alive"}],
+ def bad_amount_authorize_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{123456ErrorE00027
The transaction was unsuccessful.3P0C7C56F020A2AE2660A87637CD00B4D5C0XXXX0015MasterCard290There is one or more missing or invalid required fields.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15779095"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "898"},
+ {"Date", "Mon, 25 Dec 2017 14:22:02 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- # capture mock response
+ # capture mock response
- def successful_capture_response do
- {:ok,
- %HTTPoison.Response{body: ~s{123456OkI00001
Successful.14OKD6YP6003685493160036854931348C4ECD0F764736B012C4655BFA68EF0XXXX0015MasterCard1
This transaction has been approved.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15783402"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "899"}, {"Date", "Mon, 25 Dec 2017 14:39:28 GMT"},
- {"Connection", "keep-alive"}],
+ def successful_capture_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{123456OkI00001
Successful.14OKD6YP6003685493160036854931348C4ECD0F764736B012C4655BFA68EF0XXXX0015MasterCard1
This transaction has been approved.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15783402"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "899"},
+ {"Date", "Mon, 25 Dec 2017 14:39:28 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- def bad_id_capture do
- {:ok,
- %HTTPoison.Response{body: ~s{123456ErrorE00027
The transaction was unsuccessful.3P0A5280E2A6AA1290D451A24286692D1B0033A valid referenced transaction ID is required.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15784805"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "843"}, {"Date", "Mon, 25 Dec 2017 14:45:32 GMT"},
- {"Connection", "keep-alive"}],
+ def bad_id_capture do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{123456ErrorE00027
The transaction was unsuccessful.3P0A5280E2A6AA1290D451A24286692D1B0033A valid referenced transaction ID is required.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15784805"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "843"},
+ {"Date", "Mon, 25 Dec 2017 14:45:32 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- # refund mock response
- def successful_refund_response do
- {:ok,
- %HTTPoison.Response{body: ~s{123456OkI00001
Successful.1P6003685566160036752756169F2381B172A5AA247A01757A3E520A0XXXX0015MasterCard1
This transaction has been approved.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_f2f80544-1a98-4ad7-989b-8d267ebf5043-56152-12678232"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "884"}, {"Date", "Mon, 25 Dec 2017 15:22:19 GMT"},
- {"Connection", "keep-alive"}],
+ # refund mock response
+ def successful_refund_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{123456OkI00001
Successful.1P6003685566160036752756169F2381B172A5AA247A01757A3E520A0XXXX0015MasterCard1
This transaction has been approved.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_f2f80544-1a98-4ad7-989b-8d267ebf5043-56152-12678232"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "884"},
+ {"Date", "Mon, 25 Dec 2017 15:22:19 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- def bad_card_refund do
- {:ok,
- %HTTPoison.Response{body: ~s{ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15795999"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "511"}, {"Date", "Mon, 25 Dec 2017 15:21:20 GMT"},
- {"Connection", "keep-alive"}],
+ def bad_card_refund do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{ErrorE00003
The 'AnetApi/xml/v1/schema/AnetApiSchema.xsd:cardNumber' element is invalid - The value XX is invalid according to its datatype 'String' - The actual length is less than the MinLength value.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15795999"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "511"},
+ {"Date", "Mon, 25 Dec 2017 15:21:20 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- def debit_less_than_refund do
- {:ok,
- %HTTPoison.Response{body: ~s{123456ErrorE00027
The transaction was unsuccessful.3P060036752756A5280E2A6AA1290D451A24286692D1B00XXXX0015MasterCard55The sum of credits against the referenced transaction would exceed original debit amount.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_f2f80544-1a98-4ad7-989b-8d267ebf5043-56152-12681460"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "952"}, {"Date", "Mon, 25 Dec 2017 15:39:25 GMT"},
- {"Connection", "keep-alive"}],
+ def debit_less_than_refund do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{123456ErrorE00027
The transaction was unsuccessful.3P060036752756A5280E2A6AA1290D451A24286692D1B00XXXX0015MasterCard55The sum of credits against the referenced transaction would exceed original debit amount.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_f2f80544-1a98-4ad7-989b-8d267ebf5043-56152-12681460"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "952"},
+ {"Date", "Mon, 25 Dec 2017 15:39:25 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- # void mock response
- def successful_void do
- {:ok,
- %HTTPoison.Response{body: ~s{123456OkI00001
Successful.1ZJPVRXP6003685521760036855217F09A215511891DCEA91B6CC52B9F4E870XXXX0015MasterCard1
This transaction has been approved.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_f2f80544-1a98-4ad7-989b-8d267ebf5043-56152-12682366"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "899"}, {"Date", "Mon, 25 Dec 2017 15:43:56 GMT"},
- {"Connection", "keep-alive"}],
+ # void mock response
+ def successful_void do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{123456OkI00001
Successful.1ZJPVRXP6003685521760036855217F09A215511891DCEA91B6CC52B9F4E870XXXX0015MasterCard1
This transaction has been approved.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_f2f80544-1a98-4ad7-989b-8d267ebf5043-56152-12682366"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "899"},
+ {"Date", "Mon, 25 Dec 2017 15:43:56 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- def void_non_existent_id do
- {:ok,
- %HTTPoison.Response{body: ~s{123456ErrorE00027
The transaction was unsuccessful.3P060036855219C7C56F020A2AE2660A87637CD00B4D5C016The transaction cannot be found.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15801470"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "861"}, {"Date", "Mon, 25 Dec 2017 15:49:38 GMT"},
- {"Connection", "keep-alive"}],
+ def void_non_existent_id do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{123456ErrorE00027
The transaction was unsuccessful.3P060036855219C7C56F020A2AE2660A87637CD00B4D5C016The transaction cannot be found.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15801470"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "861"},
+ {"Date", "Mon, 25 Dec 2017 15:49:38 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- # store mock response
+ # store mock response
- def successful_store_response do
- {:ok,
- %HTTPoison.Response{body: ~s{OkI00001
Successful.18139914901808649724},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15829721"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "577"}, {"Date", "Mon, 25 Dec 2017 17:08:12 GMT"},
- {"Connection", "keep-alive"}],
+ def successful_store_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{OkI00001
Successful.18139914901808649724},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15829721"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "577"},
+ {"Date", "Mon, 25 Dec 2017 17:08:12 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
-
- def store_without_profile_fields do
- {:ok,
- %HTTPoison.Response{body: ~s{ErrorE00041
One or more fields in the profile must contain a value.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15831457"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "408"}, {"Date", "Mon, 25 Dec 2017 17:12:30 GMT"},
- {"Connection", "keep-alive"}],
+ status_code: 200
+ }}
+ end
+
+ def store_without_profile_fields do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{ErrorE00041
One or more fields in the profile must contain a value.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15831457"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "408"},
+ {"Date", "Mon, 25 Dec 2017 17:12:30 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- #unstore mock response
- def successful_unstore_response do
- {:ok,
- %HTTPoison.Response{body: ~s{OkI00001
Successful.},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15833786"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "361"}, {"Date", "Mon, 25 Dec 2017 17:21:20 GMT"},
- {"Connection", "keep-alive"}],
+ # unstore mock response
+ def successful_unstore_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{OkI00001
Successful.},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-15833786"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "361"},
+ {"Date", "Mon, 25 Dec 2017 17:21:20 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
+ status_code: 200
+ }}
+ end
- def customer_payment_profile_success_response do
- {:ok,
- %HTTPoison.Response{body: ~s{OkI00001
Successful.181401200218086700051,1,1,(TESTMODE) This transaction has been approved.,000000,P,0,none,Test transaction for ValidateCustomerPaymentProfile.,1.00,CC,auth_only,none,,,,,,,,,,,email@example.com,,,,,,,,,0.00,0.00,0.00,FALSE,none,EA9FD49A9501D0415FE26BAEF9FD8B2C,,,,,,,,,,,,,XXXX0015,MasterCard,,,,,,,,,,,,,,,,,},
- headers: [{"Cache-Control", "private"},
- {"Content-Type", "application/xml; charset=utf-8"},
- {"X-OPNET-Transaction-Trace",
- "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-17537805"},
- {"Access-Control-Allow-Origin", "*"},
- {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
- {"Access-Control-Allow-Headers",
- "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
- {"Access-Control-Allow-Credentials", "true"}, {"X-Cnection", "close"},
- {"Content-Length", "828"}, {"Date", "Thu, 28 Dec 2017 13:54:20 GMT"},
- {"Connection", "keep-alive"}],
+ def customer_payment_profile_success_response do
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s{OkI00001
Successful.181401200218086700051,1,1,(TESTMODE) This transaction has been approved.,000000,P,0,none,Test transaction for ValidateCustomerPaymentProfile.,1.00,CC,auth_only,none,,,,,,,,,,,email@example.com,,,,,,,,,0.00,0.00,0.00,FALSE,none,EA9FD49A9501D0415FE26BAEF9FD8B2C,,,,,,,,,,,,,XXXX0015,MasterCard,,,,,,,,,,,,,,,,,},
+ headers: [
+ {"Cache-Control", "private"},
+ {"Content-Type", "application/xml; charset=utf-8"},
+ {"X-OPNET-Transaction-Trace", "a2_b6b84b43-d399-4dde-bc12-fb1f8ccf4b27-51156-17537805"},
+ {"Access-Control-Allow-Origin", "*"},
+ {"Access-Control-Allow-Methods", "PUT,OPTIONS,POST,GET"},
+ {"Access-Control-Allow-Headers",
+ "x-requested-with,cache-control,content-type,origin,method,SOAPAction"},
+ {"Access-Control-Allow-Credentials", "true"},
+ {"X-Cnection", "close"},
+ {"Content-Length", "828"},
+ {"Date", "Thu, 28 Dec 2017 13:54:20 GMT"},
+ {"Connection", "keep-alive"}
+ ],
request_url: "https://apitest.authorize.net/xml/v1/request.api",
- status_code: 200}}
- end
-
- def netwok_error_non_existent_domain do
- {:error, %HTTPoison.Error{id: nil, reason: :nxdomain}}
- end
+ status_code: 200
+ }}
+ end
+
+ def netwok_error_non_existent_domain do
+ {:error, %HTTPoison.Error{id: nil, reason: :nxdomain}}
end
+end
diff --git a/test/mocks/cams_mock.exs b/test/mocks/cams_mock.exs
index e499b450..1eb50faf 100644
--- a/test/mocks/cams_mock.exs
+++ b/test/mocks/cams_mock.exs
@@ -1,211 +1,225 @@
defmodule Gringotts.Gateways.CamsMock do
def successful_purchase do
- {:ok, %HTTPoison.Response{
- body:
- "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3916017714&avsresponse=N&cvvresponse=N&orderid=&type=sale&response_code=100",
- headers: [
- {"Date", "Thu, 21 Dec 2017 12:45:16 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "137"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3916017714&avsresponse=N&cvvresponse=N&orderid=&type=sale&response_code=100",
+ headers: [
+ {"Date", "Thu, 21 Dec 2017 12:45:16 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "137"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def failed_purchase_with_bad_credit_card do
- {:ok, %HTTPoison.Response{
- body:
- "response=3&responsetext=Invalid Credit Card Number REFID:3502947912&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=sale&response_code=300",
- headers: [
- {"Date", "Thu, 21 Dec 2017 13:20:08 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "155"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=3&responsetext=Invalid Credit Card Number REFID:3502947912&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=sale&response_code=300",
+ headers: [
+ {"Date", "Thu, 21 Dec 2017 13:20:08 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "155"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def with_invalid_currency do
- {:ok, %HTTPoison.Response{
- body:
- "response=3&responsetext=The cc payment type [Visa] and/or currency [INR] is not accepted REFID:3503238709&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300",
- headers: [
- {"Date", "Tue, 26 Dec 2017 10:37:42 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "193"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=3&responsetext=The cc payment type [Visa] and/or currency [INR] is not accepted REFID:3503238709&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300",
+ headers: [
+ {"Date", "Tue, 26 Dec 2017 10:37:42 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "193"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def successful_capture do
- {:ok, %HTTPoison.Response{
- body:
- "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3921111362&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=100",
- headers: [
- {"Date", "Tue, 26 Dec 2017 12:16:55 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "138"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3921111362&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=100",
+ headers: [
+ {"Date", "Tue, 26 Dec 2017 12:16:55 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "138"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def successful_authorize do
- {:ok, %HTTPoison.Response{
- body:
- "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3921111362&avsresponse=N&cvvresponse=N&orderid=&type=auth&response_code=100",
- headers: [
- {"Date", "Tue, 26 Dec 2017 12:16:11 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "137"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=1&responsetext=SUCCESS&authcode=123456&transactionid=3921111362&avsresponse=N&cvvresponse=N&orderid=&type=auth&response_code=100",
+ headers: [
+ {"Date", "Tue, 26 Dec 2017 12:16:11 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "137"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def invalid_transaction_id do
- {:ok, %HTTPoison.Response{
- body:
- "response=3&responsetext=Transaction not found REFID:3503243979&authcode=&transactionid=3921118690&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
- headers: [
- {"Date", "Tue, 26 Dec 2017 12:39:05 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "163"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=3&responsetext=Transaction not found REFID:3503243979&authcode=&transactionid=3921118690&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
+ headers: [
+ {"Date", "Tue, 26 Dec 2017 12:39:05 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "163"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def more_than_authorization_amount do
- {:ok, %HTTPoison.Response{
- body:
- "response=3&responsetext=The specified amount of 1001 exceeds the authorization amount of 1000.00 REFID:3503244462&authcode=&transactionid=3921127126&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
- headers: [
- {"Date", "Tue, 26 Dec 2017 13:00:55 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "214"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=3&responsetext=The specified amount of 1001 exceeds the authorization amount of 1000.00 REFID:3503244462&authcode=&transactionid=3921127126&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
+ headers: [
+ {"Date", "Tue, 26 Dec 2017 13:00:55 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "214"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def successful_refund do
- {:ok, %HTTPoison.Response{
- body:
- "response=1&responsetext=SUCCESS&authcode=&transactionid=3921158933&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=100",
- headers: [
- {"Date", "Tue, 26 Dec 2017 14:00:08 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "131"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=1&responsetext=SUCCESS&authcode=&transactionid=3921158933&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=100",
+ headers: [
+ {"Date", "Tue, 26 Dec 2017 14:00:08 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "131"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def more_than_purchase_amount do
- {:ok, %HTTPoison.Response{
- body:
- "response=3&responsetext=Refund amount may not exceed the transaction balance REFID:3503249728&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300",
- headers: [
- {"Date", "Tue, 26 Dec 2017 14:05:31 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "183"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=3&responsetext=Refund amount may not exceed the transaction balance REFID:3503249728&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300",
+ headers: [
+ {"Date", "Tue, 26 Dec 2017 14:05:31 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "183"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def successful_void do
- {:ok, %HTTPoison.Response{
- body:
- "response=1&responsetext=Transaction Void Successful&authcode=123456&transactionid=3921178863&avsresponse=&cvvresponse=&orderid=&type=void&response_code=100",
- headers: [
- {"Date", "Tue, 26 Dec 2017 14:26:05 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "155"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=1&responsetext=Transaction Void Successful&authcode=123456&transactionid=3921178863&avsresponse=&cvvresponse=&orderid=&type=void&response_code=100",
+ headers: [
+ {"Date", "Tue, 26 Dec 2017 14:26:05 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "155"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def failed_authorized_with_bad_card do
- {:ok, %HTTPoison.Response{
- body:
- "response=3&responsetext=Invalid Credit Card Number REFID:3503305883&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300",
- headers: [
- {"Date", "Wed, 27 Dec 2017 09:51:45 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "155"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=3&responsetext=Invalid Credit Card Number REFID:3503305883&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300",
+ headers: [
+ {"Date", "Wed, 27 Dec 2017 09:51:45 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "155"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def multiple_capture_on_same_transaction do
- {:ok, %HTTPoison.Response{
- body:
- "response=3&responsetext=A capture requires that the existing transaction be an AUTH REFID:3503316182&authcode=&transactionid=3922433984&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
- headers: [
- {"Date", "Wed, 27 Dec 2017 13:47:12 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "201"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=3&responsetext=A capture requires that the existing transaction be an AUTH REFID:3503316182&authcode=&transactionid=3922433984&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300",
+ headers: [
+ {"Date", "Wed, 27 Dec 2017 13:47:12 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "201"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def refund_the_authorised_transaction do
- {:ok, %HTTPoison.Response{
- body:
- "response=3&responsetext=Refund amount may not exceed the transaction balance REFID:3503316128&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300",
- headers: [
- {"Date", "Wed, 27 Dec 2017 13:45:19 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "183"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=3&responsetext=Refund amount may not exceed the transaction balance REFID:3503316128&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300",
+ headers: [
+ {"Date", "Wed, 27 Dec 2017 13:45:19 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "183"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
def validate_creditcard do
- {:ok, %HTTPoison.Response{
- body:
- "response=1&responsetext=&authcode=&transactionid=3933708264&avsresponse=&cvvresponse=&orderid=&type=verify&response_code=100",
- headers: [
- {"Date", "Thu, 04 Jan 2018 11:12:20 GMT"},
- {"Server", "Apache"},
- {"Content-Length", "124"},
- {"Content-Type", "text/html; charset=UTF-8"}
- ],
- request_url: "https://secure.centralams.com/gw/api/transact.php",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ "response=1&responsetext=&authcode=&transactionid=3933708264&avsresponse=&cvvresponse=&orderid=&type=verify&response_code=100",
+ headers: [
+ {"Date", "Thu, 04 Jan 2018 11:12:20 GMT"},
+ {"Server", "Apache"},
+ {"Content-Length", "124"},
+ {"Content-Type", "text/html; charset=UTF-8"}
+ ],
+ request_url: "https://secure.centralams.com/gw/api/transact.php",
+ status_code: 200
+ }}
end
end
diff --git a/test/mocks/global_collect_mock.exs b/test/mocks/global_collect_mock.exs
index 8bb9d553..219b9551 100644
--- a/test/mocks/global_collect_mock.exs
+++ b/test/mocks/global_collect_mock.exs
@@ -1,179 +1,184 @@
defmodule Gringotts.Gateways.GlobalCollectMock do
-
def test_for_purchase_with_valid_card do
{:ok,
- %HTTPoison.Response{
- body: "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000074\",\n \"externalReference\" : \"000000122600000000740000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000740000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20180118135349\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
- headers: [{"Date", "Thu, 18 Jan 2018 12:53:49 GMT"},
+ %HTTPoison.Response{
+ body:
+ "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000074\",\n \"externalReference\" : \"000000122600000000740000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000740000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20180118135349\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
+ headers: [
+ {"Date", "Thu, 18 Jan 2018 12:53:49 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
{"Location",
"https://api-sandbox.globalcollect.com:443/v1/1226/payments/000000122600000000740000100001"},
{"X-Powered-By", "Servlet/3.0 JSP/2.2"},
{"Transfer-Encoding", "chunked"},
{"Content-Type", "application/json"}
- ],
- request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
- status_code: 201
- }
- }
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 201
+ }}
end
def test_for_purchase_with_invalid_card do
{:ok,
- %HTTPoison.Response{
- body: "{\n \"errorId\" : \"363899bd-acfb-4452-bbb0-741c0df6b4b8\",\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"980825\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000075\",\n \"externalReference\" : \"000000122600000000750000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000750000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"546247\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20180118135651\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n }\n}",
- headers: [
- {"Date", "Thu, 18 Jan 2018 12:56:51 GMT"},
- {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
- {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
- {"Connection", "close"},
- {"Transfer-Encoding", "chunked"},
- {"Content-Type", "application/json"}
- ],
- request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
- status_code: 400
- }
- }
+ %HTTPoison.Response{
+ body:
+ "{\n \"errorId\" : \"363899bd-acfb-4452-bbb0-741c0df6b4b8\",\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"980825\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000075\",\n \"externalReference\" : \"000000122600000000750000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000750000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"546247\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20180118135651\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n }\n}",
+ headers: [
+ {"Date", "Thu, 18 Jan 2018 12:56:51 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Connection", "close"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 400
+ }}
end
def test_for_purchase_with_invalid_amount do
{:ok,
%HTTPoison.Response{
- body: "{\n \"errorId\" : \"8c34dc0b-776c-44e3-8cd4-b36222960153\",\n \"errors\" : [ {\n \"code\" : \"1099\",\n \"id\" : \"INVALID_VALUE\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"message\" : \"INVALID_VALUE: '50.3' is not a valid value for field 'amount'\",\n \"httpStatusCode\" : 400\n } ]\n}",
- headers: [
- {"Date", "Wed, 24 Jan 2018 07:16:06 GMT"},
- {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
- {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
- {"Connection", "close"},
- {"Transfer-Encoding", "chunked"},
- {"Content-Type", "application/json"}],
- request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
- status_code: 400
- }
- }
+ body:
+ "{\n \"errorId\" : \"8c34dc0b-776c-44e3-8cd4-b36222960153\",\n \"errors\" : [ {\n \"code\" : \"1099\",\n \"id\" : \"INVALID_VALUE\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"message\" : \"INVALID_VALUE: '50.3' is not a valid value for field 'amount'\",\n \"httpStatusCode\" : 400\n } ]\n}",
+ headers: [
+ {"Date", "Wed, 24 Jan 2018 07:16:06 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Connection", "close"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 400
+ }}
end
def test_for_authorize_with_valid_card do
{:ok,
- %HTTPoison.Response{
- body: "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000065\",\n \"externalReference\" : \"000000122600000000650000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000650000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20180118110419\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
- headers: [
- {"Date", "Thu, 18 Jan 2018 10:04:19 GMT"},
- {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
- {"Location",
+ %HTTPoison.Response{
+ body:
+ "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000065\",\n \"externalReference\" : \"000000122600000000650000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000650000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20180118110419\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
+ headers: [
+ {"Date", "Thu, 18 Jan 2018 10:04:19 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"Location",
"https://api-sandbox.globalcollect.com:443/v1/1226/payments/000000122600000000650000100001"},
- {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
- {"Transfer-Encoding", "chunked"},
- {"Content-Type", "application/json"}
- ],
- request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
- status_code: 201
- }
- }
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 201
+ }}
end
def test_for_authorize_with_invalid_card do
{:ok,
- %HTTPoison.Response{
- body: "{\n \"errorId\" : \"dcdf5c8d-e475-4fbc-ac57-76123c1640a2\",\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"978754\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000066\",\n \"externalReference\" : \"000000122600000000660000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000660000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"978755\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20180118111508\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n }\n}",
- headers: [
- {"Date", "Thu, 18 Jan 2018 10:15:08 GMT"},
- {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
- {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
- {"Connection", "close"},
- {"Transfer-Encoding", "chunked"},
- {"Content-Type", "application/json"}
- ],
- request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
- status_code: 400
- }
- }
+ %HTTPoison.Response{
+ body:
+ "{\n \"errorId\" : \"dcdf5c8d-e475-4fbc-ac57-76123c1640a2\",\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"978754\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000066\",\n \"externalReference\" : \"000000122600000000660000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000660000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"978755\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20180118111508\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n }\n}",
+ headers: [
+ {"Date", "Thu, 18 Jan 2018 10:15:08 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Connection", "close"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 400
+ }}
end
def test_for_authorize_with_invalid_amount do
{:ok,
- %HTTPoison.Response{body: "{\n \"errorId\" : \"1dbef568-ed86-4c8d-a3c3-74ced258d5a2\",\n \"errors\" : [ {\n \"code\" : \"1099\",\n \"id\" : \"INVALID_VALUE\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"message\" : \"INVALID_VALUE: '50.3' is not a valid value for field 'amount'\",\n \"httpStatusCode\" : 400\n } ]\n}",
- headers: [
- {"Date", "Tue, 23 Jan 2018 11:18:11 GMT"},
- {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
- {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
- {"Connection", "close"},
- {"Transfer-Encoding", "chunked"},
- {"Content-Type", "application/json"}
- ],
- request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
- status_code: 400
- }
- }
+ %HTTPoison.Response{
+ body:
+ "{\n \"errorId\" : \"1dbef568-ed86-4c8d-a3c3-74ced258d5a2\",\n \"errors\" : [ {\n \"code\" : \"1099\",\n \"id\" : \"INVALID_VALUE\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"message\" : \"INVALID_VALUE: '50.3' is not a valid value for field 'amount'\",\n \"httpStatusCode\" : 400\n } ]\n}",
+ headers: [
+ {"Date", "Tue, 23 Jan 2018 11:18:11 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Connection", "close"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments",
+ status_code: 400
+ }}
end
def test_for_refund do
{:ok,
%HTTPoison.Response{
- body: "{\n \"errorId\" : \"b6ba00d2-8f11-4822-8f32-c6d0a4d8793b\",\n \"errors\" : [ {\n \"code\" : \"300450\",\n \"message\" : \"ORDER WITHOUT REFUNDABLE PAYMENTS\",\n \"httpStatusCode\" : 400\n } ]\n}",
- headers: [
- {"Date", "Wed, 24 Jan 2018 05:33:56 GMT"},
- {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
- {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
- {"Connection", "close"},
- {"Transfer-Encoding", "chunked"},
- {"Content-Type", "application/json"}
- ],
- request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments/000000122600000000870000100001/refund",
- status_code: 400
- }
- }
+ body:
+ "{\n \"errorId\" : \"b6ba00d2-8f11-4822-8f32-c6d0a4d8793b\",\n \"errors\" : [ {\n \"code\" : \"300450\",\n \"message\" : \"ORDER WITHOUT REFUNDABLE PAYMENTS\",\n \"httpStatusCode\" : 400\n } ]\n}",
+ headers: [
+ {"Date", "Wed, 24 Jan 2018 05:33:56 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Connection", "close"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url:
+ "https://api-sandbox.globalcollect.com/v1/1226/payments/000000122600000000870000100001/refund",
+ status_code: 400
+ }}
end
def test_for_capture_with_valid_paymentid do
{:ok,
%HTTPoison.Response{
- body: "{\n \"payment\" : {\n \"id\" : \"000000122600000000650000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 50,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"CAPTURE_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_CONNECT_OR_3RD_PARTY\",\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20180123140826\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
- headers: [
- {"Date", "Tue, 23 Jan 2018 13:08:26 GMT"},
- {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
- {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
- {"Transfer-Encoding", "chunked"},
- {"Content-Type", "application/json"}
- ],
- request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments/000000122600000000650000100001/approve",
- status_code: 200
- }
- }
+ body:
+ "{\n \"payment\" : {\n \"id\" : \"000000122600000000650000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 50,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"CAPTURE_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_CONNECT_OR_3RD_PARTY\",\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20180123140826\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
+ headers: [
+ {"Date", "Tue, 23 Jan 2018 13:08:26 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url:
+ "https://api-sandbox.globalcollect.com/v1/1226/payments/000000122600000000650000100001/approve",
+ status_code: 200
+ }}
end
def test_for_capture_with_invalid_paymentid do
{:ok,
%HTTPoison.Response{
- body: "{\n \"errorId\" : \"ccb99804-0240-45b6-bb28-52aaae59d71b\",\n \"errors\" : [ {\n \"code\" : \"1002\",\n \"id\" : \"UNKNOWN_PAYMENT_ID\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"propertyName\" : \"paymentId\",\n \"message\" : \"UNKNOWN_PAYMENT_ID\",\n \"httpStatusCode\" : 404\n } ]\n}",
- headers: [
- {"Date", "Tue, 23 Jan 2018 12:25:59 GMT"},
- {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
- {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
- {"Transfer-Encoding", "chunked"},
- {"Content-Type", "application/json"}
- ],
- request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments/30/approve",
- status_code: 404
- }
- }
+ body:
+ "{\n \"errorId\" : \"ccb99804-0240-45b6-bb28-52aaae59d71b\",\n \"errors\" : [ {\n \"code\" : \"1002\",\n \"id\" : \"UNKNOWN_PAYMENT_ID\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"propertyName\" : \"paymentId\",\n \"message\" : \"UNKNOWN_PAYMENT_ID\",\n \"httpStatusCode\" : 404\n } ]\n}",
+ headers: [
+ {"Date", "Tue, 23 Jan 2018 12:25:59 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments/30/approve",
+ status_code: 404
+ }}
end
def test_for_void_with_valid_card do
{:ok,
%HTTPoison.Response{
- body: "{\n \"payment\" : {\n \"id\" : \"000000122600000000870000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 50,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20180124064204\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n}",
- headers: [
- {"Date", "Wed, 24 Jan 2018 05:42:04 GMT"},
- {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
- {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
- {"Transfer-Encoding", "chunked"},
- {"Content-Type", "application/json"}
- ],
- request_url: "https://api-sandbox.globalcollect.com/v1/1226/payments/000000122600000000870000100001/cancel",
- status_code: 200
- }
- }
+ body:
+ "{\n \"payment\" : {\n \"id\" : \"000000122600000000870000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 50,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20180124064204\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n}",
+ headers: [
+ {"Date", "Wed, 24 Jan 2018 05:42:04 GMT"},
+ {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
+ {"X-Powered-By", "Servlet/3.0 JSP/2.2"},
+ {"Transfer-Encoding", "chunked"},
+ {"Content-Type", "application/json"}
+ ],
+ request_url:
+ "https://api-sandbox.globalcollect.com/v1/1226/payments/000000122600000000870000100001/cancel",
+ status_code: 200
+ }}
end
def test_for_network_failure do
diff --git a/test/mocks/trexle_mock.exs b/test/mocks/trexle_mock.exs
index 27c4d1c2..73f5aa9e 100644
--- a/test/mocks/trexle_mock.exs
+++ b/test/mocks/trexle_mock.exs
@@ -1,197 +1,213 @@
defmodule Gringotts.Gateways.TrexleMock do
def test_for_purchase_with_valid_card do
- {:ok, %HTTPoison.Response{
- body: ~s/{"response":{"token":"charge_3e89c6f073606ac1efe62e76e22dd7885441dc72","success":true,"captured":false}}/,
- headers: [
- {"Date", "Fri, 22 Dec 2017 11:57:28 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "max-age=0, private, must-revalidate"},
- {"X-Request-Id", "9b2a1d30-9bca-48f2-862e-4090766689cb"},
- {"X-Runtime", "0.777520"},
- {"Content-Length", "104"},
- {"X-Powered-By", "PleskLin"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 201
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s/{"response":{"token":"charge_3e89c6f073606ac1efe62e76e22dd7885441dc72","success":true,"captured":false}}/,
+ headers: [
+ {"Date", "Fri, 22 Dec 2017 11:57:28 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "max-age=0, private, must-revalidate"},
+ {"X-Request-Id", "9b2a1d30-9bca-48f2-862e-4090766689cb"},
+ {"X-Runtime", "0.777520"},
+ {"Content-Length", "104"},
+ {"X-Powered-By", "PleskLin"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 201
+ }}
end
def test_for_purchase_with_invalid_card do
- {:ok, %HTTPoison.Response{
- body: ~s/{"error":"Payment failed","detail":"Your card's expiration year is invalid."}/,
- headers: [
- {"Date", "Fri, 22 Dec 2017 13:20:50 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "eb8100a1-8ffa-47da-9623-8d3b2af51b84"},
- {"X-Runtime", "0.445244"},
- {"Content-Length", "77"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 400
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body: ~s/{"error":"Payment failed","detail":"Your card's expiration year is invalid."}/,
+ headers: [
+ {"Date", "Fri, 22 Dec 2017 13:20:50 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "eb8100a1-8ffa-47da-9623-8d3b2af51b84"},
+ {"X-Runtime", "0.445244"},
+ {"Content-Length", "77"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 400
+ }}
end
def test_for_purchase_with_invalid_amount do
- {:ok, %HTTPoison.Response{
- body: ~s/{"error":"Payment failed","detail":"Amount must be at least 50 cents"}/,
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:16:33 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "4ce2eea4-3ea9-4345-ac85-9bc45f22f5ac"},
- {"X-Runtime", "0.476058"},
- {"Content-Length", "70"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 400
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body: ~s/{"error":"Payment failed","detail":"Amount must be at least 50 cents"}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:16:33 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "4ce2eea4-3ea9-4345-ac85-9bc45f22f5ac"},
+ {"X-Runtime", "0.476058"},
+ {"Content-Length", "70"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 400
+ }}
end
def test_for_authorize_with_valid_card do
- {:ok, %HTTPoison.Response{
- body: ~s/{"response":{"token":"charge_8ab2b21a2f02495f5c36b34d129c8a0e836add32","success":true,"captured":false}}/,
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:33:31 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "max-age=0, private, must-revalidate"},
- {"X-Request-Id", "51d28d13-81e5-41fd-b711-1b6531fdd3dd"},
- {"X-Runtime", "0.738395"},
- {"Content-Length", "104"},
- {"X-Powered-By", "PleskLin"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 201
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s/{"response":{"token":"charge_8ab2b21a2f02495f5c36b34d129c8a0e836add32","success":true,"captured":false}}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:33:31 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "max-age=0, private, must-revalidate"},
+ {"X-Request-Id", "51d28d13-81e5-41fd-b711-1b6531fdd3dd"},
+ {"X-Runtime", "0.738395"},
+ {"Content-Length", "104"},
+ {"X-Powered-By", "PleskLin"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 201
+ }}
end
def test_for_authorize_with_invalid_card do
- {:ok, %HTTPoison.Response{
- body: ~s/{"error":"Payment failed","detail":"Your card's expiration year is invalid."}/,
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:25:40 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "239e7054-9500-4a87-bf3b-09456d550b6d"},
- {"X-Runtime", "0.466670"},
- {"Content-Length", "77"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 400
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body: ~s/{"error":"Payment failed","detail":"Your card's expiration year is invalid."}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:25:40 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "239e7054-9500-4a87-bf3b-09456d550b6d"},
+ {"X-Runtime", "0.466670"},
+ {"Content-Length", "77"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 400
+ }}
end
def test_for_authorize_with_invalid_amount do
- {:ok, %HTTPoison.Response{
- body: ~s/{"error":"Payment failed","detail":"Amount must be at least 50 cents"}/,
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:40:10 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "d58db806-8016-4a0e-8519-403a969fa1a7"},
- {"X-Runtime", "0.494636"},
- {"Content-Length", "70"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges",
- status_code: 400
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body: ~s/{"error":"Payment failed","detail":"Amount must be at least 50 cents"}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:40:10 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "d58db806-8016-4a0e-8519-403a969fa1a7"},
+ {"X-Runtime", "0.494636"},
+ {"Content-Length", "70"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges",
+ status_code: 400
+ }}
end
def test_for_refund_with_valid_token do
- {:ok, %HTTPoison.Response{
- body: ~s/{"response":{"token":"refund_a86a757cc6bdabab50d6ebbfcdcd4db4e04198dd","success":true,"amount":50,"charge":"charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b","status_message":"Transaction approved"}}/,
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:55:41 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "max-age=0, private, must-revalidate"},
- {"X-Request-Id", "b1c94703-7fb4-48f2-b1b4-32e3b6a87e57"},
- {"X-Runtime", "1.097186"},
- {"Content-Length", "198"},
- {"X-Powered-By", "PleskLin"}
- ],
- request_url:
- "https://core.trexle.com/api/v1//charges/charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b/refunds",
- status_code: 201
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s/{"response":{"token":"refund_a86a757cc6bdabab50d6ebbfcdcd4db4e04198dd","success":true,"amount":50,"charge":"charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b","status_message":"Transaction approved"}}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:55:41 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "max-age=0, private, must-revalidate"},
+ {"X-Request-Id", "b1c94703-7fb4-48f2-b1b4-32e3b6a87e57"},
+ {"X-Runtime", "1.097186"},
+ {"Content-Length", "198"},
+ {"X-Powered-By", "PleskLin"}
+ ],
+ request_url:
+ "https://core.trexle.com/api/v1//charges/charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b/refunds",
+ status_code: 201
+ }}
end
def test_for_refund_with_invalid_token do
- {:ok, %HTTPoison.Response{
- body: ~s/{"error":"Refund failed","detail":"invalid token"}/,
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:53:09 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "276fd8f5-dc21-40be-8add-fa76aabbfc5b"},
- {"X-Runtime", "0.009374"},
- {"Content-Length", "50"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges/34/refunds",
- status_code: 400
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body: ~s/{"error":"Refund failed","detail":"invalid token"}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:53:09 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "276fd8f5-dc21-40be-8add-fa76aabbfc5b"},
+ {"X-Runtime", "0.009374"},
+ {"Content-Length", "50"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges/34/refunds",
+ status_code: 400
+ }}
end
def test_for_capture_with_valid_chargetoken do
- {:ok, %HTTPoison.Response{
- body: ~s/{"response":{"token":"charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b","success":true,"captured":true,"amount":50,"status_message":"Transaction approved"}}/,
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:49:50 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "max-age=0, private, must-revalidate"},
- {"X-Request-Id", "97ca2db6-fd4f-4a5b-ae45-01fae9c13668"},
- {"X-Runtime", "1.092051"},
- {"Content-Length", "155"},
- {"X-Powered-By", "PleskLin"}
- ],
- request_url:
- "https://core.trexle.com/api/v1//charges/charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b/capture",
- status_code: 200
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s/{"response":{"token":"charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b","success":true,"captured":true,"amount":50,"status_message":"Transaction approved"}}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:49:50 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "max-age=0, private, must-revalidate"},
+ {"X-Request-Id", "97ca2db6-fd4f-4a5b-ae45-01fae9c13668"},
+ {"X-Runtime", "1.092051"},
+ {"Content-Length", "155"},
+ {"X-Powered-By", "PleskLin"}
+ ],
+ request_url:
+ "https://core.trexle.com/api/v1//charges/charge_cb17a0c34e870a479dfa13bd873e7ce7e090ec9b/capture",
+ status_code: 200
+ }}
end
def test_for_capture_with_invalid_chargetoken do
- {:ok, %HTTPoison.Response{
- body: ~s/{"error":"Capture failed","detail":"invalid token"}/,
- headers: [
- {"Date", "Sat, 23 Dec 2017 18:47:18 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "no-cache"},
- {"X-Request-Id", "b46ecb8d-7df8-4c5f-b87f-c53fae364e79"},
- {"X-Runtime", "0.010255"},
- {"Content-Length", "51"},
- {"X-Powered-By", "PleskLin"},
- {"Connection", "close"}
- ],
- request_url: "https://core.trexle.com/api/v1//charges/30/capture",
- status_code: 400
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body: ~s/{"error":"Capture failed","detail":"invalid token"}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 18:47:18 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "no-cache"},
+ {"X-Request-Id", "b46ecb8d-7df8-4c5f-b87f-c53fae364e79"},
+ {"X-Runtime", "0.010255"},
+ {"Content-Length", "51"},
+ {"X-Powered-By", "PleskLin"},
+ {"Connection", "close"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//charges/30/capture",
+ status_code: 400
+ }}
end
def test_for_store_with_valid_card do
- {:ok, %HTTPoison.Response{
- body: ~s/{"response":{"token":"token_94e333959850270460e89a86bad2246613528cbb","card":{"token":"token_2a1ba29522e4a377fafa62e8e204f76ad8ba8f1e","scheme":"master","display_number":"XXXX-XXXX-XXXX-8210","expiry_year":2018,"expiry_month":1,"cvc":123,"name":"John Doe","address_line1":"456 My Street","address_line2":null,"address_city":"Ottawa","address_state":"ON","address_postcode":"K1C2N6","address_country":"CA","primary":true}}}/,
- headers: [
- {"Date", "Sat, 23 Dec 2017 19:32:58 GMT"},
- {"Content-Type", "application/json; charset=UTF-8"},
- {"Cache-Control", "max-age=0, private, must-revalidate"},
- {"X-Request-Id", "1a334b22-8e01-4e1b-8b58-90dfd0b7c12f"},
- {"X-Runtime", "0.122441"},
- {"Content-Length", "422"},
- {"X-Powered-By", "PleskLin"}
- ],
- request_url: "https://core.trexle.com/api/v1//customers",
- status_code: 201
- }}
+ {:ok,
+ %HTTPoison.Response{
+ body:
+ ~s/{"response":{"token":"token_94e333959850270460e89a86bad2246613528cbb","card":{"token":"token_2a1ba29522e4a377fafa62e8e204f76ad8ba8f1e","scheme":"master","display_number":"XXXX-XXXX-XXXX-8210","expiry_year":2018,"expiry_month":1,"cvc":123,"name":"John Doe","address_line1":"456 My Street","address_line2":null,"address_city":"Ottawa","address_state":"ON","address_postcode":"K1C2N6","address_country":"CA","primary":true}}}/,
+ headers: [
+ {"Date", "Sat, 23 Dec 2017 19:32:58 GMT"},
+ {"Content-Type", "application/json; charset=UTF-8"},
+ {"Cache-Control", "max-age=0, private, must-revalidate"},
+ {"X-Request-Id", "1a334b22-8e01-4e1b-8b58-90dfd0b7c12f"},
+ {"X-Runtime", "0.122441"},
+ {"Content-Length", "422"},
+ {"X-Powered-By", "PleskLin"}
+ ],
+ request_url: "https://core.trexle.com/api/v1//customers",
+ status_code: 201
+ }}
end
def test_for_network_failure do
From a4082cbd810aaa2a5b71b5d6421880831f50dadf Mon Sep 17 00:00:00 2001
From: Jyoti Gautam
Date: Fri, 30 Mar 2018 12:05:03 +0530
Subject: [PATCH 38/60] [global-collect] Layout, docs improvements and code
refactors (#111)
[global-collect] Layout, docs and code refactor
===============================================
New features
------------
Risk, AVS, CVS fields added in `Response` struct!
Layout, docs
-----------
* `credo` issues resolved.
* Corrected `amount` in examples
* Ran the elixir 1.6 code formatter
- Used sigils in mocks
Code refactors
--------------
* Removed unnecessary functions
- Reduced arity of `add_money`
* Refactored Timex usage
* Removed a test on `validate_config` as it is already tested.
---
lib/gringotts/gateways/global_collect.ex | 411 ++++++++++++-----------
test/gateways/global_collect_test.exs | 30 +-
test/mocks/global_collect_mock.exs | 88 ++++-
3 files changed, 294 insertions(+), 235 deletions(-)
diff --git a/lib/gringotts/gateways/global_collect.ex b/lib/gringotts/gateways/global_collect.ex
index 624a8950..6075451d 100644
--- a/lib/gringotts/gateways/global_collect.ex
+++ b/lib/gringotts/gateways/global_collect.ex
@@ -2,9 +2,9 @@ defmodule Gringotts.Gateways.GlobalCollect do
@moduledoc """
[GlobalCollect][home] gateway implementation.
- For further details, please refer [GlobalCollect API documentation](https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/index.html).
+ For further details, please refer [GlobalCollect API documentation][docs].
- Following are the features that have been implemented for the GlobalCollect Gateway:
+ Following are the features that have been implemented for GlobalCollect:
| Action | Method |
| ------ | ------ |
@@ -14,32 +14,34 @@ defmodule Gringotts.Gateways.GlobalCollect do
| Refund | `refund/3` |
| Void | `void/2` |
- ## Optional or extra parameters
+ ## Optional parameters
Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply
optional arguments for transactions with the gateway.
- | Key | Status |
+ | Key | Remark |
| ---- | --- |
- | `merchantCustomerId` | implemented |
- | `description` | implemented |
- | `customer_name` | implemented |
- | `dob` | implemented |
- | `company` | implemented |
- | `email` | implemented |
- | `phone` | implemented |
- | `order_id` | implemented |
- | `invoice` | implemented |
- | `billingAddress` | implemented |
- | `shippingAddress` | implemented |
- | `name` | implemented |
- | `skipAuthentication` | implemented |
-
+ | `merchantCustomerId` | Identifier for the consumer that can be used as a search criteria in the Global Collect Payment Console |
+ | `description` | Descriptive text that is used towards to consumer, either during an online checkout at a third party and/or on the statement of the consumer |
+ | `dob` | The date of birth of the consumer Format: YYYYMMDD |
+ | `company` | Name of company, as a consumer |
+ | `email` | Email address of the consumer |
+ | `phone` | Phone number of the consumer |
+ | `invoice` | Object containing additional invoice data |
+ | `billingAddress` | Object containing billing address details |
+ | `shippingAddress` | Object containing shipping address details |
+ | `name` | Object containing the name details of the consumer |
+ | `skipAuthentication` | 3D Secure Authentication will be skipped for this transaction if set to true |
+
+ For more details of the required keys refer [this.][options]
## Registering your GlobalCollect account at `Gringotts`
- After creating your account successfully on [GlobalCollect](http://www.globalcollect.com/) follow the [dashboard link](https://sandbox.account.ingenico.com/#/account/apikey) to fetch the secret_api_key, api_key_id and [here](https://sandbox.account.ingenico.com/#/account/merchantid) for merchant_id.
+ After creating your account successfully on [GlobalCollect][home] open the
+ [dashboard][dashboard] to fetch the `secret_api_key`, `api_key_id` and
+ `merchant_id` from the menu.
- Here's how the secrets map to the required configuration parameters for GlobalCollect:
+ Here's how the secrets map to the required configuration parameters for
+ GlobalCollect:
| Config parameter | GlobalCollect secret |
| ------- | ---- |
@@ -47,19 +49,22 @@ defmodule Gringotts.Gateways.GlobalCollect do
| `:api_key_id` | **ApiKeyId** |
| `:merchant_id` | **MerchantId** |
- Your Application config **must include the `[:secret_api_key, :api_key_id, :merchant_id]` field(s)** and would look
- something like this:
+ Your Application config **must include the `:secret_api_key`, `:api_key_id`,
+ `:merchant_id` field(s)** and would look something like this:
config :gringotts, Gringotts.Gateways.GlobalCollect,
secret_api_key: "your_secret_secret_api_key"
api_key_id: "your_secret_api_key_id"
merchant_id: "your_secret_merchant_id"
+ ## Scope of this module
+
+ * [All amount fields in globalCollect are in cents with each amount having 2 decimals.][amountReference]
+
## Supported currencies and countries
- The GlobalCollect platform is able to support payments in [over 150 currencies][currencies]
+ The GlobalCollect platform supports payments in [over 150 currencies][currencies].
- [currencies]: https://epayments.developer-ingenico.com/best-practices/services/currency-conversion
## Following the examples
1. First, set up a sample application and configure it to work with GlobalCollect.
@@ -67,51 +72,74 @@ defmodule Gringotts.Gateways.GlobalCollect do
- To save you time, we recommend [cloning our example
repo][example] that gives you a pre-configured sample app ready-to-go.
+ You could use the same config or update it the with your "secrets"
- as described [above](#module-registering-your-globalcollect-account-at-GlobalCollect).
+ as described [above](#module-registering-your-globalcollect-account-at-gringotts).
2. Run an `iex` session with `iex -S mix` and add some variable bindings and
- aliases to it (to save some time):
+ aliases to it (to save some time):
```
iex> alias Gringotts.{Response, CreditCard, Gateways.GlobalCollect}
-
iex> shippingAddress = %{
- street: "Desertroad",
- houseNumber: "1",
- additionalInfo: "Suite II",
- zip: "84536",
- city: "Monument Valley",
- state: "Utah",
- countryCode: "US"
- }
+ street: "Desertroad",
+ houseNumber: "1",
+ additionalInfo: "Suite II",
+ zip: "84536",
+ city: "Monument Valley",
+ state: "Utah",
+ countryCode: "US"
+ }
iex> billingAddress = %{
- street: "Desertroad",
- houseNumber: "13",
- additionalInfo: "b",
- zip: "84536",
- city: "Monument Valley",
- state: "Utah",
- countryCode: "US"
- }
+ street: "Desertroad",
+ houseNumber: "13",
+ additionalInfo: "b",
+ zip: "84536",
+ city: "Monument Valley",
+ state: "Utah",
+ countryCode: "US"
+ }
iex> invoice = %{
- invoiceNumber: "000000123",
- invoiceDate: "20140306191500"
- }
+ invoiceNumber: "000000123",
+ invoiceDate: "20140306191500"
+ }
iex> name = %{
- title: "Miss",
- firstName: "Road",
- surname: "Runner"
- }
+ title: "Miss",
+ firstName: "Road",
+ surname: "Runner"
+ }
- iex> opts = [ description: "Store Purchase 1437598192", merchantCustomerId: "234", customer_name: "John Doe", dob: "19490917", company: "asma", email: "johndoe@gmail.com", phone: "7765746563", order_id: "2323", invoice: invoice, billingAddress: billingAddress, shippingAddress: shippingAddress, name: name, skipAuthentication: "true" ]
+ iex> card = %CreditCard{
+ number: "4567350000427977",
+ month: 12,
+ year: 43,
+ first_name: "John",
+ last_name: "Doe",
+ verification_code: "123",
+ brand: "VISA"
+ }
+ iex> opts = [
+ description: "Store Purchase 1437598192",
+ merchantCustomerId: "234", dob: "19490917",
+ company: "asma", email: "johndoe@gmail.com",
+ phone: "7765746563", invoice: invoice,
+ billingAddress: billingAddress,
+ shippingAddress: shippingAddress,
+ name: name, skipAuthentication: "true"
+ ]
```
We'll be using these in the examples below.
+ [home]: http://www.globalcollect.com/
+ [docs]: https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/index.html
+ [dashboard]: https://sandbox.account.ingenico.com/#/dashboard
+ [gs]: #
+ [options]: https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/java/payments/create.html#payments-create-payload
+ [currencies]: https://epayments.developer-ingenico.com/best-practices/services/currency-conversion
[example]: https://github.com/aviabird/gringotts_example
+ [amountReference]: https://epayments-api.developer-ingenico.com/c2sapi/v1/en_US/swift/services/convertAmount.html
"""
@base_url "https://api-sandbox.globalcollect.com/v1/"
@@ -127,12 +155,12 @@ defmodule Gringotts.Gateways.GlobalCollect do
alias Gringotts.{Money, CreditCard, Response}
@brand_map %{
- visa: "1",
- american_express: "2",
- master: "3",
- discover: "128",
- jcb: "125",
- diners_club: "132"
+ VISA: "1",
+ AMERICAN_EXPRESS: "2",
+ MASTER: "3",
+ DISCOVER: "128",
+ JCB: "125",
+ DINERS_CLUB: "132"
}
@doc """
@@ -143,73 +171,70 @@ defmodule Gringotts.Gateways.GlobalCollect do
also triggers risk management. Funds are not transferred.
GlobalCollect returns a payment id which can be further used to:
- * `capture/3` _an_ amount.
- * `refund/3` _an_amount
+ * `capture/3` an amount.
+ * `refund/3` an amount
* `void/2` a pre_authorization
## Example
- > The following session shows how one would (pre) authorize a payment of $100 on
+ The following example shows how one would (pre) authorize a payment of $100 on
a sample `card`.
```
iex> card = %CreditCard{
number: "4567350000427977",
month: 12,
- year: 18,
+ year: 43,
first_name: "John",
last_name: "Doe",
verification_code: "123",
- brand: "visa"
+ brand: "VISA"
}
- iex> amount = %{value: Decimal.new(100), currency: "USD"}
+ iex> amount = Money.new(100, :USD)
iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.GlobalCollect, amount, card, opts)
```
"""
@spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
- def authorize(amount, card = %CreditCard{}, opts) do
- params = create_params_for_auth_or_purchase(amount, card, opts)
+ def authorize(amount, %CreditCard{} = card, opts) do
+ params = %{
+ order: add_order(amount, opts),
+ cardPaymentMethodSpecificInput: add_card(card, opts)
+ }
+
commit(:post, "payments", params, opts)
end
@doc """
Captures a pre-authorized `amount`.
- `amount` is transferred to the merchant account by GlobalCollect used in the
- pre-authorization referenced by `payment_id`.
+ `amount` used in the pre-authorization referenced by `payment_id` is
+ transferred to the merchant account by GlobalCollect.
## Note
- > Authorized payment with PENDING_APPROVAL status only allow a single capture whereas the one with PENDING_CAPTURE status is used for payments that allow multiple captures.
- > PENDING_APPROVAL is a common status only with card and direct debit transactions.
+ Authorized payment with PENDING_APPROVAL status only allow a single capture whereas
+ the one with PENDING_CAPTURE status is used for payments that allow multiple captures.
## Example
- The following session shows how one would (partially) capture a previously
+ The following example shows how one would (partially) capture a previously
authorized a payment worth $100 by referencing the obtained authorization `id`.
```
- iex> card = %CreditCard{
- number: "4567350000427977",
- month: 12,
- year: 18,
- first_name: "John",
- last_name: "Doe",
- verification_code: "123",
- brand: "visa"
- }
-
- iex> amount = %{value: Decimal.new(100), currency: "USD"}
+ iex> amount = Money.new(100, :USD)
- iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.GlobalCollect, amount, card, opts)
+ iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.GlobalCollect, auth_result.authorization, amount, opts)
```
"""
@spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response}
def capture(payment_id, amount, opts) do
- params = create_params_for_capture(amount, opts)
+ params = %{
+ order: add_order(amount, opts)
+ }
+
commit(:post, "payments/#{payment_id}/approve", params, opts)
end
@@ -222,28 +247,28 @@ defmodule Gringotts.Gateways.GlobalCollect do
## Example
- > The following session shows how one would process a payment in one-shot,
+ The following example shows how one would process a payment in one-shot,
without (pre) authorization.
```
iex> card = %CreditCard{
number: "4567350000427977",
month: 12,
- year: 18,
+ year: 43,
first_name: "John",
last_name: "Doe",
verification_code: "123",
- brand: "visa"
+ brand: "VISA"
}
- iex> amount = %{value: Decimal.new(100), currency: "USD"}
+ iex> amount = Money.new(100, :USD)
iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.GlobalCollect, amount, card, opts)
```
"""
@spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
- def purchase(amount, card = %CreditCard{}, opts) do
+ def purchase(amount, %CreditCard{} = card, opts) do
case authorize(amount, card, opts) do
{:ok, results} ->
payment_id = results.raw["payment"]["id"]
@@ -257,50 +282,53 @@ defmodule Gringotts.Gateways.GlobalCollect do
@doc """
Voids the referenced payment.
- This makes it impossible to process the payment any further and will also try to reverse an authorization on a card.
- Reversing an authorization that you will not be utilizing will prevent you from having to pay a fee/penalty for unused authorization requests.
+ This makes it impossible to process the payment any further and will also try
+ to reverse an authorization on a card.
+ Reversing an authorization that you will not be utilizing will prevent you
+ from having to [pay a fee/penalty][void] for unused authorization requests.
+ [void]: https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/java/payments/cancel.html#payments-cancel-request
## Example
- > The following session shows how one would void a previous (pre)
+ The following example shows how one would void a previous (pre)
authorization. Remember that our `capture/3` example only did a complete
capture.
```
- iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.GlobalCollect, auth_result.payment.id, opts)
+ iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.GlobalCollect, auth_result.authorization, opts)
```
"""
@spec void(String.t(), keyword) :: {:ok | :error, Response}
def void(payment_id, opts) do
- params = nil
- commit(:post, "payments/#{payment_id}/cancel", params, opts)
+ commit(:post, "payments/#{payment_id}/cancel", [], opts)
end
@doc """
Refunds the `amount` to the customer's account with reference to a prior transfer.
- > You can refund any transaction by just calling this API
-
- ## Note
-
You always have the option to refund just a portion of the payment amount.
- It is also possible to submit multiple refund requests on one payment as long as the total amount to be refunded does not exceed the total amount that was paid.
+ It is also possible to submit multiple refund requests on one payment as long
+ as the total amount to be refunded does not exceed the total amount that was paid.
## Example
- > The following session shows how one would refund a previous purchase (and
+ The following example shows how one would refund a previous purchase (and
similarily for captures).
```
- iex> amount = %{value: Decimal.new(100), currency: "USD"}
+ iex> amount = Money.new(100, :USD)
- iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.GlobalCollect, auth_result.payment.id, amount)
+ iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.GlobalCollect, auth_result.authorization, amount)
```
"""
@spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response}
def refund(amount, payment_id, opts) do
- params = create_params_for_refund(amount, opts)
+ params = %{
+ amountOfMoney: add_money(amount),
+ customer: add_customer(opts)
+ }
+
commit(:post, "payments/#{payment_id}/refund", params, opts)
end
@@ -308,40 +336,18 @@ defmodule Gringotts.Gateways.GlobalCollect do
# PRIVATE METHODS #
###############################################################################
- # Makes the request to GlobalCollect's network.
- # For consistency with other gateway implementations, make your (final)
- # network request in here, and parse it using another private method called
- # `respond`.
-
- defp create_params_for_refund(amount, opts) do
- %{
- amountOfMoney: add_money(amount, opts),
- customer: add_customer(opts)
- }
- end
-
- defp create_params_for_auth_or_purchase(amount, payment, opts) do
- %{
- order: add_order(amount, opts),
- cardPaymentMethodSpecificInput: add_payment(payment, @brand_map, opts)
- }
- end
-
- defp create_params_for_capture(amount, opts) do
- %{
- order: add_order(amount, opts)
- }
- end
-
defp add_order(money, options) do
%{
- amountOfMoney: add_money(money, options),
+ amountOfMoney: add_money(money),
customer: add_customer(options),
- references: add_references(options)
+ references: %{
+ descriptor: options[:description],
+ invoiceData: options[:invoice]
+ }
}
end
- defp add_money(amount, options) do
+ defp add_money(amount) do
{currency, amount, _} = Money.to_integer(amount)
%{
@@ -353,94 +359,65 @@ defmodule Gringotts.Gateways.GlobalCollect do
defp add_customer(options) do
%{
merchantCustomerId: options[:merchantCustomerId],
- personalInformation: personal_info(options),
+ personalInformation: %{
+ name: options[:name]
+ },
dateOfBirth: options[:dob],
- companyInformation: company_info(options),
+ companyInformation: %{
+ name: options[:company]
+ },
billingAddress: options[:billingAddress],
shippingAddress: options[:shippingAddress],
- contactDetails: contact(options)
- }
- end
-
- defp add_references(options) do
- %{
- descriptor: options[:description],
- invoiceData: options[:invoice]
- }
- end
-
- defp personal_info(options) do
- %{
- name: options[:name]
- }
- end
-
- defp company_info(options) do
- %{
- name: options[:company]
- }
- end
-
- defp contact(options) do
- %{
- emailAddress: options[:email],
- phoneNumber: options[:phone]
+ contactDetails: %{
+ emailAddress: options[:email],
+ phoneNumber: options[:phone]
+ }
}
end
- def add_card(%CreditCard{} = payment) do
+ defp add_card(card, opts) do
%{
- cvv: payment.verification_code,
- cardNumber: payment.number,
- expiryDate: "#{payment.month}" <> "#{payment.year}",
- cardholderName: CreditCard.full_name(payment)
- }
- end
-
- defp add_payment(payment, brand_map, opts) do
- brand = payment.brand
-
- %{
- paymentProductId: Map.fetch!(brand_map, String.to_atom(brand)),
+ paymentProductId: Map.fetch!(@brand_map, String.to_atom(card.brand)),
skipAuthentication: opts[:skipAuthentication],
- card: add_card(payment)
+ card: %{
+ cvv: card.verification_code,
+ cardNumber: card.number,
+ expiryDate: "#{card.month}#{card.year}",
+ cardholderName: CreditCard.full_name(card)
+ }
}
end
- defp auth_digest(path, secret_api_key, time, opts) do
- data = "POST\napplication/json\n#{time}\n/v1/#{opts[:config][:merchant_id]}/#{path}\n"
- :crypto.hmac(:sha256, secret_api_key, data)
- end
-
defp commit(method, path, params, opts) do
headers = create_headers(path, opts)
data = Poison.encode!(params)
- url = "#{@base_url}#{opts[:config][:merchant_id]}/#{path}"
- response = HTTPoison.request(method, url, data, headers)
- response |> respond
+ merchant_id = opts[:config][:merchant_id]
+ url = "#{@base_url}#{merchant_id}/#{path}"
+
+ gateway_response = HTTPoison.request(method, url, data, headers)
+ gateway_response |> respond
end
defp create_headers(path, opts) do
- time = date
+ datetime = Timex.now() |> Timex.local()
+
+ date_string =
+ "#{Timex.format!(datetime, "%a, %d %b %Y %H:%M:%S", :strftime)} #{datetime.zone_abbr}"
- sha_signature =
- auth_digest(path, opts[:config][:secret_api_key], time, opts) |> Base.encode64()
+ api_key_id = opts[:config][:api_key_id]
- auth_token = "GCS v1HMAC:#{opts[:config][:api_key_id]}:#{sha_signature}"
+ sha_signature = auth_digest(path, date_string, opts)
- headers = [
- {"Content-Type", "application/json"},
- {"Authorization", auth_token},
- {"Date", time}
- ]
+ auth_token = "GCS v1HMAC:#{api_key_id}:#{Base.encode64(sha_signature)}"
+ [{"Content-Type", "application/json"}, {"Authorization", auth_token}, {"Date", date_string}]
end
- defp date() do
- use Timex
- datetime = Timex.now() |> Timex.local()
- strftime_str = Timex.format!(datetime, "%a, %d %b %Y %H:%M:%S ", :strftime)
- time_zone = Timex.timezone(:local, datetime)
- time = strftime_str <> "#{time_zone.abbreviation}"
+ defp auth_digest(path, date_string, opts) do
+ secret_api_key = opts[:config][:secret_api_key]
+ merchant_id = opts[:config][:merchant_id]
+
+ data = "POST\napplication/json\n#{date_string}\n/v1/#{merchant_id}/#{path}\n"
+ :crypto.hmac(:sha256, secret_api_key, data)
end
# Parses GlobalCollect's response and returns a `Gringotts.Response` struct
@@ -450,7 +427,31 @@ defmodule Gringotts.Gateways.GlobalCollect do
defp respond({:ok, %{status_code: code, body: body}}) when code in [200, 201] do
case decode(body) do
- {:ok, results} -> {:ok, Response.success(raw: results, status_code: code)}
+ {:ok, results} ->
+ {
+ :ok,
+ Response.success(
+ authorization: results["payment"]["id"],
+ raw: results,
+ status_code: code,
+ avs_result:
+ results["payment"]["paymentOutput"]["cardPaymentMethodSpecificOutput"][
+ "fraudResults"
+ ]["avsResult"],
+ cvc_result:
+ results["payment"]["paymentOutput"]["cardPaymentMethodSpecificOutput"][
+ "fraudResults"
+ ]["cvcResult"],
+ message: results["payment"]["status"],
+ fraud_review:
+ results["payment"]["paymentOutput"]["cardPaymentMethodSpecificOutput"][
+ "fraudResults"
+ ]["fraudServiceResult"]
+ )
+ }
+
+ {:error, _} ->
+ {:error, Response.error(raw: body, message: "undefined response from GlobalCollect")}
end
end
@@ -462,11 +463,13 @@ defmodule Gringotts.Gateways.GlobalCollect do
end
defp respond({:error, %HTTPoison.Error{} = error}) do
- {:error,
- Response.error(
- code: error.id,
- reason: :network_fail?,
- description: "HTTPoison says '#{error.reason}'"
- )}
+ {
+ :error,
+ Response.error(
+ code: error.id,
+ reason: :network_fail?,
+ description: "HTTPoison says '#{error.reason}'"
+ )
+ }
end
end
diff --git a/test/gateways/global_collect_test.exs b/test/gateways/global_collect_test.exs
index d64489c2..8d09449c 100644
--- a/test/gateways/global_collect_test.exs
+++ b/test/gateways/global_collect_test.exs
@@ -14,7 +14,7 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
@bad_amount Money.new("50.3", :USD)
- @shippingAddress %{
+ @shipping_address %{
street: "Desertroad",
houseNumber: "1",
additionalInfo: "Suite II",
@@ -31,7 +31,7 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
first_name: "John",
last_name: "Doe",
verification_code: "123",
- brand: "visa"
+ brand: "VISA"
}
@invalid_card %CreditCard{
@@ -41,10 +41,10 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
first_name: "John",
last_name: "Doe",
verification_code: "123",
- brand: "visa"
+ brand: "VISA"
}
- @billingAddress %{
+ @billing_address %{
street: "Desertroad",
houseNumber: "13",
additionalInfo: "b",
@@ -71,16 +71,16 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
@invalid_config [
config: %{
- secret_api_key: "Qtg9v4Q0G13sLRNcClWhHnvN1kVYWDcy4w9rG8T86XU=",
- api_key_id: "e5743abfc360ed12"
+ secret_api_key: "some_secret_api_key",
+ api_key_id: "some_api_key_id"
}
]
@options [
config: %{
- secret_api_key: "Qtg9v4Q0G13sLRNcClWhHnvN1kVYWDcy4w9rG8T86XU=",
- api_key_id: "e5743abfc360ed12",
- merchant_id: "1226"
+ secret_api_key: "some_secret_api_key",
+ api_key_id: "some_api_key_id",
+ merchant_id: "some_merchant_id"
},
description: "Store Purchase 1437598192",
merchantCustomerId: "234",
@@ -91,20 +91,12 @@ defmodule Gringotts.Gateways.GlobalCollectTest do
phone: "7468474533",
order_id: "2323",
invoice: @invoice,
- billingAddress: @billingAddress,
- shippingAddress: @shippingAddress,
+ billingAddress: @billing_address,
+ shippingAddress: @shipping_address,
name: @name,
skipAuthentication: "true"
]
- describe "validation arguments check" do
- test "with no merchant id passed in config" do
- assert_raise ArgumentError, fn ->
- GlobalCollect.validate_config(@invalid_config)
- end
- end
- end
-
describe "purchase" do
test "with valid card" do
with_mock HTTPoison,
diff --git a/test/mocks/global_collect_mock.exs b/test/mocks/global_collect_mock.exs
index 219b9551..ebedf90a 100644
--- a/test/mocks/global_collect_mock.exs
+++ b/test/mocks/global_collect_mock.exs
@@ -3,7 +3,15 @@ defmodule Gringotts.Gateways.GlobalCollectMock do
{:ok,
%HTTPoison.Response{
body:
- "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000074\",\n \"externalReference\" : \"000000122600000000740000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000740000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20180118135349\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
+ ~s/{"creationOutput":{"additionalReference":"00000012260000000074","externalReference":
+ "000000122600000000740000100001"},"payment":{"id":"000000122600000000740000100001",
+ "paymentOutput":{"amountOfMoney":{"amount":500,"currencyCode":"USD"},"references":
+ {"paymentReference":"0"},"paymentMethod":"card","cardPaymentMethodSpecificOutput":
+ {"paymentProductId":1,"authorisationCode":"OK1131","fraudResults":{"fraudServiceResult":
+ "no-advice","avsResult":"0","cvvResult":"0"},"card":{"cardNumber":"************7977",
+ "expiryDate":"1218"}}},"status":"PENDING_APPROVAL","statusOutput":{"isCancellable":true,
+ "statusCategory":"PENDING_MERCHANT","statusCode":600,"statusCodeChangeDateTime":
+ "20180118135349","isAuthorized":true,"isRefundable":false}}}/,
headers: [
{"Date", "Thu, 18 Jan 2018 12:53:49 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
@@ -22,7 +30,18 @@ defmodule Gringotts.Gateways.GlobalCollectMock do
{:ok,
%HTTPoison.Response{
body:
- "{\n \"errorId\" : \"363899bd-acfb-4452-bbb0-741c0df6b4b8\",\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"980825\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000075\",\n \"externalReference\" : \"000000122600000000750000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000750000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"546247\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20180118135651\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n }\n}",
+ ~s/{"errorId" : "363899bd-acfb-4452-bbb0-741c0df6b4b8","errors" : [ {"code" : "21000120",
+ "requestId" : "980825","propertyName" : "cardPaymentMethodSpecificInput.card.expiryDate",
+ "message" : "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT",
+ "httpStatusCode" : 400} ],"paymentResult" : {"creationOutput" : { " additionalReference" : "00000012260000000075",
+ "externalReference" : "000000122600000000750000100001"},"payment" : {"id" : "000000122600000000750000100001",
+ "paymentOutput" : {"amountOfMoney" : {"amount" : 500,"currencyCode" : "USD"},"references" : {"paymentReference" : "0"},
+ "paymentMethod" : "card","cardPaymentMethodSpecificOutput" : {"paymentProductId" : 1}},
+ "status" : "REJECTED","statusOutput" : {"errors" : [ {"code" : "21000120",
+ "requestId" : "546247","propertyName" : "cardPaymentMethodSpecificInput.card.expiryDate",
+ "message" : "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT",
+ "httpStatusCode" : 400} ],"isCancellable" : false,"statusCategory" : "UNSUCCESSFUL","statusCode" : 100,
+ "statusCodeChangeDateTime" : "20180118135651","isAuthorized" : false,"isRefundable" : false}}}}/,
headers: [
{"Date", "Thu, 18 Jan 2018 12:56:51 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
@@ -40,7 +59,10 @@ defmodule Gringotts.Gateways.GlobalCollectMock do
{:ok,
%HTTPoison.Response{
body:
- "{\n \"errorId\" : \"8c34dc0b-776c-44e3-8cd4-b36222960153\",\n \"errors\" : [ {\n \"code\" : \"1099\",\n \"id\" : \"INVALID_VALUE\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"message\" : \"INVALID_VALUE: '50.3' is not a valid value for field 'amount'\",\n \"httpStatusCode\" : 400\n } ]\n}",
+ ~s/{ "errorId" : "8c34dc0b-776c-44e3-8cd4-b36222960153","errors" : [ {"code" : "1099","id" :
+ "INVALID_VALUE","category" : "CONNECT_PLATFORM_ERROR","message" :
+ "INVALID_VALUE: '50.3' is not a valid value for field 'amount'",
+ "httpStatusCode" : 400 } ]}/,
headers: [
{"Date", "Wed, 24 Jan 2018 07:16:06 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
@@ -58,7 +80,16 @@ defmodule Gringotts.Gateways.GlobalCollectMock do
{:ok,
%HTTPoison.Response{
body:
- "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000065\",\n \"externalReference\" : \"000000122600000000650000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000650000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20180118110419\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
+ ~s/{"creationOutput" : {"additionalReference" : "00000012260000000065","externalReference" :
+ "000000122600000000650000100001"},"payment" : {"id" : "000000122600000000650000100001",
+ "paymentOutput" :{"amountOfMoney" : {"amount" : 500,"currencyCode" : "USD"},"references" :
+ {"paymentReference" : "0" },"paymentMethod" : "card","cardPaymentMethodSpecificOutput" :
+ {"paymentProductId" : 1,"authorisationCode" : "OK1131","fraudResults" :
+ {"fraudServiceResult" : "no-advice","avsResult" : "0","cvvResult" : "0"},"card" :
+ {"cardNumber" : "************7977","expiryDate" : "1218"}}},"status" : "PENDING_APPROVAL",
+ "statusOutput" : {"isCancellable" : true,"statusCategory" : "PENDING_MERCHANT","statusCode"
+ : 600,"statusCodeChangeDateTime" : "20180118110419","isAuthorized" : true,
+ "isRefundable" : false}}}/,
headers: [
{"Date", "Thu, 18 Jan 2018 10:04:19 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
@@ -77,7 +108,21 @@ defmodule Gringotts.Gateways.GlobalCollectMock do
{:ok,
%HTTPoison.Response{
body:
- "{\n \"errorId\" : \"dcdf5c8d-e475-4fbc-ac57-76123c1640a2\",\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"978754\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000066\",\n \"externalReference\" : \"000000122600000000660000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000660000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"978755\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20180118111508\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n }\n}",
+ ~s/{"errorId" : "dcdf5c8d-e475-4fbc-ac57-76123c1640a2","errors" : [ {"code" : "21000120",
+ "requestId" : "978754","propertyName" : "cardPaymentMethodSpecificInput.card.expiryDate","message":
+ "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT",
+ "httpStatusCode" : 400} ],"paymentResult" : {"creationOutput" :
+ {"additionalReference" :"00000012260000000066","externalReference" :
+ "000000122600000000660000100001"},"payment" :{"id" : "000000122600000000660000100001",
+ "paymentOutput" : {"amountOfMoney" : {"amount" : 500,"currencyCode" : "USD"},
+ "references" : {"paymentReference" : "0"},"paymentMethod" : "card",
+ "cardPaymentMethodSpecificOutput" : {"paymentProductId" : 1}},"status" : "REJECTED",
+ "statusOutput":{"errors" : [ {"code" : "21000120","requestId" : "978755","propertyName" :
+ "cardPaymentMethodSpecificInput.card.expiryDate","message" :
+ "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT",
+ "httpStatusCode" : 400} ],"isCancellable" : false,"statusCategory" :
+ "UNSUCCESSFUL","statusCode" : 100,"statusCodeChangeDateTime" : "20180118111508",
+ "isAuthorized" : false,"isRefundable" : false}}}}/,
headers: [
{"Date", "Thu, 18 Jan 2018 10:15:08 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
@@ -95,7 +140,9 @@ defmodule Gringotts.Gateways.GlobalCollectMock do
{:ok,
%HTTPoison.Response{
body:
- "{\n \"errorId\" : \"1dbef568-ed86-4c8d-a3c3-74ced258d5a2\",\n \"errors\" : [ {\n \"code\" : \"1099\",\n \"id\" : \"INVALID_VALUE\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"message\" : \"INVALID_VALUE: '50.3' is not a valid value for field 'amount'\",\n \"httpStatusCode\" : 400\n } ]\n}",
+ ~s/{"errorId" : "1dbef568-ed86-4c8d-a3c3-74ced258d5a2","errors" : [ {"code" : "1099","id" :
+ "INVALID_VALUE", "category" : "CONNECT_PLATFORM_ERROR","message" :
+ "INVALID_VALUE: '50.3' is not a valid value for field 'amount'","httpStatusCode" : 400} ]}/,
headers: [
{"Date", "Tue, 23 Jan 2018 11:18:11 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
@@ -113,7 +160,8 @@ defmodule Gringotts.Gateways.GlobalCollectMock do
{:ok,
%HTTPoison.Response{
body:
- "{\n \"errorId\" : \"b6ba00d2-8f11-4822-8f32-c6d0a4d8793b\",\n \"errors\" : [ {\n \"code\" : \"300450\",\n \"message\" : \"ORDER WITHOUT REFUNDABLE PAYMENTS\",\n \"httpStatusCode\" : 400\n } ]\n}",
+ ~s/{ "errorId" : "b6ba00d2-8f11-4822-8f32-c6d0a4d8793b", "errors" : [ {"code" : "300450",
+ "message" : "ORDER WITHOUT REFUNDABLE PAYMENTS", "httpStatusCode" : 400 } ]}/,
headers: [
{"Date", "Wed, 24 Jan 2018 05:33:56 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
@@ -131,8 +179,15 @@ defmodule Gringotts.Gateways.GlobalCollectMock do
def test_for_capture_with_valid_paymentid do
{:ok,
%HTTPoison.Response{
- body:
- "{\n \"payment\" : {\n \"id\" : \"000000122600000000650000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 50,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"CAPTURE_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_CONNECT_OR_3RD_PARTY\",\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20180123140826\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}",
+ body: ~s/{ "payment" : {"id" : "000000122600000000650000100001", "paymentOutput" : {
+ "amountOfMoney" :{"amount" : 50,"currencyCode" : "USD"},"references" : {"paymentReference"
+ : "0"},"paymentMethod" : "card","cardPaymentMethodSpecificOutput" : {"paymentProductId" :
+ 1,"authorisationCode" : "OK1131","fraudResults" : {"fraudServiceResult" : "no-advice",
+ "avsResult" : "0","cvvResult" : "0"},"card" :{"cardNumber" : "************7977",
+ "expiryDate" : "1218"}}},"status" : "CAPTURE_REQUESTED","statusOutput" :
+ {"isCancellable" : true,"statusCategory" : "PENDING_CONNECT_OR_3RD_PARTY",
+ "statusCode" : 800,"statusCodeChangeDateTime" : "20180123140826","isAuthorized" : true,
+ "isRefundable" : false} }}/,
headers: [
{"Date", "Tue, 23 Jan 2018 13:08:26 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
@@ -149,8 +204,9 @@ defmodule Gringotts.Gateways.GlobalCollectMock do
def test_for_capture_with_invalid_paymentid do
{:ok,
%HTTPoison.Response{
- body:
- "{\n \"errorId\" : \"ccb99804-0240-45b6-bb28-52aaae59d71b\",\n \"errors\" : [ {\n \"code\" : \"1002\",\n \"id\" : \"UNKNOWN_PAYMENT_ID\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"propertyName\" : \"paymentId\",\n \"message\" : \"UNKNOWN_PAYMENT_ID\",\n \"httpStatusCode\" : 404\n } ]\n}",
+ body: ~s/{ "errorId" : "ccb99804-0240-45b6-bb28-52aaae59d71b", "errors" : [
+ {"code" : "1002","id" :"UNKNOWN_PAYMENT_ID","category" : "CONNECT_PLATFORM_ERROR",
+ "propertyName" : "paymentId","message": "UNKNOWN_PAYMENT_ID","httpStatusCode" :404}]}/,
headers: [
{"Date", "Tue, 23 Jan 2018 12:25:59 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
@@ -167,7 +223,15 @@ defmodule Gringotts.Gateways.GlobalCollectMock do
{:ok,
%HTTPoison.Response{
body:
- "{\n \"payment\" : {\n \"id\" : \"000000122600000000870000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 50,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20180124064204\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n}",
+ ~s/{ "payment" : {"id" : "000000122600000000870000100001","paymentOutput" : {"amountOfMoney"
+ :{"amount" : 50,"currencyCode" : "USD"},"references" : {"paymentReference" : "0"},
+ "paymentMethod" : "card","cardPaymentMethodSpecificOutput" : {"paymentProductId" : 1,
+ "authorisationCode" : "OK1131","fraudResults" : {"fraudServiceResult" : "no-advice",
+ "avsResult" : "0","cvvResult" : "0"},"card" :{"cardNumber" : "************7977",
+ "expiryDate" : "1218"}}},"status" : "CANCELLED","statusOutput":{"isCancellable" :
+ false,"statusCategory" : "UNSUCCESSFUL","statusCode" : 99999,
+ "statusCodeChangeDateTime" : "20180124064204","isAuthorized" : false,"isRefundable" :
+ false}}}/,
headers: [
{"Date", "Wed, 24 Jan 2018 05:42:04 GMT"},
{"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},
From a4441797de0abf09a2a9c3d49b8ef4472c98b349 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Tue, 20 Mar 2018 16:58:36 +0530
Subject: [PATCH 39/60] Initial Commit \n Implemented Authorize
---
lib/gringotts/gateways/mercadopago.ex | 370 ++++++++++++++++++
test/gateways/mercadopago_test.exs | 32 ++
.../integration/gateways/mercadopago_test.exs | 36 ++
test/mocks/mercadopago_mock.exs | 9 +
4 files changed, 447 insertions(+)
create mode 100644 lib/gringotts/gateways/mercadopago.ex
create mode 100644 test/gateways/mercadopago_test.exs
create mode 100644 test/integration/gateways/mercadopago_test.exs
create mode 100644 test/mocks/mercadopago_mock.exs
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
new file mode 100644
index 00000000..06ae5dde
--- /dev/null
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -0,0 +1,370 @@
+defmodule Gringotts.Gateways.Mercadopago do
+ @moduledoc """
+ [mercadopago][home] gateway implementation.
+
+ ## Instructions!
+
+ ***This is an example `moduledoc`, and suggests some items that should be
+ documented in here.***
+
+ The quotation boxes like the one below will guide you in writing excellent
+ documentation for your gateway. All our gateways are documented in this manner
+ and we aim to keep our docs as consistent with each other as possible.
+ **Please read them and do as they suggest**. Feel free to add or skip sections
+ though.
+
+ If you'd like to make edits to the template docs, they exist at
+ `templates/gateway.eex`. We encourage you to make corrections and open a PR
+ and tag it with the label `template`.
+
+ ***Actual docs begin below this line!***
+
+ --------------------------------------------------------------------------------
+
+ > List features that have been implemented, and what "actions" they map to as
+ > per the mercadopago gateway docs.
+ > A table suits really well for this.
+
+ ## Optional or extra parameters
+
+ Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply
+ optional arguments for transactions with the gateway.
+
+ > List all available (ie, those that will be supported by this module) keys, a
+ > description of their function/role and whether they have been implemented
+ > and tested.
+ > A table suits really well for this.
+
+ ## Registering your mercadopago account at `Gringotts`
+
+ Explain how to make an account with the gateway and show how to put the
+ `required_keys` (like authentication info) to the configuration.
+
+ > Here's how the secrets map to the required configuration parameters for mercadopago:
+ >
+ > | Config parameter | mercadopago secret |
+ > | ------- | ---- |
+ > | `:public_key` | **PublicKey** |
+ > | `:access_token` | **AccessToken** |
+
+ > Your Application config **must include the `[:public_key, :access_token]` field(s)** and would look
+ > something like this:
+ >
+ > config :gringotts, Gringotts.Gateways.Mercadopago,
+ > public_key: "your_secret_public_key"
+ > access_token: "your_secret_access_token"
+
+
+ ## Scope of this module
+
+ > It's unlikely that your first iteration will support all features of the
+ > gateway, so list down those items that are missing.
+
+ ## Supported currencies and countries
+
+ > It's enough if you just add a link to the gateway's docs or FAQ that provide
+ > info about this.
+
+ ## Following the examples
+
+ 1. First, set up a sample application and configure it to work with MONEI.
+ - You could do that from scratch by following our [Getting Started][gs] guide.
+ - To save you time, we recommend [cloning our example
+ repo][example] that gives you a pre-configured sample app ready-to-go.
+ + You could use the same config or update it the with your "secrets"
+ as described [above](#module-registering-your-monei-account-at-mercadopago).
+
+ 2. Run an `iex` session with `iex -S mix` and add some variable bindings and
+ aliases to it (to save some time):
+ ```
+ iex> alias Gringotts.{Response, CreditCard, Gateways.Mercadopago}
+ iex> card = %CreditCard{first_name: "Jo",
+ last_name: "Doe",
+ number: "4200000000000000",
+ year: 2099, month: 12,
+ verification_code: "123", brand: "VISA"}
+ ```
+
+ > Add any other frequently used bindings up here.
+
+ We'll be using these in the examples below.
+
+ [gs]: https://github.com/aviabird/gringotts/wiki/
+ [home]: https://www.mercadopago.com
+ [example]: https://github.com/aviabird/gringotts_example
+ """
+
+ # The Base module has the (abstract) public API, and some utility
+ # implementations.
+ @base_url "https://api.mercadopago.com"
+ use Gringotts.Gateways.Base
+
+ # The Adapter module provides the `validate_config/1`
+ # Add the keys that must be present in the Application config in the
+ # `required_config` list
+ use Gringotts.Adapter, required_config: [:public_key, :access_token]
+
+ import Poison, only: [decode: 1]
+
+ alias Gringotts.{Money,
+ CreditCard,
+ Response}
+
+ @doc """
+ Performs a (pre) Authorize operation.
+
+ The authorization validates the `card` details with the banking network,
+ places a hold on the transaction `amount` in the customer’s issuing bank.
+
+ > ** You could perhaps:**
+ > 1. describe what are the important fields in the Response struct
+ > 2. mention what a merchant can do with these important fields (ex:
+ > `capture/3`, etc.)
+
+ ## Note
+
+ > If there's anything noteworthy about this operation, it comes here.
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+
+
+
+ @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def authorize(amount, card, opts) do
+ # commit(args, ...)
+ if(is_nil(opts[:customer_id])) do
+ customer_id = get_customer_id(opts)
+ opts = opts ++ [customer_id: customer_id]
+ end
+ token_id = get_token_id(card, opts)
+ opts = opts ++ [token_id: token_id]
+
+ body = get_authorize_body(amount, card, opts, opts[:token_id], opts[:customer_id]) |> Poison.encode!()
+ headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
+
+ response = HTTPoison.post!("#{@base_url}/v1/payments?access_token=#{opts[:config][:access_token]}", body, headers, [])
+ %HTTPoison.Response{body: body, status_code: status_code} = response
+ body = body |> Poison.decode!()
+ format_response(body, status_code, opts)
+ end
+
+ @doc """
+ Captures a pre-authorized `amount`.
+
+ `amount` is transferred to the merchant account by mercadopago used in the
+ pre-authorization referenced by `payment_id`.
+
+ ## Note
+
+ > If there's anything noteworthy about this operation, it comes here.
+ > For example, does the gateway support partial, multiple captures?
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec capture(String.t(), Money.t, keyword) :: {:ok | :error, Response}
+ def capture(payment_id, amount, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Transfers `amount` from the customer to the merchant.
+
+ mercadopago attempts to process a purchase on behalf of the customer, by
+ debiting `amount` from the customer's account by charging the customer's
+ `card`.
+
+ ## Note
+
+ > If there's anything noteworthy about this operation, it comes here.
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec purchase(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def purchase(amount, card = %CreditCard{}, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Voids the referenced payment.
+
+ This method attempts a reversal of a previous transaction referenced by
+ `payment_id`.
+
+ > As a consequence, the customer will never see any booking on his statement.
+
+ ## Note
+
+ > Which transactions can be voided?
+ > Is there a limited time window within which a void can be perfomed?
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec void(String.t(), keyword) :: {:ok | :error, Response}
+ def void(payment_id, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Refunds the `amount` to the customer's account with reference to a prior transfer.
+
+ > Refunds are allowed on which kinds of "prior" transactions?
+
+ ## Note
+
+ > The end customer will usually see two bookings/records on his statement. Is
+ > that true for mercadopago?
+ > Is there a limited time window within which a void can be perfomed?
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec refund(Money.t, String.t(), keyword) :: {:ok | :error, Response}
+ def refund(amount, payment_id, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Stores the payment-source data for later use.
+
+ > This usually enable "One Click" and/or "Recurring Payments"
+
+ ## Note
+
+ > If there's anything noteworthy about this operation, it comes here.
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def store(%CreditCard{} = card, opts) do
+ # commit(args, ...)
+ end
+
+ @doc """
+ Removes card or payment info that was previously `store/2`d
+
+ Deletes previously stored payment-source data.
+
+ ## Note
+
+ > If there's anything noteworthy about this operation, it comes here.
+
+ ## Example
+
+ > A barebones example using the bindings you've suggested in the `moduledoc`.
+ """
+ @spec unstore(String.t(), keyword) :: {:ok | :error, Response}
+ def unstore(registration_id, opts) do
+ # commit(args, ...)
+ end
+
+ ###############################################################################
+ # PRIVATE METHODS #
+ ###############################################################################
+
+ # Makes the request to mercadopago's network.
+ # For consistency with other gateway implementations, make your (final)
+ # network request in here, and parse it using another private method called
+ # `respond`.
+ @spec commit(_) :: {:ok | :error, Response}
+ defp commit(_) do
+ # resp = HTTPoison.request(args, ...)
+ # respond(resp, ...)
+ end
+
+ # Parses mercadopago's response and returns a `Gringotts.Response` struct
+ # in a `:ok`, `:error` tuple.
+ @spec respond(term) :: {:ok | :error, Response}
+ defp respond(mercadopago_response)
+ defp respond({:ok, %{status_code: 200, body: body}}), do: "something"
+ defp respond({:ok, %{status_code: status_code, body: body}}), do: "something"
+ defp respond({:error, %HTTPoison.Error{} = error}), do: "something"
+
+ defp get_customer_id(opts) do
+ body = %{"email": opts[:email]} |> Poison.encode!
+ headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
+ response = HTTPoison.post!("#{@base_url}/v1/customers?access_token=#{opts[:config][:access_token]}", body, headers, [])
+ %HTTPoison.Response{body: body} = response
+ body = body |> Poison.decode!()
+ body["id"]
+ end
+
+ defp get_token_body(card) do
+ %{
+ "expirationYear": card[:year],
+ "expirationMonth": card[:month],
+ "cardNumber": card[:number],
+ "securityCode": card[:verification_code],
+ "cardholder": %{
+ "name": card[:first_name] <> card[:last_name]
+ }
+ }
+ end
+
+ defp get_token_id(card, opts) do
+ body = get_token_body(card) |> Poison.encode!()
+ headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
+ token = HTTPoison.post!("#{@base_url}/v1/card_tokens/#{opts[:customer_id]}?public_key=#{opts[:config][:public_key]}", body, headers, [])
+ %HTTPoison.Response{body: body} = token
+ body = body |> Poison.decode!()
+ body["id"]
+ end
+
+ defp get_authorize_body(amount, card, opts, token_id, customer_id) do
+ %{
+ "payer": %{
+ "type": "customer",
+ "id": customer_id,
+ "first_name": card[:first_name],
+ "last_name": card[:last_name]
+ },
+
+ "order": %{
+ "type": "mercadopago",
+ "id": opts[:order_id]
+ },
+ "installments": 1,
+ "transaction_amount": amount,
+ "payment_method_id": opts[:payment_method_id], #visa
+ "token": token_id,
+ capture: false
+ }
+ end
+
+ defp get_success_body(body, status_code, opts) do
+ %Response{
+ success: true,
+ id: body["id"],
+ token: opts[:customer_id],
+ status_code: status_code,
+ message: body["status"]
+ }
+ end
+ defp get_error_body(body, status_code, opts) do
+ %Response{
+ success: false,
+ token: opts[:customer_id],
+ status_code: status_code,
+ message: body["message"]
+ }
+ end
+
+ defp format_response(body, status_code, opts) do
+ case body["cause"] do
+ nil -> {:ok, get_success_body(body, status_code, opts)}
+ _ -> {:error, get_error_body(body, status_code, opts)}
+ end
+ end
+
+end
diff --git a/test/gateways/mercadopago_test.exs b/test/gateways/mercadopago_test.exs
new file mode 100644
index 00000000..62bf1ae7
--- /dev/null
+++ b/test/gateways/mercadopago_test.exs
@@ -0,0 +1,32 @@
+defmodule Gringotts.Gateways.MercadopagoTest do
+ # The file contains mocked tests for Mercadopago
+
+ # We recommend using [mock][1] for this, you can place the mock responses from
+ # the Gateway in `test/mocks/mercadopago_mock.exs` file, which has also been
+ # generated for you.
+ #
+ # [1]: https://github.com/jjh42/mock
+
+ # Load the mock response file before running the tests.
+ Code.require_file "../mocks/mercadopago_mock.exs", __DIR__
+
+ use ExUnit.Case, async: false
+ alias Gringotts.Gateways.Mercadopago
+ import Mock
+
+ # Group the test cases by public api
+ describe "purchase" do
+ end
+
+ describe "authorize" do
+ end
+
+ describe "capture" do
+ end
+
+ describe "void" do
+ end
+
+ describe "refund" do
+ end
+end
diff --git a/test/integration/gateways/mercadopago_test.exs b/test/integration/gateways/mercadopago_test.exs
new file mode 100644
index 00000000..8d60dd1a
--- /dev/null
+++ b/test/integration/gateways/mercadopago_test.exs
@@ -0,0 +1,36 @@
+defmodule Gringotts.Integration.Gateways.MercadopagoTest do
+ # Integration tests for the Mercadopago
+
+ use ExUnit.Case, async: false
+ alias Gringotts.Gateways.Mercadopago
+
+ @moduletag :integration
+
+ setup_all do
+ Application.put_env(:gringotts, Gringotts.Gateways.Mercadopago,
+ [
+ public_key: "your_secret_public_key",
+ access_token: "your_secret_access_token"
+ ]
+ )
+ end
+
+ # Group the test cases by public api
+ describe "purchase" do
+ end
+
+ describe "authorize" do
+ end
+
+ describe "capture" do
+ end
+
+ describe "void" do
+ end
+
+ describe "refund" do
+ end
+
+ describe "environment setup" do
+ end
+end
diff --git a/test/mocks/mercadopago_mock.exs b/test/mocks/mercadopago_mock.exs
new file mode 100644
index 00000000..f7a73709
--- /dev/null
+++ b/test/mocks/mercadopago_mock.exs
@@ -0,0 +1,9 @@
+defmodule Gringotts.Gateways.MercadopagoMock do
+
+ # The module should include mock responses for test cases in mercadopago_test.exs.
+ # e.g.
+ # def successful_purchase do
+ # {:ok, %HTTPoison.Response{body: ~s[{data: "successful_purchase"}]}
+ # end
+
+end
From ea9c91234f4f5d46a16d898e4ed6d780d7401412 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Tue, 20 Mar 2018 20:27:54 +0530
Subject: [PATCH 40/60] Implemented authorize
Created all the helper functions, body for HTTPoison requests, response methods.
---
lib/gringotts/gateways/mercadopago.ex | 1 -
1 file changed, 1 deletion(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index 06ae5dde..0c50911d 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -329,7 +329,6 @@ defmodule Gringotts.Gateways.Mercadopago do
"first_name": card[:first_name],
"last_name": card[:last_name]
},
-
"order": %{
"type": "mercadopago",
"id": opts[:order_id]
From 2127fe9dfaba3c6d9ad6cd8db7c5a73c9cafe801 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Wed, 21 Mar 2018 15:19:46 +0530
Subject: [PATCH 41/60] Refined authorize function
1. Completed the docs
2. Used predefined CreditCard and Money data type
3. Added brand in CreditCard
---
lib/gringotts/gateways/mercadopago.ex | 165 ++++++++++++++------------
1 file changed, 86 insertions(+), 79 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index 0c50911d..56042f4d 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -1,51 +1,40 @@
defmodule Gringotts.Gateways.Mercadopago do
@moduledoc """
- [mercadopago][home] gateway implementation.
+ [MERCADOPAGO][home] gateway implementation.
- ## Instructions!
-
- ***This is an example `moduledoc`, and suggests some items that should be
- documented in here.***
+ For reference see [MERCADOPAGO API (v1) documentation][docs].
- The quotation boxes like the one below will guide you in writing excellent
- documentation for your gateway. All our gateways are documented in this manner
- and we aim to keep our docs as consistent with each other as possible.
- **Please read them and do as they suggest**. Feel free to add or skip sections
- though.
+ The following features of MERCADOPAGO are implemented:
- If you'd like to make edits to the template docs, they exist at
- `templates/gateway.eex`. We encourage you to make corrections and open a PR
- and tag it with the label `template`.
+ | Action | Method |
+ | ------ | ------ |
+ | Pre-authorize | `authorize/3` |
- ***Actual docs begin below this line!***
-
- --------------------------------------------------------------------------------
+ [home]: https://www.mercadopago.com/
+ [docs]: https://www.mercadopago.com.ar/developers/en/api-docs/
- > List features that have been implemented, and what "actions" they map to as
- > per the mercadopago gateway docs.
- > A table suits really well for this.
+ ## The `opts` argument
- ## Optional or extra parameters
+ Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply
+ optional arguments for transactions with the MERCADOAPGO
+ gateway. The following keys are supported:
- Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply
- optional arguments for transactions with the gateway.
-
- > List all available (ie, those that will be supported by this module) keys, a
- > description of their function/role and whether they have been implemented
- > and tested.
- > A table suits really well for this.
+ | Key | Remark |
+ | ---- | --- |
+ | `email` | Email of the customer. Type - string . |
+ | `order_id` | Order id issued by the merchant. Type- integer. |
+ | `payment_method_id` | Payment network operators, eg. `visa`, `mastercard`. Type- string. |
+ | `customer_id` | Unique customer id issued by the gateway. For new customer it must be nil. Type- string |
- ## Registering your mercadopago account at `Gringotts`
+ ## Registering your MERCADOPAGO account at `Gringotts`
- Explain how to make an account with the gateway and show how to put the
- `required_keys` (like authentication info) to the configuration.
-
- > Here's how the secrets map to the required configuration parameters for mercadopago:
- >
- > | Config parameter | mercadopago secret |
- > | ------- | ---- |
- > | `:public_key` | **PublicKey** |
- > | `:access_token` | **AccessToken** |
+ After [making an account on MERCADOPAGO][credentials], head to the credentials and find
+ your account "secrets" in the `Checkout Transparent`.
+
+ | Config parameter | MERCADOPAGO secret |
+ | ------- | ---- |
+ | `:access_token` | **Access Token** |
+ | `:public_key` | **Public Key** |
> Your Application config **must include the `[:public_key, :access_token]` field(s)** and would look
> something like this:
@@ -53,40 +42,42 @@ defmodule Gringotts.Gateways.Mercadopago do
> config :gringotts, Gringotts.Gateways.Mercadopago,
> public_key: "your_secret_public_key"
> access_token: "your_secret_access_token"
-
-
- ## Scope of this module
- > It's unlikely that your first iteration will support all features of the
- > gateway, so list down those items that are missing.
+ [credentials]: https://www.mercadopago.com/mlb/account/credentials?type=basic
+
+ * MERCADOPAGO does not process money in cents, and the `amount` is rounded to 2
+ decimal places.
+
+ > Please [raise an issue][new-issue] if you'd like us to add support for more
+ > currencies
+ [new-issue]: https://github.com/aviabird/gringotts/issues
## Supported currencies and countries
- > It's enough if you just add a link to the gateway's docs or FAQ that provide
- > info about this.
+ > MERCADOPAGO supports the currencies listed [here][all-currency-list]
+ [all-currency-list]: https://www.mercadopago.com.br/developers/en/api-docs/account/payments/search
+ :ARS, :BRL, :VEF,:CLP, :MXN, :COP, :PEN, :UYU
## Following the examples
- 1. First, set up a sample application and configure it to work with MONEI.
+ 1. First, set up a sample application and configure it to work with MERCADOPAGO.
- You could do that from scratch by following our [Getting Started][gs] guide.
- To save you time, we recommend [cloning our example
repo][example] that gives you a pre-configured sample app ready-to-go.
+ You could use the same config or update it the with your "secrets"
as described [above](#module-registering-your-monei-account-at-mercadopago).
- 2. Run an `iex` session with `iex -S mix` and add some variable bindings and
- aliases to it (to save some time):
+ 2. Run an `iex` session with `iex -S mix` and add some variable bindings :
```
- iex> alias Gringotts.{Response, CreditCard, Gateways.Mercadopago}
- iex> card = %CreditCard{first_name: "Jo",
- last_name: "Doe",
- number: "4200000000000000",
- year: 2099, month: 12,
- verification_code: "123", brand: "VISA"}
+ iex> card = %{first_name: "John",
+ last_name: "Doe",
+ number: "4509953566233704",
+ year: 2099,
+ month: 12,
+ verification_code: "123",
+ brand: "VISA"}
```
- > Add any other frequently used bindings up here.
-
We'll be using these in the examples below.
[gs]: https://github.com/aviabird/gringotts/wiki/
@@ -98,7 +89,7 @@ defmodule Gringotts.Gateways.Mercadopago do
# implementations.
@base_url "https://api.mercadopago.com"
use Gringotts.Gateways.Base
-
+ alias Gringotts.CreditCard
# The Adapter module provides the `validate_config/1`
# Add the keys that must be present in the Application config in the
# `required_config` list
@@ -106,8 +97,7 @@ defmodule Gringotts.Gateways.Mercadopago do
import Poison, only: [decode: 1]
- alias Gringotts.{Money,
- CreditCard,
+ alias Gringotts.{CreditCard,
Response}
@doc """
@@ -116,25 +106,42 @@ defmodule Gringotts.Gateways.Mercadopago do
The authorization validates the `card` details with the banking network,
places a hold on the transaction `amount` in the customer’s issuing bank.
- > ** You could perhaps:**
- > 1. describe what are the important fields in the Response struct
- > 2. mention what a merchant can do with these important fields (ex:
- > `capture/3`, etc.)
+ MERCADOPAGO's `authorize` returns a map containing authorization ID string which can be used to:
+
+ * `capture/3` _an_ amount.
+ * `void/2` a pre-authorization.
## Note
> If there's anything noteworthy about this operation, it comes here.
## Example
+ iex> amount = Money.new(42, :BRL)
+ iex> card = %Gringotts.CreditCard{
+ first_name: "John",
+ last_name: "Doe",
+ number: "4509953566233704",
+ year: 2099,
+ month: 12,
+ verification_code: "123",
+ brand: "VISA"
+ }
+ iex> opts = [email: "xyz@test.com",
+ order_id: 123125,
+ customer_id: "308537342-HStv9cJCgK0dWU",
+ payment_method_id: "visa",
+ config: %{access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
+ public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"}]
+ iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Mercadopago, amount, card, opts)
+ iex> auth_result.id # This is the authorization ID
+ iex> auth_result.token # This is the customer ID/token
- > A barebones example using the bindings you've suggested in the `moduledoc`.
"""
-
-
@spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
- def authorize(amount, card, opts) do
+ def authorize(amount , %CreditCard{} = card, opts) do
# commit(args, ...)
+ {_, value, _, _} = Money.to_integer_exp(amount)
if(is_nil(opts[:customer_id])) do
customer_id = get_customer_id(opts)
opts = opts ++ [customer_id: customer_id]
@@ -142,7 +149,7 @@ defmodule Gringotts.Gateways.Mercadopago do
token_id = get_token_id(card, opts)
opts = opts ++ [token_id: token_id]
- body = get_authorize_body(amount, card, opts, opts[:token_id], opts[:customer_id]) |> Poison.encode!()
+ body = get_authorize_body(value, card, opts, opts[:token_id], opts[:customer_id]) |> Poison.encode!()
headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
response = HTTPoison.post!("#{@base_url}/v1/payments?access_token=#{opts[:config][:access_token]}", body, headers, [])
@@ -187,7 +194,7 @@ defmodule Gringotts.Gateways.Mercadopago do
> A barebones example using the bindings you've suggested in the `moduledoc`.
"""
@spec purchase(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response}
- def purchase(amount, card = %CreditCard{}, opts) do
+ def purchase(amount, %CreditCard{} = card, opts) do
# commit(args, ...)
end
@@ -300,19 +307,19 @@ defmodule Gringotts.Gateways.Mercadopago do
body["id"]
end
- defp get_token_body(card) do
+ defp get_token_body(%CreditCard{} = card) do
%{
- "expirationYear": card[:year],
- "expirationMonth": card[:month],
- "cardNumber": card[:number],
- "securityCode": card[:verification_code],
+ "expirationYear": card.year,
+ "expirationMonth": card.month,
+ "cardNumber": card.number,
+ "securityCode": card.verification_code,
"cardholder": %{
- "name": card[:first_name] <> card[:last_name]
+ "name": card.first_name <> card.last_name
}
}
end
- defp get_token_id(card, opts) do
+ defp get_token_id(%CreditCard{} = card, opts) do
body = get_token_body(card) |> Poison.encode!()
headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
token = HTTPoison.post!("#{@base_url}/v1/card_tokens/#{opts[:customer_id]}?public_key=#{opts[:config][:public_key]}", body, headers, [])
@@ -321,20 +328,20 @@ defmodule Gringotts.Gateways.Mercadopago do
body["id"]
end
- defp get_authorize_body(amount, card, opts, token_id, customer_id) do
+ defp get_authorize_body(value, %CreditCard{} = card, opts, token_id, customer_id) do
%{
"payer": %{
"type": "customer",
"id": customer_id,
- "first_name": card[:first_name],
- "last_name": card[:last_name]
+ "first_name": card.first_name,
+ "last_name": card.last_name
},
"order": %{
"type": "mercadopago",
"id": opts[:order_id]
},
"installments": 1,
- "transaction_amount": amount,
+ "transaction_amount": value,
"payment_method_id": opts[:payment_method_id], #visa
"token": token_id,
capture: false
From d7d74b994ad6000071d6bd68bb72e51399b92a9f Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Wed, 21 Mar 2018 20:58:37 +0530
Subject: [PATCH 42/60] Implemented authorize
1. Implemented authorize
2. Completed docs
3. Used CreditCard and Money datatype
---
lib/gringotts/gateways/mercadopago.ex | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index 56042f4d..535d858a 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -140,7 +140,6 @@ defmodule Gringotts.Gateways.Mercadopago do
@spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
def authorize(amount , %CreditCard{} = card, opts) do
- # commit(args, ...)
{_, value, _, _} = Money.to_integer_exp(amount)
if(is_nil(opts[:customer_id])) do
customer_id = get_customer_id(opts)
@@ -175,7 +174,12 @@ defmodule Gringotts.Gateways.Mercadopago do
"""
@spec capture(String.t(), Money.t, keyword) :: {:ok | :error, Response}
def capture(payment_id, amount, opts) do
- # commit(args, ...)
+ body = %{"capture": true} |> Poison.encode!
+ headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
+ response = HTTPoison.put!("#{@base_url}/v1/payments/#{payment_id}?access_token=#{opts[:config][:access_token]}", body, headers, [])
+ %HTTPoison.Response{body: body, status_code: status_code} = response
+ body = body |> Poison.decode!()
+ format_response(body, status_code, opts)
end
@doc """
@@ -237,7 +241,13 @@ defmodule Gringotts.Gateways.Mercadopago do
"""
@spec refund(Money.t, String.t(), keyword) :: {:ok | :error, Response}
def refund(amount, payment_id, opts) do
- # commit(args, ...)
+ {_, value, _, _} = Money.to_integer_exp(amount)
+ body = %{"amount": value} |> Poison.encode!
+ headers = [{"content-type", "application/json"}]
+ response = HTTPoison.post!("#{@base_url}/v1/payments/#{payment_id}/refunds?access_token=#{opts[:config][:access_token]}", body, headers, [])
+ %HTTPoison.Response{body: body, status_code: status_code} = response
+ body = body |> Poison.decode!
+ format_response(body, status_code, opts)
end
@doc """
@@ -344,7 +354,7 @@ defmodule Gringotts.Gateways.Mercadopago do
"transaction_amount": value,
"payment_method_id": opts[:payment_method_id], #visa
"token": token_id,
- capture: false
+ "capture": false
}
end
From b776dc9527293357414a21ec936ed08b645af22d Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Mon, 26 Mar 2018 21:03:24 +0530
Subject: [PATCH 43/60] jgkg
---
lib/gringotts/gateways/mercadopago.ex | 324 +++++++++++++-------------
1 file changed, 168 insertions(+), 156 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index 535d858a..0fb936a9 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -1,14 +1,18 @@
defmodule Gringotts.Gateways.Mercadopago do
@moduledoc """
- [MERCADOPAGO][home] gateway implementation.
+ [mercadopago][home] gateway implementation.
- For reference see [MERCADOPAGO API (v1) documentation][docs].
+ For reference see [mercadopago documentation][docs].
- The following features of MERCADOPAGO are implemented:
+ The following features of mercadopago are implemented:
| Action | Method |
| ------ | ------ |
| Pre-authorize | `authorize/3` |
+ | Capture | `capture/3` |
+ | Purchase | `purchase/3` |
+ | Reversal | `void/2` |
+ | Refund | `refund/3` |
[home]: https://www.mercadopago.com/
[docs]: https://www.mercadopago.com.ar/developers/en/api-docs/
@@ -16,7 +20,7 @@ defmodule Gringotts.Gateways.Mercadopago do
## The `opts` argument
Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply
- optional arguments for transactions with the MERCADOAPGO
+ optional arguments for transactions with the mercadopago
gateway. The following keys are supported:
| Key | Remark |
@@ -26,9 +30,9 @@ defmodule Gringotts.Gateways.Mercadopago do
| `payment_method_id` | Payment network operators, eg. `visa`, `mastercard`. Type- string. |
| `customer_id` | Unique customer id issued by the gateway. For new customer it must be nil. Type- string |
- ## Registering your MERCADOPAGO account at `Gringotts`
+ ## Registering your mercadopago account at `Gringotts`
- After [making an account on MERCADOPAGO][credentials], head to the credentials and find
+ After [making an account on mercadopago][credentials], head to the credentials and find
your account "secrets" in the `Checkout Transparent`.
| Config parameter | MERCADOPAGO secret |
@@ -45,18 +49,13 @@ defmodule Gringotts.Gateways.Mercadopago do
[credentials]: https://www.mercadopago.com/mlb/account/credentials?type=basic
- * MERCADOPAGO does not process money in cents, and the `amount` is rounded to 2
+ * mercadopago does not process money in cents, and the `amount` is rounded to 2
decimal places.
- > Please [raise an issue][new-issue] if you'd like us to add support for more
- > currencies
- [new-issue]: https://github.com/aviabird/gringotts/issues
-
## Supported currencies and countries
- > MERCADOPAGO supports the currencies listed [here][all-currency-list]
- [all-currency-list]: https://www.mercadopago.com.br/developers/en/api-docs/account/payments/search
- :ARS, :BRL, :VEF,:CLP, :MXN, :COP, :PEN, :UYU
+ > mercadoapgo supports the currencies listed here :
+ :ARS, :BRL, :VEF, :CLP, :MXN, :COP, :PEN, :UYU
## Following the examples
@@ -69,13 +68,7 @@ defmodule Gringotts.Gateways.Mercadopago do
2. Run an `iex` session with `iex -S mix` and add some variable bindings :
```
- iex> card = %{first_name: "John",
- last_name: "Doe",
- number: "4509953566233704",
- year: 2099,
- month: 12,
- verification_code: "123",
- brand: "VISA"}
+ iex> card = %CreditCard{first_name: "John", last_name: "Doe", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
```
We'll be using these in the examples below.
@@ -97,8 +90,7 @@ defmodule Gringotts.Gateways.Mercadopago do
import Poison, only: [decode: 1]
- alias Gringotts.{CreditCard,
- Response}
+ alias Gringotts.{CreditCard, Response}
@doc """
Performs a (pre) Authorize operation.
@@ -106,32 +98,30 @@ defmodule Gringotts.Gateways.Mercadopago do
The authorization validates the `card` details with the banking network,
places a hold on the transaction `amount` in the customer’s issuing bank.
- MERCADOPAGO's `authorize` returns a map containing authorization ID string which can be used to:
+ mercadoapgo's `authorize` returns a map containing authorization ID string which can be used to:
* `capture/3` _an_ amount.
* `void/2` a pre-authorization.
-
## Note
- > If there's anything noteworthy about this operation, it comes here.
+ > For a new customer, `customer_id` field should be ignored. Otherwise it should be provided.
## Example
+
+ ### Authorization using `email`, for new customer. (Ignore `customer_id`)
+ The following example shows how one would (pre) authorize a payment of 42 BRL on a sample `card`.
iex> amount = Money.new(42, :BRL)
- iex> card = %Gringotts.CreditCard{
- first_name: "John",
- last_name: "Doe",
- number: "4509953566233704",
- year: 2099,
- month: 12,
- verification_code: "123",
- brand: "VISA"
- }
- iex> opts = [email: "xyz@test.com",
- order_id: 123125,
- customer_id: "308537342-HStv9cJCgK0dWU",
- payment_method_id: "visa",
- config: %{access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
- public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"}]
+ iex> card = %Gringotts.CreditCard{first_name: "Lord", last_name: "Voldemort", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> opts = [email: "tommarvolo@riddle.com", order_id: 123123, payment_method_id: "visa", config: %{access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510", public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"}]
+ iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Mercadopago, amount, card, opts)
+ iex> auth_result.id # This is the authorization ID
+ iex> auth_result.token # This is the customer ID/token
+
+ ### Authorization using `customer_id`, for old customer. (Mention `customer_id`)
+ The following example shows how one would (pre) authorize a payment of 42 BRL on a sample `card`.
+ iex> amount = Money.new(42, :BRL)
+ iex> card = %Gringotts.CreditCard{first_name: "Hermione", last_name: "Granger", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> opts = [email: "hermione@granger.com", order_id: 123125, customer_id: "308537342-HStv9cJCgK0dWU", payment_method_id: "visa", config: %{access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510", public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"}]
iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Mercadopago, amount, card, opts)
iex> auth_result.id # This is the authorization ID
iex> auth_result.token # This is the customer ID/token
@@ -140,22 +130,18 @@ defmodule Gringotts.Gateways.Mercadopago do
@spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
def authorize(amount , %CreditCard{} = card, opts) do
- {_, value, _, _} = Money.to_integer_exp(amount)
- if(is_nil(opts[:customer_id])) do
- customer_id = get_customer_id(opts)
- opts = opts ++ [customer_id: customer_id]
+ if Keyword.has_key?(opts, :customer_id) do
+ auth_token(amount, card, opts, opts[:customer_id], false)
+ else
+ {state, res} = get_customer_id(opts)
+ if state == :error do
+ {state, res}
+ else
+ auth_token(amount, card, opts, res, false)
+ end
end
- token_id = get_token_id(card, opts)
- opts = opts ++ [token_id: token_id]
-
- body = get_authorize_body(value, card, opts, opts[:token_id], opts[:customer_id]) |> Poison.encode!()
- headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
-
- response = HTTPoison.post!("#{@base_url}/v1/payments?access_token=#{opts[:config][:access_token]}", body, headers, [])
- %HTTPoison.Response{body: body, status_code: status_code} = response
- body = body |> Poison.decode!()
- format_response(body, status_code, opts)
end
+
@doc """
Captures a pre-authorized `amount`.
@@ -164,22 +150,31 @@ defmodule Gringotts.Gateways.Mercadopago do
pre-authorization referenced by `payment_id`.
## Note
+ mercadopago allows partial captures also. However, you can make a partial capture to a payment only **once**.
- > If there's anything noteworthy about this operation, it comes here.
- > For example, does the gateway support partial, multiple captures?
+ > The authorization will be valid for 7 days. If you do not capture it by that time, it will be cancelled.
+
+ > The specified amount can not exceed the originally reserved.
+
+ > If you do not specify the amount, all the reserved money is captured.
+
+ > In Argentina only available for Visa and American Express cards.
## Example
- > A barebones example using the bindings you've suggested in the `moduledoc`.
+ The following example shows how one would (partially) capture a previously
+ authorized a payment worth 35 BRL by referencing the obtained authorization `id`.
+
+ iex> amount = Money.new(35, :BRL)
+ iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Mercadopago, auth_result.id, amount, opts)
"""
@spec capture(String.t(), Money.t, keyword) :: {:ok | :error, Response}
def capture(payment_id, amount, opts) do
- body = %{"capture": true} |> Poison.encode!
+ {_, value, _, _} = Money.to_integer_exp(amount)
+ url = "#{@base_url}/v1/payments/#{payment_id}?access_token=#{opts[:config][:access_token]}"
+ body = %{"capture": true, "transaction_amount": value} |> Poison.encode!
headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
- response = HTTPoison.put!("#{@base_url}/v1/payments/#{payment_id}?access_token=#{opts[:config][:access_token]}", body, headers, [])
- %HTTPoison.Response{body: body, status_code: status_code} = response
- body = body |> Poison.decode!()
- format_response(body, status_code, opts)
+ commit(:put, url, body, headers) |> respond(opts)
end
@doc """
@@ -189,17 +184,29 @@ defmodule Gringotts.Gateways.Mercadopago do
debiting `amount` from the customer's account by charging the customer's
`card`.
- ## Note
+ ## Example
- > If there's anything noteworthy about this operation, it comes here.
+ The following example shows how one would process a payment worth 42 BRL in
+ one-shot, without (pre) authorization.
- ## Example
+ iex> amount = Money.new(42, :BRL)
+ iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.Mercadopago, amount, card, opts)
+ iex> purchase_result.token # This is the customer ID/token
- > A barebones example using the bindings you've suggested in the `moduledoc`.
"""
@spec purchase(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response}
def purchase(amount, %CreditCard{} = card, opts) do
- # commit(args, ...)
+ if Keyword.has_key?(opts, :customer_id) do
+ auth_token(amount, card, opts, opts[:customer_id], true)
+ else
+ {state, res} = get_customer_id(opts)
+ if state == :error do
+ {state, res}
+ else
+ auth_token(amount, card, opts, res, true)
+ end
+ end
end
@doc """
@@ -212,79 +219,60 @@ defmodule Gringotts.Gateways.Mercadopago do
## Note
- > Which transactions can be voided?
- > Is there a limited time window within which a void can be perfomed?
+ > Only pending or in_process payments can be cancelled.
+
+ > Cancelled coupon payments, deposits and/or transfers will be deposited in the buyer’s Mercadopago account.
## Example
- > A barebones example using the bindings you've suggested in the `moduledoc`.
+ The following example shows how one would void a previous (pre)
+ authorization. Remember that our `capture/3` example only did a partial
+ capture.
+
+ iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.Mercadopago, auth_result.id, opts)
+
"""
@spec void(String.t(), keyword) :: {:ok | :error, Response}
def void(payment_id, opts) do
- # commit(args, ...)
+ url = "#{@base_url}/v1/payments/#{payment_id}?access_token=#{opts[:config][:access_token]}"
+ headers = [{"content-type", "application/json"}]
+ body = %{"status": "cancelled"} |> Poison.encode!
+ commit(:put, url, body, headers) |> respond(opts)
end
@doc """
Refunds the `amount` to the customer's account with reference to a prior transfer.
- > Refunds are allowed on which kinds of "prior" transactions?
+ mercadopago processes a full or partial refund worth `amount`, referencing a
+ previous `purchase/3` or `capture/3`.
## Note
- > The end customer will usually see two bookings/records on his statement. Is
- > that true for mercadopago?
- > Is there a limited time window within which a void can be perfomed?
+ > You must have enough available money in your account so you can refund the payment amount successfully. Otherwise, you'll get a 400 Bad Request error.
+
+ > You can refund a payment within 90 days after it was accredited.
+
+ > You can only refund approved payments.
+
+ > You can perform up to 20 partial refunds in one payment.
## Example
- > A barebones example using the bindings you've suggested in the `moduledoc`.
+ The following example shows how one would (completely) refund a previous
+ purchase (and similarily for captures).
+
+ iex> amount = Money.new(35, :BRL)
+ iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.Mercadopago, purchase_result.id, amount)
"""
@spec refund(Money.t, String.t(), keyword) :: {:ok | :error, Response}
- def refund(amount, payment_id, opts) do
+ def refund(payment_id, amount, opts) do
{_, value, _, _} = Money.to_integer_exp(amount)
+ url = "#{@base_url}/v1/payments/#{payment_id}/refunds?access_token=#{opts[:config][:access_token]}"
body = %{"amount": value} |> Poison.encode!
headers = [{"content-type", "application/json"}]
- response = HTTPoison.post!("#{@base_url}/v1/payments/#{payment_id}/refunds?access_token=#{opts[:config][:access_token]}", body, headers, [])
- %HTTPoison.Response{body: body, status_code: status_code} = response
- body = body |> Poison.decode!
- format_response(body, status_code, opts)
+ commit(:post, url, body, headers) |> respond(opts)
end
- @doc """
- Stores the payment-source data for later use.
-
- > This usually enable "One Click" and/or "Recurring Payments"
-
- ## Note
-
- > If there's anything noteworthy about this operation, it comes here.
-
- ## Example
-
- > A barebones example using the bindings you've suggested in the `moduledoc`.
- """
- @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response}
- def store(%CreditCard{} = card, opts) do
- # commit(args, ...)
- end
-
- @doc """
- Removes card or payment info that was previously `store/2`d
-
- Deletes previously stored payment-source data.
-
- ## Note
-
- > If there's anything noteworthy about this operation, it comes here.
-
- ## Example
-
- > A barebones example using the bindings you've suggested in the `moduledoc`.
- """
- @spec unstore(String.t(), keyword) :: {:ok | :error, Response}
- def unstore(registration_id, opts) do
- # commit(args, ...)
- end
###############################################################################
# PRIVATE METHODS #
@@ -294,27 +282,44 @@ defmodule Gringotts.Gateways.Mercadopago do
# For consistency with other gateway implementations, make your (final)
# network request in here, and parse it using another private method called
# `respond`.
- @spec commit(_) :: {:ok | :error, Response}
- defp commit(_) do
- # resp = HTTPoison.request(args, ...)
- # respond(resp, ...)
+ #@spec commit(_, _, _, _) :: {:ok | :error, Response}
+ defp commit(method, path, body, headers) do
+ HTTPoison.request(method, path, body, headers, [])
end
# Parses mercadopago's response and returns a `Gringotts.Response` struct
# in a `:ok`, `:error` tuple.
- @spec respond(term) :: {:ok | :error, Response}
- defp respond(mercadopago_response)
- defp respond({:ok, %{status_code: 200, body: body}}), do: "something"
- defp respond({:ok, %{status_code: status_code, body: body}}), do: "something"
- defp respond({:error, %HTTPoison.Error{} = error}), do: "something"
+ defp auth_token(amount, %CreditCard{} = card, opts, customer_id, capture) do
+ opts = opts ++ [customer_id: customer_id]
+ {state, res} = get_token_id(card, opts)
+ if state == :error do
+ {state, res}
+ else
+ auth(amount, card, opts, res, capture)
+ end
+ end
+
+ defp auth(amount, %CreditCard{} = card, opts, token_id, capture) do
+ {_, value, _, _} = Money.to_integer_exp(amount)
+ opts = opts ++ [token_id: token_id]
+ url = "#{@base_url}/v1/payments?access_token=#{opts[:config][:access_token]}"
+ body = get_authorize_body(value, card, opts, opts[:token_id], opts[:customer_id], capture) |> Poison.encode!
+ headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
+ commit(:post, url, body, headers)
+ |> respond(opts)
+ end
+
defp get_customer_id(opts) do
+ url = "#{@base_url}/v1/customers?access_token=#{opts[:config][:access_token]}"
body = %{"email": opts[:email]} |> Poison.encode!
headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
- response = HTTPoison.post!("#{@base_url}/v1/customers?access_token=#{opts[:config][:access_token]}", body, headers, [])
- %HTTPoison.Response{body: body} = response
- body = body |> Poison.decode!()
- body["id"]
+ {state, res} = commit(:post, url, body, headers) |> respond(opts)
+ if state == :error do
+ {state, res}
+ else
+ {state, res.id}
+ end
end
defp get_token_body(%CreditCard{} = card) do
@@ -323,64 +328,71 @@ defmodule Gringotts.Gateways.Mercadopago do
"expirationMonth": card.month,
"cardNumber": card.number,
"securityCode": card.verification_code,
- "cardholder": %{
- "name": card.first_name <> card.last_name
- }
+ "cardholder": %{"name": CreditCard.full_name(card)}
}
- end
+ end
defp get_token_id(%CreditCard{} = card, opts) do
+ url = "#{@base_url}/v1/card_tokens/#{opts[:customer_id]}?public_key=#{opts[:config][:public_key]}"
body = get_token_body(card) |> Poison.encode!()
headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
- token = HTTPoison.post!("#{@base_url}/v1/card_tokens/#{opts[:customer_id]}?public_key=#{opts[:config][:public_key]}", body, headers, [])
- %HTTPoison.Response{body: body} = token
- body = body |> Poison.decode!()
- body["id"]
+ {state, res} = commit(:post, url, body, headers) |> respond(opts)
+ if state == :error do
+ {state, res}
+ else
+ {state, res.id}
+ end
end
- defp get_authorize_body(value, %CreditCard{} = card, opts, token_id, customer_id) do
+ defp get_authorize_body(value, %CreditCard{} = card, opts, token_id, customer_id, capture) do
%{
"payer": %{
"type": "customer",
"id": customer_id,
"first_name": card.first_name,
"last_name": card.last_name
- },
+ },
"order": %{
"type": "mercadopago",
"id": opts[:order_id]
- },
+ },
"installments": 1,
"transaction_amount": value,
- "payment_method_id": opts[:payment_method_id], #visa
+ "payment_method_id": opts[:payment_method_id],
"token": token_id,
- "capture": false
- }
+ "capture": capture
+ }
end
defp get_success_body(body, status_code, opts) do
%Response{
- success: true,
- id: body["id"],
- token: opts[:customer_id],
- status_code: status_code,
- message: body["status"]
- }
+ success: true,
+ id: body["id"],
+ token: opts[:customer_id],
+ status_code: status_code,
+ message: body["status"]
+ }
end
+
defp get_error_body(body, status_code, opts) do
%Response{
- success: false,
- token: opts[:customer_id],
- status_code: status_code,
- message: body["message"]
+ success: false,
+ token: opts[:customer_id],
+ status_code: status_code,
+ message: body["message"]
}
end
- defp format_response(body, status_code, opts) do
+ defp respond({:ok, %HTTPoison.Response{body: body, status_code: status_code}}, opts) do
+ body = body |> Poison.decode!
case body["cause"] do
nil -> {:ok, get_success_body(body, status_code, opts)}
_ -> {:error, get_error_body(body, status_code, opts)}
end
end
-end
+ defp respond({:error, %HTTPoison.Error{id: _, reason: _}}, opts) do
+ {:error, "Network Error"}
+ end
+
+end
\ No newline at end of file
From 4a99128e411288b64b4a59f353095dfb63e0c478 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Mon, 26 Mar 2018 21:10:42 +0530
Subject: [PATCH 44/60] Modification in authorize/3, and implemented capture
1. Used commit for HTTPoison requests.
2. Handled network and arguement errors.
3. Updated docs
---
lib/gringotts/gateways/mercadopago.ex | 94 ---------------------------
1 file changed, 94 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index 0fb936a9..cb58b120 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -177,101 +177,7 @@ defmodule Gringotts.Gateways.Mercadopago do
commit(:put, url, body, headers) |> respond(opts)
end
- @doc """
- Transfers `amount` from the customer to the merchant.
-
- mercadopago attempts to process a purchase on behalf of the customer, by
- debiting `amount` from the customer's account by charging the customer's
- `card`.
-
- ## Example
-
- The following example shows how one would process a payment worth 42 BRL in
- one-shot, without (pre) authorization.
-
- iex> amount = Money.new(42, :BRL)
- iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.Mercadopago, amount, card, opts)
- iex> purchase_result.token # This is the customer ID/token
-
- """
- @spec purchase(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response}
- def purchase(amount, %CreditCard{} = card, opts) do
- if Keyword.has_key?(opts, :customer_id) do
- auth_token(amount, card, opts, opts[:customer_id], true)
- else
- {state, res} = get_customer_id(opts)
- if state == :error do
- {state, res}
- else
- auth_token(amount, card, opts, res, true)
- end
- end
- end
-
- @doc """
- Voids the referenced payment.
-
- This method attempts a reversal of a previous transaction referenced by
- `payment_id`.
-
- > As a consequence, the customer will never see any booking on his statement.
-
- ## Note
-
- > Only pending or in_process payments can be cancelled.
-
- > Cancelled coupon payments, deposits and/or transfers will be deposited in the buyer’s Mercadopago account.
-
- ## Example
-
- The following example shows how one would void a previous (pre)
- authorization. Remember that our `capture/3` example only did a partial
- capture.
-
- iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.Mercadopago, auth_result.id, opts)
- """
- @spec void(String.t(), keyword) :: {:ok | :error, Response}
- def void(payment_id, opts) do
- url = "#{@base_url}/v1/payments/#{payment_id}?access_token=#{opts[:config][:access_token]}"
- headers = [{"content-type", "application/json"}]
- body = %{"status": "cancelled"} |> Poison.encode!
- commit(:put, url, body, headers) |> respond(opts)
- end
-
- @doc """
- Refunds the `amount` to the customer's account with reference to a prior transfer.
-
- mercadopago processes a full or partial refund worth `amount`, referencing a
- previous `purchase/3` or `capture/3`.
-
- ## Note
-
- > You must have enough available money in your account so you can refund the payment amount successfully. Otherwise, you'll get a 400 Bad Request error.
-
- > You can refund a payment within 90 days after it was accredited.
-
- > You can only refund approved payments.
-
- > You can perform up to 20 partial refunds in one payment.
-
- ## Example
-
- The following example shows how one would (completely) refund a previous
- purchase (and similarily for captures).
-
- iex> amount = Money.new(35, :BRL)
- iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.Mercadopago, purchase_result.id, amount)
- """
- @spec refund(Money.t, String.t(), keyword) :: {:ok | :error, Response}
- def refund(payment_id, amount, opts) do
- {_, value, _, _} = Money.to_integer_exp(amount)
- url = "#{@base_url}/v1/payments/#{payment_id}/refunds?access_token=#{opts[:config][:access_token]}"
- body = %{"amount": value} |> Poison.encode!
- headers = [{"content-type", "application/json"}]
- commit(:post, url, body, headers) |> respond(opts)
- end
###############################################################################
From a6b42982c8346668bea9e8a4a2fbdff56dd5bf8e Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Mon, 26 Mar 2018 21:28:08 +0530
Subject: [PATCH 45/60] Modification in authorize/3, and implemented capture
1. Used commit for HTTPoison requests.
2. Handled network and arguement errors.
3. Updated docs
---
lib/gringotts/gateways/mercadopago.ex | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index cb58b120..e9e6e231 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -180,6 +180,9 @@ defmodule Gringotts.Gateways.Mercadopago do
+
+
+
###############################################################################
# PRIVATE METHODS #
###############################################################################
From 94f5b1c4b2a8cc7a50da7649c459f56d201093c5 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Mon, 26 Mar 2018 21:32:07 +0530
Subject: [PATCH 46/60] Modification in authorize/3, and implemented capture
1. Used commit for HTTPoison requests.
2. Handled network and arguement errors.
3. Updated docs
---
lib/gringotts/gateways/mercadopago.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index e9e6e231..38b2267f 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -64,7 +64,7 @@ defmodule Gringotts.Gateways.Mercadopago do
- To save you time, we recommend [cloning our example
repo][example] that gives you a pre-configured sample app ready-to-go.
+ You could use the same config or update it the with your "secrets"
- as described [above](#module-registering-your-monei-account-at-mercadopago).
+ as described [above](#module-registering-your-mercadopago-account-at-mercadopago).
2. Run an `iex` session with `iex -S mix` and add some variable bindings :
```
From 1d46184c6332734c1f92ea3a42c42fa9f16360ee Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Mon, 26 Mar 2018 21:38:51 +0530
Subject: [PATCH 47/60] Modification in authorize/3, and implemented capture
1. Used commit for HTTPoison requests.
2. Handled network and arguement errors.
3. Updated docs
---
lib/gringotts/gateways/mercadopago.ex | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index 38b2267f..50c0170a 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -70,6 +70,7 @@ defmodule Gringotts.Gateways.Mercadopago do
```
iex> card = %CreditCard{first_name: "John", last_name: "Doe", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
```
+
We'll be using these in the examples below.
From 5956d5b7edf9bd5fa9c18764875960b9f141e9cc Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Wed, 28 Mar 2018 00:02:30 +0530
Subject: [PATCH 48/60] Updated authorize/3 1. functions are renamed 2. if else
replaced with case 3. docs updated
---
lib/gringotts/gateways/mercadopago.ex | 94 ++++++++-------------------
1 file changed, 26 insertions(+), 68 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index 50c0170a..dddfe791 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -49,13 +49,11 @@ defmodule Gringotts.Gateways.Mercadopago do
[credentials]: https://www.mercadopago.com/mlb/account/credentials?type=basic
- * mercadopago does not process money in cents, and the `amount` is rounded to 2
- decimal places.
-
## Supported currencies and countries
- > mercadoapgo supports the currencies listed here :
- :ARS, :BRL, :VEF, :CLP, :MXN, :COP, :PEN, :UYU
+ mercadopago supports the currencies listed [here][currencies].
+
+ [currencies]: https://api.mercadopago.com/currencies
## Following the examples
@@ -64,7 +62,7 @@ defmodule Gringotts.Gateways.Mercadopago do
- To save you time, we recommend [cloning our example
repo][example] that gives you a pre-configured sample app ready-to-go.
+ You could use the same config or update it the with your "secrets"
- as described [above](#module-registering-your-mercadopago-account-at-mercadopago).
+ as described [above](#module-registering-your-mercadopago-account-at-gringotts).
2. Run an `iex` session with `iex -S mix` and add some variable bindings :
```
@@ -99,30 +97,30 @@ defmodule Gringotts.Gateways.Mercadopago do
The authorization validates the `card` details with the banking network,
places a hold on the transaction `amount` in the customer’s issuing bank.
- mercadoapgo's `authorize` returns a map containing authorization ID string which can be used to:
+ mercadoapgo's `authorize` returns authorization ID :
* `capture/3` _an_ amount.
* `void/2` a pre-authorization.
## Note
- > For a new customer, `customer_id` field should be ignored. Otherwise it should be provided.
+ For a new customer, `customer_id` field should be ignored. Otherwise it should be provided.
## Example
- ### Authorization using `email`, for new customer. (Ignore `customer_id`)
+ ### Authorization for new customer. (Ignore `customer_id`)
The following example shows how one would (pre) authorize a payment of 42 BRL on a sample `card`.
iex> amount = Money.new(42, :BRL)
iex> card = %Gringotts.CreditCard{first_name: "Lord", last_name: "Voldemort", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> opts = [email: "tommarvolo@riddle.com", order_id: 123123, payment_method_id: "visa", config: %{access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510", public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"}]
+ iex> opts = [email: "tommarvolo@riddle.com", order_id: 123123, payment_method_id: "visa", config: %{access_token: YOUR_ACCESS_TOKEN, public_key: YOUR_PUBLIC_KEY}]
iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Mercadopago, amount, card, opts)
iex> auth_result.id # This is the authorization ID
iex> auth_result.token # This is the customer ID/token
- ### Authorization using `customer_id`, for old customer. (Mention `customer_id`)
+ ### Authorization for old customer. (Mention `customer_id`)
The following example shows how one would (pre) authorize a payment of 42 BRL on a sample `card`.
iex> amount = Money.new(42, :BRL)
iex> card = %Gringotts.CreditCard{first_name: "Hermione", last_name: "Granger", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> opts = [email: "hermione@granger.com", order_id: 123125, customer_id: "308537342-HStv9cJCgK0dWU", payment_method_id: "visa", config: %{access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510", public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"}]
+ iex> opts = [email: "hermione@granger.com", order_id: 123125, customer_id: "308537342-HStv9cJCgK0dWU", payment_method_id: "visa", config: %{access_token: YOUR_ACCESS_TOKEN, public_key: YOUR_PUBLIC_KEY}]
iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Mercadopago, amount, card, opts)
iex> auth_result.id # This is the authorization ID
iex> auth_result.token # This is the customer ID/token
@@ -134,56 +132,16 @@ defmodule Gringotts.Gateways.Mercadopago do
if Keyword.has_key?(opts, :customer_id) do
auth_token(amount, card, opts, opts[:customer_id], false)
else
- {state, res} = get_customer_id(opts)
- if state == :error do
- {state, res}
- else
- auth_token(amount, card, opts, res, false)
+ case create_customer(opts) do
+ {:error, res} -> {:error, res}
+ {:ok, customer_id} -> auth_token(amount, card, opts, customer_id, false)
end
end
end
- @doc """
- Captures a pre-authorized `amount`.
-
- `amount` is transferred to the merchant account by mercadopago used in the
- pre-authorization referenced by `payment_id`.
-
- ## Note
- mercadopago allows partial captures also. However, you can make a partial capture to a payment only **once**.
-
- > The authorization will be valid for 7 days. If you do not capture it by that time, it will be cancelled.
-
- > The specified amount can not exceed the originally reserved.
-
- > If you do not specify the amount, all the reserved money is captured.
-
- > In Argentina only available for Visa and American Express cards.
-
- ## Example
-
- The following example shows how one would (partially) capture a previously
- authorized a payment worth 35 BRL by referencing the obtained authorization `id`.
-
- iex> amount = Money.new(35, :BRL)
- iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Mercadopago, auth_result.id, amount, opts)
- """
- @spec capture(String.t(), Money.t, keyword) :: {:ok | :error, Response}
- def capture(payment_id, amount, opts) do
- {_, value, _, _} = Money.to_integer_exp(amount)
- url = "#{@base_url}/v1/payments/#{payment_id}?access_token=#{opts[:config][:access_token]}"
- body = %{"capture": true, "transaction_amount": value} |> Poison.encode!
- headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
- commit(:put, url, body, headers) |> respond(opts)
- end
-
-
-
-
-
###############################################################################
# PRIVATE METHODS #
###############################################################################
@@ -202,7 +160,7 @@ defmodule Gringotts.Gateways.Mercadopago do
defp auth_token(amount, %CreditCard{} = card, opts, customer_id, capture) do
opts = opts ++ [customer_id: customer_id]
- {state, res} = get_token_id(card, opts)
+ {state, res} = create_token(card, opts)
if state == :error do
{state, res}
else
@@ -214,13 +172,13 @@ defmodule Gringotts.Gateways.Mercadopago do
{_, value, _, _} = Money.to_integer_exp(amount)
opts = opts ++ [token_id: token_id]
url = "#{@base_url}/v1/payments?access_token=#{opts[:config][:access_token]}"
- body = get_authorize_body(value, card, opts, opts[:token_id], opts[:customer_id], capture) |> Poison.encode!
+ body = authorize_params(value, card, opts, opts[:token_id], opts[:customer_id], capture) |> Poison.encode!
headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
commit(:post, url, body, headers)
|> respond(opts)
end
- defp get_customer_id(opts) do
+ defp create_customer(opts) do
url = "#{@base_url}/v1/customers?access_token=#{opts[:config][:access_token]}"
body = %{"email": opts[:email]} |> Poison.encode!
headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
@@ -232,7 +190,7 @@ defmodule Gringotts.Gateways.Mercadopago do
end
end
- defp get_token_body(%CreditCard{} = card) do
+ defp token_params(%CreditCard{} = card) do
%{
"expirationYear": card.year,
"expirationMonth": card.month,
@@ -242,19 +200,18 @@ defmodule Gringotts.Gateways.Mercadopago do
}
end
- defp get_token_id(%CreditCard{} = card, opts) do
+ defp create_token(%CreditCard{} = card, opts) do
url = "#{@base_url}/v1/card_tokens/#{opts[:customer_id]}?public_key=#{opts[:config][:public_key]}"
- body = get_token_body(card) |> Poison.encode!()
+ body = token_params(card) |> Poison.encode!()
headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
{state, res} = commit(:post, url, body, headers) |> respond(opts)
- if state == :error do
- {state, res}
- else
- {state, res.id}
+ case state do
+ :error -> {state, res}
+ _ -> {state, res.id}
end
end
- defp get_authorize_body(value, %CreditCard{} = card, opts, token_id, customer_id, capture) do
+ defp authorize_params(value, %CreditCard{} = card, opts, token_id, customer_id, capture) do
%{
"payer": %{
"type": "customer",
@@ -266,7 +223,7 @@ defmodule Gringotts.Gateways.Mercadopago do
"type": "mercadopago",
"id": opts[:order_id]
},
- "installments": 1,
+ "installments": opts[:installments],
"transaction_amount": value,
"payment_method_id": opts[:payment_method_id],
"token": token_id,
@@ -305,4 +262,5 @@ defmodule Gringotts.Gateways.Mercadopago do
{:error, "Network Error"}
end
-end
\ No newline at end of file
+end
+
From fd135db986c13df96eac272d00aac86c6e443b1e Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Wed, 28 Mar 2018 00:32:21 +0530
Subject: [PATCH 49/60] Updated authorize/3 1. changed if else to case 2.
changed function name 3. updated docs
---
lib/gringotts/gateways/mercadopago.ex | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index dddfe791..3f64a3e5 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -231,7 +231,7 @@ defmodule Gringotts.Gateways.Mercadopago do
}
end
- defp get_success_body(body, status_code, opts) do
+ defp success_body(body, status_code, opts) do
%Response{
success: true,
id: body["id"],
@@ -241,7 +241,7 @@ defmodule Gringotts.Gateways.Mercadopago do
}
end
- defp get_error_body(body, status_code, opts) do
+ defp error_body(body, status_code, opts) do
%Response{
success: false,
token: opts[:customer_id],
@@ -253,8 +253,8 @@ defmodule Gringotts.Gateways.Mercadopago do
defp respond({:ok, %HTTPoison.Response{body: body, status_code: status_code}}, opts) do
body = body |> Poison.decode!
case body["cause"] do
- nil -> {:ok, get_success_body(body, status_code, opts)}
- _ -> {:error, get_error_body(body, status_code, opts)}
+ nil -> {:ok, success_body(body, status_code, opts)}
+ _ -> {:error, error_body(body, status_code, opts)}
end
end
From e7349da379cd6da1b9d5da20d3c99e69d88c5257 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Thu, 29 Mar 2018 03:06:19 +0530
Subject: [PATCH 50/60] Updated authorize/3
1. Added default value of `installments` in function `authorize_params/6` as 1.
2. Added `headers` and `respond`function call in commit function itself.
3. Used `reason` in `respond` function, in case of network error.
4. Removed block quotes '>'.
5. Added money processing note.
---
lib/gringotts/gateways/mercadopago.ex | 47 +++++++++++++++------------
1 file changed, 26 insertions(+), 21 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index 3f64a3e5..1b2bf198 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -49,6 +49,10 @@ defmodule Gringotts.Gateways.Mercadopago do
[credentials]: https://www.mercadopago.com/mlb/account/credentials?type=basic
+ ## Note
+
+ mercadopago processes money with upto two decimal places.
+
## Supported currencies and countries
mercadopago supports the currencies listed [here][currencies].
@@ -68,7 +72,6 @@ defmodule Gringotts.Gateways.Mercadopago do
```
iex> card = %CreditCard{first_name: "John", last_name: "Doe", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
```
-
We'll be using these in the examples below.
@@ -97,7 +100,7 @@ defmodule Gringotts.Gateways.Mercadopago do
The authorization validates the `card` details with the banking network,
places a hold on the transaction `amount` in the customer’s issuing bank.
- mercadoapgo's `authorize` returns authorization ID :
+ mercadoapgo's `authorize` returns authorization ID(available in the `Response.id` field) :
* `capture/3` _an_ amount.
* `void/2` a pre-authorization.
@@ -107,8 +110,9 @@ defmodule Gringotts.Gateways.Mercadopago do
## Example
- ### Authorization for new customer. (Ignore `customer_id`)
+ ### Authorization for new customer.
The following example shows how one would (pre) authorize a payment of 42 BRL on a sample `card`.
+ Ignore `customer_id`.
iex> amount = Money.new(42, :BRL)
iex> card = %Gringotts.CreditCard{first_name: "Lord", last_name: "Voldemort", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> opts = [email: "tommarvolo@riddle.com", order_id: 123123, payment_method_id: "visa", config: %{access_token: YOUR_ACCESS_TOKEN, public_key: YOUR_PUBLIC_KEY}]
@@ -116,8 +120,9 @@ defmodule Gringotts.Gateways.Mercadopago do
iex> auth_result.id # This is the authorization ID
iex> auth_result.token # This is the customer ID/token
- ### Authorization for old customer. (Mention `customer_id`)
+ ### Authorization for old customer.
The following example shows how one would (pre) authorize a payment of 42 BRL on a sample `card`.
+ Mention `customer_id`.
iex> amount = Money.new(42, :BRL)
iex> card = %Gringotts.CreditCard{first_name: "Hermione", last_name: "Granger", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
iex> opts = [email: "hermione@granger.com", order_id: 123125, customer_id: "308537342-HStv9cJCgK0dWU", payment_method_id: "visa", config: %{access_token: YOUR_ACCESS_TOKEN, public_key: YOUR_PUBLIC_KEY}]
@@ -138,9 +143,6 @@ defmodule Gringotts.Gateways.Mercadopago do
end
end
end
-
-
-
###############################################################################
# PRIVATE METHODS #
@@ -151,8 +153,9 @@ defmodule Gringotts.Gateways.Mercadopago do
# network request in here, and parse it using another private method called
# `respond`.
#@spec commit(_, _, _, _) :: {:ok | :error, Response}
- defp commit(method, path, body, headers) do
- HTTPoison.request(method, path, body, headers, [])
+ defp commit(method, path, body, opts) do
+ headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
+ HTTPoison.request(method, path, body, headers, []) |> respond(opts)
end
# Parses mercadopago's response and returns a `Gringotts.Response` struct
@@ -173,16 +176,13 @@ defmodule Gringotts.Gateways.Mercadopago do
opts = opts ++ [token_id: token_id]
url = "#{@base_url}/v1/payments?access_token=#{opts[:config][:access_token]}"
body = authorize_params(value, card, opts, opts[:token_id], opts[:customer_id], capture) |> Poison.encode!
- headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
- commit(:post, url, body, headers)
- |> respond(opts)
+ commit(:post, url, body, opts)
end
defp create_customer(opts) do
url = "#{@base_url}/v1/customers?access_token=#{opts[:config][:access_token]}"
body = %{"email": opts[:email]} |> Poison.encode!
- headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
- {state, res} = commit(:post, url, body, headers) |> respond(opts)
+ {state, res} = commit(:post, url, body, opts)
if state == :error do
{state, res}
else
@@ -202,9 +202,8 @@ defmodule Gringotts.Gateways.Mercadopago do
defp create_token(%CreditCard{} = card, opts) do
url = "#{@base_url}/v1/card_tokens/#{opts[:customer_id]}?public_key=#{opts[:config][:public_key]}"
- body = token_params(card) |> Poison.encode!()
- headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
- {state, res} = commit(:post, url, body, headers) |> respond(opts)
+ body = token_params(card) |> Poison.encode!
+ {state, res} = commit(:post, url, body, opts)
case state do
:error -> {state, res}
_ -> {state, res.id}
@@ -223,9 +222,9 @@ defmodule Gringotts.Gateways.Mercadopago do
"type": "mercadopago",
"id": opts[:order_id]
},
- "installments": opts[:installments],
+ "installments": opts[:installments] || 1,
"transaction_amount": value,
- "payment_method_id": opts[:payment_method_id],
+ "payment_method_id": String.downcase(card.brand),
"token": token_id,
"capture": capture
}
@@ -258,8 +257,14 @@ defmodule Gringotts.Gateways.Mercadopago do
end
end
- defp respond({:error, %HTTPoison.Error{id: _, reason: _}}, opts) do
- {:error, "Network Error"}
+ defp respond({:error, %HTTPoison.Error{} = error}, opts) do
+ {
+ :error,
+ Response.error(
+ reason: "network related failure",
+ message: "HTTPoison says '#{error.reason}' [ID: #{error.id || "nil"}]"
+ )
+ }
end
end
From 5eabe3438bec35d55ad0e743dc0c297a2b35a024 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Sat, 31 Mar 2018 14:01:41 +0530
Subject: [PATCH 51/60] Updated authorize/3
1. Removed unnecessary `opts` from arguements of error `respond` function.
2. Used fifth arguement in `HTTPoison.Request` to send part of url as keyword list.
3. Added @spec for `commit` function.
---
lib/gringotts/gateways/mercadopago.ex | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index 1b2bf198..aa55c50a 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -152,10 +152,10 @@ defmodule Gringotts.Gateways.Mercadopago do
# For consistency with other gateway implementations, make your (final)
# network request in here, and parse it using another private method called
# `respond`.
- #@spec commit(_, _, _, _) :: {:ok | :error, Response}
- defp commit(method, path, body, opts) do
+ @spec commit(atom, String.t(), String.t(), keyword, keyword) :: {:ok | :error, Response.t()}
+ defp commit(method, path, body, opts, url_params) do
headers = [{"content-type", "application/json"}, {"accept", "application/json"}]
- HTTPoison.request(method, path, body, headers, []) |> respond(opts)
+ HTTPoison.request(method, "#{@base_url}#{path}", body, headers, url_params) |> respond(opts)
end
# Parses mercadopago's response and returns a `Gringotts.Response` struct
@@ -174,15 +174,16 @@ defmodule Gringotts.Gateways.Mercadopago do
defp auth(amount, %CreditCard{} = card, opts, token_id, capture) do
{_, value, _, _} = Money.to_integer_exp(amount)
opts = opts ++ [token_id: token_id]
- url = "#{@base_url}/v1/payments?access_token=#{opts[:config][:access_token]}"
+ url_params = [access_token: opts[:config][:access_token]]
body = authorize_params(value, card, opts, opts[:token_id], opts[:customer_id], capture) |> Poison.encode!
- commit(:post, url, body, opts)
+ commit(:post, "/v1/payments", body, opts, params: url_params )
end
defp create_customer(opts) do
- url = "#{@base_url}/v1/customers?access_token=#{opts[:config][:access_token]}"
+ url_params = [access_token: opts[:config][:access_token]]
body = %{"email": opts[:email]} |> Poison.encode!
- {state, res} = commit(:post, url, body, opts)
+
+ {state, res} = commit(:post, "/v1/customers", body, opts, params: url_params)
if state == :error do
{state, res}
else
@@ -201,9 +202,9 @@ defmodule Gringotts.Gateways.Mercadopago do
end
defp create_token(%CreditCard{} = card, opts) do
- url = "#{@base_url}/v1/card_tokens/#{opts[:customer_id]}?public_key=#{opts[:config][:public_key]}"
+ url_params = [public_key: opts[:config][:public_key]]
body = token_params(card) |> Poison.encode!
- {state, res} = commit(:post, url, body, opts)
+ {state, res} = commit(:post, "/v1/card_tokens/#{opts[:customer_id]}", body, opts, params: url_params)
case state do
:error -> {state, res}
_ -> {state, res.id}
@@ -257,7 +258,7 @@ defmodule Gringotts.Gateways.Mercadopago do
end
end
- defp respond({:error, %HTTPoison.Error{} = error}, opts) do
+ defp respond({:error, %HTTPoison.Error{} = error}, _) do
{
:error,
Response.error(
From b51e7f16de9371f3802d833458eddf4cd69fd0ab Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Sat, 31 Mar 2018 20:58:59 +0530
Subject: [PATCH 52/60] Authorize integration test cases created
---
.../integration/gateways/mercadopago_test.exs | 137 +++++++++++++++---
1 file changed, 118 insertions(+), 19 deletions(-)
diff --git a/test/integration/gateways/mercadopago_test.exs b/test/integration/gateways/mercadopago_test.exs
index 8d60dd1a..7cb658a9 100644
--- a/test/integration/gateways/mercadopago_test.exs
+++ b/test/integration/gateways/mercadopago_test.exs
@@ -2,35 +2,134 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
# Integration tests for the Mercadopago
use ExUnit.Case, async: false
- alias Gringotts.Gateways.Mercadopago
+ use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
+ alias Gringotts.Gateways.Mercadopago, as: Gateway
@moduletag :integration
- setup_all do
- Application.put_env(:gringotts, Gringotts.Gateways.Mercadopago,
- [
- public_key: "your_secret_public_key",
- access_token: "your_secret_access_token"
- ]
- )
- end
+ @amount Money.new(45, :BRL)
+ @sub_amount Money.new(30, :BRL)
+ @good_card %Gringotts.CreditCard{
+ first_name: "Hermoine",
+ last_name: "Granger",
+ number: "4509953566233704",
+ year: 2030,
+ month: 07,
+ verification_code: "123",
+ brand: "VISA"
+ }
+
+ @bad_card %Gringotts.CreditCard{
+ first_name: "Hermoine",
+ last_name: "Granger",
+ number: "4509953566233704",
+ year: 2000,
+ month: 07,
+ verification_code: "123",
+ brand: "VISA"
+ }
+
+ @good_opts [email: "hermoine@granger.com",
+ order_id: 123126,
+ customer_id: "311211654-YrXF6J0QikpIWX",
+ config: [access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
+ public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
+ installments: 1
+ ]
+ @new_cutomer_good_opts [order_id: 123126,
+ config: [access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
+ public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
+ installments: 1
+ ]
+ @new_cutomer_bad_opts [order_id: 123126,
+ config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
+ installments: 1
+ ]
+ @bad_opts [email: "hermoine@granger.com",
+ order_id: 123126,
+ customer_id: "311211654-YrXF6J0QikpIWX",
+ config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
+ installments: 1
+ ]
+
# Group the test cases by public api
- describe "purchase" do
- end
- describe "authorize" do
+ def new_email_opts(good) do
+ no1 = :rand.uniform(1_000_00) |> to_string
+ no2 = :rand.uniform(1_000_00) |> to_string
+ no3 = :rand.uniform(1_000_00) |> to_string
+ email = "hp" <> no1 <> no2 <> no3 <> "@potter.com"
+ case good do
+ true -> @new_cutomer_good_opts ++ [email: email]
+ _ -> @new_cutomer_bad_opts ++ [email: email]
+ end
end
- describe "capture" do
- end
+ describe "[authorize]" do
+ test "old customer with good_opts and good_card" do
+ use_cassette "mercadopago/authorize_old customer with good_opts and good_card" do
+ assert {:ok, response} = Gateway.authorize(@amount, @good_card, @good_opts)
+ assert response.success == true
+ assert response.status_code == 201
+ end
+ end
+ test "old customer with good_opts and bad_card" do
+ use_cassette "mercadopago/authorize_old customer with good_opts and bad_card" do
+ assert {:error, response} = Gateway.authorize(@amount, @bad_card, @good_opts)
+ assert response.success == false
+ assert response.status_code == 400
+ end
+ end
- describe "void" do
- end
+ test "old customer with bad_opts and good_card" do
+ use_cassette "mercadopago/authorize_old customer with bad_opts and good_card" do
+ assert {:error, response} = Gateway.authorize(@amount, @good_card, @bad_opts)
+ assert response.success == false
+ assert response.status_code == 401
+ end
+ end
+ test "old customer with bad_opts and bad_opts" do
+ use_cassette "mercadopago/authorize_old customer with bad_opts and bad_opts" do
+ assert {:error, response} = Gateway.authorize(@amount, @bad_card, @bad_opts)
+ assert response.success == false
+ assert response.status_code == 400
+ end
+ end
- describe "refund" do
- end
+ test "new cutomer with good_opts and good_card" do
+ use_cassette "mercadopago/authorize_new cutomer with good_opts and good_card" do
+ opts = new_email_opts(true)
+ assert {:ok, response} = Gateway.authorize(@amount, @good_card, opts)
+ assert response.success == true
+ assert response.status_code == 201
+ end
+ end
+
+ test "new customer with good_opts and bad_card" do
+ use_cassette "mercadopago/authorize_new customer with good_opts and bad_card" do
+ opts = new_email_opts(true)
+ assert {:error, response} = Gateway.authorize(@amount, @bad_card, opts)
+ assert response.success == false
+ assert response.status_code == 400
+ end
+ end
- describe "environment setup" do
+ test "new customer with bad_opts and good_card" do
+ use_cassette "mercadopago/authorize_new customer with bad_opts and good_card" do
+ opts = new_email_opts(false)
+ assert {:error, response} = Gateway.authorize(@amount, @good_card, opts)
+ assert response.success == false
+ assert response.status_code == 401
+ end
+ end
+ test "new customer with bad_opts and bad_card" do
+ use_cassette "mercadopago/authorize_new customer with bad_opts and bad_card" do
+ opts = new_email_opts(false)
+ assert {:error, response} = Gateway.authorize(@amount, @bad_card, opts)
+ assert response.success == false
+ assert response.status_code == 401
+ end
+ end
end
end
From d0277b29c1cb931c18067b709c30e104d82937cd Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Sat, 31 Mar 2018 21:10:55 +0530
Subject: [PATCH 53/60] Authorize integration test cases created
---
test/integration/gateways/mercadopago_test.exs | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/test/integration/gateways/mercadopago_test.exs b/test/integration/gateways/mercadopago_test.exs
index 7cb658a9..f33a48c5 100644
--- a/test/integration/gateways/mercadopago_test.exs
+++ b/test/integration/gateways/mercadopago_test.exs
@@ -67,13 +67,14 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
end
describe "[authorize]" do
- test "old customer with good_opts and good_card" do
- use_cassette "mercadopago/authorize_old customer with good_opts and good_card" do
- assert {:ok, response} = Gateway.authorize(@amount, @good_card, @good_opts)
- assert response.success == true
- assert response.status_code == 201
- end
+ test "old customer with good_opts and good_card" do
+ use_cassette "mercadopago/authorize_old customer with good_opts and good_card" do
+ assert {:ok, response} = Gateway.authorize(@amount, @good_card, @good_opts)
+ assert response.success == true
+ assert response.status_code == 201
end
+ end
+
test "old customer with good_opts and bad_card" do
use_cassette "mercadopago/authorize_old customer with good_opts and bad_card" do
assert {:error, response} = Gateway.authorize(@amount, @bad_card, @good_opts)
@@ -89,6 +90,7 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
assert response.status_code == 401
end
end
+
test "old customer with bad_opts and bad_opts" do
use_cassette "mercadopago/authorize_old customer with bad_opts and bad_opts" do
assert {:error, response} = Gateway.authorize(@amount, @bad_card, @bad_opts)
@@ -123,6 +125,7 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
assert response.status_code == 401
end
end
+
test "new customer with bad_opts and bad_card" do
use_cassette "mercadopago/authorize_new customer with bad_opts and bad_card" do
opts = new_email_opts(false)
@@ -132,4 +135,6 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
end
end
end
+
end
+
From 0e159abc93fb5739a80df0ae3118c1648c9c7164 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Sat, 31 Mar 2018 21:28:17 +0530
Subject: [PATCH 54/60] Authorize integration test cases created
---
mix.exs | 1 +
mix.lock | 2 ++
test/integration/gateways/mercadopago_test.exs | 3 ---
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/mix.exs b/mix.exs
index 244a6405..c3074816 100644
--- a/mix.exs
+++ b/mix.exs
@@ -62,6 +62,7 @@ defmodule Gringotts.Mixfile do
{:mock, "~> 0.3.0", only: :test},
{:bypass, "~> 0.8", only: :test},
{:excoveralls, "~> 0.8", only: :test},
+ {:exvcr, "~> 0.10", only: :test},
# various analyses tools
{:credo, "~> 0.3", only: [:dev, :test]},
diff --git a/mix.lock b/mix.lock
index aaaa5cc1..83a82360 100644
--- a/mix.lock
+++ b/mix.lock
@@ -16,8 +16,10 @@
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "1.3.1", "50a117654dff8f8ee6958e68a65d0c2835a7e2f1aff94c1ea8f582c04fdf0bd4", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 1.4.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.1 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"ex_money": {:hex, :ex_money, "1.1.3", "843eed0a5673206de33be47cdc06574401abc3e2d33cbcf6d74e160226791ae4", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}, {:ex_cldr, "~> 1.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 1.0", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}], "hexpm"},
+ "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.8.1", "0bbf67f22c7dbf7503981d21a5eef5db8bbc3cb86e70d3798e8c802c74fa5e27", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
+ "exvcr": {:hex, :exvcr, "0.10.0", "5150808404d9f48dbda636f70f7f8fefd93e2433cd39f695f810e73b3a9d1736", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 0.13", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.0", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.11.0", "4951ee019df102492dabba66a09e305f61919a8a183a7860236c0fde586134b6", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
diff --git a/test/integration/gateways/mercadopago_test.exs b/test/integration/gateways/mercadopago_test.exs
index f33a48c5..0a1cdbd3 100644
--- a/test/integration/gateways/mercadopago_test.exs
+++ b/test/integration/gateways/mercadopago_test.exs
@@ -52,9 +52,6 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
installments: 1
]
-
- # Group the test cases by public api
-
def new_email_opts(good) do
no1 = :rand.uniform(1_000_00) |> to_string
no2 = :rand.uniform(1_000_00) |> to_string
From d0bda62c3acc59ee3469382211c2a3bd2c2223be Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Tue, 17 Apr 2018 23:23:45 +0530
Subject: [PATCH 55/60] Applied elixer formatter.
---
lib/gringotts/gateways/mercadopago.ex | 80 +++++++++-------
.../integration/gateways/mercadopago_test.exs | 95 ++++++++++---------
2 files changed, 95 insertions(+), 80 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index aa55c50a..6aa7804f 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -39,7 +39,7 @@ defmodule Gringotts.Gateways.Mercadopago do
| ------- | ---- |
| `:access_token` | **Access Token** |
| `:public_key` | **Public Key** |
-
+
> Your Application config **must include the `[:public_key, :access_token]` field(s)** and would look
> something like this:
>
@@ -89,7 +89,7 @@ defmodule Gringotts.Gateways.Mercadopago do
# Add the keys that must be present in the Application config in the
# `required_config` list
use Gringotts.Adapter, required_config: [:public_key, :access_token]
-
+
import Poison, only: [decode: 1]
alias Gringotts.{CreditCard, Response}
@@ -133,13 +133,13 @@ defmodule Gringotts.Gateways.Mercadopago do
"""
@spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
- def authorize(amount , %CreditCard{} = card, opts) do
+ def authorize(amount, %CreditCard{} = card, opts) do
if Keyword.has_key?(opts, :customer_id) do
auth_token(amount, card, opts, opts[:customer_id], false)
else
case create_customer(opts) do
{:error, res} -> {:error, res}
- {:ok, customer_id} -> auth_token(amount, card, opts, customer_id, false)
+ {:ok, customer_id} -> auth_token(amount, card, opts, customer_id, false)
end
end
end
@@ -147,7 +147,7 @@ defmodule Gringotts.Gateways.Mercadopago do
###############################################################################
# PRIVATE METHODS #
###############################################################################
-
+
# Makes the request to mercadopago's network.
# For consistency with other gateway implementations, make your (final)
# network request in here, and parse it using another private method called
@@ -164,6 +164,7 @@ defmodule Gringotts.Gateways.Mercadopago do
defp auth_token(amount, %CreditCard{} = card, opts, customer_id, capture) do
opts = opts ++ [customer_id: customer_id]
{state, res} = create_token(card, opts)
+
if state == :error do
{state, res}
else
@@ -175,15 +176,20 @@ defmodule Gringotts.Gateways.Mercadopago do
{_, value, _, _} = Money.to_integer_exp(amount)
opts = opts ++ [token_id: token_id]
url_params = [access_token: opts[:config][:access_token]]
- body = authorize_params(value, card, opts, opts[:token_id], opts[:customer_id], capture) |> Poison.encode!
- commit(:post, "/v1/payments", body, opts, params: url_params )
+
+ body =
+ authorize_params(value, card, opts, opts[:token_id], opts[:customer_id], capture)
+ |> Poison.encode!()
+
+ commit(:post, "/v1/payments", body, opts, params: url_params)
end
-
+
defp create_customer(opts) do
url_params = [access_token: opts[:config][:access_token]]
- body = %{"email": opts[:email]} |> Poison.encode!
+ body = %{email: opts[:email]} |> Poison.encode!()
{state, res} = commit(:post, "/v1/customers", body, opts, params: url_params)
+
if state == :error do
{state, res}
else
@@ -193,41 +199,44 @@ defmodule Gringotts.Gateways.Mercadopago do
defp token_params(%CreditCard{} = card) do
%{
- "expirationYear": card.year,
- "expirationMonth": card.month,
- "cardNumber": card.number,
- "securityCode": card.verification_code,
- "cardholder": %{"name": CreditCard.full_name(card)}
+ expirationYear: card.year,
+ expirationMonth: card.month,
+ cardNumber: card.number,
+ securityCode: card.verification_code,
+ cardholder: %{name: CreditCard.full_name(card)}
}
end
defp create_token(%CreditCard{} = card, opts) do
url_params = [public_key: opts[:config][:public_key]]
- body = token_params(card) |> Poison.encode!
- {state, res} = commit(:post, "/v1/card_tokens/#{opts[:customer_id]}", body, opts, params: url_params)
+ body = token_params(card) |> Poison.encode!()
+
+ {state, res} =
+ commit(:post, "/v1/card_tokens/#{opts[:customer_id]}", body, opts, params: url_params)
+
case state do
- :error -> {state, res}
- _ -> {state, res.id}
+ :error -> {state, res}
+ _ -> {state, res.id}
end
end
defp authorize_params(value, %CreditCard{} = card, opts, token_id, customer_id, capture) do
%{
- "payer": %{
- "type": "customer",
- "id": customer_id,
- "first_name": card.first_name,
- "last_name": card.last_name
- },
- "order": %{
- "type": "mercadopago",
- "id": opts[:order_id]
- },
- "installments": opts[:installments] || 1,
- "transaction_amount": value,
- "payment_method_id": String.downcase(card.brand),
- "token": token_id,
- "capture": capture
+ payer: %{
+ type: "customer",
+ id: customer_id,
+ first_name: card.first_name,
+ last_name: card.last_name
+ },
+ order: %{
+ type: "mercadopago",
+ id: opts[:order_id]
+ },
+ installments: opts[:installments] || 1,
+ transaction_amount: value,
+ payment_method_id: String.downcase(card.brand),
+ token: token_id,
+ capture: capture
}
end
@@ -251,7 +260,8 @@ defmodule Gringotts.Gateways.Mercadopago do
end
defp respond({:ok, %HTTPoison.Response{body: body, status_code: status_code}}, opts) do
- body = body |> Poison.decode!
+ body = body |> Poison.decode!()
+
case body["cause"] do
nil -> {:ok, success_body(body, status_code, opts)}
_ -> {:error, error_body(body, status_code, opts)}
@@ -267,6 +277,4 @@ defmodule Gringotts.Gateways.Mercadopago do
)
}
end
-
end
-
diff --git a/test/integration/gateways/mercadopago_test.exs b/test/integration/gateways/mercadopago_test.exs
index 0a1cdbd3..fbfa5bd7 100644
--- a/test/integration/gateways/mercadopago_test.exs
+++ b/test/integration/gateways/mercadopago_test.exs
@@ -5,61 +5,70 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
alias Gringotts.Gateways.Mercadopago, as: Gateway
- @moduletag :integration
+ @moduletag integration: true
@amount Money.new(45, :BRL)
@sub_amount Money.new(30, :BRL)
@good_card %Gringotts.CreditCard{
- first_name: "Hermoine",
- last_name: "Granger",
- number: "4509953566233704",
- year: 2030,
- month: 07,
- verification_code: "123",
- brand: "VISA"
- }
+ first_name: "Hermoine",
+ last_name: "Granger",
+ number: "4509953566233704",
+ year: 2030,
+ month: 07,
+ verification_code: "123",
+ brand: "VISA"
+ }
@bad_card %Gringotts.CreditCard{
- first_name: "Hermoine",
- last_name: "Granger",
- number: "4509953566233704",
- year: 2000,
- month: 07,
- verification_code: "123",
- brand: "VISA"
- }
-
- @good_opts [email: "hermoine@granger.com",
- order_id: 123126,
- customer_id: "311211654-YrXF6J0QikpIWX",
- config: [access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
- public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
- installments: 1
- ]
- @new_cutomer_good_opts [order_id: 123126,
- config: [access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
- public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
- installments: 1
- ]
- @new_cutomer_bad_opts [order_id: 123126,
- config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
- installments: 1
- ]
- @bad_opts [email: "hermoine@granger.com",
- order_id: 123126,
- customer_id: "311211654-YrXF6J0QikpIWX",
- config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
- installments: 1
- ]
+ first_name: "Hermoine",
+ last_name: "Granger",
+ number: "4509953566233704",
+ year: 2000,
+ month: 07,
+ verification_code: "123",
+ brand: "VISA"
+ }
+
+ @good_opts [
+ email: "hermoine@granger.com",
+ order_id: 123_126,
+ customer_id: "311211654-YrXF6J0QikpIWX",
+ config: [
+ access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
+ public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"
+ ],
+ installments: 1
+ ]
+ @new_cutomer_good_opts [
+ order_id: 123_126,
+ config: [
+ access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
+ public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"
+ ],
+ installments: 1
+ ]
+ @new_cutomer_bad_opts [
+ order_id: 123_126,
+ config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
+ installments: 1
+ ]
+ @bad_opts [
+ email: "hermoine@granger.com",
+ order_id: 123_126,
+ customer_id: "311211654-YrXF6J0QikpIWX",
+ config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
+ installments: 1
+ ]
def new_email_opts(good) do
no1 = :rand.uniform(1_000_00) |> to_string
no2 = :rand.uniform(1_000_00) |> to_string
no3 = :rand.uniform(1_000_00) |> to_string
email = "hp" <> no1 <> no2 <> no3 <> "@potter.com"
+
case good do
- true -> @new_cutomer_good_opts ++ [email: email]
- _ -> @new_cutomer_bad_opts ++ [email: email]
+ true -> @new_cutomer_good_opts ++ [email: email]
+ _ -> @new_cutomer_bad_opts ++ [email: email]
end
end
@@ -132,6 +141,4 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
end
end
end
-
end
-
From 1357aa8ea4f536c752674b1d1c0e774e71c24a4c Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Wed, 18 Apr 2018 01:58:01 +0530
Subject: [PATCH 56/60] Capture and test cases
1. Implemented capture
2. Added test cases for capture
---
lib/gringotts/gateways/mercadopago.ex | 33 +++++
.../integration/gateways/mercadopago_test.exs | 114 +++---------------
2 files changed, 49 insertions(+), 98 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index 6aa7804f..df4189ef 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -144,6 +144,39 @@ defmodule Gringotts.Gateways.Mercadopago do
end
end
+ @doc """
+ Captures a pre-authorized `amount`.
+
+ `amount` is transferred to the merchant account by mercadopago used in the
+ pre-authorization referenced by `payment_id`.
+
+ ## Note
+ mercadopago allows partial captures also. However, you can make a partial capture to a payment only **once**.
+
+ > The authorization will be valid for 7 days. If you do not capture it by that time, it will be cancelled.
+
+ > The specified amount can not exceed the originally reserved.
+
+ > If you do not specify the amount, all the reserved money is captured.
+
+ > In Argentina only available for Visa and American Express cards.
+
+ ## Example
+
+ The following example shows how one would (partially) capture a previously
+ authorized a payment worth 35 BRL by referencing the obtained authorization `id`.
+
+ iex> amount = Money.new(35, :BRL)
+ iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Mercadopago, auth_result.id, amount, opts)
+ """
+ @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response}
+ def capture(payment_id, amount, opts) do
+ {_, value, _, _} = Money.to_integer_exp(amount)
+ url_params = [access_token: opts[:config][:access_token]]
+ body = %{capture: true, transaction_amount: value} |> Poison.encode!()
+ commit(:put, "/v1/payments/#{payment_id}", body, opts, params: url_params)
+ end
+
###############################################################################
# PRIVATE METHODS #
###############################################################################
diff --git a/test/integration/gateways/mercadopago_test.exs b/test/integration/gateways/mercadopago_test.exs
index fbfa5bd7..afff1e87 100644
--- a/test/integration/gateways/mercadopago_test.exs
+++ b/test/integration/gateways/mercadopago_test.exs
@@ -19,16 +19,6 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
brand: "VISA"
}
- @bad_card %Gringotts.CreditCard{
- first_name: "Hermoine",
- last_name: "Granger",
- number: "4509953566233704",
- year: 2000,
- month: 07,
- verification_code: "123",
- brand: "VISA"
- }
-
@good_opts [
email: "hermoine@granger.com",
order_id: 123_126,
@@ -39,106 +29,34 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
],
installments: 1
]
- @new_cutomer_good_opts [
- order_id: 123_126,
- config: [
- access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
- public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"
- ],
- installments: 1
- ]
- @new_cutomer_bad_opts [
- order_id: 123_126,
- config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
- installments: 1
- ]
- @bad_opts [
- email: "hermoine@granger.com",
- order_id: 123_126,
- customer_id: "311211654-YrXF6J0QikpIWX",
- config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
- installments: 1
- ]
-
- def new_email_opts(good) do
- no1 = :rand.uniform(1_000_00) |> to_string
- no2 = :rand.uniform(1_000_00) |> to_string
- no3 = :rand.uniform(1_000_00) |> to_string
- email = "hp" <> no1 <> no2 <> no3 <> "@potter.com"
- case good do
- true -> @new_cutomer_good_opts ++ [email: email]
- _ -> @new_cutomer_bad_opts ++ [email: email]
- end
- end
-
- describe "[authorize]" do
- test "old customer with good_opts and good_card" do
- use_cassette "mercadopago/authorize_old customer with good_opts and good_card" do
- assert {:ok, response} = Gateway.authorize(@amount, @good_card, @good_opts)
+ describe "[capture]" do
+ test "capture success" do
+ use_cassette "mercadopago/capture_success" do
+ {:ok, response} = Gateway.authorize(@sub_amount, @good_card, @good_opts)
+ {:ok, response} = Gateway.capture(response.id, @sub_amount, @good_opts)
assert response.success == true
- assert response.status_code == 201
- end
- end
-
- test "old customer with good_opts and bad_card" do
- use_cassette "mercadopago/authorize_old customer with good_opts and bad_card" do
- assert {:error, response} = Gateway.authorize(@amount, @bad_card, @good_opts)
- assert response.success == false
- assert response.status_code == 400
- end
- end
-
- test "old customer with bad_opts and good_card" do
- use_cassette "mercadopago/authorize_old customer with bad_opts and good_card" do
- assert {:error, response} = Gateway.authorize(@amount, @good_card, @bad_opts)
- assert response.success == false
- assert response.status_code == 401
+ assert response.status_code == 200
end
end
- test "old customer with bad_opts and bad_opts" do
- use_cassette "mercadopago/authorize_old customer with bad_opts and bad_opts" do
- assert {:error, response} = Gateway.authorize(@amount, @bad_card, @bad_opts)
+ test "invalid payment_id" do
+ use_cassette "mercadopago/capture_invalid_payment_id" do
+ {:ok, response} = Gateway.authorize(@sub_amount, @good_card, @good_opts)
+ id = response.id + 1
+ {:error, response} = Gateway.capture(id, @sub_amount, @good_opts)
assert response.success == false
- assert response.status_code == 400
+ assert response.status_code == 404
end
end
- test "new cutomer with good_opts and good_card" do
- use_cassette "mercadopago/authorize_new cutomer with good_opts and good_card" do
- opts = new_email_opts(true)
- assert {:ok, response} = Gateway.authorize(@amount, @good_card, opts)
- assert response.success == true
- assert response.status_code == 201
- end
- end
-
- test "new customer with good_opts and bad_card" do
- use_cassette "mercadopago/authorize_new customer with good_opts and bad_card" do
- opts = new_email_opts(true)
- assert {:error, response} = Gateway.authorize(@amount, @bad_card, opts)
+ test "accessing amount" do
+ use_cassette "mercadopago/access_amount" do
+ {:ok, response} = Gateway.authorize(@sub_amount, @good_card, @good_opts)
+ {:error, response} = Gateway.capture(response.id, @amount, @good_opts)
assert response.success == false
assert response.status_code == 400
end
end
-
- test "new customer with bad_opts and good_card" do
- use_cassette "mercadopago/authorize_new customer with bad_opts and good_card" do
- opts = new_email_opts(false)
- assert {:error, response} = Gateway.authorize(@amount, @good_card, opts)
- assert response.success == false
- assert response.status_code == 401
- end
- end
-
- test "new customer with bad_opts and bad_card" do
- use_cassette "mercadopago/authorize_new customer with bad_opts and bad_card" do
- opts = new_email_opts(false)
- assert {:error, response} = Gateway.authorize(@amount, @bad_card, opts)
- assert response.success == false
- assert response.status_code == 401
- end
- end
end
end
From 699876ff30effe200d23023fa32858f3c0d963ea Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Wed, 18 Apr 2018 02:05:06 +0530
Subject: [PATCH 57/60] Added test cases for capture
---
test/integration/gateways/mercadopago_test.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/integration/gateways/mercadopago_test.exs b/test/integration/gateways/mercadopago_test.exs
index afff1e87..d8078cbe 100644
--- a/test/integration/gateways/mercadopago_test.exs
+++ b/test/integration/gateways/mercadopago_test.exs
@@ -50,7 +50,7 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
end
end
- test "accessing amount" do
+ test "extra amount capture" do
use_cassette "mercadopago/access_amount" do
{:ok, response} = Gateway.authorize(@sub_amount, @good_card, @good_opts)
{:error, response} = Gateway.capture(response.id, @amount, @good_opts)
From 3254c5408910d2ac2fd185ece3e3c84a4fe20983 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Wed, 18 Apr 2018 02:22:31 +0530
Subject: [PATCH 58/60] Implemented purchase
1. added purchase
2. added test cases
---
lib/gringotts/gateways/mercadopago.ex | 88 +++-----------
.../integration/gateways/mercadopago_test.exs | 114 +++++++++++++++---
2 files changed, 117 insertions(+), 85 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index df4189ef..da3a5f4e 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -95,88 +95,38 @@ defmodule Gringotts.Gateways.Mercadopago do
alias Gringotts.{CreditCard, Response}
@doc """
- Performs a (pre) Authorize operation.
+ Transfers `amount` from the customer to the merchant.
- The authorization validates the `card` details with the banking network,
- places a hold on the transaction `amount` in the customer’s issuing bank.
-
- mercadoapgo's `authorize` returns authorization ID(available in the `Response.id` field) :
-
- * `capture/3` _an_ amount.
- * `void/2` a pre-authorization.
- ## Note
-
- For a new customer, `customer_id` field should be ignored. Otherwise it should be provided.
+ mercadopago attempts to process a purchase on behalf of the customer, by
+ debiting `amount` from the customer's account by charging the customer's
+ `card`.
## Example
- ### Authorization for new customer.
- The following example shows how one would (pre) authorize a payment of 42 BRL on a sample `card`.
- Ignore `customer_id`.
- iex> amount = Money.new(42, :BRL)
- iex> card = %Gringotts.CreditCard{first_name: "Lord", last_name: "Voldemort", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> opts = [email: "tommarvolo@riddle.com", order_id: 123123, payment_method_id: "visa", config: %{access_token: YOUR_ACCESS_TOKEN, public_key: YOUR_PUBLIC_KEY}]
- iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Mercadopago, amount, card, opts)
- iex> auth_result.id # This is the authorization ID
- iex> auth_result.token # This is the customer ID/token
-
- ### Authorization for old customer.
- The following example shows how one would (pre) authorize a payment of 42 BRL on a sample `card`.
- Mention `customer_id`.
+ The following example shows how one would process a payment worth 42 BRL in
+ one-shot, without (pre) authorization.
+
iex> amount = Money.new(42, :BRL)
- iex> card = %Gringotts.CreditCard{first_name: "Hermione", last_name: "Granger", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
- iex> opts = [email: "hermione@granger.com", order_id: 123125, customer_id: "308537342-HStv9cJCgK0dWU", payment_method_id: "visa", config: %{access_token: YOUR_ACCESS_TOKEN, public_key: YOUR_PUBLIC_KEY}]
- iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Mercadopago, amount, card, opts)
- iex> auth_result.id # This is the authorization ID
- iex> auth_result.token # This is the customer ID/token
+ iex> card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"}
+ iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.Mercadopago, amount, card, opts)
+ iex> purchase_result.token # This is the customer ID/token
"""
-
- @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
- def authorize(amount, %CreditCard{} = card, opts) do
+ @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
+ def purchase(amount, %CreditCard{} = card, opts) do
if Keyword.has_key?(opts, :customer_id) do
- auth_token(amount, card, opts, opts[:customer_id], false)
+ auth_token(amount, card, opts, opts[:customer_id], true)
else
- case create_customer(opts) do
- {:error, res} -> {:error, res}
- {:ok, customer_id} -> auth_token(amount, card, opts, customer_id, false)
+ {state, res} = create_customer(opts)
+
+ if state == :error do
+ {state, res}
+ else
+ auth_token(amount, card, opts, res, true)
end
end
end
- @doc """
- Captures a pre-authorized `amount`.
-
- `amount` is transferred to the merchant account by mercadopago used in the
- pre-authorization referenced by `payment_id`.
-
- ## Note
- mercadopago allows partial captures also. However, you can make a partial capture to a payment only **once**.
-
- > The authorization will be valid for 7 days. If you do not capture it by that time, it will be cancelled.
-
- > The specified amount can not exceed the originally reserved.
-
- > If you do not specify the amount, all the reserved money is captured.
-
- > In Argentina only available for Visa and American Express cards.
-
- ## Example
-
- The following example shows how one would (partially) capture a previously
- authorized a payment worth 35 BRL by referencing the obtained authorization `id`.
-
- iex> amount = Money.new(35, :BRL)
- iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.Mercadopago, auth_result.id, amount, opts)
- """
- @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response}
- def capture(payment_id, amount, opts) do
- {_, value, _, _} = Money.to_integer_exp(amount)
- url_params = [access_token: opts[:config][:access_token]]
- body = %{capture: true, transaction_amount: value} |> Poison.encode!()
- commit(:put, "/v1/payments/#{payment_id}", body, opts, params: url_params)
- end
-
###############################################################################
# PRIVATE METHODS #
###############################################################################
diff --git a/test/integration/gateways/mercadopago_test.exs b/test/integration/gateways/mercadopago_test.exs
index d8078cbe..9d396546 100644
--- a/test/integration/gateways/mercadopago_test.exs
+++ b/test/integration/gateways/mercadopago_test.exs
@@ -19,6 +19,16 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
brand: "VISA"
}
+ @bad_card %Gringotts.CreditCard{
+ first_name: "Hermoine",
+ last_name: "Granger",
+ number: "4509953566233704",
+ year: 2000,
+ month: 07,
+ verification_code: "123",
+ brand: "VISA"
+ }
+
@good_opts [
email: "hermoine@granger.com",
order_id: 123_126,
@@ -29,34 +39,106 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
],
installments: 1
]
+ @new_cutomer_good_opts [
+ order_id: 123_126,
+ config: [
+ access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
+ public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"
+ ],
+ installments: 1
+ ]
+ @new_cutomer_bad_opts [
+ order_id: 123_126,
+ config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
+ installments: 1
+ ]
+ @bad_opts [
+ email: "hermoine@granger.com",
+ order_id: 123_126,
+ customer_id: "311211654-YrXF6J0QikpIWX",
+ config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
+ installments: 1
+ ]
+
+ def new_email_opts(good) do
+ no1 = :rand.uniform(1_000_00) |> to_string
+ no2 = :rand.uniform(1_000_00) |> to_string
+ no3 = :rand.uniform(1_000_00) |> to_string
+ email = "hp" <> no1 <> no2 <> no3 <> "@potter.com"
- describe "[capture]" do
- test "capture success" do
- use_cassette "mercadopago/capture_success" do
- {:ok, response} = Gateway.authorize(@sub_amount, @good_card, @good_opts)
- {:ok, response} = Gateway.capture(response.id, @sub_amount, @good_opts)
+ case good do
+ true -> @new_cutomer_good_opts ++ [email: email]
+ _ -> @new_cutomer_bad_opts ++ [email: email]
+ end
+ end
+
+ describe "[purchase]" do
+ test "old customer with good_opts and good_card" do
+ use_cassette "mercadopago/purchase_old customer with good_opts and good_card" do
+ assert {:ok, response} = Gateway.purchase(@amount, @good_card, @good_opts)
assert response.success == true
- assert response.status_code == 200
+ assert response.status_code == 201
+ end
+ end
+
+ test "old customer with good_opts and bad_card" do
+ use_cassette "mercadopago/purchase_old customer with good_opts and bad_card" do
+ assert {:error, response} = Gateway.purchase(@amount, @bad_card, @good_opts)
+ assert response.success == false
+ assert response.status_code == 400
+ end
+ end
+
+ test "old customer with bad_opts and good_card" do
+ use_cassette "mercadopago/purchase_old customer with bad_opts and good_card" do
+ assert {:error, response} = Gateway.purchase(@amount, @good_card, @bad_opts)
+ assert response.success == false
+ assert response.status_code == 401
end
end
- test "invalid payment_id" do
- use_cassette "mercadopago/capture_invalid_payment_id" do
- {:ok, response} = Gateway.authorize(@sub_amount, @good_card, @good_opts)
- id = response.id + 1
- {:error, response} = Gateway.capture(id, @sub_amount, @good_opts)
+ test "old customer with bad_opts and bad_opts" do
+ use_cassette "mercadopago/purchase_old customer with bad_opts and bad_opts" do
+ assert {:error, response} = Gateway.purchase(@amount, @bad_card, @bad_opts)
assert response.success == false
- assert response.status_code == 404
+ assert response.status_code == 400
end
end
- test "extra amount capture" do
- use_cassette "mercadopago/access_amount" do
- {:ok, response} = Gateway.authorize(@sub_amount, @good_card, @good_opts)
- {:error, response} = Gateway.capture(response.id, @amount, @good_opts)
+ test "new cutomer with good_opts and good_card" do
+ use_cassette "mercadopago/purchase_new cutomer with good_opts and good_card" do
+ opts = new_email_opts(true)
+ assert {:ok, response} = Gateway.purchase(@amount, @good_card, opts)
+ assert response.success == true
+ assert response.status_code == 201
+ end
+ end
+
+ test "new customer with good_opts and bad_card" do
+ use_cassette "mercadopago/purchase_new customer with good_opts and bad_card" do
+ opts = new_email_opts(true)
+ assert {:error, response} = Gateway.purchase(@amount, @bad_card, opts)
assert response.success == false
assert response.status_code == 400
end
end
+
+ test "new customer with bad_opts and good_card" do
+ use_cassette "mercadopago/purchase_new customer with bad_opts and good_card" do
+ opts = new_email_opts(false)
+ assert {:error, response} = Gateway.purchase(@amount, @good_card, opts)
+ assert response.success == false
+ assert response.status_code == 401
+ end
+ end
+
+ test "new customer with bad_opts and bad_card" do
+ use_cassette "mercadopago/purchase_new customer with bad_opts and bad_card" do
+ opts = new_email_opts(false)
+ assert {:error, response} = Gateway.purchase(@amount, @bad_card, opts)
+ assert response.success == false
+ assert response.status_code == 401
+ end
+ end
end
end
From 8b752610bc8a64ff72f3b4c39c54d3f5733afb57 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Wed, 18 Apr 2018 23:38:12 +0530
Subject: [PATCH 59/60] Applied mix formatter.
---
test/gateways/mercadopago_test.exs | 10 +++++-----
test/mocks/mercadopago_mock.exs | 2 --
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/test/gateways/mercadopago_test.exs b/test/gateways/mercadopago_test.exs
index 62bf1ae7..74e9c0e4 100644
--- a/test/gateways/mercadopago_test.exs
+++ b/test/gateways/mercadopago_test.exs
@@ -1,15 +1,15 @@
defmodule Gringotts.Gateways.MercadopagoTest do
# The file contains mocked tests for Mercadopago
-
+
# We recommend using [mock][1] for this, you can place the mock responses from
# the Gateway in `test/mocks/mercadopago_mock.exs` file, which has also been
# generated for you.
#
# [1]: https://github.com/jjh42/mock
-
+
# Load the mock response file before running the tests.
- Code.require_file "../mocks/mercadopago_mock.exs", __DIR__
-
+ Code.require_file("../mocks/mercadopago_mock.exs", __DIR__)
+
use ExUnit.Case, async: false
alias Gringotts.Gateways.Mercadopago
import Mock
@@ -21,7 +21,7 @@ defmodule Gringotts.Gateways.MercadopagoTest do
describe "authorize" do
end
- describe "capture" do
+ describe "capture" do
end
describe "void" do
diff --git a/test/mocks/mercadopago_mock.exs b/test/mocks/mercadopago_mock.exs
index f7a73709..14ed9338 100644
--- a/test/mocks/mercadopago_mock.exs
+++ b/test/mocks/mercadopago_mock.exs
@@ -1,9 +1,7 @@
defmodule Gringotts.Gateways.MercadopagoMock do
-
# The module should include mock responses for test cases in mercadopago_test.exs.
# e.g.
# def successful_purchase do
# {:ok, %HTTPoison.Response{body: ~s[{data: "successful_purchase"}]}
# end
-
end
From 7b2b36a51f7e1ed3e51d334b12d5c3136759b613 Mon Sep 17 00:00:00 2001
From: siriusdark
Date: Thu, 19 Apr 2018 00:10:07 +0530
Subject: [PATCH 60/60] 1. Updated purchase function. 2. Updated test cases.
---
lib/gringotts/gateways/mercadopago.ex | 59 ++++++------------
.../integration/gateways/mercadopago_test.exs | 60 ++-----------------
2 files changed, 25 insertions(+), 94 deletions(-)
diff --git a/lib/gringotts/gateways/mercadopago.ex b/lib/gringotts/gateways/mercadopago.ex
index da3a5f4e..8b9ea2f2 100644
--- a/lib/gringotts/gateways/mercadopago.ex
+++ b/lib/gringotts/gateways/mercadopago.ex
@@ -114,16 +114,15 @@ defmodule Gringotts.Gateways.Mercadopago do
"""
@spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response}
def purchase(amount, %CreditCard{} = card, opts) do
- if Keyword.has_key?(opts, :customer_id) do
- auth_token(amount, card, opts, opts[:customer_id], true)
- else
- {state, res} = create_customer(opts)
+ with {:ok, customer_id} <- create_customer(opts),
+ {:ok, card_token} <- create_token(card, opts) do
+ {_, value, _, _} = Money.to_integer_exp(amount)
+ url_params = [access_token: opts[:config][:access_token]]
- if state == :error do
- {state, res}
- else
- auth_token(amount, card, opts, res, true)
- end
+ body =
+ authorize_params(value, card, opts, card_token, customer_id, false) |> Poison.encode!()
+
+ commit(:post, "/v1/payments", body, opts, params: url_params)
end
end
@@ -144,39 +143,19 @@ defmodule Gringotts.Gateways.Mercadopago do
# Parses mercadopago's response and returns a `Gringotts.Response` struct
# in a `:ok`, `:error` tuple.
- defp auth_token(amount, %CreditCard{} = card, opts, customer_id, capture) do
- opts = opts ++ [customer_id: customer_id]
- {state, res} = create_token(card, opts)
-
- if state == :error do
- {state, res}
- else
- auth(amount, card, opts, res, capture)
- end
- end
-
- defp auth(amount, %CreditCard{} = card, opts, token_id, capture) do
- {_, value, _, _} = Money.to_integer_exp(amount)
- opts = opts ++ [token_id: token_id]
- url_params = [access_token: opts[:config][:access_token]]
-
- body =
- authorize_params(value, card, opts, opts[:token_id], opts[:customer_id], capture)
- |> Poison.encode!()
-
- commit(:post, "/v1/payments", body, opts, params: url_params)
- end
-
defp create_customer(opts) do
- url_params = [access_token: opts[:config][:access_token]]
- body = %{email: opts[:email]} |> Poison.encode!()
-
- {state, res} = commit(:post, "/v1/customers", body, opts, params: url_params)
-
- if state == :error do
- {state, res}
+ if Keyword.has_key?(opts, :customer_id) do
+ {:ok, opts[:customer_id]}
else
- {state, res.id}
+ url_params = [access_token: opts[:config][:access_token]]
+ body = %{email: opts[:email]} |> Poison.encode!()
+ {state, res} = commit(:post, "/v1/customers", body, opts, params: url_params)
+
+ if state == :error do
+ {state, res}
+ else
+ {state, res.id}
+ end
end
end
diff --git a/test/integration/gateways/mercadopago_test.exs b/test/integration/gateways/mercadopago_test.exs
index 9d396546..3a961610 100644
--- a/test/integration/gateways/mercadopago_test.exs
+++ b/test/integration/gateways/mercadopago_test.exs
@@ -9,6 +9,10 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
@amount Money.new(45, :BRL)
@sub_amount Money.new(30, :BRL)
+ @config [
+ access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
+ public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"
+ ]
@good_card %Gringotts.CreditCard{
first_name: "Hermoine",
last_name: "Granger",
@@ -33,30 +37,12 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
email: "hermoine@granger.com",
order_id: 123_126,
customer_id: "311211654-YrXF6J0QikpIWX",
- config: [
- access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
- public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"
- ],
+ config: @config,
installments: 1
]
@new_cutomer_good_opts [
order_id: 123_126,
- config: [
- access_token: "TEST-2774702803649645-031303-1b9d3d63acb57cdad3458d386eee62bd-307592510",
- public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"
- ],
- installments: 1
- ]
- @new_cutomer_bad_opts [
- order_id: 123_126,
- config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
- installments: 1
- ]
- @bad_opts [
- email: "hermoine@granger.com",
- order_id: 123_126,
- customer_id: "311211654-YrXF6J0QikpIWX",
- config: [public_key: "TEST-911f45a1-0560-4c16-915e-a8833830b29a"],
+ config: @config,
installments: 1
]
@@ -89,22 +75,6 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
end
end
- test "old customer with bad_opts and good_card" do
- use_cassette "mercadopago/purchase_old customer with bad_opts and good_card" do
- assert {:error, response} = Gateway.purchase(@amount, @good_card, @bad_opts)
- assert response.success == false
- assert response.status_code == 401
- end
- end
-
- test "old customer with bad_opts and bad_opts" do
- use_cassette "mercadopago/purchase_old customer with bad_opts and bad_opts" do
- assert {:error, response} = Gateway.purchase(@amount, @bad_card, @bad_opts)
- assert response.success == false
- assert response.status_code == 400
- end
- end
-
test "new cutomer with good_opts and good_card" do
use_cassette "mercadopago/purchase_new cutomer with good_opts and good_card" do
opts = new_email_opts(true)
@@ -122,23 +92,5 @@ defmodule Gringotts.Integration.Gateways.MercadopagoTest do
assert response.status_code == 400
end
end
-
- test "new customer with bad_opts and good_card" do
- use_cassette "mercadopago/purchase_new customer with bad_opts and good_card" do
- opts = new_email_opts(false)
- assert {:error, response} = Gateway.purchase(@amount, @good_card, opts)
- assert response.success == false
- assert response.status_code == 401
- end
- end
-
- test "new customer with bad_opts and bad_card" do
- use_cassette "mercadopago/purchase_new customer with bad_opts and bad_card" do
- opts = new_email_opts(false)
- assert {:error, response} = Gateway.purchase(@amount, @bad_card, opts)
- assert response.success == false
- assert response.status_code == 401
- end
- end
end
end