From d43cdb54d4e994eff699312e9963eb01b9dc8138 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Wed, 21 Mar 2018 15:38:33 +0530 Subject: [PATCH 01/26] Implemented and Optimised `authorize/3` function. `authorize/3` takes 3 parameters `amount`, `card` and `opts` addded documentations for `authorize/3` for merchants. --- lib/gringotts/gateways/pin_pay.ex | 325 ++++++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 lib/gringotts/gateways/pin_pay.ex diff --git a/lib/gringotts/gateways/pin_pay.ex b/lib/gringotts/gateways/pin_pay.ex new file mode 100644 index 00000000..da8e6ba5 --- /dev/null +++ b/lib/gringotts/gateways/pin_pay.ex @@ -0,0 +1,325 @@ +defmodule Gringotts.Gateways.Pinpay do + @moduledoc """ + [PinPay][home] gateway implementation. + + The login credentials are: + + | Key | Credentials | + | ------ | -------- | + | username | `api_key` | + | password | `` | + + + The following features of PinPayments are implemented: + + | Action | Method | + | ------ | ------ | + | Authorize | `authorize/3` | + | Capture | `capture/3` | + | Purchase | `purchase/3` | + | Store | `store/2` | + | Refund | `refund/3` | + | Respond | `respond/1` | + + + ## The `opts` argument + + Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply + optional arguments for transactions with the PINPAY gateway. The following keys + are supported: + + | Key | Type | Remark | + | ---- | ---- | --- | + | `address` | `map` | The address of the customer | + | `email_id` | `String.t` | Merchant provided email addres | + | `description` | `String.t` | Merchant provided description of the transaction | + | `ip_address` | `String.t` | Merchant provided ip address (optional) | + + + > PINPAY 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 + + + ### Schema + + * `address` is a `map` from `atoms` to `String.t`, and can include any + of the keys from: + `[:street1, :street2, :city, :region, :postal_code, :country, :phone, ]` + + + ## Registering your PINPAY account at `Gringotts` + + | Config parameter | PINPAY secret | + | ------- | ---- | + | `:username` | **API_SECRET_KEY** | + | `:password` | Empty string | + + > Your Application config **must include the `:username`, `:password` + > fields** and would look something like this: + + config :gringotts, Gringotts.Gateways.Pinpay, + username: "your_secret_key", + password: "", + + * PINPAY **does not** process money in cents. + * Although PINPAY supports payments various cards. This module only + accepts payments via `VISA`, `MASTER`, and `AMERICAN EXPRESS`. + + ## Supported countries + PINPAY supports the countries listed [here][all-country-list] + $ AUD, $ USD, $ NZD, $ SGD, € EUR, £ GBP, $ CAD, ¥ JPY + + ## Supported currencies + PINPAY supports the currencies listed [here][all-currency-list] + :AUD, :USD, :NZD, :SGD, :EUR, :GBP, :CAD, :HKD, :JPY, :MYR, :THB, :PHP, :ZAR, :IDR, :TWD + + + ## 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-PinPay). + + 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.Pinpay} + 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://pinpayments.com + [docs]: https://pinpayments.com/developers/api-reference + [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: [] + + import Poison, only: [decode: 1] + + alias Gringotts.{Money, CreditCard, Response} + + @test_url "https://test-api.pinpayments.com/1/" + @production_url "https://api.pinpayments.com/1/" + + @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.) + + PINPAY returns a **Payment Id** (available in the `Response.authorization` + field) which can be used later to: + * `capture/3` an amount. + * `refund/3` the amount. + + ## Optional Fields + options[ + email_id: String, + description: String, + ip_address: String (optional) + ] + + + + + ## Example + + The following example shows how one would (pre) authorize a payment of $20 on + a sample `card`. + ``` + iex> card = %CreditCard{first_name: "Jo", + last_name: "Doe", + number: "4200000000000000", + year: 2099, month: 12, + verification_code: "123", brand: "VISA"} + iex> money = %{value: Decimal.new(20), currency: "USD"} + iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Pinpay, money, card) + ``` + """ + + @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response} + def authorize(amount, %CreditCard{} = card, opts) do + {currency, value, _} = Money.to_integer(amount) + + params = + [ + amount: value, + capture: false + ] ++ card_params(card, opts) ++ Keyword.delete(opts, :address) + + commit(:post, "charges", params, [{:currency, currency} | opts]) + end + + def authorize(amount, card, opts) do + {currency, value, _} = Money.to_integer(amount) + + params = + [ + amount: value, + capture: false, + card_token: card + ] ++ Keyword.delete(opts, :address) + + commit(:post, "charges", params, [{:currency, currency} | opts]) + end + + @doc """ + Captures a pre-authorized `amount`. + + `amount` is transferred to the merchant account by PinPay 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 + url = @test_url <> "/1/charges/" <> payment_id <> "/capture" + commit(:put, url) + end + + @doc """ + Transfers `amount` from the customer to the merchant. + + PinPay 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`. + """ + + ############################################################################### + # PRIVATE METHODS # + ############################################################################### + + # Makes the request to PinPay'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 card_params(card, opts) do + [ + "card[number]": card.number, + "card[name]": card.first_name <> card.last_name, + "card[expiry_month]": card.month |> Integer.to_string() |> String.pad_leading(2, "0"), + "card[expiry_year]": card.year |> Integer.to_string(), + "card[cvc]": card.verification_code, + "card[address_line1]": opts[:address].street1, + "card[address_city]": opts[:address].city, + "card[address_country]": opts[:address].country + ] + end + + @spec commit(atom, String.t(), keyword, keyword) :: {:ok | :error, Response} + defp commit(:post, endpoint, param, opts) do + auth_token = encoded_credentials("c4nxgznanW4XZUaEQhxS6g", "") + + headers = [ + {"Content-Type", "application/x-www-form-urlencoded"}, + {"Authorization", auth_token} + ] + + url = @test_url <> "#{endpoint}" + + url + |> HTTPoison.post({:form, param}, headers) + |> respond + end + + defp commit(method, url) do + auth_token = encoded_credentials("c4nxgznanW4XZUaEQhxS6g", "") + + headers = [ + {"Content-Type", "application/x-www-form-urlencoded"}, + {"Authorization", auth_token} + ] + + HTTPoison.request(method, url, [], headers) + |> respond + end + + defp encoded_credentials(login, password) do + [login, password] + |> join_string(":") + |> Base.encode64() + |> (&("Basic " <> &1)).() + end + + defp join_string(list_of_words, joiner), do: Enum.join(list_of_words, joiner) + + # Parses PinPay's response and returns a `Gringotts.Response` struct + # in a `:ok`, `:error` tuple. + @spec respond(term) :: {:ok | :error, Response} + + defp respond({:ok, %{status_code: 200, body: body}}) do + parsed = Poison.decode!(body) + + {:ok, + %{ + success: true, + id: Map.get(parsed, "token"), + token: Map.get(parsed["card"], "token"), + status_code: 201, + reason: nil, + message: "Card succesfully authorized", + avs_result: nil, + cvc_result: nil, + raw: body, + fraud_review: nil, + email: Map.get(parsed, "email"), + description: Map.get(parsed, "description") + }} + end + + defp respond({:ok, %{body: body, status_code: code}}) do + {:error, %Response{raw: body, status_code: code}} + end + + defp respond({:error, %HTTPoison.Error{} = error}) do + { + :error, + %Response{ + reason: "network related failure", + message: "HTTPoison says '#{error.reason}' [ID: #{error.id || "nil"}]" + } + } + end +end From b6958a9ce4d623cb2d57825891174a5b9315579d Mon Sep 17 00:00:00 2001 From: ravirocx Date: Wed, 21 Mar 2018 16:33:34 +0530 Subject: [PATCH 02/26] Core Functions implemented --- lib/gringotts/gateways/pin_pay.ex | 100 ++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/lib/gringotts/gateways/pin_pay.ex b/lib/gringotts/gateways/pin_pay.ex index da8e6ba5..07c3cdf4 100644 --- a/lib/gringotts/gateways/pin_pay.ex +++ b/lib/gringotts/gateways/pin_pay.ex @@ -225,6 +225,106 @@ defmodule Gringotts.Gateways.Pinpay 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 + {currency, value, _} = Money.to_integer(amount) + params = + [ + amount: value + ] ++ card_params(card, opts) ++ opts + commit(:post, "charges", params, [{:currency, currency} | opts]) + end + + def purchase(amount, card, opts) do + {currency, value, _} = Money.to_integer(amount) + params = + [ + amount: value, + card_token: card, + ] ++ card_params(card, opts) ++ opts + commit(:post, "charges", params, [{:currency, currency} | opts]) + 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 + #can't be implemented in pinpayments + 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 PinPay? + > 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 + url=@test_url <> "charges/" <> payment_id <> "/refunds" + commit(:post, url) + 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(:post, "cards", card_for_token(card, opts), opts) + 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 + # can't be implemented in pinpayments + end ############################################################################### # PRIVATE METHODS # From 1e353b234f83dc4703ed5c2fe835ffd19c3d055b Mon Sep 17 00:00:00 2001 From: Ravi Raj Date: Thu, 22 Mar 2018 10:37:53 +0530 Subject: [PATCH 03/26] Fixed guard clause bug `def authorize(amount, card, opts) when is_binary(card) do` --- lib/gringotts/gateways/pin_pay.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gringotts/gateways/pin_pay.ex b/lib/gringotts/gateways/pin_pay.ex index 07c3cdf4..9837b287 100644 --- a/lib/gringotts/gateways/pin_pay.ex +++ b/lib/gringotts/gateways/pin_pay.ex @@ -176,7 +176,7 @@ defmodule Gringotts.Gateways.Pinpay do commit(:post, "charges", params, [{:currency, currency} | opts]) end - def authorize(amount, card, opts) do + def authorize(amount, card, opts) when is_binary(card) do {currency, value, _} = Money.to_integer(amount) params = @@ -235,7 +235,7 @@ defmodule Gringotts.Gateways.Pinpay do commit(:post, "charges", params, [{:currency, currency} | opts]) end - def purchase(amount, card, opts) do + def purchase(amount, card, opts) when is_binary(card) do {currency, value, _} = Money.to_integer(amount) params = [ From 32d6707ff2381cfb430b36a16703cd3220a8cd32 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Thu, 22 Mar 2018 12:40:06 +0530 Subject: [PATCH 04/26] Optimised `purchase`, `capture`, 'refund`, `store` and their private functions fucntions. --- lib/gringotts/gateways/pin_pay.ex | 62 +++++++++++++++---------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/lib/gringotts/gateways/pin_pay.ex b/lib/gringotts/gateways/pin_pay.ex index 07c3cdf4..73fb1ec3 100644 --- a/lib/gringotts/gateways/pin_pay.ex +++ b/lib/gringotts/gateways/pin_pay.ex @@ -176,7 +176,7 @@ defmodule Gringotts.Gateways.Pinpay do commit(:post, "charges", params, [{:currency, currency} | opts]) end - def authorize(amount, card, opts) do + def authorize(amount, card, opts) when is_binary(card) do {currency, value, _} = Money.to_integer(amount) params = @@ -206,7 +206,7 @@ defmodule Gringotts.Gateways.Pinpay do """ @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response} def capture(payment_id, amount, opts) do - url = @test_url <> "/1/charges/" <> payment_id <> "/capture" + url = @test_url <> "charges/" <> payment_id <> "/capture" commit(:put, url) end @@ -231,17 +231,17 @@ defmodule Gringotts.Gateways.Pinpay do params = [ amount: value - ] ++ card_params(card, opts) ++ opts + ] ++ card_params(card, opts) ++ Keyword.delete(opts, :address) commit(:post, "charges", params, [{:currency, currency} | opts]) end - def purchase(amount, card, opts) do + def purchase(amount, card, opts) when is_binary(card) do {currency, value, _} = Money.to_integer(amount) params = [ amount: value, card_token: card, - ] ++ card_params(card, opts) ++ opts + ] ++ Keyword.delete(opts, :address) commit(:post, "charges", params, [{:currency, currency} | opts]) end @@ -348,6 +348,19 @@ defmodule Gringotts.Gateways.Pinpay do ] end + defp card_for_token(card, opts) do + [ + "number": card.number, + "name": card.first_name <> card.last_name, + "expiry_month": card.month |> Integer.to_string() |> String.pad_leading(2, "0"), + "expiry_year": card.year |> Integer.to_string(), + "cvc": card.verification_code, + "address_line1": opts[:Address][:street1], + "address_city": opts[:Address][:city], + "address_country": opts[:Address][:country] + ] + end + @spec commit(atom, String.t(), keyword, keyword) :: {:ok | :error, Response} defp commit(:post, endpoint, param, opts) do auth_token = encoded_credentials("c4nxgznanW4XZUaEQhxS6g", "") @@ -389,37 +402,22 @@ defmodule Gringotts.Gateways.Pinpay do # in a `:ok`, `:error` tuple. @spec respond(term) :: {:ok | :error, Response} - defp respond({:ok, %{status_code: 200, body: body}}) do - parsed = Poison.decode!(body) - - {:ok, - %{ - success: true, - id: Map.get(parsed, "token"), - token: Map.get(parsed["card"], "token"), - status_code: 201, - reason: nil, - message: "Card succesfully authorized", - avs_result: nil, - cvc_result: nil, - raw: body, - fraud_review: nil, - email: Map.get(parsed, "email"), - description: Map.get(parsed, "description") - }} + defp respond({:ok, %{status_code: code, body: body}}) when code in [200, 201] do + {:ok, parsed} = decode(body) + token = parsed["response"]["token"] + message = parsed["response"]["status_message"] + { + :ok, Response.success(authorization: token, message: message, raw: parsed, status_code: code) + } end - defp respond({:ok, %{body: body, status_code: code}}) do - {:error, %Response{raw: body, status_code: code}} + defp respond({:ok, %{status_code: status_code, body: body}}) do + {:ok, parsed} = decode(body) + detail = parsed["detail"] + {:error, Response.error(status_code: status_code, message: detail, raw: parsed)} end defp respond({:error, %HTTPoison.Error{} = error}) do - { - :error, - %Response{ - reason: "network related failure", - message: "HTTPoison says '#{error.reason}' [ID: #{error.id || "nil"}]" - } - } + {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")} end end From 50962f1d304087fb8615e8d3ea953c6b3ecfcf6d Mon Sep 17 00:00:00 2001 From: ravirocx Date: Thu, 22 Mar 2018 23:03:02 +0530 Subject: [PATCH 05/26] bug fixes and optimisation of core functions --- lib/gringotts/gateways/pin_pay.ex | 83 ++++++++++++++++--------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/lib/gringotts/gateways/pin_pay.ex b/lib/gringotts/gateways/pin_pay.ex index 73fb1ec3..c5f888da 100644 --- a/lib/gringotts/gateways/pin_pay.ex +++ b/lib/gringotts/gateways/pin_pay.ex @@ -1,4 +1,4 @@ -defmodule Gringotts.Gateways.Pinpay do +defmodule Gringotts.Gateways.PinPayments do @moduledoc """ [PinPay][home] gateway implementation. @@ -139,7 +139,7 @@ defmodule Gringotts.Gateways.Pinpay do * `refund/3` the amount. ## Optional Fields - options[ + options=[ email_id: String, description: String, ip_address: String (optional) @@ -167,26 +167,31 @@ defmodule Gringotts.Gateways.Pinpay do def authorize(amount, %CreditCard{} = card, opts) do {currency, value, _} = Money.to_integer(amount) + card_token = commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) + |> extract_card_token params = [ amount: value, - capture: false - ] ++ card_params(card, opts) ++ Keyword.delete(opts, :address) + capture: false, + card_token: card_token, + currency: currency + ] ++ Keyword.delete(opts, :address) - commit(:post, "charges", params, [{:currency, currency} | opts]) + commit(:post, "charges", params) end - def authorize(amount, card, opts) when is_binary(card) do + def authorize(amount, card_token, opts) when is_binary(card_token) do {currency, value, _} = Money.to_integer(amount) params = [ amount: value, capture: false, - card_token: card + currency: currency, + card_token: card_token ] ++ Keyword.delete(opts, :address) - commit(:post, "charges", params, [{:currency, currency} | opts]) + commit(:post, "charges", params) end @doc """ @@ -207,7 +212,7 @@ defmodule Gringotts.Gateways.Pinpay do @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response} def capture(payment_id, amount, opts) do url = @test_url <> "charges/" <> payment_id <> "/capture" - commit(:put, url) + commit(:put, url, opts) end @doc """ @@ -228,21 +233,28 @@ defmodule Gringotts.Gateways.Pinpay do @spec purchase(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response} def purchase(amount, card = %CreditCard{}, opts) do {currency, value, _} = Money.to_integer(amount) + + card_token = commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) + |> extract_card_token params = [ - amount: value - ] ++ card_params(card, opts) ++ Keyword.delete(opts, :address) - commit(:post, "charges", params, [{:currency, currency} | opts]) + amount: value, + card_token: card_token, + currency: currency + ] ++ Keyword.delete(opts, :address) + + commit(:post, "charges", params) end - def purchase(amount, card, opts) when is_binary(card) do + def purchase(amount, card_token, opts) when is_binary(card_token) do {currency, value, _} = Money.to_integer(amount) params = [ amount: value, - card_token: card, + card_token: card_token, + currency: currency ] ++ Keyword.delete(opts, :address) - commit(:post, "charges", params, [{:currency, currency} | opts]) + commit(:post, "charges", params) end @doc """ @@ -285,7 +297,7 @@ defmodule Gringotts.Gateways.Pinpay do @spec refund(Money.t, String.t(), keyword) :: {:ok | :error, Response} def refund(amount, payment_id, opts) do url=@test_url <> "charges/" <> payment_id <> "/refunds" - commit(:post, url) + commit(:post, url, opts) end @@ -305,7 +317,7 @@ defmodule Gringotts.Gateways.Pinpay do @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response} def store(%CreditCard{} = card, opts) do - commit(:post, "cards", card_for_token(card, opts), opts) + commit(:post, "cards", card_for_token(card, opts) ++ opts) end @doc """ @@ -335,23 +347,12 @@ defmodule Gringotts.Gateways.Pinpay do # network request in here, and parse it using another private method called # `respond`. - defp card_params(card, opts) do - [ - "card[number]": card.number, - "card[name]": card.first_name <> card.last_name, - "card[expiry_month]": card.month |> Integer.to_string() |> String.pad_leading(2, "0"), - "card[expiry_year]": card.year |> Integer.to_string(), - "card[cvc]": card.verification_code, - "card[address_line1]": opts[:address].street1, - "card[address_city]": opts[:address].city, - "card[address_country]": opts[:address].country - ] - end + defp card_for_token(card, opts) do [ "number": card.number, - "name": card.first_name <> card.last_name, + "name": CreditCard.full_name(card), "expiry_month": card.month |> Integer.to_string() |> String.pad_leading(2, "0"), "expiry_year": card.year |> Integer.to_string(), "cvc": card.verification_code, @@ -361,9 +362,9 @@ defmodule Gringotts.Gateways.Pinpay do ] end - @spec commit(atom, String.t(), keyword, keyword) :: {:ok | :error, Response} - defp commit(:post, endpoint, param, opts) do - auth_token = encoded_credentials("c4nxgznanW4XZUaEQhxS6g", "") + @spec commit(atom, String.t(), keyword) :: {:ok | :error, Response} + defp commit(:post, endpoint, param) do + auth_token = encoded_credentials(param[:config].apiKey, param[:config].pass) headers = [ {"Content-Type", "application/x-www-form-urlencoded"}, @@ -371,14 +372,14 @@ defmodule Gringotts.Gateways.Pinpay do ] url = @test_url <> "#{endpoint}" - + param = Keyword.delete(param, :config) url |> HTTPoison.post({:form, param}, headers) |> respond end - defp commit(method, url) do - auth_token = encoded_credentials("c4nxgznanW4XZUaEQhxS6g", "") + defp commit(method, url, opts) do + auth_token = encoded_credentials(opts[:config].apiKey, opts[:config].pass) headers = [ {"Content-Type", "application/x-www-form-urlencoded"}, @@ -390,13 +391,13 @@ defmodule Gringotts.Gateways.Pinpay do end defp encoded_credentials(login, password) do - [login, password] - |> join_string(":") - |> Base.encode64() - |> (&("Basic " <> &1)).() + hash = Base.encode64("#{login}:#{password}") + "Basic #{hash}" end - defp join_string(list_of_words, joiner), do: Enum.join(list_of_words, joiner) + defp extract_card_token({:ok, %{status_code: code, authorization: token}}) do + token + end # Parses PinPay's response and returns a `Gringotts.Response` struct # in a `:ok`, `:error` tuple. From a7a01d701cb38239c2c428fd3fde445236c073fc Mon Sep 17 00:00:00 2001 From: ravirocx Date: Fri, 23 Mar 2018 16:30:24 +0530 Subject: [PATCH 06/26] Bug fixes and Documentation completed --- lib/gringotts/gateways/pin_pay.ex | 275 ++++++++++++++---------------- 1 file changed, 132 insertions(+), 143 deletions(-) diff --git a/lib/gringotts/gateways/pin_pay.ex b/lib/gringotts/gateways/pin_pay.ex index c5f888da..5b446852 100644 --- a/lib/gringotts/gateways/pin_pay.ex +++ b/lib/gringotts/gateways/pin_pay.ex @@ -1,13 +1,12 @@ defmodule Gringotts.Gateways.PinPayments do @moduledoc """ - [PinPay][home] gateway implementation. + [PinPayments][home] gateway implementation. The login credentials are: | Key | Credentials | | ------ | -------- | | username | `api_key` | - | password | `` | The following features of PinPayments are implemented: @@ -19,13 +18,12 @@ defmodule Gringotts.Gateways.PinPayments do | Purchase | `purchase/3` | | Store | `store/2` | | Refund | `refund/3` | - | Respond | `respond/1` | - ## The `opts` argument + ## The `opts` argument Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply - optional arguments for transactions with the PINPAY gateway. The following keys + optional arguments for transactions with the PinPayments gateway. The following keys are supported: | Key | Type | Remark | @@ -33,10 +31,10 @@ defmodule Gringotts.Gateways.PinPayments do | `address` | `map` | The address of the customer | | `email_id` | `String.t` | Merchant provided email addres | | `description` | `String.t` | Merchant provided description of the transaction | - | `ip_address` | `String.t` | Merchant provided ip address (optional) | + | `ip_address` | `String.t` | Merchant provided ip address (optional) | - > PINPAY supports more optional keys and you can raise an [issue][issues] if + > PinPayments supports more optional keys and you can raise [issues] if this is important to you. [issues]: https://github.com/aviabird/gringotts/issues/new @@ -44,45 +42,46 @@ defmodule Gringotts.Gateways.PinPayments do ### Schema - * `address` is a `map` from `atoms` to `String.t`, and can include any + * `address` is a structure from Gringotts.Address , and include any of the keys from: - `[:street1, :street2, :city, :region, :postal_code, :country, :phone, ]` + `[:street1, :street2, :city, :region, :postal_code, :country, :phone ]` - ## Registering your PINPAY account at `Gringotts` - | Config parameter | PINPAY secret | - | ------- | ---- | - | `:username` | **API_SECRET_KEY** | - | `:password` | Empty string | + ## Registering your PinPayments account at `Gringotts` - > Your Application config **must include the `:username`, `:password` + | Config parameter | PinPayments secret | + | ------- | ---- | + | `:username` | `**API_SECRET_KEY**` | + + > Your Application config **must include the `:username` > fields** and would look something like this: config :gringotts, Gringotts.Gateways.Pinpay, username: "your_secret_key", - password: "", + - * PINPAY **does not** process money in cents. - * Although PINPAY supports payments various cards. This module only + * PinPayments **does** process money in cents. + * Although PinPayments supports payments various cards. This module only accepts payments via `VISA`, `MASTER`, and `AMERICAN EXPRESS`. ## Supported countries - PINPAY supports the countries listed [here][all-country-list] - $ AUD, $ USD, $ NZD, $ SGD, € EUR, £ GBP, $ CAD, ¥ JPY + PinPayments supports the countries listed + * Australia + * New Zealand ## Supported currencies - PINPAY supports the currencies listed [here][all-currency-list] - :AUD, :USD, :NZD, :SGD, :EUR, :GBP, :CAD, :HKD, :JPY, :MYR, :THB, :PHP, :ZAR, :IDR, :TWD + PinPayments supports the currencies listed [here](https://pinPayments.com/developers/api-reference/currency-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][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" + - 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-PinPay). 2. Run an `iex` session with `iex -S mix` and add some variable bindings and @@ -96,13 +95,11 @@ defmodule Gringotts.Gateways.PinPayments do 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://pinpayments.com - [docs]: https://pinpayments.com/developers/api-reference + [home]: https://pinPayments.com + [docs]: https://pinPayments.com/developers/api-reference [example]: https://github.com/aviabird/gringotts_example """ @@ -119,34 +116,18 @@ defmodule Gringotts.Gateways.PinPayments do alias Gringotts.{Money, CreditCard, Response} - @test_url "https://test-api.pinpayments.com/1/" - @production_url "https://api.pinpayments.com/1/" + @test_url "https://test-api.pinPayments.com/1/" + @production_url "https://api.pinPayments.com/1/" @doc """ - Performs a (pre) Authorize operation. + Creates a new charge and returns its details. 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.) - PINPAY returns a **Payment Id** (available in the `Response.authorization` - field) which can be used later to: + PinPayments returns a **Token Id** which can be used later to: * `capture/3` an amount. - * `refund/3` the amount. - - ## Optional Fields - options=[ - email_id: String, - description: String, - ip_address: String (optional) - ] - - - ## Example @@ -158,17 +139,19 @@ defmodule Gringotts.Gateways.PinPayments do number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"} - iex> money = %{value: Decimal.new(20), currency: "USD"} - iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Pinpay, money, card) + iex> money = Money.new(20000, :USD) + iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.PinPayments, amount, card, opts) ``` """ - @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response} + @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response} def authorize(amount, %CreditCard{} = card, opts) do {currency, value, _} = Money.to_integer(amount) - card_token = commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) - |> extract_card_token + card_token = + commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) + |> extract_card_token + params = [ amount: value, @@ -195,47 +178,75 @@ defmodule Gringotts.Gateways.PinPayments do end @doc """ - Captures a pre-authorized `amount`. + Captures a previously authorised charge and returns its details. - `amount` is transferred to the merchant account by PinPay used in the + `amount` is transferred to the merchant account by PinPayments used in the pre-authorization referenced by `payment_id`. - ## Note + Captures a previously authorised charge and returns its details. + Currently, you can only capture the full amount that was originally authorised. - > If there's anything noteworthy about this operation, it comes here. - > For example, does the gateway support partial, multiple captures? + PinPayments returns a **Payment Id** which can be used later to: + * `refund/3` the amount. - ## Example + ## Examples - > A barebones example using the bindings you've suggested in the `moduledoc`. + The following example shows how one would capture a previously + authorized a payment worth $10 by referencing the obtained authorization `id`. + ``` + iex> card = %CreditCard{first_name: "Harry", + last_name: "Potter", + number: "4200000000000000", + year: 2099, + month: 12, + verification_code: "999", + brand: "VISA"} + iex> money = Money.new(10000, :USD) + iex> authorization = auth_result.authorization + # authorization = "some_authorization_transaction_id" + iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.PinPayments, amount, card, opts) + ``` """ @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response} def capture(payment_id, amount, opts) do url = @test_url <> "charges/" <> payment_id <> "/capture" - commit(:put, url, opts) + commit_short(:put, url, opts) end @doc """ Transfers `amount` from the customer to the merchant. - PinPay attempts to process a purchase on behalf of the customer, by + PinPayments 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. + PinPayments returns a **Payment Id** which can be used later to: + * `refund/3` the amount. - ## Example + ## Examples - > A barebones example using the bindings you've suggested in the `moduledoc`. + 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: "4200000000000000", + year: 2099, + month: 12, + verification_code: "999", + brand: "VISA"} + iex> money = Money.new(20, :USD) + iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.PinPayments, amount, card, opts) + ``` """ - @spec purchase(Money.t, CreditCard.t(), keyword) :: {:ok | :error, Response} - def purchase(amount, card = %CreditCard{}, opts) do + @spec purchase(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response} + def purchase(amount, %CreditCard{} = card, opts) do {currency, value, _} = Money.to_integer(amount) - card_token = commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) - |> extract_card_token + card_token = + commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) + |> extract_card_token + params = [ amount: value, @@ -248,94 +259,71 @@ defmodule Gringotts.Gateways.PinPayments do def purchase(amount, card_token, opts) when is_binary(card_token) do {currency, value, _} = Money.to_integer(amount) + params = [ amount: value, card_token: card_token, currency: currency ] ++ Keyword.delete(opts, :address) - commit(:post, "charges", params) - 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 - #can't be implemented in pinpayments + commit(:post, "charges", params) 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? + PinPayments processes a full 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 PinPay? - > Is there a limited time window within which a void can be perfomed? + The end customer will usually see two bookings/records on his statement. ## Example - - > A barebones example using the bindings you've suggested in the `moduledoc`. + ``` + iex> money = Money.new(20, :USD) + iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.PinPayments, amount, payment_id, opts) + ``` """ - @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 - url=@test_url <> "charges/" <> payment_id <> "/refunds" - commit(:post, url, opts) - end + url = @test_url <> "charges/" <> payment_id <> "/refunds" + commit_short(:post, url, opts) + end @doc """ Stores the payment-source data for later use. - > This usually enable "One Click" and/or "Recurring Payments" + PinPayments can store the payment-source details, for example card or bank details + which can be used to effectively process _One-Click_ and _Recurring_ payments, + and return a card token for reference. + ## Note - > If there's anything noteworthy about this operation, it comes here. + * _One-Click_ and _Recurring_ payments are currently not implemented. + * Payment details can be saved during a `purchase/3` or `capture/3`. + ## Example - > A barebones example using the bindings you've suggested in the `moduledoc`. + The following example shows how one would store a card (a payment-source) for + future use. + ``` + iex> card = %CreditCard{first_name: "Harry", + last_name: "Potter", + number: "4200000000000000", + year: 2099, + month: 12, + verification_code: "999", + brand: "VISA"} + iex> {:ok, store_result} = Gringotts.store(Gringotts.Gateways.PinPayments, card, opts) + ``` """ - + @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response} def store(%CreditCard{} = card, opts) do - commit(:post, "cards", card_for_token(card, opts) ++ opts) - 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 - # can't be implemented in pinpayments + commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) end ############################################################################### @@ -347,18 +335,16 @@ defmodule Gringotts.Gateways.PinPayments do # network request in here, and parse it using another private method called # `respond`. - - defp card_for_token(card, opts) do [ - "number": card.number, - "name": CreditCard.full_name(card), - "expiry_month": card.month |> Integer.to_string() |> String.pad_leading(2, "0"), - "expiry_year": card.year |> Integer.to_string(), - "cvc": card.verification_code, - "address_line1": opts[:Address][:street1], - "address_city": opts[:Address][:city], - "address_country": opts[:Address][:country] + number: card.number, + name: CreditCard.full_name(card), + expiry_month: card.month |> Integer.to_string() |> String.pad_leading(2, "0"), + expiry_year: card.year |> Integer.to_string(), + cvc: card.verification_code, + address_line1: opts[:Address][:street1], + address_city: opts[:Address][:city], + address_country: opts[:Address][:country] ] end @@ -373,12 +359,13 @@ defmodule Gringotts.Gateways.PinPayments do url = @test_url <> "#{endpoint}" param = Keyword.delete(param, :config) + url |> HTTPoison.post({:form, param}, headers) |> respond end - defp commit(method, url, opts) do + defp commit_short(method, url, opts) do auth_token = encoded_credentials(opts[:config].apiKey, opts[:config].pass) headers = [ @@ -395,8 +382,8 @@ defmodule Gringotts.Gateways.PinPayments do "Basic #{hash}" end - defp extract_card_token({:ok, %{status_code: code, authorization: token}}) do - token + defp extract_card_token({:ok, %{status_code: code, token: token}}) do + token end # Parses PinPay's response and returns a `Gringotts.Response` struct @@ -407,8 +394,10 @@ defmodule Gringotts.Gateways.PinPayments do {:ok, parsed} = decode(body) token = parsed["response"]["token"] message = parsed["response"]["status_message"] + { - :ok, Response.success(authorization: token, message: message, raw: parsed, status_code: code) + :ok, + Response.success(token: token, message: message, raw: parsed, status_code: code) } end From c8907b7888468930079c91c702b560b65124637d Mon Sep 17 00:00:00 2001 From: ravirocx Date: Wed, 28 Mar 2018 23:34:15 +0530 Subject: [PATCH 07/26] Optimised --- lib/gringotts/gateways/pin_pay.ex | 185 +++--------------------------- 1 file changed, 16 insertions(+), 169 deletions(-) diff --git a/lib/gringotts/gateways/pin_pay.ex b/lib/gringotts/gateways/pin_pay.ex index 5b446852..700c29d1 100644 --- a/lib/gringotts/gateways/pin_pay.ex +++ b/lib/gringotts/gateways/pin_pay.ex @@ -26,12 +26,12 @@ defmodule Gringotts.Gateways.PinPayments do optional arguments for transactions with the PinPayments gateway. The following keys are supported: - | Key | Type | Remark | - | ---- | ---- | --- | - | `address` | `map` | The address of the customer | - | `email_id` | `String.t` | Merchant provided email addres | - | `description` | `String.t` | Merchant provided description of the transaction | - | `ip_address` | `String.t` | Merchant provided ip address (optional) | + | Key | Type | Remark | + | ---- | ---- | --- | + | `address` | `Address.t`| The address of the customer | + | `email_id` | `String.t` | The email address of the purchaser. | + | `description` | `String.t` | A description of the item purchased (e.g. 500g of single origin beans) | + | `ip_address` | `String.t` | The IP address of the person submitting the payment(optional) | > PinPayments supports more optional keys and you can raise [issues] if @@ -39,15 +39,6 @@ defmodule Gringotts.Gateways.PinPayments do [issues]: https://github.com/aviabird/gringotts/issues/new - - ### Schema - - * `address` is a structure from Gringotts.Address , and include any - of the keys from: - - `[:street1, :street2, :city, :region, :postal_code, :country, :phone ]` - - ## Registering your PinPayments account at `Gringotts` | Config parameter | PinPayments secret | @@ -77,7 +68,7 @@ defmodule Gringotts.Gateways.PinPayments do ## 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 PinPayments. - 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. @@ -139,7 +130,7 @@ defmodule Gringotts.Gateways.PinPayments do number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"} - iex> money = Money.new(20000, :USD) + iex> money = Money.new(20, :USD) iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.PinPayments, amount, card, opts) ``` """ @@ -177,155 +168,7 @@ defmodule Gringotts.Gateways.PinPayments do commit(:post, "charges", params) end - @doc """ - Captures a previously authorised charge and returns its details. - - `amount` is transferred to the merchant account by PinPayments used in the - pre-authorization referenced by `payment_id`. - - Captures a previously authorised charge and returns its details. - Currently, you can only capture the full amount that was originally authorised. - - PinPayments returns a **Payment Id** which can be used later to: - * `refund/3` the amount. - - ## Examples - - The following example shows how one would capture a previously - authorized a payment worth $10 by referencing the obtained authorization `id`. - ``` - iex> card = %CreditCard{first_name: "Harry", - last_name: "Potter", - number: "4200000000000000", - year: 2099, - month: 12, - verification_code: "999", - brand: "VISA"} - iex> money = Money.new(10000, :USD) - iex> authorization = auth_result.authorization - # authorization = "some_authorization_transaction_id" - iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.PinPayments, amount, card, opts) - ``` - """ - @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response} - def capture(payment_id, amount, opts) do - url = @test_url <> "charges/" <> payment_id <> "/capture" - commit_short(:put, url, opts) - end - - @doc """ - Transfers `amount` from the customer to the merchant. - - PinPayments attempts to process a purchase on behalf of the customer, by - debiting `amount` from the customer's account by charging the customer's - `card`. - - PinPayments returns a **Payment Id** which can be used later to: - * `refund/3` the amount. - - ## Examples - - 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: "4200000000000000", - year: 2099, - month: 12, - verification_code: "999", - brand: "VISA"} - iex> money = Money.new(20, :USD) - iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.PinPayments, amount, card, opts) - ``` - """ - @spec purchase(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response} - def purchase(amount, %CreditCard{} = card, opts) do - {currency, value, _} = Money.to_integer(amount) - - card_token = - commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) - |> extract_card_token - - params = - [ - amount: value, - card_token: card_token, - currency: currency - ] ++ Keyword.delete(opts, :address) - - commit(:post, "charges", params) - end - - def purchase(amount, card_token, opts) when is_binary(card_token) do - {currency, value, _} = Money.to_integer(amount) - - params = - [ - amount: value, - card_token: card_token, - currency: currency - ] ++ Keyword.delete(opts, :address) - - commit(:post, "charges", params) - end - - @doc """ - Refunds the `amount` to the customer's account with reference to a prior transfer. - - PinPayments processes a full refund worth `amount`, referencing a - previous `purchase/3` or `capture/3`. - - The end customer will usually see two bookings/records on his statement. - - ## Example - ``` - iex> money = Money.new(20, :USD) - iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.PinPayments, amount, payment_id, opts) - ``` - """ - @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response} - def refund(amount, payment_id, opts) do - url = @test_url <> "charges/" <> payment_id <> "/refunds" - - commit_short(:post, url, opts) - end - - @doc """ - Stores the payment-source data for later use. - - PinPayments can store the payment-source details, for example card or bank details - which can be used to effectively process _One-Click_ and _Recurring_ payments, - and return a card token for reference. - - - ## Note - - * _One-Click_ and _Recurring_ payments are currently not implemented. - * Payment details can be saved during a `purchase/3` or `capture/3`. - - - ## Example - - The following example shows how one would store a card (a payment-source) for - future use. - ``` - iex> card = %CreditCard{first_name: "Harry", - last_name: "Potter", - number: "4200000000000000", - year: 2099, - month: 12, - verification_code: "999", - brand: "VISA"} - iex> {:ok, store_result} = Gringotts.store(Gringotts.Gateways.PinPayments, card, opts) - ``` - """ - - @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response} - def store(%CreditCard{} = card, opts) do - commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) - end - + ############################################################################### # PRIVATE METHODS # ############################################################################### @@ -344,13 +187,17 @@ defmodule Gringotts.Gateways.PinPayments do cvc: card.verification_code, address_line1: opts[:Address][:street1], address_city: opts[:Address][:city], - address_country: opts[:Address][:country] + address_country: opts[:Address][:country], + address_line2: opts[:Address][:street2], + address_postcode: opts[:Address][:postal_code], + address_state: opts[:Address][:region] + ] end @spec commit(atom, String.t(), keyword) :: {:ok | :error, Response} defp commit(:post, endpoint, param) do - auth_token = encoded_credentials(param[:config].apiKey, param[:config].pass) + auth_token = encoded_credentials(param[:config].apiKey, "") headers = [ {"Content-Type", "application/x-www-form-urlencoded"}, @@ -366,7 +213,7 @@ defmodule Gringotts.Gateways.PinPayments do end defp commit_short(method, url, opts) do - auth_token = encoded_credentials(opts[:config].apiKey, opts[:config].pass) + auth_token = encoded_credentials(opts[:config].apiKey, "") headers = [ {"Content-Type", "application/x-www-form-urlencoded"}, From f36920070232a33a439c6c9f741d53bd4dc07ebf Mon Sep 17 00:00:00 2001 From: ravirocx Date: Thu, 29 Mar 2018 16:30:56 +0530 Subject: [PATCH 08/26] bug fixed --- lib/gringotts/gateways/pin_pay.ex | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/gringotts/gateways/pin_pay.ex b/lib/gringotts/gateways/pin_pay.ex index 700c29d1..16f45331 100644 --- a/lib/gringotts/gateways/pin_pay.ex +++ b/lib/gringotts/gateways/pin_pay.ex @@ -6,7 +6,7 @@ defmodule Gringotts.Gateways.PinPayments do | Key | Credentials | | ------ | -------- | - | username | `api_key` | + | apiKey | `api_key` | The following features of PinPayments are implemented: @@ -43,13 +43,13 @@ defmodule Gringotts.Gateways.PinPayments do | Config parameter | PinPayments secret | | ------- | ---- | - | `:username` | `**API_SECRET_KEY**` | + | `:apiKey` | `**API_SECRET_KEY**` | - > Your Application config **must include the `:username` + > Your Application config **must include the `:apiKey` > fields** and would look something like this: config :gringotts, Gringotts.Gateways.Pinpay, - username: "your_secret_key", + apiKey: "your_secret_key", * PinPayments **does** process money in cents. @@ -73,7 +73,7 @@ defmodule Gringotts.Gateways.PinPayments 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-PinPay). + as described [above](#module-configuring-your-pinpay-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): @@ -101,7 +101,7 @@ defmodule Gringotts.Gateways.PinPayments do # 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: [] + use Gringotts.Adapter, required_config: [:apiKey] import Poison, only: [decode: 1] @@ -185,19 +185,19 @@ defmodule Gringotts.Gateways.PinPayments do expiry_month: card.month |> Integer.to_string() |> String.pad_leading(2, "0"), expiry_year: card.year |> Integer.to_string(), cvc: card.verification_code, - address_line1: opts[:Address][:street1], - address_city: opts[:Address][:city], - address_country: opts[:Address][:country], - address_line2: opts[:Address][:street2], - address_postcode: opts[:Address][:postal_code], - address_state: opts[:Address][:region] + address_line1: opts[:address].street1, + address_city: opts[:address].city, + address_country: opts[:address].country, + address_line2: opts[:address].street2, + address_postcode: opts[:address].postal_code, + address_state: opts[:address].region ] end @spec commit(atom, String.t(), keyword) :: {:ok | :error, Response} defp commit(:post, endpoint, param) do - auth_token = encoded_credentials(param[:config].apiKey, "") + auth_token = encoded_credentials(param[:config].apiKey) headers = [ {"Content-Type", "application/x-www-form-urlencoded"}, @@ -213,7 +213,7 @@ defmodule Gringotts.Gateways.PinPayments do end defp commit_short(method, url, opts) do - auth_token = encoded_credentials(opts[:config].apiKey, "") + auth_token = encoded_credentials(opts[:config].apiKey) headers = [ {"Content-Type", "application/x-www-form-urlencoded"}, @@ -224,8 +224,8 @@ defmodule Gringotts.Gateways.PinPayments do |> respond end - defp encoded_credentials(login, password) do - hash = Base.encode64("#{login}:#{password}") + defp encoded_credentials(login) do + hash = Base.encode64("#{login}:") "Basic #{hash}" end From fe28bb9b4ceb52c7087f5d0e0d039eef1ba92fd7 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Thu, 29 Mar 2018 17:42:16 +0530 Subject: [PATCH 09/26] bug fixes --- lib/gringotts/gateways/pin_payments.ex | 253 +++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 lib/gringotts/gateways/pin_payments.ex diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex new file mode 100644 index 00000000..75d4b7ec --- /dev/null +++ b/lib/gringotts/gateways/pin_payments.ex @@ -0,0 +1,253 @@ +defmodule Gringotts.Gateways.PinPayments do + @moduledoc """ + [PinPayments][home] gateway implementation. + + The following features of PinPayments are implemented: + + | Action | Method | + | ------ | ------ | + | Authorize | `authorize/3` | + | Capture | `capture/3` | + | Purchase | `purchase/3` | + | Store | `store/2` | + | Refund | `refund/3` | + + + ## The `opts` argument + + Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply + optional arguments for transactions with the PinPayments gateway. The following keys + are supported: + + | Key | Type | Remark | + | ---- | ---- | --- | + | `address` | `Address.t`| The address of the customer | + | `email_id` | `String.t` | The email address of the purchaser. | + | `description` | `String.t` | A description of the item purchased (e.g. 500g of single origin beans) | + | `ip_address` | `String.t` | The IP address of the person submitting the payment(optional) | + + + > PinPayments supports more optional keys and you can raise [issues] if + this is important to you. + + [issues]: https://github.com/aviabird/gringotts/issues/new + + ## Registering your PinPayments account at `Gringotts` + + | Config parameter | PinPayments secret | + | ------- | ---- | + | `:api_key` | `**API_SECRET_KEY**` | + + > Your Application config **must include the `:api_Key` + > fields** and would look something like this: + + config :gringotts, Gringotts.Gateways.Pinpay, + api_key: "your_secret_key", + + + * PinPayments **does** process money in cents. + * Although PinPayments supports payments various cards. This module only + accepts payments via `VISA`, `MASTER`, and `AMERICAN EXPRESS`. + + ## Supported countries + PinPayments supports the countries listed + * Australia + * New Zealand + + ## Supported currencies + PinPayments supports the currencies listed [here](https://pinPayments.com/developers/api-reference/currency-support) + + + + ## Following the examples + + 1. First, set up a sample application and configure it to work with PinPayments. + - 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-pinpayments-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.Pinpay} + iex> card = %CreditCard{first_name: "Jo", + last_name: "Doe", + number: "4200000000000000", + year: 2099, month: 12, + verification_code: "123", brand: "VISA"} + ``` + + We'll be using these in the examples below. + + [gs]: https://github.com/aviabird/gringotts/wiki/ + [home]: https://pinPayments.com + [docs]: https://pinPayments.com/developers/api-reference + [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: [:api_key] + + import Poison, only: [decode: 1] + + alias Gringotts.{Money, CreditCard, Response} + + @test_url "https://test-api.pinPayments.com/1/" + @production_url "https://api.pinPayments.com/1/" + + @doc """ + Creates a new charge and returns its details. + + The authorization validates the `card` details with the banking network, + places a hold on the transaction `amount` in the customer’s issuing bank. + + + PinPayments returns a **Token Id** which can be used later to: + * `capture/3` an amount. + + ## Example + + The following example shows how one would (pre) authorize a payment of $20 on + a sample `card`. + ``` + iex> card = %CreditCard{first_name: "Jo", + last_name: "Doe", + number: "4200000000000000", + year: 2099, month: 12, + verification_code: "123", brand: "VISA"} + iex> money = Money.new(20, :USD) + iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.PinPayments, amount, card, opts) + ``` + """ + + @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response} + def authorize(amount, %CreditCard{} = card, opts) do + {currency, value, _} = Money.to_integer(amount) + + card_token = + commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) + |> extract_card_token + + params = + [ + amount: value, + capture: false, + card_token: card_token, + currency: currency + ] ++ Keyword.delete(opts, :address) + + commit(:post, "charges", params) + end + + def authorize(amount, card_token, opts) when is_binary(card_token) do + {currency, value, _} = Money.to_integer(amount) + + params = + [ + amount: value, + capture: false, + currency: currency, + card_token: card_token + ] ++ Keyword.delete(opts, :address) + + commit(:post, "charges", params) + end + + + ############################################################################### + # PRIVATE METHODS # + ############################################################################### + + # Makes the request to PinPay'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 card_for_token(card, opts) do + [ + number: card.number, + name: CreditCard.full_name(card), + expiry_month: card.month |> Integer.to_string() |> String.pad_leading(2, "0"), + expiry_year: card.year |> Integer.to_string(), + cvc: card.verification_code, + address_line1: opts[:address].street1, + address_city: opts[:address].city, + address_country: opts[:address].country, + address_line2: opts[:address].street2, + address_postcode: opts[:address].postal_code, + address_state: opts[:address].region + + ] + end + + @spec commit(atom, String.t(), keyword) :: {:ok | :error, Response} + defp commit(:post, endpoint, param) do + auth_token = encoded_credentials(param[:config].apiKey) + + headers = [ + {"Content-Type", "application/x-www-form-urlencoded"}, + {"Authorization", auth_token} + ] + + url = @test_url <> "#{endpoint}" + param = Keyword.delete(param, :config) + + url + |> HTTPoison.post({:form, param}, headers) + |> respond + end + + defp commit_short(method, url, opts) do + auth_token = encoded_credentials(opts[:config].apiKey) + + headers = [ + {"Content-Type", "application/x-www-form-urlencoded"}, + {"Authorization", auth_token} + ] + + HTTPoison.request(method, url, [], headers) + |> respond + end + + defp encoded_credentials(login) do + hash = Base.encode64("#{login}:") + "Basic #{hash}" + end + + defp extract_card_token({:ok, %{status_code: code, token: token}}) do + token + end + + # Parses PinPay's response and returns a `Gringotts.Response` struct + # in a `:ok`, `:error` tuple. + @spec respond(term) :: {:ok | :error, Response} + + defp respond({:ok, %{status_code: code, body: body}}) when code in [200, 201] do + {:ok, parsed} = decode(body) + token = parsed["response"]["token"] + message = parsed["response"]["status_message"] + + { + :ok, + Response.success(token: token, message: message, raw: parsed, status_code: code) + } + end + + defp respond({:ok, %{status_code: status_code, body: body}}) do + {:ok, parsed} = decode(body) + detail = parsed["detail"] + {:error, Response.error(status_code: status_code, message: detail, raw: parsed)} + end + + defp respond({:error, %HTTPoison.Error{} = error}) do + {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")} + end +end From f584e758d25ca54b0eca6aaea414184158fabcd9 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Thu, 29 Mar 2018 17:44:45 +0530 Subject: [PATCH 10/26] after merge --- test/gateways/pin_pay_test.exs | 32 ++++++++++++++++++++++++++++++++ test/mocks/pin_pay_mock.exs | 9 +++++++++ 2 files changed, 41 insertions(+) create mode 100644 test/gateways/pin_pay_test.exs create mode 100644 test/mocks/pin_pay_mock.exs diff --git a/test/gateways/pin_pay_test.exs b/test/gateways/pin_pay_test.exs new file mode 100644 index 00000000..7041bbcb --- /dev/null +++ b/test/gateways/pin_pay_test.exs @@ -0,0 +1,32 @@ +defmodule Gringotts.Gateways.PinpaymentsTest do + # The file contains mocked tests for Pinpayments + + # We recommend using [mock][1] for this, you can place the mock responses from + # the Gateway in `test/mocks/pin_pay_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/pin_pay_mock.exs", __DIR__ + + use ExUnit.Case, async: false + alias Gringotts.Gateways.Pinpayments + 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/mocks/pin_pay_mock.exs b/test/mocks/pin_pay_mock.exs new file mode 100644 index 00000000..919bf9bb --- /dev/null +++ b/test/mocks/pin_pay_mock.exs @@ -0,0 +1,9 @@ +defmodule Gringotts.Gateways.PinpaymentsMock do + + # The module should include mock responses for test cases in pin_pay_test.exs. + # e.g. + # def successful_purchase do + # {:ok, %HTTPoison.Response{body: ~s[{data: "successful_purchase"}]} + # end + +end From befe5d1c6e46cfe701c22f50b26d423a38fefabb Mon Sep 17 00:00:00 2001 From: ravirocx Date: Thu, 29 Mar 2018 17:47:28 +0530 Subject: [PATCH 11/26] bug fixes --- lib/gringotts/gateways/pin_pay.ex | 260 ------------------------------ 1 file changed, 260 deletions(-) delete mode 100644 lib/gringotts/gateways/pin_pay.ex diff --git a/lib/gringotts/gateways/pin_pay.ex b/lib/gringotts/gateways/pin_pay.ex deleted file mode 100644 index 16f45331..00000000 --- a/lib/gringotts/gateways/pin_pay.ex +++ /dev/null @@ -1,260 +0,0 @@ -defmodule Gringotts.Gateways.PinPayments do - @moduledoc """ - [PinPayments][home] gateway implementation. - - The login credentials are: - - | Key | Credentials | - | ------ | -------- | - | apiKey | `api_key` | - - - The following features of PinPayments are implemented: - - | Action | Method | - | ------ | ------ | - | Authorize | `authorize/3` | - | Capture | `capture/3` | - | Purchase | `purchase/3` | - | Store | `store/2` | - | Refund | `refund/3` | - - - ## The `opts` argument - - Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply - optional arguments for transactions with the PinPayments gateway. The following keys - are supported: - - | Key | Type | Remark | - | ---- | ---- | --- | - | `address` | `Address.t`| The address of the customer | - | `email_id` | `String.t` | The email address of the purchaser. | - | `description` | `String.t` | A description of the item purchased (e.g. 500g of single origin beans) | - | `ip_address` | `String.t` | The IP address of the person submitting the payment(optional) | - - - > PinPayments supports more optional keys and you can raise [issues] if - this is important to you. - - [issues]: https://github.com/aviabird/gringotts/issues/new - - ## Registering your PinPayments account at `Gringotts` - - | Config parameter | PinPayments secret | - | ------- | ---- | - | `:apiKey` | `**API_SECRET_KEY**` | - - > Your Application config **must include the `:apiKey` - > fields** and would look something like this: - - config :gringotts, Gringotts.Gateways.Pinpay, - apiKey: "your_secret_key", - - - * PinPayments **does** process money in cents. - * Although PinPayments supports payments various cards. This module only - accepts payments via `VISA`, `MASTER`, and `AMERICAN EXPRESS`. - - ## Supported countries - PinPayments supports the countries listed - * Australia - * New Zealand - - ## Supported currencies - PinPayments supports the currencies listed [here](https://pinPayments.com/developers/api-reference/currency-support) - - - - ## Following the examples - - 1. First, set up a sample application and configure it to work with PinPayments. - - 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-configuring-your-pinpay-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.Pinpay} - iex> card = %CreditCard{first_name: "Jo", - last_name: "Doe", - number: "4200000000000000", - year: 2099, month: 12, - verification_code: "123", brand: "VISA"} - ``` - - We'll be using these in the examples below. - - [gs]: https://github.com/aviabird/gringotts/wiki/ - [home]: https://pinPayments.com - [docs]: https://pinPayments.com/developers/api-reference - [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: [:apiKey] - - import Poison, only: [decode: 1] - - alias Gringotts.{Money, CreditCard, Response} - - @test_url "https://test-api.pinPayments.com/1/" - @production_url "https://api.pinPayments.com/1/" - - @doc """ - Creates a new charge and returns its details. - - The authorization validates the `card` details with the banking network, - places a hold on the transaction `amount` in the customer’s issuing bank. - - - PinPayments returns a **Token Id** which can be used later to: - * `capture/3` an amount. - - ## Example - - The following example shows how one would (pre) authorize a payment of $20 on - a sample `card`. - ``` - iex> card = %CreditCard{first_name: "Jo", - last_name: "Doe", - number: "4200000000000000", - year: 2099, month: 12, - verification_code: "123", brand: "VISA"} - iex> money = Money.new(20, :USD) - iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.PinPayments, amount, card, opts) - ``` - """ - - @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response} - def authorize(amount, %CreditCard{} = card, opts) do - {currency, value, _} = Money.to_integer(amount) - - card_token = - commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) - |> extract_card_token - - params = - [ - amount: value, - capture: false, - card_token: card_token, - currency: currency - ] ++ Keyword.delete(opts, :address) - - commit(:post, "charges", params) - end - - def authorize(amount, card_token, opts) when is_binary(card_token) do - {currency, value, _} = Money.to_integer(amount) - - params = - [ - amount: value, - capture: false, - currency: currency, - card_token: card_token - ] ++ Keyword.delete(opts, :address) - - commit(:post, "charges", params) - end - - - ############################################################################### - # PRIVATE METHODS # - ############################################################################### - - # Makes the request to PinPay'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 card_for_token(card, opts) do - [ - number: card.number, - name: CreditCard.full_name(card), - expiry_month: card.month |> Integer.to_string() |> String.pad_leading(2, "0"), - expiry_year: card.year |> Integer.to_string(), - cvc: card.verification_code, - address_line1: opts[:address].street1, - address_city: opts[:address].city, - address_country: opts[:address].country, - address_line2: opts[:address].street2, - address_postcode: opts[:address].postal_code, - address_state: opts[:address].region - - ] - end - - @spec commit(atom, String.t(), keyword) :: {:ok | :error, Response} - defp commit(:post, endpoint, param) do - auth_token = encoded_credentials(param[:config].apiKey) - - headers = [ - {"Content-Type", "application/x-www-form-urlencoded"}, - {"Authorization", auth_token} - ] - - url = @test_url <> "#{endpoint}" - param = Keyword.delete(param, :config) - - url - |> HTTPoison.post({:form, param}, headers) - |> respond - end - - defp commit_short(method, url, opts) do - auth_token = encoded_credentials(opts[:config].apiKey) - - headers = [ - {"Content-Type", "application/x-www-form-urlencoded"}, - {"Authorization", auth_token} - ] - - HTTPoison.request(method, url, [], headers) - |> respond - end - - defp encoded_credentials(login) do - hash = Base.encode64("#{login}:") - "Basic #{hash}" - end - - defp extract_card_token({:ok, %{status_code: code, token: token}}) do - token - end - - # Parses PinPay's response and returns a `Gringotts.Response` struct - # in a `:ok`, `:error` tuple. - @spec respond(term) :: {:ok | :error, Response} - - defp respond({:ok, %{status_code: code, body: body}}) when code in [200, 201] do - {:ok, parsed} = decode(body) - token = parsed["response"]["token"] - message = parsed["response"]["status_message"] - - { - :ok, - Response.success(token: token, message: message, raw: parsed, status_code: code) - } - end - - defp respond({:ok, %{status_code: status_code, body: body}}) do - {:ok, parsed} = decode(body) - detail = parsed["detail"] - {:error, Response.error(status_code: status_code, message: detail, raw: parsed)} - end - - defp respond({:error, %HTTPoison.Error{} = error}) do - {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")} - end -end From 6049aaf834b382ca5d3aae46173996c2ca0561f5 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Thu, 29 Mar 2018 20:55:13 +0530 Subject: [PATCH 12/26] formatted --- lib/gringotts/gateways/pin_payments.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index 75d4b7ec..6c56204e 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -161,7 +161,6 @@ defmodule Gringotts.Gateways.PinPayments do commit(:post, "charges", params) end - ############################################################################### # PRIVATE METHODS # ############################################################################### @@ -184,7 +183,6 @@ defmodule Gringotts.Gateways.PinPayments do address_line2: opts[:address].street2, address_postcode: opts[:address].postal_code, address_state: opts[:address].region - ] end From 24e357bec83702a6cba39ade506171d030536192 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Thu, 29 Mar 2018 21:00:49 +0530 Subject: [PATCH 13/26] formatted all files --- test/gateways/pin_pay_test.exs | 10 +++++----- test/mocks/pin_pay_mock.exs | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/test/gateways/pin_pay_test.exs b/test/gateways/pin_pay_test.exs index 7041bbcb..9442c6ef 100644 --- a/test/gateways/pin_pay_test.exs +++ b/test/gateways/pin_pay_test.exs @@ -1,15 +1,15 @@ defmodule Gringotts.Gateways.PinpaymentsTest do # The file contains mocked tests for Pinpayments - + # We recommend using [mock][1] for this, you can place the mock responses from # the Gateway in `test/mocks/pin_pay_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/pin_pay_mock.exs", __DIR__ - + Code.require_file("../mocks/pin_pay_mock.exs", __DIR__) + use ExUnit.Case, async: false alias Gringotts.Gateways.Pinpayments import Mock @@ -21,7 +21,7 @@ defmodule Gringotts.Gateways.PinpaymentsTest do describe "authorize" do end - describe "capture" do + describe "capture" do end describe "void" do diff --git a/test/mocks/pin_pay_mock.exs b/test/mocks/pin_pay_mock.exs index 919bf9bb..232a1913 100644 --- a/test/mocks/pin_pay_mock.exs +++ b/test/mocks/pin_pay_mock.exs @@ -1,9 +1,7 @@ defmodule Gringotts.Gateways.PinpaymentsMock do - # The module should include mock responses for test cases in pin_pay_test.exs. # e.g. # def successful_purchase do # {:ok, %HTTPoison.Response{body: ~s[{data: "successful_purchase"}]} # end - end From 49a72fe2a129c8f1be32ca5af1f743379c3150f7 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Fri, 30 Mar 2018 12:21:45 +0530 Subject: [PATCH 14/26] improved code readability --- lib/gringotts/gateways/pin_payments.ex | 25 ++++---------------- test/gateways/pin_pay_test.exs | 32 -------------------------- test/mocks/pin_pay_mock.exs | 7 ------ 3 files changed, 4 insertions(+), 60 deletions(-) delete mode 100644 test/gateways/pin_pay_test.exs delete mode 100644 test/mocks/pin_pay_mock.exs diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index 6c56204e..baab7c0a 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -12,7 +12,6 @@ defmodule Gringotts.Gateways.PinPayments do | Store | `store/2` | | Refund | `refund/3` | - ## The `opts` argument Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply @@ -26,7 +25,6 @@ defmodule Gringotts.Gateways.PinPayments do | `description` | `String.t` | A description of the item purchased (e.g. 500g of single origin beans) | | `ip_address` | `String.t` | The IP address of the person submitting the payment(optional) | - > PinPayments supports more optional keys and you can raise [issues] if this is important to you. @@ -38,7 +36,7 @@ defmodule Gringotts.Gateways.PinPayments do | ------- | ---- | | `:api_key` | `**API_SECRET_KEY**` | - > Your Application config **must include the `:api_Key` + > Your Application config **must include the `:api_key` > fields** and would look something like this: config :gringotts, Gringotts.Gateways.Pinpay, @@ -57,8 +55,6 @@ defmodule Gringotts.Gateways.PinPayments do ## Supported currencies PinPayments supports the currencies listed [here](https://pinPayments.com/developers/api-reference/currency-support) - - ## Following the examples 1. First, set up a sample application and configure it to work with PinPayments. @@ -88,7 +84,7 @@ defmodule Gringotts.Gateways.PinPayments do """ # The Base module has the (abstract) public API, and some utility - # implementations. + # implementations. use Gringotts.Gateways.Base # The Adapter module provides the `validate_config/1` @@ -109,7 +105,6 @@ defmodule Gringotts.Gateways.PinPayments do The authorization validates the `card` details with the banking network, places a hold on the transaction `amount` in the customer’s issuing bank. - PinPayments returns a **Token Id** which can be used later to: * `capture/3` an amount. @@ -202,25 +197,13 @@ defmodule Gringotts.Gateways.PinPayments do |> HTTPoison.post({:form, param}, headers) |> respond end - - defp commit_short(method, url, opts) do - auth_token = encoded_credentials(opts[:config].apiKey) - - headers = [ - {"Content-Type", "application/x-www-form-urlencoded"}, - {"Authorization", auth_token} - ] - - HTTPoison.request(method, url, [], headers) - |> respond - end - + defp encoded_credentials(login) do hash = Base.encode64("#{login}:") "Basic #{hash}" end - defp extract_card_token({:ok, %{status_code: code, token: token}}) do + defp extract_card_token({:ok, %{status_code: _, token: token}}) do token end diff --git a/test/gateways/pin_pay_test.exs b/test/gateways/pin_pay_test.exs deleted file mode 100644 index 9442c6ef..00000000 --- a/test/gateways/pin_pay_test.exs +++ /dev/null @@ -1,32 +0,0 @@ -defmodule Gringotts.Gateways.PinpaymentsTest do - # The file contains mocked tests for Pinpayments - - # We recommend using [mock][1] for this, you can place the mock responses from - # the Gateway in `test/mocks/pin_pay_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/pin_pay_mock.exs", __DIR__) - - use ExUnit.Case, async: false - alias Gringotts.Gateways.Pinpayments - 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/mocks/pin_pay_mock.exs b/test/mocks/pin_pay_mock.exs deleted file mode 100644 index 232a1913..00000000 --- a/test/mocks/pin_pay_mock.exs +++ /dev/null @@ -1,7 +0,0 @@ -defmodule Gringotts.Gateways.PinpaymentsMock do - # The module should include mock responses for test cases in pin_pay_test.exs. - # e.g. - # def successful_purchase do - # {:ok, %HTTPoison.Response{body: ~s[{data: "successful_purchase"}]} - # end -end From ce625c8292e01782286dced5a455e560efa1884b Mon Sep 17 00:00:00 2001 From: ravirocx Date: Fri, 30 Mar 2018 12:39:24 +0530 Subject: [PATCH 15/26] improved code readability --- lib/gringotts/gateways/pin_payments.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index baab7c0a..c1d58ea2 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -97,7 +97,6 @@ defmodule Gringotts.Gateways.PinPayments do alias Gringotts.{Money, CreditCard, Response} @test_url "https://test-api.pinPayments.com/1/" - @production_url "https://api.pinPayments.com/1/" @doc """ Creates a new charge and returns its details. @@ -128,7 +127,8 @@ defmodule Gringotts.Gateways.PinPayments do {currency, value, _} = Money.to_integer(amount) card_token = - commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) + :post + |> commit("cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) |> extract_card_token params = @@ -197,7 +197,7 @@ defmodule Gringotts.Gateways.PinPayments do |> HTTPoison.post({:form, param}, headers) |> respond end - + defp encoded_credentials(login) do hash = Base.encode64("#{login}:") "Basic #{hash}" From 3a5bf76b9169599e38df4105995547b0ee24614d Mon Sep 17 00:00:00 2001 From: ravirocx Date: Fri, 30 Mar 2018 13:44:30 +0530 Subject: [PATCH 16/26] bug fixed --- lib/gringotts/gateways/pin_payments.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index c1d58ea2..795962c0 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -203,7 +203,7 @@ defmodule Gringotts.Gateways.PinPayments do "Basic #{hash}" end - defp extract_card_token({:ok, %{status_code: _, token: token}}) do + defp extract_card_token({:ok, %{token: token}}) do token end From 5242a1b11fe773b2dd68f338023c03fe8c73f5f5 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Fri, 30 Mar 2018 13:57:38 +0530 Subject: [PATCH 17/26] bug fixes --- lib/gringotts/gateways/pin_payments.ex | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index 795962c0..fa709cb5 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -126,20 +126,24 @@ defmodule Gringotts.Gateways.PinPayments do def authorize(amount, %CreditCard{} = card, opts) do {currency, value, _} = Money.to_integer(amount) - card_token = + card_token_response = :post |> commit("cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) |> extract_card_token - params = + case card_token_response do + {:error, error} -> {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")} + {:ok, token} -> params = [ amount: value, capture: false, - card_token: card_token, + card_token: token, currency: currency ] ++ Keyword.delete(opts, :address) commit(:post, "charges", params) + end + end def authorize(amount, card_token, opts) when is_binary(card_token) do @@ -204,7 +208,11 @@ defmodule Gringotts.Gateways.PinPayments do end defp extract_card_token({:ok, %{token: token}}) do - token + {:ok, token} + end + + defp extract_card_token({:error, %HTTPoison.Error{} = error}) do + {:error, error} end # Parses PinPay's response and returns a `Gringotts.Response` struct From f874e63777a3dd50100862ffc0ffb15fc0a18cba Mon Sep 17 00:00:00 2001 From: ravirocx Date: Fri, 30 Mar 2018 15:24:42 +0530 Subject: [PATCH 18/26] improved code readability --- lib/gringotts/gateways/pin_payments.ex | 34 +++++++++++++++----------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index fa709cb5..0865ca32 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -131,19 +131,21 @@ defmodule Gringotts.Gateways.PinPayments do |> commit("cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) |> extract_card_token - case card_token_response do - {:error, error} -> {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")} - {:ok, token} -> params = - [ - amount: value, - capture: false, - card_token: token, - currency: currency - ] ++ Keyword.delete(opts, :address) - - commit(:post, "charges", params) - end - + case card_token_response do + {:error, error} -> + {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")} + + {:ok, token} -> + params = + [ + amount: value, + capture: false, + card_token: token, + currency: currency + ] ++ Keyword.delete(opts, :address) + + commit(:post, "charges", params) + end end def authorize(amount, card_token, opts) when is_binary(card_token) do @@ -207,10 +209,14 @@ defmodule Gringotts.Gateways.PinPayments do "Basic #{hash}" end - defp extract_card_token({:ok, %{token: token}}) do + defp extract_card_token({:ok, %{status_code: code, token: token}}) when code in 200..299 do {:ok, token} end + defp extract_card_token({:ok, %{body: body}}) do + {:error, body} + end + defp extract_card_token({:error, %HTTPoison.Error{} = error}) do {:error, error} end From ef13ca53ce698b4e51352965deb1702a30d7c387 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Sat, 31 Mar 2018 14:20:37 +0530 Subject: [PATCH 19/26] bug fixes --- lib/gringotts/gateways/pin_payments.ex | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index 0865ca32..46f008a0 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -209,23 +209,19 @@ defmodule Gringotts.Gateways.PinPayments do "Basic #{hash}" end - defp extract_card_token({:ok, %{status_code: code, token: token}}) when code in 200..299 do + defp extract_card_token({:ok, %{ token: token}}) do {:ok, token} end - defp extract_card_token({:ok, %{body: body}}) do - {:error, body} - end - - defp extract_card_token({:error, %HTTPoison.Error{} = error}) do - {:error, error} + defp extract_card_token({:error, error_response}) do + {:error, error_response} end # Parses PinPay's response and returns a `Gringotts.Response` struct # in a `:ok`, `:error` tuple. @spec respond(term) :: {:ok | :error, Response} - defp respond({:ok, %{status_code: code, body: body}}) when code in [200, 201] do + defp respond({:ok, %{status_code: code, body: body}}) when code in 200..299 do {:ok, parsed} = decode(body) token = parsed["response"]["token"] message = parsed["response"]["status_message"] From 5ab9ae7d227f0ae626d177850093b718bdc5db1f Mon Sep 17 00:00:00 2001 From: ravirocx Date: Sat, 31 Mar 2018 14:23:23 +0530 Subject: [PATCH 20/26] bug fixes --- lib/gringotts/gateways/pin_payments.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index 46f008a0..4239ac1e 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -133,7 +133,7 @@ defmodule Gringotts.Gateways.PinPayments do case card_token_response do {:error, error} -> - {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")} + {:error, error} {:ok, token} -> params = @@ -209,7 +209,7 @@ defmodule Gringotts.Gateways.PinPayments do "Basic #{hash}" end - defp extract_card_token({:ok, %{ token: token}}) do + defp extract_card_token({:ok, %{token: token}}) do {:ok, token} end From 34ceb97e918d7ae97e7d532b2e2fd2e6ffc6065a Mon Sep 17 00:00:00 2001 From: ravirocx Date: Sun, 1 Apr 2018 02:09:39 +0530 Subject: [PATCH 21/26] [PinPayments] function with test cases --- lib/gringotts/gateways/pin_payments.ex | 98 +++++++++++++++ .../gateways/pin_payments_test.exs | 119 ++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 test/integration/gateways/pin_payments_test.exs diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index 4239ac1e..40f472ed 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -162,6 +162,104 @@ defmodule Gringotts.Gateways.PinPayments do commit(:post, "charges", params) end + @doc """ + Transfers `amount` from the customer to the merchant. + + PinPayments attempts to process a purchase on behalf of the customer, by + debiting `amount` from the customer's account by charging the customer's + `card`. + + PinPayments returns a **Payment Id** which can be used later to: + * `refund/3` the amount. + + ## Examples + + 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: "4200000000000000", + year: 2099, + month: 12, + verification_code: "999", + brand: "VISA"} + iex> money = Money.new(20, :USD) + iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.PinPayments, amount, card, opts) + ``` + """ + + @spec purchase(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response} + def purchase(amount, %CreditCard{} = card, opts) do + {currency, value, _} = Money.to_integer(amount) + + card_token_response = + :post + |> commit("cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) + |> extract_card_token + + case card_token_response do + {:error, error} -> + {:error, error} + + {:ok, token} -> + params = + [ + amount: value, + card_token: token, + currency: currency + ] ++ Keyword.delete(opts, :address) + + commit(:post, "charges", params) + end + end + + def purchase(amount, card_token, opts) when is_binary(card_token) do + {currency, value, _} = Money.to_integer(amount) + + params = + [ + amount: value, + card_token: card_token, + currency: currency + ] ++ Keyword.delete(opts, :address) + + commit(:post, "charges", params) + end + + @doc """ + Stores the payment-source data for later use. + + PinPayments can store the payment-source details, for example card or bank details + which can be used to effectively process _One-Click_ and _Recurring_ payments, + and return a card token for reference. + + ## Note + + * _One-Click_ and _Recurring_ payments are currently not implemented. + * Payment details can be saved during a `purchase/3` or `capture/3`. + + ## Example + + The following example shows how one would store a card (a payment-source) for + future use. + ``` + iex> card = %CreditCard{first_name: "Harry", + last_name: "Potter", + number: "4200000000000000", + year: 2099, + month: 12, + verification_code: "999", + brand: "VISA"} + iex> {:ok, store_result} = Gringotts.store(Gringotts.Gateways.PinPayments, card, opts) + ``` + """ + + @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response} + def store(%CreditCard{} = card, opts) do + commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) + end + ############################################################################### # PRIVATE METHODS # ############################################################################### diff --git a/test/integration/gateways/pin_payments_test.exs b/test/integration/gateways/pin_payments_test.exs new file mode 100644 index 00000000..35065d47 --- /dev/null +++ b/test/integration/gateways/pin_payments_test.exs @@ -0,0 +1,119 @@ +defmodule Gringotts.Integration.Gateways.PinPaymentsTest do + # Integration tests for the PinPayments + + use ExUnit.Case, async: false + use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney + + alias Gringotts.{ + CreditCard, + Address + } + + alias Gringotts.Gateways.PinPayments, as: Gateway + + # @moduletag :integration + + @amount Money.new(420, :AUD) + + @bad_card1 %CreditCard{ + first_name: "Harry", + last_name: "Potter", + number: "4100000000000001", + year: 2019, + month: 12, + verification_code: "123", + brand: "VISA" + } + + @bad_card2 %CreditCard{ + first_name: "Harry", + last_name: "Potter", + number: "4600000000000006", + year: 2019, + month: 12, + verification_code: "123", + brand: "VISA" + } + + @bad_card3 %CreditCard{ + first_name: "Harry", + last_name: "Potter", + number: "4600000000000006", + year: 2009, + month: 12, + verification_code: "123", + brand: "VISA" + } + + @good_card %CreditCard{ + first_name: "Harry", + last_name: "Potter", + number: "4200000000000000", + year: 2019, + month: 12, + verification_code: "123", + brand: "VISA" + } + + @add %Address{ + street1: "OBH", + street2: "AIT", + city: "PUNE", + region: "Maharashtra", + country: "IN", + postal_code: "411015", + phone: "8007810916" + } + + @opts [ + description: "hello", + email: "hi@hello.com", + ip_address: "1.1.1.1", + config: %{apiKey: "c4nxgznanW4XZUaEQhxS6g", pass: ""} + ] ++ [address: @add] + + # Group the test cases by public api + describe "purchase" do + test "[purchase] with CreditCard" do + use_cassette "pin_pay/purchase_with_credit_card" do + assert {:ok, response} = Gateway.purchase(@amount, @good_card, @opts) + assert response.success == true + assert response.status_code == 201 + end + end + + test "[purchase] with bad CreditCard 1" do + use_cassette "pin_pay/purchase_with_bad_credit_card1" do + assert {:error, response} = Gateway.purchase(@amount, @bad_card1, @opts) + assert response.success == false + assert response.status_code == 400 + end + end + + test "[purchase] with bad CreditCard 2" do + use_cassette "pin_pay/purchase_with_bad_credit_card2" do + assert {:error, response} = Gateway.purchase(@amount, @bad_card2, @opts) + assert response.success == false + assert response.status_code == 400 + end + end + + test "[purchase] with bad CreditCard 3" do + use_cassette "pin_pay/purchase_with_bad_credit_card3" do + assert {:error, response} = Gateway.purchase(@amount, @bad_card3, @opts) + assert response.success == false + assert response.status_code == 422 + end + end + + test "[purchase] with card_token" do + use_cassette "pin_pay/purchase_with_card_token" do + assert {:ok, response} = Gateway.store(@good_card, @opts) + assert response.success == true + assert response.status_code == 201 + card_token = response.token + assert {:ok, response} = Gateway.purchase(@amount, card_token, @opts) + end + end + end +end From 2e443f4f8752573f7dac5b2467138da081595b20 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Sun, 1 Apr 2018 02:15:28 +0530 Subject: [PATCH 22/26] bug fixes --- test/integration/gateways/pin_payments_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/gateways/pin_payments_test.exs b/test/integration/gateways/pin_payments_test.exs index 35065d47..9f1de651 100644 --- a/test/integration/gateways/pin_payments_test.exs +++ b/test/integration/gateways/pin_payments_test.exs @@ -11,7 +11,7 @@ defmodule Gringotts.Integration.Gateways.PinPaymentsTest do alias Gringotts.Gateways.PinPayments, as: Gateway - # @moduletag :integration + @moduletag :integration @amount Money.new(420, :AUD) From 2a7ee32cb6360fac98572ca3615e64ea7a5089c9 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Sat, 28 Apr 2018 17:51:09 +0530 Subject: [PATCH 23/26] testing --- lib/gringotts/gateways/pin_payments.ex | 109 +++++++++++------- mix.exs | 1 + mix.lock | 2 + .../gateways/pin_payments_test.exs | 16 ++- 4 files changed, 86 insertions(+), 42 deletions(-) diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index 40f472ed..3a95b728 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -122,29 +122,22 @@ defmodule Gringotts.Gateways.PinPayments do ``` """ - @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response} + @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response} def authorize(amount, %CreditCard{} = card, opts) do {currency, value, _} = Money.to_integer(amount) - card_token_response = - :post - |> commit("cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) - |> extract_card_token - - case card_token_response do - {:error, error} -> - {:error, error} - - {:ok, token} -> - params = - [ - amount: value, - capture: false, - card_token: token, - currency: currency - ] ++ Keyword.delete(opts, :address) - - commit(:post, "charges", params) + with {:ok, card_token_response} <- + commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)), + {:ok, card_token} <- extract_card_token(card_token_response) do + params = + [ + amount: value, + capture: false, + card_token: card_token, + currency: currency + ] ++ Keyword.delete(opts, :address) + + commit(:post, "charges", params) end end @@ -193,24 +186,17 @@ defmodule Gringotts.Gateways.PinPayments do def purchase(amount, %CreditCard{} = card, opts) do {currency, value, _} = Money.to_integer(amount) - card_token_response = - :post - |> commit("cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) - |> extract_card_token - - case card_token_response do - {:error, error} -> - {:error, error} - - {:ok, token} -> - params = - [ - amount: value, - card_token: token, - currency: currency - ] ++ Keyword.delete(opts, :address) - - commit(:post, "charges", params) + with {:ok, card_token_response} <- + commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)), + {:ok, card_token} <- extract_card_token(card_token_response) do + params = + [ + amount: value, + card_token: card_token, + currency: currency + ] ++ Keyword.delete(opts, :address) + + commit(:post, "charges", params) end end @@ -227,6 +213,27 @@ defmodule Gringotts.Gateways.PinPayments do commit(:post, "charges", params) end + @doc """ + Refunds the `amount` to the customer's account with reference to a prior transfer. + + PinPayments processes a full refund worth `amount`, referencing a + previous `purchase/3` or `capture/3`. + + The end customer will usually see two bookings/records on his statement. + + ## Example + ``` + iex> money = Money.new(20, :USD) + iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.PinPayments, payment_id, opts) + ``` + """ + @spec refund( String.t(), keyword) :: {:ok | :error, Response} + def refund(payment_id, opts) do + url = @test_url <> "charges/#{payment_id}/refunds" + + commit_short(:post, url, opts) + end + @doc """ Stores the payment-source data for later use. @@ -299,6 +306,18 @@ defmodule Gringotts.Gateways.PinPayments do url |> HTTPoison.post({:form, param}, headers) + #|> respond + end + + defp commit_short(method, url, opts) do + auth_token = encoded_credentials(opts[:config].apiKey) + + headers = [ + {"Content-Type", "application/x-www-form-urlencoded"}, + {"Authorization", auth_token} + ] + + HTTPoison.request(method, url, [], headers) |> respond end @@ -307,7 +326,7 @@ defmodule Gringotts.Gateways.PinPayments do "Basic #{hash}" end - defp extract_card_token({:ok, %{token: token}}) do + defp extract_card_token(%{id: token}) do {:ok, token} end @@ -321,12 +340,20 @@ defmodule Gringotts.Gateways.PinPayments do defp respond({:ok, %{status_code: code, body: body}}) when code in 200..299 do {:ok, parsed} = decode(body) - token = parsed["response"]["token"] + + token = parsed["response"]["card"]["token"] + id = parsed["response"]["token"] message = parsed["response"]["status_message"] { :ok, - Response.success(token: token, message: message, raw: parsed, status_code: code) + Response.success( + id: id, + token: token, + message: message, + raw: parsed, + status_code: code + ) } end 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/pin_payments_test.exs b/test/integration/gateways/pin_payments_test.exs index 9f1de651..d8859dcd 100644 --- a/test/integration/gateways/pin_payments_test.exs +++ b/test/integration/gateways/pin_payments_test.exs @@ -11,7 +11,7 @@ defmodule Gringotts.Integration.Gateways.PinPaymentsTest do alias Gringotts.Gateways.PinPayments, as: Gateway - @moduletag :integration + #@moduletag :integration @amount Money.new(420, :AUD) @@ -116,4 +116,18 @@ defmodule Gringotts.Integration.Gateways.PinPaymentsTest do end end end + + describe "Refunds" do + test "[Refunds]" do + # use_cassette "pin_pay/refunds" do + assert {:ok, response} = Gateway.purchase(@amount, @good_card, @opts) + assert response.success == true + assert response.status_code == 201 + payment_id = response.id + assert {:ok, response} = Gateway.refund(payment_id, @opts) + assert response.success == true + assert response.status_code == 201 + # end + end + end end From 92a03172f94d4a8cf7302946f6cdf79cd0c071d1 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Sat, 28 Apr 2018 19:14:54 +0530 Subject: [PATCH 24/26] test commit --- lib/gringotts/gateways/pin_payments.ex | 27 ++++++++++--------- .../gateways/pin_payments_test.exs | 6 ++--- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index 3a95b728..0295a303 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -267,6 +267,7 @@ defmodule Gringotts.Gateways.PinPayments do commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) end + ############################################################################### # PRIVATE METHODS # ############################################################################### @@ -292,32 +293,32 @@ defmodule Gringotts.Gateways.PinPayments do ] end - @spec commit(atom, String.t(), keyword) :: {:ok | :error, Response} - defp commit(:post, endpoint, param) do - auth_token = encoded_credentials(param[:config].apiKey) + defp commit_short(method, url, opts) do + auth_token = encoded_credentials(opts[:config][:api_key]) headers = [ {"Content-Type", "application/x-www-form-urlencoded"}, {"Authorization", auth_token} ] - url = @test_url <> "#{endpoint}" - param = Keyword.delete(param, :config) - - url - |> HTTPoison.post({:form, param}, headers) - #|> respond + HTTPoison.request(method, url, [], headers) + |> respond end - defp commit_short(method, url, opts) do - auth_token = encoded_credentials(opts[:config].apiKey) + @spec commit(atom, String.t(), keyword) :: {:ok | :error, Response} + defp commit(:post, endpoint, param) do + auth_token = encoded_credentials(param[:config][:api_key]) headers = [ {"Content-Type", "application/x-www-form-urlencoded"}, {"Authorization", auth_token} ] - HTTPoison.request(method, url, [], headers) + url = @test_url <> "#{endpoint}" + param = Keyword.delete(param, :config) + + url + |> HTTPoison.post({:form, param}, headers) |> respond end @@ -366,4 +367,4 @@ defmodule Gringotts.Gateways.PinPayments do defp respond({:error, %HTTPoison.Error{} = error}) do {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")} end -end +end \ No newline at end of file diff --git a/test/integration/gateways/pin_payments_test.exs b/test/integration/gateways/pin_payments_test.exs index d8859dcd..79467840 100644 --- a/test/integration/gateways/pin_payments_test.exs +++ b/test/integration/gateways/pin_payments_test.exs @@ -69,7 +69,7 @@ defmodule Gringotts.Integration.Gateways.PinPaymentsTest do description: "hello", email: "hi@hello.com", ip_address: "1.1.1.1", - config: %{apiKey: "c4nxgznanW4XZUaEQhxS6g", pass: ""} + config: [api_key: "c4nxgznanW4XZUaEQhxS6g"] ] ++ [address: @add] # Group the test cases by public api @@ -119,7 +119,7 @@ defmodule Gringotts.Integration.Gateways.PinPaymentsTest do describe "Refunds" do test "[Refunds]" do - # use_cassette "pin_pay/refunds" do + use_cassette "pin_pay/refunds" do assert {:ok, response} = Gateway.purchase(@amount, @good_card, @opts) assert response.success == true assert response.status_code == 201 @@ -127,7 +127,7 @@ defmodule Gringotts.Integration.Gateways.PinPaymentsTest do assert {:ok, response} = Gateway.refund(payment_id, @opts) assert response.success == true assert response.status_code == 201 - # end + end end end end From 9f1bdfd37ecef9f7f8398a3635e4b0bcc397a570 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Sat, 28 Apr 2018 19:15:37 +0530 Subject: [PATCH 25/26] test commit --- lib/gringotts/gateways/pin_payments.ex | 11 +++++----- .../gateways/pin_payments_test.exs | 20 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/gringotts/gateways/pin_payments.ex b/lib/gringotts/gateways/pin_payments.ex index 0295a303..211cb54c 100644 --- a/lib/gringotts/gateways/pin_payments.ex +++ b/lib/gringotts/gateways/pin_payments.ex @@ -122,7 +122,7 @@ defmodule Gringotts.Gateways.PinPayments do ``` """ - @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response} + @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: {:ok | :error, Response} def authorize(amount, %CreditCard{} = card, opts) do {currency, value, _} = Money.to_integer(amount) @@ -213,7 +213,7 @@ defmodule Gringotts.Gateways.PinPayments do commit(:post, "charges", params) end - @doc """ + @doc """ Refunds the `amount` to the customer's account with reference to a prior transfer. PinPayments processes a full refund worth `amount`, referencing a @@ -227,7 +227,7 @@ defmodule Gringotts.Gateways.PinPayments do iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.PinPayments, payment_id, opts) ``` """ - @spec refund( String.t(), keyword) :: {:ok | :error, Response} + @spec refund(String.t(), keyword) :: {:ok | :error, Response} def refund(payment_id, opts) do url = @test_url <> "charges/#{payment_id}/refunds" @@ -267,7 +267,6 @@ defmodule Gringotts.Gateways.PinPayments do commit(:post, "cards", card_for_token(card, opts) ++ Keyword.delete(opts, :address)) end - ############################################################################### # PRIVATE METHODS # ############################################################################### @@ -305,7 +304,7 @@ defmodule Gringotts.Gateways.PinPayments do |> respond end - @spec commit(atom, String.t(), keyword) :: {:ok | :error, Response} + @spec commit(atom, String.t(), keyword) :: {:ok | :error, Response} defp commit(:post, endpoint, param) do auth_token = encoded_credentials(param[:config][:api_key]) @@ -367,4 +366,4 @@ defmodule Gringotts.Gateways.PinPayments do defp respond({:error, %HTTPoison.Error{} = error}) do {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}'")} end -end \ No newline at end of file +end diff --git a/test/integration/gateways/pin_payments_test.exs b/test/integration/gateways/pin_payments_test.exs index 79467840..21c11312 100644 --- a/test/integration/gateways/pin_payments_test.exs +++ b/test/integration/gateways/pin_payments_test.exs @@ -11,7 +11,7 @@ defmodule Gringotts.Integration.Gateways.PinPaymentsTest do alias Gringotts.Gateways.PinPayments, as: Gateway - #@moduletag :integration + # @moduletag :integration @amount Money.new(420, :AUD) @@ -119,15 +119,15 @@ defmodule Gringotts.Integration.Gateways.PinPaymentsTest do describe "Refunds" do test "[Refunds]" do - use_cassette "pin_pay/refunds" do - assert {:ok, response} = Gateway.purchase(@amount, @good_card, @opts) - assert response.success == true - assert response.status_code == 201 - payment_id = response.id - assert {:ok, response} = Gateway.refund(payment_id, @opts) - assert response.success == true - assert response.status_code == 201 - end + use_cassette "pin_pay/refunds" do + assert {:ok, response} = Gateway.purchase(@amount, @good_card, @opts) + assert response.success == true + assert response.status_code == 201 + payment_id = response.id + assert {:ok, response} = Gateway.refund(payment_id, @opts) + assert response.success == true + assert response.status_code == 201 + end end end end From 0d09a8154e46f791fa0a8b93d94aae2b7f880c40 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Tue, 1 May 2018 16:42:57 +0530 Subject: [PATCH 26/26] rectified limitations --- test/integration/gateways/pin_payments_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/gateways/pin_payments_test.exs b/test/integration/gateways/pin_payments_test.exs index 21c11312..96b4d7e3 100644 --- a/test/integration/gateways/pin_payments_test.exs +++ b/test/integration/gateways/pin_payments_test.exs @@ -13,7 +13,7 @@ defmodule Gringotts.Integration.Gateways.PinPaymentsTest do # @moduletag :integration - @amount Money.new(420, :AUD) + @amount Money.new(4, :AUD) @bad_card1 %CreditCard{ first_name: "Harry", @@ -111,7 +111,7 @@ defmodule Gringotts.Integration.Gateways.PinPaymentsTest do assert {:ok, response} = Gateway.store(@good_card, @opts) assert response.success == true assert response.status_code == 201 - card_token = response.token + card_token = response.id assert {:ok, response} = Gateway.purchase(@amount, card_token, @opts) end end