Skip to content

Commit

Permalink
Implemented authorize, capture, purchase, void, refund, and integrati…
Browse files Browse the repository at this point in the history
…on tests.
  • Loading branch information
anantanant2015 committed May 7, 2018
1 parent 18b84ba commit 96000d8
Show file tree
Hide file tree
Showing 2 changed files with 297 additions and 1 deletion.
200 changes: 199 additions & 1 deletion lib/gringotts/gateways/adyen.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,201 @@
defmodule Gringotts.Gateways.Adyen do
@moduledoc """
[ADYEN][home] gateway implementation.
For refernce see [ADYEN's API (v2.1) documentation][docs].
The following features of ADYEN are implemented:
| Action | Method |
| ------ | ------ |
## The `opts` argument
Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply
optional arguments for transactions with the ADYEN gateway.
## Registering your ADYEN account at `Gringotts`
After [making an account on ADYEN][dashboard], head to the dashboard and find
your account "secrets".
Here's how the secrets map to the required configuration parameters for ADYEN:
| Config parameter | ADYEN secret |
| ------- | ---- |
[home]: https://www.adyen.com/
[docs]: https://docs.adyen.com/developers
[dashboard]:
[gs]: https://github.com/aviabird/gringotts/wiki
[example-repo]: https://github.com/aviabird/gringotts_example
[currency-support]: https://docs.adyen.com/developers/currency-codes
[country-support]: https://docs.adyen.com/developers/currency-codes
[pci-dss]:
"""

use Gringotts.Gateways.Base
use Gringotts.Adapter, required_config: [:username, :password, :account, :mode]

alias Gringotts.{Response, Money, CreditCard}

@base_url "https://pal-test.adyen.com/pal/servlet/Payment/v30/"
@headers [{"Content-Type", "application/json"}]

@spec authorize(Money.t(), String.t(), keyword) :: {:ok | :error, Response.t()}
def authorize(amount, card, opts) do
params = authorize_params(card, amount, opts)
commit(:post, "authorise", params, opts)
end

@spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response.t()}
def capture(id, amount, opts) do
params = capture_and_refund_params(id, amount, opts)
commit(:post, "capture", params, opts)
end

@spec purchase(Money.t(), String.t(), keyword) :: {:ok | :error, Response.t()}
def purchase(amount, card, opts) do
{auth_atom, auth_response} = authorize(amount, card, opts)
case auth_atom do
:ok -> capture(auth_response.id, amount, opts)
_ -> {auth_atom, auth_response}
end
end

@spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response.t()}
def refund(amount, id, opts) do
param = capture_and_refund_params(id, amount, opts)
commit(:post, "refund", param, opts)
end

@spec void(String.t(), keyword) :: {:ok | :error, Response.t()}
def void(id, opts) do
param = void_params(id, opts)
commit(:post, "cancel", param, opts)
end

defp authorize_params(%CreditCard{} = rec_card, amount, opts) do
case Keyword.get(opts, :requestParameters) do
nil ->
body = get_authorize_params(rec_card, amount, opts)
_ ->
body = Enum.into(opts[:requestParameters], get_authorize_params(rec_card, amount, opts))
end

end
Poison.encode!(body)
end

defp get_authorize_params(rec_card, amount, opts) do
%{
card: card_map(rec_card),
amount: amount_map(amount),
merchantAccount: opts[:config][:account]
}
end

defp capture_and_refund_params(id, amount, opts) do
case Keyword.get(opts, :requestParameters) do
nil ->
body = get_capture_and_refund_params(id, amount, opts)
_ ->
body = Enum.into(opts[:requestParameters], get_capture_and_refund_params(id, amount, opts))
end
Poison.encode!(body)
end

defp get_capture_and_refund_params(id, amount, opts) do
%{
"originalReference": id,
"modificationAmount": amount_map(amount),
"merchantAccount": opts[:config][:account]
}
end

defp void_params(id, opts) do
case Keyword.get(opts, :requestParameters) do
nil ->
body = get_void_params(id, opts)
_ ->
body = Enum.into(opts[:requestParameters], get_void_params(id, opts))
end

Poison.encode!(body)
end

defp get_void_params(id, opts) do
%{
"originalReference": id,
"merchantAccount": opts[:config][:account]
}
end

defp card_map(%CreditCard{} = rec_card) do
%{
number: rec_card.number,
expiryMonth: rec_card.month,
expiryYear: rec_card.year,
cvc: rec_card.verification_code,
holderName: CreditCard.full_name(rec_card)
}
end

