Skip to content

Commit

Permalink
Refactored ResponseHandler, for new Response.t
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
oyeb authored and ashish173 committed Apr 22, 2018
1 parent 9ec3c12 commit a528cbf
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 114 deletions.
187 changes: 93 additions & 94 deletions lib/gringotts/gateways/authorize_net.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Expand All @@ -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

##############################################################################
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"}}
Loading

0 comments on commit a528cbf

Please sign in to comment.