From a4082cbd810aaa2a5b71b5d6421880831f50dadf Mon Sep 17 00:00:00 2001 From: Jyoti Gautam Date: Fri, 30 Mar 2018 12:05:03 +0530 Subject: [PATCH] [global-collect] Layout, docs improvements and code refactors (#111) [global-collect] Layout, docs and code refactor =============================================== New features ------------ Risk, AVS, CVS fields added in `Response` struct! Layout, docs ----------- * `credo` issues resolved. * Corrected `amount` in examples * Ran the elixir 1.6 code formatter - Used sigils in mocks Code refactors -------------- * Removed unnecessary functions - Reduced arity of `add_money` * Refactored Timex usage * Removed a test on `validate_config` as it is already tested. --- lib/gringotts/gateways/global_collect.ex | 411 ++++++++++++----------- test/gateways/global_collect_test.exs | 30 +- test/mocks/global_collect_mock.exs | 88 ++++- 3 files changed, 294 insertions(+), 235 deletions(-) diff --git a/lib/gringotts/gateways/global_collect.ex b/lib/gringotts/gateways/global_collect.ex index 624a8950..6075451d 100644 --- a/lib/gringotts/gateways/global_collect.ex +++ b/lib/gringotts/gateways/global_collect.ex @@ -2,9 +2,9 @@ defmodule Gringotts.Gateways.GlobalCollect do @moduledoc """ [GlobalCollect][home] gateway implementation. - For further details, please refer [GlobalCollect API documentation](https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/index.html). + For further details, please refer [GlobalCollect API documentation][docs]. - Following are the features that have been implemented for the GlobalCollect Gateway: + Following are the features that have been implemented for GlobalCollect: | Action | Method | | ------ | ------ | @@ -14,32 +14,34 @@ defmodule Gringotts.Gateways.GlobalCollect do | Refund | `refund/3` | | Void | `void/2` | - ## Optional or extra parameters + ## Optional parameters Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply optional arguments for transactions with the gateway. - | Key | Status | + | Key | Remark | | ---- | --- | - | `merchantCustomerId` | implemented | - | `description` | implemented | - | `customer_name` | implemented | - | `dob` | implemented | - | `company` | implemented | - | `email` | implemented | - | `phone` | implemented | - | `order_id` | implemented | - | `invoice` | implemented | - | `billingAddress` | implemented | - | `shippingAddress` | implemented | - | `name` | implemented | - | `skipAuthentication` | implemented | - + | `merchantCustomerId` | Identifier for the consumer that can be used as a search criteria in the Global Collect Payment Console | + | `description` | Descriptive text that is used towards to consumer, either during an online checkout at a third party and/or on the statement of the consumer | + | `dob` | The date of birth of the consumer Format: YYYYMMDD | + | `company` | Name of company, as a consumer | + | `email` | Email address of the consumer | + | `phone` | Phone number of the consumer | + | `invoice` | Object containing additional invoice data | + | `billingAddress` | Object containing billing address details | + | `shippingAddress` | Object containing shipping address details | + | `name` | Object containing the name details of the consumer | + | `skipAuthentication` | 3D Secure Authentication will be skipped for this transaction if set to true | + + For more details of the required keys refer [this.][options] ## Registering your GlobalCollect account at `Gringotts` - After creating your account successfully on [GlobalCollect](http://www.globalcollect.com/) follow the [dashboard link](https://sandbox.account.ingenico.com/#/account/apikey) to fetch the secret_api_key, api_key_id and [here](https://sandbox.account.ingenico.com/#/account/merchantid) for merchant_id. + After creating your account successfully on [GlobalCollect][home] open the + [dashboard][dashboard] to fetch the `secret_api_key`, `api_key_id` and + `merchant_id` from the menu. - Here's how the secrets map to the required configuration parameters for GlobalCollect: + Here's how the secrets map to the required configuration parameters for + GlobalCollect: | Config parameter | GlobalCollect secret | | ------- | ---- | @@ -47,19 +49,22 @@ defmodule Gringotts.Gateways.GlobalCollect do | `:api_key_id` | **ApiKeyId** | | `:merchant_id` | **MerchantId** | - Your Application config **must include the `[:secret_api_key, :api_key_id, :merchant_id]` field(s)** and would look - something like this: + Your Application config **must include the `:secret_api_key`, `:api_key_id`, + `:merchant_id` field(s)** and would look something like this: config :gringotts, Gringotts.Gateways.GlobalCollect, secret_api_key: "your_secret_secret_api_key" api_key_id: "your_secret_api_key_id" merchant_id: "your_secret_merchant_id" + ## Scope of this module + + * [All amount fields in globalCollect are in cents with each amount having 2 decimals.][amountReference] + ## Supported currencies and countries - The GlobalCollect platform is able to support payments in [over 150 currencies][currencies] + The GlobalCollect platform supports payments in [over 150 currencies][currencies]. - [currencies]: https://epayments.developer-ingenico.com/best-practices/services/currency-conversion ## Following the examples 1. First, set up a sample application and configure it to work with GlobalCollect. @@ -67,51 +72,74 @@ defmodule Gringotts.Gateways.GlobalCollect do - To save you time, we recommend [cloning our example repo][example] that gives you a pre-configured sample app ready-to-go. + You could use the same config or update it the with your "secrets" - as described [above](#module-registering-your-globalcollect-account-at-GlobalCollect). + as described [above](#module-registering-your-globalcollect-account-at-gringotts). 2. Run an `iex` session with `iex -S mix` and add some variable bindings and - aliases to it (to save some time): + aliases to it (to save some time): ``` iex> alias Gringotts.{Response, CreditCard, Gateways.GlobalCollect} - iex> shippingAddress = %{ - street: "Desertroad", - houseNumber: "1", - additionalInfo: "Suite II", - zip: "84536", - city: "Monument Valley", - state: "Utah", - countryCode: "US" - } + street: "Desertroad", + houseNumber: "1", + additionalInfo: "Suite II", + zip: "84536", + city: "Monument Valley", + state: "Utah", + countryCode: "US" + } iex> billingAddress = %{ - street: "Desertroad", - houseNumber: "13", - additionalInfo: "b", - zip: "84536", - city: "Monument Valley", - state: "Utah", - countryCode: "US" - } + street: "Desertroad", + houseNumber: "13", + additionalInfo: "b", + zip: "84536", + city: "Monument Valley", + state: "Utah", + countryCode: "US" + } iex> invoice = %{ - invoiceNumber: "000000123", - invoiceDate: "20140306191500" - } + invoiceNumber: "000000123", + invoiceDate: "20140306191500" + } iex> name = %{ - title: "Miss", - firstName: "Road", - surname: "Runner" - } + title: "Miss", + firstName: "Road", + surname: "Runner" + } - iex> opts = [ description: "Store Purchase 1437598192", merchantCustomerId: "234", customer_name: "John Doe", dob: "19490917", company: "asma", email: "johndoe@gmail.com", phone: "7765746563", order_id: "2323", invoice: invoice, billingAddress: billingAddress, shippingAddress: shippingAddress, name: name, skipAuthentication: "true" ] + iex> card = %CreditCard{ + number: "4567350000427977", + month: 12, + year: 43, + first_name: "John", + last_name: "Doe", + verification_code: "123", + brand: "VISA" + } + iex> opts = [ + description: "Store Purchase 1437598192", + merchantCustomerId: "234", dob: "19490917", + company: "asma", email: "johndoe@gmail.com", + phone: "7765746563", invoice: invoice, + billingAddress: billingAddress, + shippingAddress: shippingAddress, + name: name, skipAuthentication: "true" + ] ``` We'll be using these in the examples below. + [home]: http://www.globalcollect.com/ + [docs]: https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/index.html + [dashboard]: https://sandbox.account.ingenico.com/#/dashboard + [gs]: # + [options]: https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/java/payments/create.html#payments-create-payload + [currencies]: https://epayments.developer-ingenico.com/best-practices/services/currency-conversion [example]: https://github.com/aviabird/gringotts_example + [amountReference]: https://epayments-api.developer-ingenico.com/c2sapi/v1/en_US/swift/services/convertAmount.html """ @base_url "https://api-sandbox.globalcollect.com/v1/" @@ -127,12 +155,12 @@ defmodule Gringotts.Gateways.GlobalCollect do alias Gringotts.{Money, CreditCard, Response} @brand_map %{ - visa: "1", - american_express: "2", - master: "3", - discover: "128", - jcb: "125", - diners_club: "132" + VISA: "1", + AMERICAN_EXPRESS: "2", + MASTER: "3", + DISCOVER: "128", + JCB: "125", + DINERS_CLUB: "132" } @doc """ @@ -143,73 +171,70 @@ defmodule Gringotts.Gateways.GlobalCollect do also triggers risk management. Funds are not transferred. GlobalCollect returns a payment id which can be further used to: - * `capture/3` _an_ amount. - * `refund/3` _an_amount + * `capture/3` an amount. + * `refund/3` an amount * `void/2` a pre_authorization ## Example - > The following session shows how one would (pre) authorize a payment of $100 on + The following example shows how one would (pre) authorize a payment of $100 on a sample `card`. ``` iex> card = %CreditCard{ number: "4567350000427977", month: 12, - year: 18, + year: 43, first_name: "John", last_name: "Doe", verification_code: "123", - brand: "visa" + brand: "VISA" } - iex> amount = %{value: Decimal.new(100), currency: "USD"} + iex> amount = Money.new(100, :USD) iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.GlobalCollect, amount, card, opts) ``` """ @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response} - def authorize(amount, card = %CreditCard{}, opts) do - params = create_params_for_auth_or_purchase(amount, card, opts) + def authorize(amount, %CreditCard{} = card, opts) do + params = %{ + order: add_order(amount, opts), + cardPaymentMethodSpecificInput: add_card(card, opts) + } + commit(:post, "payments", params, opts) end @doc """ Captures a pre-authorized `amount`. - `amount` is transferred to the merchant account by GlobalCollect used in the - pre-authorization referenced by `payment_id`. + `amount` used in the pre-authorization referenced by `payment_id` is + transferred to the merchant account by GlobalCollect. ## Note - > Authorized payment with PENDING_APPROVAL status only allow a single capture whereas the one with PENDING_CAPTURE status is used for payments that allow multiple captures. - > PENDING_APPROVAL is a common status only with card and direct debit transactions. + Authorized payment with PENDING_APPROVAL status only allow a single capture whereas + the one with PENDING_CAPTURE status is used for payments that allow multiple captures. ## Example - The following session shows how one would (partially) capture a previously + The following example shows how one would (partially) capture a previously authorized a payment worth $100 by referencing the obtained authorization `id`. ``` - iex> card = %CreditCard{ - number: "4567350000427977", - month: 12, - year: 18, - first_name: "John", - last_name: "Doe", - verification_code: "123", - brand: "visa" - } - - iex> amount = %{value: Decimal.new(100), currency: "USD"} + iex> amount = Money.new(100, :USD) - iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.GlobalCollect, amount, card, opts) + iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.GlobalCollect, auth_result.authorization, amount, opts) ``` """ @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response} def capture(payment_id, amount, opts) do - params = create_params_for_capture(amount, opts) + params = %{ + order: add_order(amount, opts) + } + commit(:post, "payments/#{payment_id}/approve", params, opts) end @@ -222,28 +247,28 @@ defmodule Gringotts.Gateways.GlobalCollect do ## Example - > The following session shows how one would process a payment in one-shot, + The following example shows how one would process a payment in one-shot, without (pre) authorization. ``` iex> card = %CreditCard{ number: "4567350000427977", month: 12, - year: 18, + year: 43, first_name: "John", last_name: "Doe", verification_code: "123", - brand: "visa" + brand: "VISA" } - iex> amount = %{value: Decimal.new(100), currency: "USD"} + iex> amount = Money.new(100, :USD) iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.GlobalCollect, amount, card, opts) ``` """ @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response} - def purchase(amount, card = %CreditCard{}, opts) do + def purchase(amount, %CreditCard{} = card, opts) do case authorize(amount, card, opts) do {:ok, results} -> payment_id = results.raw["payment"]["id"] @@ -257,50 +282,53 @@ defmodule Gringotts.Gateways.GlobalCollect do @doc """ Voids the referenced payment. - This makes it impossible to process the payment any further and will also try to reverse an authorization on a card. - Reversing an authorization that you will not be utilizing will prevent you from having to pay a fee/penalty for unused authorization requests. + This makes it impossible to process the payment any further and will also try + to reverse an authorization on a card. + Reversing an authorization that you will not be utilizing will prevent you + from having to [pay a fee/penalty][void] for unused authorization requests. + [void]: https://epayments-api.developer-ingenico.com/s2sapi/v1/en_US/java/payments/cancel.html#payments-cancel-request ## Example - > The following session shows how one would void a previous (pre) + The following example shows how one would void a previous (pre) authorization. Remember that our `capture/3` example only did a complete capture. ``` - iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.GlobalCollect, auth_result.payment.id, opts) + iex> {:ok, void_result} = Gringotts.void(Gringotts.Gateways.GlobalCollect, auth_result.authorization, opts) ``` """ @spec void(String.t(), keyword) :: {:ok | :error, Response} def void(payment_id, opts) do - params = nil - commit(:post, "payments/#{payment_id}/cancel", params, opts) + commit(:post, "payments/#{payment_id}/cancel", [], opts) end @doc """ Refunds the `amount` to the customer's account with reference to a prior transfer. - > You can refund any transaction by just calling this API - - ## Note - You always have the option to refund just a portion of the payment amount. - It is also possible to submit multiple refund requests on one payment as long as the total amount to be refunded does not exceed the total amount that was paid. + It is also possible to submit multiple refund requests on one payment as long + as the total amount to be refunded does not exceed the total amount that was paid. ## Example - > The following session shows how one would refund a previous purchase (and + The following example shows how one would refund a previous purchase (and similarily for captures). ``` - iex> amount = %{value: Decimal.new(100), currency: "USD"} + iex> amount = Money.new(100, :USD) - iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.GlobalCollect, auth_result.payment.id, amount) + iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.GlobalCollect, auth_result.authorization, amount) ``` """ @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response} def refund(amount, payment_id, opts) do - params = create_params_for_refund(amount, opts) + params = %{ + amountOfMoney: add_money(amount), + customer: add_customer(opts) + } + commit(:post, "payments/#{payment_id}/refund", params, opts) end @@ -308,40 +336,18 @@ defmodule Gringotts.Gateways.GlobalCollect do # PRIVATE METHODS # ############################################################################### - # Makes the request to GlobalCollect's network. - # For consistency with other gateway implementations, make your (final) - # network request in here, and parse it using another private method called - # `respond`. - - defp create_params_for_refund(amount, opts) do - %{ - amountOfMoney: add_money(amount, opts), - customer: add_customer(opts) - } - end - - defp create_params_for_auth_or_purchase(amount, payment, opts) do - %{ - order: add_order(amount, opts), - cardPaymentMethodSpecificInput: add_payment(payment, @brand_map, opts) - } - end - - defp create_params_for_capture(amount, opts) do - %{ - order: add_order(amount, opts) - } - end - defp add_order(money, options) do %{ - amountOfMoney: add_money(money, options), + amountOfMoney: add_money(money), customer: add_customer(options), - references: add_references(options) + references: %{ + descriptor: options[:description], + invoiceData: options[:invoice] + } } end - defp add_money(amount, options) do + defp add_money(amount) do {currency, amount, _} = Money.to_integer(amount) %{ @@ -353,94 +359,65 @@ defmodule Gringotts.Gateways.GlobalCollect do defp add_customer(options) do %{ merchantCustomerId: options[:merchantCustomerId], - personalInformation: personal_info(options), + personalInformation: %{ + name: options[:name] + }, dateOfBirth: options[:dob], - companyInformation: company_info(options), + companyInformation: %{ + name: options[:company] + }, billingAddress: options[:billingAddress], shippingAddress: options[:shippingAddress], - contactDetails: contact(options) - } - end - - defp add_references(options) do - %{ - descriptor: options[:description], - invoiceData: options[:invoice] - } - end - - defp personal_info(options) do - %{ - name: options[:name] - } - end - - defp company_info(options) do - %{ - name: options[:company] - } - end - - defp contact(options) do - %{ - emailAddress: options[:email], - phoneNumber: options[:phone] + contactDetails: %{ + emailAddress: options[:email], + phoneNumber: options[:phone] + } } end - def add_card(%CreditCard{} = payment) do + defp add_card(card, opts) do %{ - cvv: payment.verification_code, - cardNumber: payment.number, - expiryDate: "#{payment.month}" <> "#{payment.year}", - cardholderName: CreditCard.full_name(payment) - } - end - - defp add_payment(payment, brand_map, opts) do - brand = payment.brand - - %{ - paymentProductId: Map.fetch!(brand_map, String.to_atom(brand)), + paymentProductId: Map.fetch!(@brand_map, String.to_atom(card.brand)), skipAuthentication: opts[:skipAuthentication], - card: add_card(payment) + card: %{ + cvv: card.verification_code, + cardNumber: card.number, + expiryDate: "#{card.month}#{card.year}", + cardholderName: CreditCard.full_name(card) + } } end - defp auth_digest(path, secret_api_key, time, opts) do - data = "POST\napplication/json\n#{time}\n/v1/#{opts[:config][:merchant_id]}/#{path}\n" - :crypto.hmac(:sha256, secret_api_key, data) - end - defp commit(method, path, params, opts) do headers = create_headers(path, opts) data = Poison.encode!(params) - url = "#{@base_url}#{opts[:config][:merchant_id]}/#{path}" - response = HTTPoison.request(method, url, data, headers) - response |> respond + merchant_id = opts[:config][:merchant_id] + url = "#{@base_url}#{merchant_id}/#{path}" + + gateway_response = HTTPoison.request(method, url, data, headers) + gateway_response |> respond end defp create_headers(path, opts) do - time = date + datetime = Timex.now() |> Timex.local() + + date_string = + "#{Timex.format!(datetime, "%a, %d %b %Y %H:%M:%S", :strftime)} #{datetime.zone_abbr}" - sha_signature = - auth_digest(path, opts[:config][:secret_api_key], time, opts) |> Base.encode64() + api_key_id = opts[:config][:api_key_id] - auth_token = "GCS v1HMAC:#{opts[:config][:api_key_id]}:#{sha_signature}" + sha_signature = auth_digest(path, date_string, opts) - headers = [ - {"Content-Type", "application/json"}, - {"Authorization", auth_token}, - {"Date", time} - ] + auth_token = "GCS v1HMAC:#{api_key_id}:#{Base.encode64(sha_signature)}" + [{"Content-Type", "application/json"}, {"Authorization", auth_token}, {"Date", date_string}] end - defp date() do - use Timex - datetime = Timex.now() |> Timex.local() - strftime_str = Timex.format!(datetime, "%a, %d %b %Y %H:%M:%S ", :strftime) - time_zone = Timex.timezone(:local, datetime) - time = strftime_str <> "#{time_zone.abbreviation}" + defp auth_digest(path, date_string, opts) do + secret_api_key = opts[:config][:secret_api_key] + merchant_id = opts[:config][:merchant_id] + + data = "POST\napplication/json\n#{date_string}\n/v1/#{merchant_id}/#{path}\n" + :crypto.hmac(:sha256, secret_api_key, data) end # Parses GlobalCollect's response and returns a `Gringotts.Response` struct @@ -450,7 +427,31 @@ defmodule Gringotts.Gateways.GlobalCollect do defp respond({:ok, %{status_code: code, body: body}}) when code in [200, 201] do case decode(body) do - {:ok, results} -> {:ok, Response.success(raw: results, status_code: code)} + {:ok, results} -> + { + :ok, + Response.success( + authorization: results["payment"]["id"], + raw: results, + status_code: code, + avs_result: + results["payment"]["paymentOutput"]["cardPaymentMethodSpecificOutput"][ + "fraudResults" + ]["avsResult"], + cvc_result: + results["payment"]["paymentOutput"]["cardPaymentMethodSpecificOutput"][ + "fraudResults" + ]["cvcResult"], + message: results["payment"]["status"], + fraud_review: + results["payment"]["paymentOutput"]["cardPaymentMethodSpecificOutput"][ + "fraudResults" + ]["fraudServiceResult"] + ) + } + + {:error, _} -> + {:error, Response.error(raw: body, message: "undefined response from GlobalCollect")} end end @@ -462,11 +463,13 @@ defmodule Gringotts.Gateways.GlobalCollect do end defp respond({:error, %HTTPoison.Error{} = error}) do - {:error, - Response.error( - code: error.id, - reason: :network_fail?, - description: "HTTPoison says '#{error.reason}'" - )} + { + :error, + Response.error( + code: error.id, + reason: :network_fail?, + description: "HTTPoison says '#{error.reason}'" + ) + } end end diff --git a/test/gateways/global_collect_test.exs b/test/gateways/global_collect_test.exs index d64489c2..8d09449c 100644 --- a/test/gateways/global_collect_test.exs +++ b/test/gateways/global_collect_test.exs @@ -14,7 +14,7 @@ defmodule Gringotts.Gateways.GlobalCollectTest do @bad_amount Money.new("50.3", :USD) - @shippingAddress %{ + @shipping_address %{ street: "Desertroad", houseNumber: "1", additionalInfo: "Suite II", @@ -31,7 +31,7 @@ defmodule Gringotts.Gateways.GlobalCollectTest do first_name: "John", last_name: "Doe", verification_code: "123", - brand: "visa" + brand: "VISA" } @invalid_card %CreditCard{ @@ -41,10 +41,10 @@ defmodule Gringotts.Gateways.GlobalCollectTest do first_name: "John", last_name: "Doe", verification_code: "123", - brand: "visa" + brand: "VISA" } - @billingAddress %{ + @billing_address %{ street: "Desertroad", houseNumber: "13", additionalInfo: "b", @@ -71,16 +71,16 @@ defmodule Gringotts.Gateways.GlobalCollectTest do @invalid_config [ config: %{ - secret_api_key: "Qtg9v4Q0G13sLRNcClWhHnvN1kVYWDcy4w9rG8T86XU=", - api_key_id: "e5743abfc360ed12" + secret_api_key: "some_secret_api_key", + api_key_id: "some_api_key_id" } ] @options [ config: %{ - secret_api_key: "Qtg9v4Q0G13sLRNcClWhHnvN1kVYWDcy4w9rG8T86XU=", - api_key_id: "e5743abfc360ed12", - merchant_id: "1226" + secret_api_key: "some_secret_api_key", + api_key_id: "some_api_key_id", + merchant_id: "some_merchant_id" }, description: "Store Purchase 1437598192", merchantCustomerId: "234", @@ -91,20 +91,12 @@ defmodule Gringotts.Gateways.GlobalCollectTest do phone: "7468474533", order_id: "2323", invoice: @invoice, - billingAddress: @billingAddress, - shippingAddress: @shippingAddress, + billingAddress: @billing_address, + shippingAddress: @shipping_address, name: @name, skipAuthentication: "true" ] - describe "validation arguments check" do - test "with no merchant id passed in config" do - assert_raise ArgumentError, fn -> - GlobalCollect.validate_config(@invalid_config) - end - end - end - describe "purchase" do test "with valid card" do with_mock HTTPoison, diff --git a/test/mocks/global_collect_mock.exs b/test/mocks/global_collect_mock.exs index 219b9551..ebedf90a 100644 --- a/test/mocks/global_collect_mock.exs +++ b/test/mocks/global_collect_mock.exs @@ -3,7 +3,15 @@ defmodule Gringotts.Gateways.GlobalCollectMock do {:ok, %HTTPoison.Response{ body: - "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000074\",\n \"externalReference\" : \"000000122600000000740000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000740000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20180118135349\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}", + ~s/{"creationOutput":{"additionalReference":"00000012260000000074","externalReference": + "000000122600000000740000100001"},"payment":{"id":"000000122600000000740000100001", + "paymentOutput":{"amountOfMoney":{"amount":500,"currencyCode":"USD"},"references": + {"paymentReference":"0"},"paymentMethod":"card","cardPaymentMethodSpecificOutput": + {"paymentProductId":1,"authorisationCode":"OK1131","fraudResults":{"fraudServiceResult": + "no-advice","avsResult":"0","cvvResult":"0"},"card":{"cardNumber":"************7977", + "expiryDate":"1218"}}},"status":"PENDING_APPROVAL","statusOutput":{"isCancellable":true, + "statusCategory":"PENDING_MERCHANT","statusCode":600,"statusCodeChangeDateTime": + "20180118135349","isAuthorized":true,"isRefundable":false}}}/, headers: [ {"Date", "Thu, 18 Jan 2018 12:53:49 GMT"}, {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"}, @@ -22,7 +30,18 @@ defmodule Gringotts.Gateways.GlobalCollectMock do {:ok, %HTTPoison.Response{ body: - "{\n \"errorId\" : \"363899bd-acfb-4452-bbb0-741c0df6b4b8\",\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"980825\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000075\",\n \"externalReference\" : \"000000122600000000750000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000750000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"546247\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20180118135651\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n }\n}", + ~s/{"errorId" : "363899bd-acfb-4452-bbb0-741c0df6b4b8","errors" : [ {"code" : "21000120", + "requestId" : "980825","propertyName" : "cardPaymentMethodSpecificInput.card.expiryDate", + "message" : "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT", + "httpStatusCode" : 400} ],"paymentResult" : {"creationOutput" : { " additionalReference" : "00000012260000000075", + "externalReference" : "000000122600000000750000100001"},"payment" : {"id" : "000000122600000000750000100001", + "paymentOutput" : {"amountOfMoney" : {"amount" : 500,"currencyCode" : "USD"},"references" : {"paymentReference" : "0"}, + "paymentMethod" : "card","cardPaymentMethodSpecificOutput" : {"paymentProductId" : 1}}, + "status" : "REJECTED","statusOutput" : {"errors" : [ {"code" : "21000120", + "requestId" : "546247","propertyName" : "cardPaymentMethodSpecificInput.card.expiryDate", + "message" : "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT", + "httpStatusCode" : 400} ],"isCancellable" : false,"statusCategory" : "UNSUCCESSFUL","statusCode" : 100, + "statusCodeChangeDateTime" : "20180118135651","isAuthorized" : false,"isRefundable" : false}}}}/, headers: [ {"Date", "Thu, 18 Jan 2018 12:56:51 GMT"}, {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"}, @@ -40,7 +59,10 @@ defmodule Gringotts.Gateways.GlobalCollectMock do {:ok, %HTTPoison.Response{ body: - "{\n \"errorId\" : \"8c34dc0b-776c-44e3-8cd4-b36222960153\",\n \"errors\" : [ {\n \"code\" : \"1099\",\n \"id\" : \"INVALID_VALUE\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"message\" : \"INVALID_VALUE: '50.3' is not a valid value for field 'amount'\",\n \"httpStatusCode\" : 400\n } ]\n}", + ~s/{ "errorId" : "8c34dc0b-776c-44e3-8cd4-b36222960153","errors" : [ {"code" : "1099","id" : + "INVALID_VALUE","category" : "CONNECT_PLATFORM_ERROR","message" : + "INVALID_VALUE: '50.3' is not a valid value for field 'amount'", + "httpStatusCode" : 400 } ]}/, headers: [ {"Date", "Wed, 24 Jan 2018 07:16:06 GMT"}, {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"}, @@ -58,7 +80,16 @@ defmodule Gringotts.Gateways.GlobalCollectMock do {:ok, %HTTPoison.Response{ body: - "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000065\",\n \"externalReference\" : \"000000122600000000650000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000650000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20180118110419\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}", + ~s/{"creationOutput" : {"additionalReference" : "00000012260000000065","externalReference" : + "000000122600000000650000100001"},"payment" : {"id" : "000000122600000000650000100001", + "paymentOutput" :{"amountOfMoney" : {"amount" : 500,"currencyCode" : "USD"},"references" : + {"paymentReference" : "0" },"paymentMethod" : "card","cardPaymentMethodSpecificOutput" : + {"paymentProductId" : 1,"authorisationCode" : "OK1131","fraudResults" : + {"fraudServiceResult" : "no-advice","avsResult" : "0","cvvResult" : "0"},"card" : + {"cardNumber" : "************7977","expiryDate" : "1218"}}},"status" : "PENDING_APPROVAL", + "statusOutput" : {"isCancellable" : true,"statusCategory" : "PENDING_MERCHANT","statusCode" + : 600,"statusCodeChangeDateTime" : "20180118110419","isAuthorized" : true, + "isRefundable" : false}}}/, headers: [ {"Date", "Thu, 18 Jan 2018 10:04:19 GMT"}, {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"}, @@ -77,7 +108,21 @@ defmodule Gringotts.Gateways.GlobalCollectMock do {:ok, %HTTPoison.Response{ body: - "{\n \"errorId\" : \"dcdf5c8d-e475-4fbc-ac57-76123c1640a2\",\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"978754\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000012260000000066\",\n \"externalReference\" : \"000000122600000000660000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000122600000000660000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 500,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"21000120\",\n \"requestId\" : \"978755\",\n \"propertyName\" : \"cardPaymentMethodSpecificInput.card.expiryDate\",\n \"message\" : \"cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT\",\n \"httpStatusCode\" : 400\n } ],\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20180118111508\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n }\n}", + ~s/{"errorId" : "dcdf5c8d-e475-4fbc-ac57-76123c1640a2","errors" : [ {"code" : "21000120", + "requestId" : "978754","propertyName" : "cardPaymentMethodSpecificInput.card.expiryDate","message": + "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT", + "httpStatusCode" : 400} ],"paymentResult" : {"creationOutput" : + {"additionalReference" :"00000012260000000066","externalReference" : + "000000122600000000660000100001"},"payment" :{"id" : "000000122600000000660000100001", + "paymentOutput" : {"amountOfMoney" : {"amount" : 500,"currencyCode" : "USD"}, + "references" : {"paymentReference" : "0"},"paymentMethod" : "card", + "cardPaymentMethodSpecificOutput" : {"paymentProductId" : 1}},"status" : "REJECTED", + "statusOutput":{"errors" : [ {"code" : "21000120","requestId" : "978755","propertyName" : + "cardPaymentMethodSpecificInput.card.expiryDate","message" : + "cardPaymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT", + "httpStatusCode" : 400} ],"isCancellable" : false,"statusCategory" : + "UNSUCCESSFUL","statusCode" : 100,"statusCodeChangeDateTime" : "20180118111508", + "isAuthorized" : false,"isRefundable" : false}}}}/, headers: [ {"Date", "Thu, 18 Jan 2018 10:15:08 GMT"}, {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"}, @@ -95,7 +140,9 @@ defmodule Gringotts.Gateways.GlobalCollectMock do {:ok, %HTTPoison.Response{ body: - "{\n \"errorId\" : \"1dbef568-ed86-4c8d-a3c3-74ced258d5a2\",\n \"errors\" : [ {\n \"code\" : \"1099\",\n \"id\" : \"INVALID_VALUE\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"message\" : \"INVALID_VALUE: '50.3' is not a valid value for field 'amount'\",\n \"httpStatusCode\" : 400\n } ]\n}", + ~s/{"errorId" : "1dbef568-ed86-4c8d-a3c3-74ced258d5a2","errors" : [ {"code" : "1099","id" : + "INVALID_VALUE", "category" : "CONNECT_PLATFORM_ERROR","message" : + "INVALID_VALUE: '50.3' is not a valid value for field 'amount'","httpStatusCode" : 400} ]}/, headers: [ {"Date", "Tue, 23 Jan 2018 11:18:11 GMT"}, {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"}, @@ -113,7 +160,8 @@ defmodule Gringotts.Gateways.GlobalCollectMock do {:ok, %HTTPoison.Response{ body: - "{\n \"errorId\" : \"b6ba00d2-8f11-4822-8f32-c6d0a4d8793b\",\n \"errors\" : [ {\n \"code\" : \"300450\",\n \"message\" : \"ORDER WITHOUT REFUNDABLE PAYMENTS\",\n \"httpStatusCode\" : 400\n } ]\n}", + ~s/{ "errorId" : "b6ba00d2-8f11-4822-8f32-c6d0a4d8793b", "errors" : [ {"code" : "300450", + "message" : "ORDER WITHOUT REFUNDABLE PAYMENTS", "httpStatusCode" : 400 } ]}/, headers: [ {"Date", "Wed, 24 Jan 2018 05:33:56 GMT"}, {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"}, @@ -131,8 +179,15 @@ defmodule Gringotts.Gateways.GlobalCollectMock do def test_for_capture_with_valid_paymentid do {:ok, %HTTPoison.Response{ - body: - "{\n \"payment\" : {\n \"id\" : \"000000122600000000650000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 50,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"CAPTURE_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_CONNECT_OR_3RD_PARTY\",\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20180123140826\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}", + body: ~s/{ "payment" : {"id" : "000000122600000000650000100001", "paymentOutput" : { + "amountOfMoney" :{"amount" : 50,"currencyCode" : "USD"},"references" : {"paymentReference" + : "0"},"paymentMethod" : "card","cardPaymentMethodSpecificOutput" : {"paymentProductId" : + 1,"authorisationCode" : "OK1131","fraudResults" : {"fraudServiceResult" : "no-advice", + "avsResult" : "0","cvvResult" : "0"},"card" :{"cardNumber" : "************7977", + "expiryDate" : "1218"}}},"status" : "CAPTURE_REQUESTED","statusOutput" : + {"isCancellable" : true,"statusCategory" : "PENDING_CONNECT_OR_3RD_PARTY", + "statusCode" : 800,"statusCodeChangeDateTime" : "20180123140826","isAuthorized" : true, + "isRefundable" : false} }}/, headers: [ {"Date", "Tue, 23 Jan 2018 13:08:26 GMT"}, {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"}, @@ -149,8 +204,9 @@ defmodule Gringotts.Gateways.GlobalCollectMock do def test_for_capture_with_invalid_paymentid do {:ok, %HTTPoison.Response{ - body: - "{\n \"errorId\" : \"ccb99804-0240-45b6-bb28-52aaae59d71b\",\n \"errors\" : [ {\n \"code\" : \"1002\",\n \"id\" : \"UNKNOWN_PAYMENT_ID\",\n \"category\" : \"CONNECT_PLATFORM_ERROR\",\n \"propertyName\" : \"paymentId\",\n \"message\" : \"UNKNOWN_PAYMENT_ID\",\n \"httpStatusCode\" : 404\n } ]\n}", + body: ~s/{ "errorId" : "ccb99804-0240-45b6-bb28-52aaae59d71b", "errors" : [ + {"code" : "1002","id" :"UNKNOWN_PAYMENT_ID","category" : "CONNECT_PLATFORM_ERROR", + "propertyName" : "paymentId","message": "UNKNOWN_PAYMENT_ID","httpStatusCode" :404}]}/, headers: [ {"Date", "Tue, 23 Jan 2018 12:25:59 GMT"}, {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"}, @@ -167,7 +223,15 @@ defmodule Gringotts.Gateways.GlobalCollectMock do {:ok, %HTTPoison.Response{ body: - "{\n \"payment\" : {\n \"id\" : \"000000122600000000870000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 50,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"1218\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20180124064204\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n }\n}", + ~s/{ "payment" : {"id" : "000000122600000000870000100001","paymentOutput" : {"amountOfMoney" + :{"amount" : 50,"currencyCode" : "USD"},"references" : {"paymentReference" : "0"}, + "paymentMethod" : "card","cardPaymentMethodSpecificOutput" : {"paymentProductId" : 1, + "authorisationCode" : "OK1131","fraudResults" : {"fraudServiceResult" : "no-advice", + "avsResult" : "0","cvvResult" : "0"},"card" :{"cardNumber" : "************7977", + "expiryDate" : "1218"}}},"status" : "CANCELLED","statusOutput":{"isCancellable" : + false,"statusCategory" : "UNSUCCESSFUL","statusCode" : 99999, + "statusCodeChangeDateTime" : "20180124064204","isAuthorized" : false,"isRefundable" : + false}}}/, headers: [ {"Date", "Wed, 24 Jan 2018 05:42:04 GMT"}, {"Server", "Apache/2.4.27 (Unix) OpenSSL/1.0.2l"},