defp amount_map(amount) do
{currency, int_value, _} = Money.to_integer(amount)
%{
value: int_value,
currency: currency
}
end

defp commit(method, endpoint, params, opts) do
head = headers(opts)
method
|> HTTPoison.request(base_url(opts) <> endpoint, params, headers(opts))
|> respond(opts)
end

defp respond({:ok, response}, opts) do
case Poison.decode(response.body) do
{:ok, parsed_resp} ->
gateway_code = parsed_resp["status"]

status = if gateway_code == 200 || response.status_code == 200, do: :ok, else: :error
{status,
%Response{
id: parsed_resp["pspReference"],
status_code: response.status_code,
gateway_code: gateway_code,
reason: parsed_resp["errorType"],
message: parsed_resp["message"] || parsed_resp["resultCode"] || parsed_resp["response"],
raw: response.body
}}

:error ->
{:error,
%Response{raw: response.body, reason: "could not parse ADYEN response"}}
end
end

defp respond({:error, %HTTPoison.Error{} = response}, opts) do
{
:error,
Response.error(
reason: "network related failure",
message: "HTTPoison says '#{response.reason}' [ID: #{response.id || "nil"}]"
)
}
end

defp headers(opts) do
arg = get_in(opts, [:config, :username]) <> ":" <> get_in(opts, [:config, :password])
[
{"Authorization", "Basic #{Base.encode64(arg)}"}
| @headers
]
end

defp base_url(opts), do: opts[:config][:url] || @base_url
end
98 changes: 98 additions & 0 deletions test/integration/gateways/adyen_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
defmodule Gringotts.Integration.Gateways.AdyenTest do
use ExUnit.Case, async: true
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney

alias Gringotts.Gateways.Adyen, as: Gateway
alias Gringotts.{CreditCard}

@moduletag integration: true

@amount Money.new(4200, :EUR)

@reference "payment-#{DateTime.utc_now |> DateTime.to_string |> String.replace([" ", ":", "."], "-")}"

@card %CreditCard{
brand: "VISA",
first_name: "John",
last_name: "Smith",
number: "4988438843884305",
month: "08",
year: "2018",
verification_code: "737"
}

setup_all do
Application.put_env(
:gringotts,
Gateway,
username: "[email protected]",
password: "R+F#@D38SI2+DEy5vTDs?IFwN",
account: "AviabirdCOM",
mode: :test
)

on_exit(fn ->
Application.delete_env(:gringotts, Gateway)
end)
end

setup do
[opts: [requestParameters: %{"reference" => @reference}]]
end

describe "authorize" do
test "with valid card and currency", context do
use_cassette "adyen/authorize with valid card and currency" do
{:ok, response} = Gringotts.authorize(Gateway, @amount, @card, context.opts)
assert response.message == "Authorised"
assert response.status_code == 200
end
end
end

describe "capture" do
test "with valid card currency", context do
use_cassette "adyen/capture with valid card currency" do
{:ok, response} = Gringotts.authorize(Gateway, @amount, @card, context.opts)
payment_id = response.id
{:ok, response_cap} = Gringotts.capture(Gateway, payment_id, @amount, context.opts)
assert response_cap.message == "[capture-received]"
assert response_cap.status_code == 200
end
end
end

describe "purchase" do
test "with valid card currency", context do
use_cassette "adyen purchase with valid card currency" do
{:ok, response} = Gringotts.purchase(Gateway, @amount, @card, context.opts)
assert response.message == "[capture-received]"
assert response.status_code == 200
end
end
end

describe "refund" do
test "with valid card currency", context do
use_cassette "adyen/refund with valid card currency" do
{:ok, response} = Gringotts.purchase(Gateway, @amount, @card, context.opts)
trans_id = response.id
{:ok, response_ref} = Gringotts.refund(Gateway, @amount, trans_id)
assert response_ref.message == "[refund-received]"
assert response_ref.status_code == 200
end
end
end

describe "void" do
test "with valid card currency", context do
use_cassette "adyen/void with valid card currency" do
{:ok, response} = Gringotts.authorize(Gateway, @amount, @card, context.opts)
auth_id = response.id
{:ok, response_void} = Gringotts.void(Gateway, auth_id)
assert response_void.message == "[cancel-received]"
assert response_void.status_code == 200
end
end
end
end

0 comments on commit 96000d8

Please sign in to comment.