From 683fa16d5513bcdfbb758a9a51dbe0ce7246ca7c Mon Sep 17 00:00:00 2001 From: ravirocx Date: Thu, 26 Apr 2018 16:14:45 +0530 Subject: [PATCH 1/7] initial commit --- lib/gringotts/gateways/we_pay.ex | 404 ++++++++++++++++++++++ test/integration/gateways/we_pay_test.exs | 159 +++++++++ 2 files changed, 563 insertions(+) create mode 100644 lib/gringotts/gateways/we_pay.ex create mode 100644 test/integration/gateways/we_pay_test.exs diff --git a/lib/gringotts/gateways/we_pay.ex b/lib/gringotts/gateways/we_pay.ex new file mode 100644 index 00000000..2bd732e7 --- /dev/null +++ b/lib/gringotts/gateways/we_pay.ex @@ -0,0 +1,404 @@ +defmodule Gringotts.Gateways.WePay do + @moduledoc """ + [wepay][home] gateway implementation. + + ## Instructions! + + ***This is an example `moduledoc`, and suggests some items that should be + documented in here.*** + + The quotation boxes like the one below will guide you in writing excellent + documentation for your gateway. All our gateways are documented in this manner + and we aim to keep our docs as consistent with each other as possible. + **Please read them and do as they suggest**. Feel free to add or skip sections + though. + + If you'd like to make edits to the template docs, they exist at + `templates/gateway.eex`. We encourage you to make corrections and open a PR + and tag it with the label `template`. + + ***Actual docs begin below this line!*** + + -------------------------------------------------------------------------------- + + > List features that have been implemented, and what "actions" they map to as + > per the wepay gateway docs. + > A table suits really well for this. + + ## Optional or extra parameters + + Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply + optional arguments for transactions with the gateway. + + > List all available (ie, those that will be supported by this module) keys, a + > description of their function/role and whether they have been implemented + > and tested. + > A table suits really well for this. + + ## Registering your wepay account at `Gringotts` + + Explain how to make an account with the gateway and show how to put the + `required_keys` (like authentication info) to the configuration. + + > Here's how the secrets map to the required configuration parameters for wepay: + > + > | Config parameter | wepay secret | + > | --------------- | ---------- | + > | `:access_token` | **AccessToken**| + + > Your Application config **must include the `[:access_token]` field(s)** and would look + > something like this: + > + > config :gringotts, Gringotts.Gateways.WePay, + > access_token: "your_secret_access_token" + + + ## Scope of this module + + > It's unlikely that your first iteration will support all features of the + > gateway, so list down those items that are missing. + + ## Supported currencies and countries + + > It's enough if you just add a link to the gateway's docs or FAQ that provide + > info about this. + + ## Following the examples + + 1. First, set up a sample application and configure it to work with MONEI. + - You could do that from scratch by following our [Getting Started][gs] guide. + - To save you time, we recommend [cloning our example + repo][example] that gives you a pre-configured sample app ready-to-go. + + You could use the same config or update it the with your "secrets" + as described [above](#module-registering-your-monei-account-at-wepay). + + 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.WePay} + 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://go.wepay.com + [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: [:access_token] + + import Poison, only: [decode: 1] + + alias Gringotts.{Money, CreditCard, Response} + + @test_url "https://stage.wepayapi.com/v2" + + @doc """ + Performs a (pre) Authorize operation. + + The authorization validates the `card` details with the banking network, + places a hold on the transaction `amount` in the customer’s issuing bank. + + > ** You could perhaps:** + > 1. describe what are the important fields in the Response struct + > 2. mention what a merchant can do with these important fields (ex: + > `capture/3`, etc.) + + ## Note + + > If there's anything noteworthy about this operation, it comes here. + + ## Example + + > A barebones example using the bindings you've suggested in the `moduledoc`. + """ + @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response} + def authorize(amount, card = %CreditCard{}, opts) do + {currency, value, _} = Money.to_integer(amount) + + with {:ok, card_token_response} <- store(card, opts), + {:ok, card_token} <- extract_card_token(card_token_response) do + body = + Poison.encode!(%{ + account_id: opts[:account_id], + short_description: opts[:short_description], + type: opts[:type], + amount: value, + currency: currency, + long_description: opts[:long_description], + callback_uri: opts[:callback_uri], + auto_release: true, + unique_id: opts[:unique_id], + reference_id: opts[:reference_id], + delivery_type: opts[:delivery_type], + payment_method: %{ + type: "credit_card", + credit_card: %{ + id: card_token, + auto_capture: false + } + } + }) + + commit(:post, "/checkout/create/", body, opts) + end + end + + @doc """ + Captures a pre-authorized `amount`. + + `amount` is transferred to the merchant account by wepay 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 + body = + Poison.encode!(%{ + checkout_id: payment_id + }) + + commit(:post, "/checkout/capture/", body, opts) + end + + @doc """ + Transfers `amount` from the customer to the merchant. + + wepay attempts to process a purchase on behalf of the customer, by + debiting `amount` from the customer's account by charging the customer's + `card`. + + ## Note + + > If there's anything noteworthy about this operation, it comes here. + + ## Example + + > A barebones example using the bindings you've suggested in the `moduledoc`. + """ + @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response} + def purchase(amount, card = %CreditCard{}, opts) do + {currency, value, _} = Money.to_integer(amount) + + with {:ok, card_token_response} <- store(card, opts), + {:ok, card_token} <- extract_card_token(card_token_response) do + body = + Poison.encode!(%{ + account_id: opts[:account_id], + short_description: opts[:short_description], + type: opts[:type], + amount: value, + currency: currency, + long_description: opts[:long_description], + callback_uri: opts[:callback_uri], + auto_release: true, + unique_id: opts[:unique_id], + reference_id: opts[:reference_id], + delivery_type: opts[:delivery_type], + payment_method: %{ + type: "credit_card", + credit_card: %{ + id: card_token + } + } + }) + + commit(:post, "/checkout/create/", body, opts) + end + end + + @doc """ + Voids the referenced payment. + + This method attempts a reversal of a previous transaction referenced by + `payment_id`. + + > As a consequence, the customer will never see any booking on his statement. + + ## Note + + > 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 + body = + Poison.encode!(%{ + checkout_id: payment_id, + cancel_reason: opts[:cancel_reason] + }) + + commit(:post, "/checkout/cancel/", body, opts) + end + + @doc """ + Refunds the `amount` to the customer's account with reference to a prior transfer. + + > Refunds are allowed on which kinds of "prior" transactions? + + ## Note + + > The end customer will usually see two bookings/records on his statement. Is + > that true for wepay? + > 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 + body = + Poison.encode!(%{ + checkout_id: payment_id, + refund_reason: opts[:refund_reason] + }) + + commit(:post, "/checkout/refund/", body, opts) + end + + @doc """ + Stores the payment-source data for later use. + + > This usually enable "One Click" and/or "Recurring Payments" + + ## Note + + > If there's anything noteworthy about this operation, it comes here. + + ## Example + + > A barebones example using the bindings you've suggested in the `moduledoc`. + """ + @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response} + def store(%CreditCard{} = card, opts) do + body = + Poison.encode!(%{ + client_id: opts[:client_id], + cc_number: card.number, + user_name: CreditCard.full_name(card), + email: opts[:email], + cvv: card.verification_code, + expiration_month: card.month, + expiration_year: card.year, + original_ip: opts[:original_ip], + original_device: opts[:original_device], + reference_id: opts[:reference_id], + address: %{ + address1: opts[:address].street1, + address2: opts[:address].street2, + city: opts[:address].city, + region: opts[:address].region, + country: opts[:address].country, + postal_code: opts[:address].postal_code + } + }) + + commit(:post, "/credit_card/create/", body, 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 + body = + Poison.encode!(%{ + client_id: opts[:client_id], + client_secret: opts[:client_secret], + credit_card_id: registration_id + }) + + commit(:post, "/credit_card/delete/", body, opts) + end + + ############################################################################### + # PRIVATE METHODS # + ############################################################################### + + # Makes the request to wepay's network. + # For consistency with other gateway implementations, make your (final) + # network request in here, and parse it using another private method called + # `respond`. + @spec commit(atom, String.t(), String.t(), keyword) :: {:ok | :error, Response} + defp commit(:post, endpoint, body, opts) do + url = @test_url <> "#{endpoint}" + + headers = [ + {"Content-Type", "application/json"}, + {"Authorization", "Bearer " <> opts[:config][:access_token]} + ] + + HTTPoison.request(:post, url, body, headers) + |> respond + end + + defp extract_card_token(%{token: token}) do + {:ok, token} + end + + # Parses wepay'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..299 do + {:ok, parsed} = decode(body) + token = parsed["credit_card_id"] + id = parsed["checkout_id"] + message = parsed["state"] + + { + :ok, + Response.success(id: id, message: message, token: token, raw: parsed, status_code: code) + } + end + + defp respond({:ok, %{status_code: status_code, body: body}}) do + {:ok, parsed} = decode(body) + detail = parsed["error_description"] + + { + :error, + Response.error(status_code: status_code, message: detail, raw: body) + } + end + + 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 diff --git a/test/integration/gateways/we_pay_test.exs b/test/integration/gateways/we_pay_test.exs new file mode 100644 index 00000000..0bd22bb3 --- /dev/null +++ b/test/integration/gateways/we_pay_test.exs @@ -0,0 +1,159 @@ +defmodule Gringotts.Integration.Gateways.WePayTest do + # Integration tests for the WePay + + use ExUnit.Case, async: false + alias Gringotts.Gateways.WePay + + alias Gringotts.{ + CreditCard, + Address + } + + alias Gringotts.Gateways.WePay, as: Gateway + + @moduletag :integration + + @amount Money.new(420, :USD) + + @bad_card1 %CreditCard{ + first_name: "Harry", + last_name: "Potter", + number: "4100000000000001", + 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: "MH", + country: "IN", + postal_code: "411015", + phone: "8007810916" + } + + @opts [ + client_id: 113_167, + email: "hi@hello.com", + original_ip: "1.1.1.1", + client_secret: "e9d1d9af6c", + account_id: 1_145_345_748, + short_description: "test payment", + type: "service", + refund_reason: "the product was defective", + cancel_reason: "the product was defective, i don't want", + config: [ + access_token: "STAGE_a24e062d0fc2d399412ea0ff1c1e748b04598b341769b3797d3bd207ff8cf6b2" + ], + address: @add + ] + + describe "store" do + test "[Store] with CreditCard" do + use_cassette "WePay/store_with_valid_card" do + assert {:ok, response} = Gateway.store(@good_card, @opts) + assert response.success == true + assert response.status_code == 200 + end + end + + test "[Store] with bad CreditCard" do + use_cassette "WePay/store_with_invalid_card" do + assert {:error, response} = Gateway.store(@bad_card1, @opts) + assert response.success == false + assert response.status_code == 400 + end + end + end + + describe "authorize" do + test "[authorize] with good parameters" do + use_cassette "WePay/authorize_with_valid_card" do + assert {:ok, response} = Gateway.authorize(@amount, @good_card, @opts) + assert response.success == true + assert response.status_code == 200 + end + end + + test "[authorize] with bad CreditCard" do + use_cassette "WePay/authorize_with_invalid_card" do + assert {:error, response} = Gateway.authorize(@amount, @bad_card1, @opts) + assert response.success == false + assert response.status_code == 400 + end + end + end + + describe "purchase" do + test "[purchase] with good parameters" do + use_cassette "WePay/purchase_with_valid_card" do + assert {:ok, response} = Gateway.purchase(@amount, @good_card, @opts) + assert response.success == true + assert response.status_code == 200 + end + end + + test "[purchase] with bad CreditCard" do + use_cassette "WePay/purchase_with_invalid_card" do + assert {:error, response} = Gateway.purchase(@amount, @bad_card1, @opts) + assert response.success == false + assert response.status_code == 400 + end + end + end + + describe "capture" do + test "[Capture]" do + use_cassette "WePay/capture" do + assert {:ok, response} = Gateway.authorize(@amount, @good_card, @opts) + assert response.success == true + assert response.status_code == 200 + payment_id = response.id + assert {:ok, response} = Gateway.capture(payment_id, @amount, @opts) + assert response.success == true + assert response.status_code == 200 + end + end + end + + describe "Void" do + test "[Void]" do + use_cassette "WePay/void" do + assert {:ok, response} = Gateway.purchase(@amount, @good_card, @opts) + assert response.success == true + assert response.status_code == 200 + payment_id = response.id + assert {:ok, response} = Gateway.void(payment_id, @opts) + assert response.success == true + assert response.status_code == 200 + end + end + end + + describe "Unstore" do + test "[Unstore]" do + use_cassette "WePay/unstore" do + assert {:ok, response} = Gateway.store(@good_card, @opts) + assert response.success == true + assert response.status_code == 200 + payment_id = response.token + assert {:ok, response} = Gateway.unstore(payment_id, @opts) + assert response.success == true + assert response.status_code == 200 + end + end + end +end \ No newline at end of file From 2a551795b153c5baa0cb6eb35e2574789e539333 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Thu, 26 Apr 2018 16:20:43 +0530 Subject: [PATCH 2/7] improved code readability --- lib/gringotts/gateways/we_pay.ex | 4 ++-- test/integration/gateways/we_pay_test.exs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gringotts/gateways/we_pay.ex b/lib/gringotts/gateways/we_pay.ex index 2bd732e7..a446b160 100644 --- a/lib/gringotts/gateways/we_pay.ex +++ b/lib/gringotts/gateways/we_pay.ex @@ -359,7 +359,7 @@ defmodule Gringotts.Gateways.WePay do @spec commit(atom, String.t(), String.t(), keyword) :: {:ok | :error, Response} defp commit(:post, endpoint, body, opts) do url = @test_url <> "#{endpoint}" - + headers = [ {"Content-Type", "application/json"}, {"Authorization", "Bearer " <> opts[:config][:access_token]} @@ -401,4 +401,4 @@ defmodule Gringotts.Gateways.WePay 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/we_pay_test.exs b/test/integration/gateways/we_pay_test.exs index 0bd22bb3..81db171d 100644 --- a/test/integration/gateways/we_pay_test.exs +++ b/test/integration/gateways/we_pay_test.exs @@ -156,4 +156,4 @@ defmodule Gringotts.Integration.Gateways.WePayTest do end end end -end \ No newline at end of file +end From e97dbc3f7a78713e6fb457da95d5f389f0041dc8 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Fri, 27 Apr 2018 21:07:36 +0530 Subject: [PATCH 3/7] Added docs, added more features --- lib/gringotts/gateways/we_pay.ex | 208 +++++++++++++++++-------------- 1 file changed, 115 insertions(+), 93 deletions(-) diff --git a/lib/gringotts/gateways/we_pay.ex b/lib/gringotts/gateways/we_pay.ex index a446b160..477085cf 100644 --- a/lib/gringotts/gateways/we_pay.ex +++ b/lib/gringotts/gateways/we_pay.ex @@ -2,70 +2,42 @@ defmodule Gringotts.Gateways.WePay do @moduledoc """ [wepay][home] gateway implementation. - ## Instructions! + A module for working with the WePay payment gateway. - ***This is an example `moduledoc`, and suggests some items that should be - documented in here.*** + Refer the official WePay [API docs][docs]. - The quotation boxes like the one below will guide you in writing excellent - documentation for your gateway. All our gateways are documented in this manner - and we aim to keep our docs as consistent with each other as possible. - **Please read them and do as they suggest**. Feel free to add or skip sections - though. + The following set of functions for WePay have been implemented: - If you'd like to make edits to the template docs, they exist at - `templates/gateway.eex`. We encourage you to make corrections and open a PR - and tag it with the label `template`. - - ***Actual docs begin below this line!*** - - -------------------------------------------------------------------------------- - - > List features that have been implemented, and what "actions" they map to as - > per the wepay gateway docs. - > A table suits really well for this. + | Action | Method | + | ------ | ------ | + | Authorize a Credit Card | `authorize/3` | + | Capture a previously authorized amount | `capture/3` | + | Charge a Credit Card | `purchase/3` | + | Refund a transaction | `refund/3` | + | Void a transaction | `void/2` | + | Create Customer Profile | `store/2` | + | Delete Customer Profile | `unstore/2` | ## Optional or extra parameters Most `Gringotts` API calls accept an optional `Keyword` list `opts` to supply optional arguments for transactions with the gateway. - > List all available (ie, those that will be supported by this module) keys, a - > description of their function/role and whether they have been implemented - > and tested. - > A table suits really well for this. - - ## Registering your wepay account at `Gringotts` - - Explain how to make an account with the gateway and show how to put the - `required_keys` (like authentication info) to the configuration. - - > Here's how the secrets map to the required configuration parameters for wepay: - > - > | Config parameter | wepay secret | - > | --------------- | ---------- | - > | `:access_token` | **AccessToken**| - - > Your Application config **must include the `[:access_token]` field(s)** and would look - > something like this: - > - > config :gringotts, Gringotts.Gateways.WePay, - > access_token: "your_secret_access_token" - - - ## Scope of this module + To know more about these keywords visit [Request and Response][req-resp] tabs for each + API method. - > It's unlikely that your first iteration will support all features of the - > gateway, so list down those items that are missing. + [docs]: hhttps://developer.wepay.com/ + [req-resp]: https://developer.wepay.com/api/reference/structures ## Supported currencies and countries - > It's enough if you just add a link to the gateway's docs or FAQ that provide - > info about this. + WePay supports the countries listed [here][all-country-list] + + [all-country-list]: [https://support.wepay.com/hc/en-us/articles/203611643-Is-WePay-International-] ## 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 WePay. - 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. @@ -82,14 +54,12 @@ defmodule Gringotts.Gateways.WePay do 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://go.wepay.com [example]: https://github.com/aviabird/gringotts_example + """ # The Base module has the (abstract) public API, and some utility @@ -113,19 +83,18 @@ defmodule Gringotts.Gateways.WePay do The authorization validates the `card` details with the banking network, places a hold on the transaction `amount` in the customer’s issuing bank. - > ** You could perhaps:** - > 1. describe what are the important fields in the Response struct - > 2. mention what a merchant can do with these important fields (ex: - > `capture/3`, etc.) - - ## Note + WePay returns an ID string which can be used to: - > If there's anything noteworthy about this operation, it comes here. + * `capture/3` _an_ amount. ## Example - - > A barebones example using the bindings you've suggested in the `moduledoc`. + ``` + iex> amount = Money.new(42, :USD) + iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.WePay, amount, card, opts) + iex> auth_result.id # This is the authorization ID + ``` """ + @spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response} def authorize(amount, card = %CreditCard{}, opts) do {currency, value, _} = Money.to_integer(amount) @@ -158,6 +127,35 @@ defmodule Gringotts.Gateways.WePay do end end + # authorize with card token. + def authorize(amount, card_token, opts) do + {currency, value, _} = Money.to_integer(amount) + + body = + Poison.encode!(%{ + account_id: opts[:account_id], + short_description: opts[:short_description], + type: opts[:type], + amount: value, + currency: currency, + long_description: opts[:long_description], + callback_uri: opts[:callback_uri], + auto_release: true, + unique_id: opts[:unique_id], + reference_id: opts[:reference_id], + delivery_type: opts[:delivery_type], + payment_method: %{ + type: "credit_card", + credit_card: %{ + id: card_token, + auto_capture: false + } + } + }) + + commit(:post, "/checkout/create/", body, opts) + end + @doc """ Captures a pre-authorized `amount`. @@ -166,12 +164,12 @@ defmodule Gringotts.Gateways.WePay do ## Note - > If there's anything noteworthy about this operation, it comes here. - > For example, does the gateway support partial, multiple captures? + > WePay **do not** support partial captures. ## Example - - > A barebones example using the bindings you've suggested in the `moduledoc`. + ``` + iex> {:ok, capture_result} = Gringotts.capture(Gringotts.Gateways.WePay, amount, auth_result.id, opts) + ``` """ @spec capture(String.t(), Money.t(), keyword) :: {:ok | :error, Response} def capture(payment_id, amount, opts) do @@ -190,13 +188,12 @@ defmodule Gringotts.Gateways.WePay do 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`. + ``` + iex> amount = Money.new(42, :USD) + iex> {:ok, purchase_result} = Gringotts.purchase(Gringotts.Gateways.WePay, amount, card, opts) + iex> purchase_result.id # This is the checkout ID + ``` """ @spec purchase(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response} def purchase(amount, card = %CreditCard{}, opts) do @@ -229,6 +226,34 @@ defmodule Gringotts.Gateways.WePay do end end + # purchase with card token. + def purchase(amount, card_token, opts) do + {currency, value, _} = Money.to_integer(amount) + + body = + Poison.encode!(%{ + account_id: opts[:account_id], + short_description: opts[:short_description], + type: opts[:type], + amount: value, + currency: currency, + long_description: opts[:long_description], + callback_uri: opts[:callback_uri], + auto_release: true, + unique_id: opts[:unique_id], + reference_id: opts[:reference_id], + delivery_type: opts[:delivery_type], + payment_method: %{ + type: "credit_card", + credit_card: %{ + id: card_token + } + } + }) + + commit(:post, "/checkout/create/", body, opts) + end + @doc """ Voids the referenced payment. @@ -238,13 +263,13 @@ defmodule Gringotts.Gateways.WePay do > 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? + > As a consequence, the customer will never see any booking on his statement. + > Checkout must be in purchased or captured state. ## Example - - > A barebones example using the bindings you've suggested in the `moduledoc`. + ``` + iex> {:ok, void_result} = Gringotts.capture(Gringotts.Gateways.WePay, purchase_result.id, opts) + ``` """ @spec void(String.t(), keyword) :: {:ok | :error, Response} def void(payment_id, opts) do @@ -260,17 +285,17 @@ defmodule Gringotts.Gateways.WePay do @doc """ Refunds the `amount` to the customer's account with reference to a prior transfer. - > Refunds are allowed on which kinds of "prior" transactions? + > Refunds are allowed on Captured / purchased transraction. ## Note - > The end customer will usually see two bookings/records on his statement. Is - > that true for wepay? - > Is there a limited time window within which a void can be perfomed? + * It is recommended to refund the transraction after 5 to 10 min. + * WePay does not support partial refunds. ## Example - - > A barebones example using the bindings you've suggested in the `moduledoc`. + ``` + iex> {:ok, refund_result} = Gringotts.refund(Gringotts.Gateways.WePay, purchase_result.id, amount) + ``` """ @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response} def refund(amount, payment_id, opts) do @@ -286,15 +311,15 @@ defmodule Gringotts.Gateways.WePay do @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. + > Currently "Recurring Payments" are not implemented. ## Example - - > A barebones example using the bindings you've suggested in the `moduledoc`. + ``` + iex> {:ok, store_result} = Gringotts.store(Gringotts.Gateways.WePay, card, opts) + iex> store_result.token #card token + ``` """ @spec store(CreditCard.t(), keyword) :: {:ok | :error, Response} def store(%CreditCard{} = card, opts) do @@ -327,14 +352,11 @@ defmodule Gringotts.Gateways.WePay do 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`. + ``` + iex> {:ok, store_result} = Gringotts.unstore(Gringotts.Gateways.WePay, store_result.token, opts) + ``` """ @spec unstore(String.t(), keyword) :: {:ok | :error, Response} def unstore(registration_id, opts) do From 1ce20d12650f8c0f27d7ff925782ccbe2bb7ea82 Mon Sep 17 00:00:00 2001 From: ravirocx Date: Fri, 27 Apr 2018 21:22:45 +0530 Subject: [PATCH 4/7] improved tests, docs --- lib/gringotts/gateways/we_pay.ex | 4 ++-- test/integration/gateways/we_pay_test.exs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/gringotts/gateways/we_pay.ex b/lib/gringotts/gateways/we_pay.ex index 477085cf..e9c05ea4 100644 --- a/lib/gringotts/gateways/we_pay.ex +++ b/lib/gringotts/gateways/we_pay.ex @@ -32,7 +32,7 @@ defmodule Gringotts.Gateways.WePay do ## Supported currencies and countries WePay supports the countries listed [here][all-country-list] - + [all-country-list]: [https://support.wepay.com/hc/en-us/articles/203611643-Is-WePay-International-] ## Following the examples @@ -352,7 +352,7 @@ defmodule Gringotts.Gateways.WePay do Removes card or payment info that was previously `store/2`d Deletes previously stored payment-source data. - + ## Example ``` iex> {:ok, store_result} = Gringotts.unstore(Gringotts.Gateways.WePay, store_result.token, opts) diff --git a/test/integration/gateways/we_pay_test.exs b/test/integration/gateways/we_pay_test.exs index 81db171d..4a900bcb 100644 --- a/test/integration/gateways/we_pay_test.exs +++ b/test/integration/gateways/we_pay_test.exs @@ -2,6 +2,7 @@ defmodule Gringotts.Integration.Gateways.WePayTest do # Integration tests for the WePay use ExUnit.Case, async: false + use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney alias Gringotts.Gateways.WePay alias Gringotts.{ From 9ec615ec6d01c49dd85b94a9a69ee31cb724596e Mon Sep 17 00:00:00 2001 From: ravirocx Date: Sat, 16 Jun 2018 17:04:24 +0530 Subject: [PATCH 5/7] fixed errors, removed redundant, partial refunds support --- lib/gringotts/gateways/we_pay.ex | 97 +++++++++-------------- test/integration/gateways/we_pay_test.exs | 50 ++++++------ 2 files changed, 65 insertions(+), 82 deletions(-) diff --git a/lib/gringotts/gateways/we_pay.ex b/lib/gringotts/gateways/we_pay.ex index e9c05ea4..30c03ec8 100644 --- a/lib/gringotts/gateways/we_pay.ex +++ b/lib/gringotts/gateways/we_pay.ex @@ -73,7 +73,7 @@ defmodule Gringotts.Gateways.WePay do import Poison, only: [decode: 1] - alias Gringotts.{Money, CreditCard, Response} + alias Gringotts.{CreditCard, Money, Response} @test_url "https://stage.wepayapi.com/v2" @@ -102,18 +102,8 @@ defmodule Gringotts.Gateways.WePay do with {:ok, card_token_response} <- store(card, opts), {:ok, card_token} <- extract_card_token(card_token_response) do body = - Poison.encode!(%{ - account_id: opts[:account_id], - short_description: opts[:short_description], - type: opts[:type], - amount: value, - currency: currency, - long_description: opts[:long_description], - callback_uri: opts[:callback_uri], - auto_release: true, - unique_id: opts[:unique_id], - reference_id: opts[:reference_id], - delivery_type: opts[:delivery_type], + build(value, currency, opts) + |> Map.merge(%{ payment_method: %{ type: "credit_card", credit_card: %{ @@ -122,6 +112,7 @@ defmodule Gringotts.Gateways.WePay do } } }) + |> Poison.encode!() commit(:post, "/checkout/create/", body, opts) end @@ -132,18 +123,8 @@ defmodule Gringotts.Gateways.WePay do {currency, value, _} = Money.to_integer(amount) body = - Poison.encode!(%{ - account_id: opts[:account_id], - short_description: opts[:short_description], - type: opts[:type], - amount: value, - currency: currency, - long_description: opts[:long_description], - callback_uri: opts[:callback_uri], - auto_release: true, - unique_id: opts[:unique_id], - reference_id: opts[:reference_id], - delivery_type: opts[:delivery_type], + build(value, currency, opts) + |> Map.merge(%{ payment_method: %{ type: "credit_card", credit_card: %{ @@ -152,6 +133,7 @@ defmodule Gringotts.Gateways.WePay do } } }) + |> Poison.encode!() commit(:post, "/checkout/create/", body, opts) end @@ -202,18 +184,8 @@ defmodule Gringotts.Gateways.WePay do with {:ok, card_token_response} <- store(card, opts), {:ok, card_token} <- extract_card_token(card_token_response) do body = - Poison.encode!(%{ - account_id: opts[:account_id], - short_description: opts[:short_description], - type: opts[:type], - amount: value, - currency: currency, - long_description: opts[:long_description], - callback_uri: opts[:callback_uri], - auto_release: true, - unique_id: opts[:unique_id], - reference_id: opts[:reference_id], - delivery_type: opts[:delivery_type], + build(value, currency, opts) + |> Map.merge(%{ payment_method: %{ type: "credit_card", credit_card: %{ @@ -221,6 +193,7 @@ defmodule Gringotts.Gateways.WePay do } } }) + |> Poison.encode!() commit(:post, "/checkout/create/", body, opts) end @@ -231,18 +204,8 @@ defmodule Gringotts.Gateways.WePay do {currency, value, _} = Money.to_integer(amount) body = - Poison.encode!(%{ - account_id: opts[:account_id], - short_description: opts[:short_description], - type: opts[:type], - amount: value, - currency: currency, - long_description: opts[:long_description], - callback_uri: opts[:callback_uri], - auto_release: true, - unique_id: opts[:unique_id], - reference_id: opts[:reference_id], - delivery_type: opts[:delivery_type], + build(value, currency, opts) + |> Map.merge(%{ payment_method: %{ type: "credit_card", credit_card: %{ @@ -250,6 +213,7 @@ defmodule Gringotts.Gateways.WePay do } } }) + |> Poison.encode!() commit(:post, "/checkout/create/", body, opts) end @@ -299,9 +263,12 @@ defmodule Gringotts.Gateways.WePay do """ @spec refund(Money.t(), String.t(), keyword) :: {:ok | :error, Response} def refund(amount, payment_id, opts) do + {currency, value, _} = Money.to_integer(amount) + body = Poison.encode!(%{ checkout_id: payment_id, + amount: value, refund_reason: opts[:refund_reason] }) @@ -311,10 +278,6 @@ defmodule Gringotts.Gateways.WePay do @doc """ Stores the payment-source data for later use. - ## Note - - > Currently "Recurring Payments" are not implemented. - ## Example ``` iex> {:ok, store_result} = Gringotts.store(Gringotts.Gateways.WePay, card, opts) @@ -325,7 +288,7 @@ defmodule Gringotts.Gateways.WePay do def store(%CreditCard{} = card, opts) do body = Poison.encode!(%{ - client_id: opts[:client_id], + client_id: opts[:config][:client_id], cc_number: card.number, user_name: CreditCard.full_name(card), email: opts[:email], @@ -362,8 +325,8 @@ defmodule Gringotts.Gateways.WePay do def unstore(registration_id, opts) do body = Poison.encode!(%{ - client_id: opts[:client_id], - client_secret: opts[:client_secret], + client_id: opts[:config][:client_id], + client_secret: opts[:config][:client_secret], credit_card_id: registration_id }) @@ -395,6 +358,22 @@ defmodule Gringotts.Gateways.WePay do {:ok, token} end + defp build(value, currency, opts) do + %{ + account_id: opts[:config][:account_id], + short_description: opts[:short_description], + type: opts[:type], + amount: value, + currency: currency, + long_description: opts[:long_description], + callback_uri: opts[:callback_uri], + auto_release: true, + unique_id: opts[:unique_id], + reference_id: opts[:reference_id], + delivery_type: opts[:delivery_type] + } + end + # Parses wepay's response and returns a `Gringotts.Response` struct # in a `:ok`, `:error` tuple. @spec respond(term) :: {:ok | :error, Response} @@ -406,7 +385,7 @@ defmodule Gringotts.Gateways.WePay do { :ok, - Response.success(id: id, message: message, token: token, raw: parsed, status_code: code) + %Response{id: id, message: message, token: token, raw: parsed, status_code: code} } end @@ -416,11 +395,11 @@ defmodule Gringotts.Gateways.WePay do { :error, - Response.error(status_code: status_code, message: detail, raw: body) + %Response{status_code: status_code, message: detail, raw: body} } end defp respond({:error, %HTTPoison.Error{} = error}) do - {:error, Response.error(code: error.id, message: "HTTPoison says '#{error.reason}")} + {:error, %Response{status_code: 400, message: "HTTPoison says '#{error.reason}"}} end end diff --git a/test/integration/gateways/we_pay_test.exs b/test/integration/gateways/we_pay_test.exs index 4a900bcb..e39adf62 100644 --- a/test/integration/gateways/we_pay_test.exs +++ b/test/integration/gateways/we_pay_test.exs @@ -1,20 +1,21 @@ defmodule Gringotts.Integration.Gateways.WePayTest do # Integration tests for the WePay - use ExUnit.Case, async: false + use ExUnit.Case, async: true use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney alias Gringotts.Gateways.WePay alias Gringotts.{ + Address, CreditCard, - Address + FakeMoney } alias Gringotts.Gateways.WePay, as: Gateway - @moduletag :integration + # @moduletag :integration - @amount Money.new(420, :USD) + @amount FakeMoney.new(5, :USD) @bad_card1 %CreditCard{ first_name: "Harry", @@ -47,17 +48,17 @@ defmodule Gringotts.Integration.Gateways.WePayTest do } @opts [ - client_id: 113_167, email: "hi@hello.com", original_ip: "1.1.1.1", - client_secret: "e9d1d9af6c", - account_id: 1_145_345_748, short_description: "test payment", type: "service", refund_reason: "the product was defective", cancel_reason: "the product was defective, i don't want", config: [ - access_token: "STAGE_a24e062d0fc2d399412ea0ff1c1e748b04598b341769b3797d3bd207ff8cf6b2" + client_id: 134_871, + client_secret: "81dbb22c9b", + account_id: 1_155_820_743, + access_token: "STAGE_3286b628c1cb7630e75402e95ecebd53e0c4fc86c71685c1a3a05ac5c5cb5aae" ], address: @add ] @@ -66,7 +67,7 @@ defmodule Gringotts.Integration.Gateways.WePayTest do test "[Store] with CreditCard" do use_cassette "WePay/store_with_valid_card" do assert {:ok, response} = Gateway.store(@good_card, @opts) - assert response.success == true + refute response.token == nil assert response.status_code == 200 end end @@ -74,7 +75,7 @@ defmodule Gringotts.Integration.Gateways.WePayTest do test "[Store] with bad CreditCard" do use_cassette "WePay/store_with_invalid_card" do assert {:error, response} = Gateway.store(@bad_card1, @opts) - assert response.success == false + assert response.token == nil assert response.status_code == 400 end end @@ -84,7 +85,6 @@ defmodule Gringotts.Integration.Gateways.WePayTest do test "[authorize] with good parameters" do use_cassette "WePay/authorize_with_valid_card" do assert {:ok, response} = Gateway.authorize(@amount, @good_card, @opts) - assert response.success == true assert response.status_code == 200 end end @@ -92,7 +92,6 @@ defmodule Gringotts.Integration.Gateways.WePayTest do test "[authorize] with bad CreditCard" do use_cassette "WePay/authorize_with_invalid_card" do assert {:error, response} = Gateway.authorize(@amount, @bad_card1, @opts) - assert response.success == false assert response.status_code == 400 end end @@ -102,7 +101,6 @@ defmodule Gringotts.Integration.Gateways.WePayTest do test "[purchase] with good parameters" do use_cassette "WePay/purchase_with_valid_card" do assert {:ok, response} = Gateway.purchase(@amount, @good_card, @opts) - assert response.success == true assert response.status_code == 200 end end @@ -110,7 +108,6 @@ defmodule Gringotts.Integration.Gateways.WePayTest do test "[purchase] with bad CreditCard" do use_cassette "WePay/purchase_with_invalid_card" do assert {:error, response} = Gateway.purchase(@amount, @bad_card1, @opts) - assert response.success == false assert response.status_code == 400 end end @@ -120,25 +117,34 @@ defmodule Gringotts.Integration.Gateways.WePayTest do test "[Capture]" do use_cassette "WePay/capture" do assert {:ok, response} = Gateway.authorize(@amount, @good_card, @opts) - assert response.success == true assert response.status_code == 200 payment_id = response.id assert {:ok, response} = Gateway.capture(payment_id, @amount, @opts) - assert response.success == true assert response.status_code == 200 end end end describe "Void" do - test "[Void]" do - use_cassette "WePay/void" do - assert {:ok, response} = Gateway.purchase(@amount, @good_card, @opts) - assert response.success == true + test "[Void] after authorize" do + use_cassette "WePay/void_after_authorize" do + assert {:ok, response} = Gateway.authorize(@amount, @good_card, @opts) + assert response.status_code == 200 + payment_id = response.id + assert {:ok, response} = Gateway.void(payment_id, @opts) + assert response.status_code == 200 + end + end + + test "[Void] after capture" do + use_cassette "WePay/void_after_capture" do + assert {:ok, response} = Gateway.authorize(@amount, @good_card, @opts) + assert response.status_code == 200 + payment_id = response.id + assert {:ok, response} = Gateway.capture(payment_id, @amount, @opts) assert response.status_code == 200 payment_id = response.id assert {:ok, response} = Gateway.void(payment_id, @opts) - assert response.success == true assert response.status_code == 200 end end @@ -148,11 +154,9 @@ defmodule Gringotts.Integration.Gateways.WePayTest do test "[Unstore]" do use_cassette "WePay/unstore" do assert {:ok, response} = Gateway.store(@good_card, @opts) - assert response.success == true assert response.status_code == 200 payment_id = response.token assert {:ok, response} = Gateway.unstore(payment_id, @opts) - assert response.success == true assert response.status_code == 200 end end From 5a3c50977f95e95b5e6e862be3f9188c83f42e32 Mon Sep 17 00:00:00 2001 From: Ravi Raj Date: Mon, 18 Jun 2018 23:54:49 +1100 Subject: [PATCH 6/7] fixed typo --- lib/gringotts/gateways/we_pay.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gringotts/gateways/we_pay.ex b/lib/gringotts/gateways/we_pay.ex index 30c03ec8..1730bcff 100644 --- a/lib/gringotts/gateways/we_pay.ex +++ b/lib/gringotts/gateways/we_pay.ex @@ -254,7 +254,7 @@ defmodule Gringotts.Gateways.WePay do ## Note * It is recommended to refund the transraction after 5 to 10 min. - * WePay does not support partial refunds. + * WePay does support partial refunds. ## Example ``` From 5237a1aa3501d72920a2c42c66323e9588243197 Mon Sep 17 00:00:00 2001 From: Ravi Raj Date: Mon, 18 Jun 2018 23:55:46 +1100 Subject: [PATCH 7/7] fixed typo --- test/integration/gateways/we_pay_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/gateways/we_pay_test.exs b/test/integration/gateways/we_pay_test.exs index e39adf62..7f57b840 100644 --- a/test/integration/gateways/we_pay_test.exs +++ b/test/integration/gateways/we_pay_test.exs @@ -13,7 +13,7 @@ defmodule Gringotts.Integration.Gateways.WePayTest do alias Gringotts.Gateways.WePay, as: Gateway - # @moduletag :integration + @moduletag :integration @amount FakeMoney.new(5, :USD)