diff --git a/.credo.exs b/.credo.exs index 9381d3f7..07079234 100644 --- a/.credo.exs +++ b/.credo.exs @@ -132,11 +132,7 @@ # Deprecated checks (these will be deleted after a grace period) # - {Credo.Check.Readability.Specs, false}, - {Credo.Check.Warning.NameRedeclarationByAssignment, false}, - {Credo.Check.Warning.NameRedeclarationByCase, false}, - {Credo.Check.Warning.NameRedeclarationByDef, false}, - {Credo.Check.Warning.NameRedeclarationByFn, false}, + {Credo.Check.Readability.Specs, false} # Custom checks can be created using `mix credo.gen.check`. # diff --git a/.scripts/post-commit b/.scripts/post-commit index ada60194..0af54c84 100755 --- a/.scripts/post-commit +++ b/.scripts/post-commit @@ -7,6 +7,6 @@ RED='\033[1;31m' LGRAY='\033[1;30m' NC='\033[0m' # No Color -printf "${RED}Running 'mix credo --strict --format=oneline' on project...${NC}\n" -mix credo --strict --format=oneline +printf "${RED}Running 'mix credo --strict' on project...${NC}\n" +mix credo --strict echo diff --git a/.travis.yml b/.travis.yml index d12dcef4..43b9619d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,10 +20,9 @@ matrix: - elixir: "1.5.3" script: - mix coveralls.json - - elixir: "1.6.2" + - elixir: "1.6.5" notifications: email: recipients: - - ananya95+travis@gmail.com - ashish+travis@aviabird.com diff --git a/CHANGELOG.md b/CHANGELOG.md index f5b42e88..06b30a57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,37 @@ # Changelog +## [`v1.1.1-rc`][tag-1_1_1-rc] (2018-06-04) + +### Added + +* **core:** Remove the protocol implementation for `ex_money` since they + implement it for us + [e1cb32](https://github.com/kipcole9/money/commit/e1cb325a28a8318864ff1cbfbbb67574379a82c0) +* **development:** Removed those annoying compiler warnings! +* **docs:** Add docs about the money protocol. + +### Changed + +Remove support of [Wirecard](http://wirecard.com/) as we failed to implement it completely and it is +not at all usable. It is being archived in +[`wirecard`](https://github.com/aviabird/gringotts/tree/wirecard) branch. + ## [`v1.1.0`][tag-1_1_0] (2018-04-22) ### Added * **api:** Introduces a `Money` protocol ([#71][pr#71]) -* **core:** Introduces Response.t ([#119][pr#91]) -* **development:** Adds a useful mix task gringotts.new ([#78][pr#78]) +* **core:** Introduces `Response.t` ([#119][pr#91]) +* **development:** Adds a useful mix task `gringotts.new` ([#78][pr#78]) to help + with adding more gateways! * **docs:** Adds changelog, contributing guide ([#117][pr#117]) ### Changed -* **api:** Deprecates use of `floats` for money amounts, check issue [#62][iss#62] ([#71][pr#71]) -* **core:** Removes payment worker, no application, no worker now after josevalim [pointed it][jose-feedback] ([#118][pr#118]) +* **api:** Deprecates use of `floats` for money amounts, check issue + [#62][iss#62] ([#71][pr#71]) +* **core:** Removes payment worker, no application, no worker now after + @josevalim [pointed it][joses-feedback] ([#118][pr#118]) [iss#62]: https://github.com/aviabird/gringotts/issues/62 [pr#71]: https://github.com/aviabird/gringotts/pulls/71 @@ -21,14 +40,13 @@ [pr#117]: https://github.com/aviabird/gringotts/pulls/117 [pr#78]:https://github.com/aviabird/gringotts/pulls/78 [pr#86]:https://github.com/aviabird/gringotts/pulls/86 -[jose-feedback]:https://elixirforum.com/t/gringotts-a-complete-payment-library-for-elixir-and-phoenix-framework/11054/41 - +[joses-feedback]:https://elixirforum.com/t/gringotts-a-complete-payment-library-for-elixir-and-phoenix-framework/11054/41 ## [`v1.0.2`][tag-1_0_2] (2017-12-27) ### Added -* New Gateway: **Trexle** +* Gringotts now supports [Trexle](http://trexle.com/) as well :tada: ### Changed @@ -49,12 +67,13 @@ * **api:** Initial public API release. * **core:** Single worker architecture, config fetched from `config.exs` * **api:** Supported Gateways: - - Stripe - - MONEI - - Paymill - - WireCard - - CAMSa + - [Stripe](http://stripe.com/) + - [MONEI](http://monei.net/) + - [Paymill](https://www.paymill.com/en/) + - [WireCard](http://wirecard.com/) + - [CAMS](http://www.centralams.com/) +[tag-1_1_1_rc]: https://github.com/aviabird/gringotts/releases/tag/v1.1.1-rc [tag-1_1_0]: https://github.com/aviabird/gringotts/compare/1.1.0...1.0.2 [tag-1_0_2]: https://github.com/aviabird/gringotts/compare/1.0.2...1.0.1 [tag-1_0_1]: https://github.com/aviabird/gringotts/compare/1.0.1...1.0.0 diff --git a/README.md b/README.md index 8f4f4a82..4ad35834 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@
- Gringotts is a payment processing library in Elixir integrating various payment gateways, drawing motivation for Shopify's activemerchant
gem. Checkout the Demo here.
+ Gringotts is a payment processing library in Elixir integrating various payment gateways, drawing motivation for Shopify's activemerchant
gem. Checkout the demo here.
@@ -25,10 +25,10 @@ Add `gringotts` to the list of dependencies of your application. def deps do [ - {:gringotts, "~> 1.0"}, + {:gringotts, "~> 1.1"}, # ex_money provides an excellent Money library, and integrates # out-of-the-box with Gringotts - {:ex_money, "~> 1.1.0"} + {:ex_money, "> 2.5.0"} ] end ``` @@ -51,11 +51,12 @@ config :gringotts, Gringotts.Gateways.Monei, entityId: "your_secret_channel_id" ``` -Copy and paste this code in a module or an `IEx` session +Copy and paste this code in a module or an `IEx` session, or use this handy +[`.iex.exs`][monei-bindings] for all the bindings. ```elixir alias Gringotts.Gateways.Monei -alias Gringotts.{CreditCard} +alias Gringotts.CreditCard # a fake sample card that will work now because the Gateway is by default # in "test" mode. @@ -83,18 +84,51 @@ end [hexpm]: https://hex.pm/packages/gringotts [monei]: http://www.monei.net +[monei-bindings]: https://gist.github.com/oyeb/a2e2ac5986cc90a12a6136f6bf1357e5 + +## On the `Gringotts.Money` protocol and money representation + +All financial applications must take proper care when representing money in +their system. Using simple `float`ing values might lead to losses in the real +world due to [various reasons][floating-issues]. + +Most payment gateways are strict about the formatting of the `amount` in the +request, hence we cannot render arbitrary floating amounts like +`$4.99999`. Moreover, such amounts might mean something to your application but +they don't have any value in the real world (since you can't charge someone for +a fraction of a US cent). + +Your application **must round** such amounts before invoking Gringotts **and manage +any remainders sensibly** yourself. + +> Gringotts may perform rounding using the [`half-even`][wiki-half-even] +strategy, but it will discard remainders if any. + +### Supported "Money" libraries + +Gringotts does not ship with any library to work with monies. You are free to +choose any monie library you wish, as long as they implement the +[`Gringotts.Money`][protocol] for their type! + +That said, we recommend [`ex_money`][ex_money] (above [`v2.6.0`][2_6_0]) to +represent monies. You just have to add it in your `deps()`. + +[protocol]: https://github.com/aviabird/gringotts/blob/dev/lib/gringotts/money.ex +[floating-issues]: https://elixirforum.com/t/comparison-of-decimals-not-logical/770/21 +[wiki-half-even]: https://en.wikipedia.org/wiki/Rounding#Round_half_to_even +[ex-money]: https://github.com/kipcole9/money +[2_6_0]: https://github.com/kipcole9/money/releases/tag/v2.6.0 ## Supported Gateways -| Gateway | Supported countries | -| ------ | ----- | -| [Authorize.Net][anet] | AD, AT, AU, BE, BG, CA, CH, CY, CZ, DE, DK, ES, FI, FR, GB, GB, GI, GR, HU, IE, IT, LI, LU, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, SM, TR, US, VA | -| [CAMS][cams] | AU, US | -| [MONEI][monei] | DE, EE, ES, FR, IT, US | -| [PAYMILL][paymill] | AD, AT, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HU, IE, IL, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK, TR, VA | -| [Stripe][stripe] | AT, AU, BE, CA, CH, DE, DK, ES, FI, FR, GB, IE, IN, IT, LU, NL, NO, SE, SG, US | -| [TREXLE][trexle] | AD, AE, AT, AU, BD, BE, BG, BN, CA, CH, CY, CZ, DE, DK, EE, EG, ES, FI, FR, GB, GI, GR, HK, HU, ID, IE, IL, IM, IN, IS, IT, JO, KW, LB, LI, LK, LT, LU, LV, MC, MT, MU, MV, MX, MY, NL, NO, NZ, OM, PH, PL, PT, QA, RO, SA, SE, SG, SI, SK, SM, TR, TT, UM, US, VA, VN, ZA | -| [Wirecard][wirecard] | AD, AT, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FR, GB, GI, GR, HU, IE, IL, IM, IS, IT, LI, LT, LU, LV, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, SM, TR, VA | +| Gateway | PCI compliance | `purchase` | `authorize` | `capture` | `void` | `refund` | (card) `store` | (card) `unstore` | +|-----------------------|----------------|------------|-------------|-----------|----------|----------|----------------|------------------| +| [Authorize.Net][anet] | mandatory | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [CAMS][cams] | mandatory | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | +| [MONEI][monei] | mandatory | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | +| [PAYMILL][paymill] | optional | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | +| [Stripe][stripe] | optional | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [TREXLE][trexle] | mandatory | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | [anet]: http://www.authorize.net/ [cams]: https://www.centralams.com/ @@ -105,7 +139,7 @@ end [wirecard]: http://www.wirecard.com [demo]: https://gringottspay.herokuapp.com/ -## Road Map +## [Road Map][roadmap] Apart from supporting more and more gateways, we also keep a somewhat detailed plan for the future on our [wiki][roadmap]. @@ -116,19 +150,6 @@ plan for the future on our [wiki][roadmap]. Gringotts has a nice ring to it. Also [this][reason]. -#### 2. What is the worker doing in the middle? - -We wanted to "supervise" our payments, and power utilities to process recurring -payments, subscriptions with it. But yes, as of now, it is a bottle neck and -unnecessary. - -It's slated to be removed in [`v2.0.0`][milestone-2_0_0_alpha] and any -supervised/async/parallel work can be explicitly managed via native elixir -constructs. - -**In fact, it's already been removed from our [dev](#) branch.** - -[milestone-2_0_0_alpha]: https://github.com/aviabird/gringotts/milestone/3 [reason]: http://harrypotter.wikia.com/wiki/Gringotts ## License diff --git a/lib/gringotts.ex b/lib/gringotts.ex index 161d1ae8..63b23f6f 100644 --- a/lib/gringotts.ex +++ b/lib/gringotts.ex @@ -9,9 +9,6 @@ defmodule Gringotts do ## Standard API arguments - All requests to Gringotts are served by a supervised worker, this might be - made optional in future releases. - ### `gateway` (Module) Name The `gateway` to which this request is made. This is required in all API calls @@ -29,24 +26,24 @@ defmodule Gringotts do the transaction. `amount` is polymorphic thanks to the `Gringotts.Money` protocol which can even be implemented by your very own custom Money type! + > Currently, we support only [`ex_money`][ex_money]'s `Money` type. Please + don't forget to add the `ex_money` lib to your deps! + #### Note Gringotts supports [`ex_money`][ex_money] out of the box, just drop `ex_money` types in this argument and everything will work as expected. - > Support for [`monetized`][monetized] and [`money`][money] is on the - > way, track it [here][iss-money-lib-support]! - - Otherwise, just wrap your `amount` with the `currency` together in a `Map` like so, - money = %{value: Decimal.new("100.50"), currency: "USD"} + > Support for [`monetized`][monetized] and [`money`][money] would be nice, but + is currently not planned. > When this highly precise `amount` is serialized into the network request, we > use a potentially lossy `Gringotts.Money.to_string/1` or > `Gringotts.Money.to_integer/1` to perform rounding (if required) using the > [`half-even`][wiki-half-even] strategy. > - > **Hence, to ensure transparency, protect sanity and save _real_ money, we - > STRONGLY RECOMMEND that merchants perform any required rounding and handle + > **Hence, to protect your interests, and save _real_ money, we STRONGLY + > RECOMMEND that (you) merchants perform any required rounding and handle > remainders in their application logic -- before passing the `amount` to > Gringotts's API.** @@ -63,14 +60,23 @@ defmodule Gringotts do [ex_money]: https://hexdocs.pm/ex_money/readme.html [monetized]: https://hexdocs.pm/monetized/ [money]: https://hexdocs.pm/money/Money.html - [iss-money-lib-support]: https://github.com/aviabird/gringotts/projects/3#card-6801146 [wiki-half-even]: https://en.wikipedia.org/wiki/Rounding#Round_half_to_even ### `card`, a payment source - Gringotts provides a `Gringotts.CreditCard` type to hold card parameters - which merchants fetch from their clients. The same type can also hold Debit - card details. + Gringotts provides a `Gringotts.CreditCard` type to hold card parameters which + merchants fetch from their clients. You (the merchant) needs to be PCI-DSS + compliant to be able to use this. + + > The same type can also hold Debit card details. + + Some gateways do not provide direct API integrations even for PCI compliant + merchants, and force every merchant to use their client-side integration to + generate a card token. + + Thus for such gateways, Gringotts accepts a `string` card-token instead of (or + in addition to) a `CreditCard.t` struct. Please refer the "Notes" section of + your chosen gateway to find what the card argument accepts. #### Note @@ -153,9 +159,14 @@ defmodule Gringotts do gateway, amount = Money.new("4.2", :USD) - # IF YOU DON'T USE ex_money - # amount = %{value: Decimal.new("4.2"), currency: "EUR"} - card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"} + card = %Gringotts.CreditCard{ + first_name: "Harry", + last_name: "Potter", + number: "4200000000000000", + year: 2099, month: 12, + verification_code: "123", + brand: "VISA" + } {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.XYZ, amount, card, opts) """ def authorize(gateway, amount, card, opts \\ []) do @@ -175,10 +186,8 @@ defmodule Gringotts do To capture $4.20 on a previously authorized payment worth $4.20 by referencing the obtained authorization `id` with the `XYZ` gateway, + auth_result # The result of an `authorize/3` request. amount = Money.new("4.2", :USD) - # IF YOU DON'T USE ex_money - # amount = %{value: Decimal.new("4.2"), currency: "EUR"} - card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"} Gringotts.capture(Gringotts.Gateways.XYZ, amount, auth_result.id, opts) """ def capture(gateway, id, amount, opts \\ []) do @@ -204,9 +213,14 @@ defmodule Gringotts do To process a purchase worth $4.2, with the `XYZ` gateway, amount = Money.new("4.2", :USD) - # IF YOU DON'T USE ex_money - # amount = %{value: Decimal.new("4.2"), currency: "EUR"} - card = %Gringotts.CreditCard{first_name: "Harry", last_name: "Potter", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"} + card = %Gringotts.CreditCard{ + first_name: "Harry", + last_name: "Potter", + number: "4200000000000000", + year: 2099, month: 12, + verification_code: "123", + brand: "VISA" + } Gringotts.purchase(Gringotts.Gateways.XYZ, amount, card, opts) """ def purchase(gateway, amount, card, opts \\ []) do @@ -225,8 +239,6 @@ defmodule Gringotts do gateway, amount = Money.new("4.2", :USD) - # IF YOU DON'T USE ex_money - # amount = %{value: Decimal.new("4.2"), currency: "EUR"} Gringotts.purchase(Gringotts.Gateways.XYZ, amount, id, opts) """ def refund(gateway, amount, id, opts \\ []) do @@ -237,7 +249,7 @@ defmodule Gringotts do @doc """ Stores the payment-source data for later use, returns a `token`. - > The token must be returned in the `Response.authorization` field. + > The token must be returned in the `Response.token` field. ## Note @@ -247,7 +259,14 @@ defmodule Gringotts do To store a card (a payment-source) for future use, with the `XYZ` gateway, - card = %Gringotts.CreditCard{first_name: "Jo", last_name: "Doe", number: "4200000000000000", year: 2099, month: 12, verification_code: "123", brand: "VISA"} + card = %Gringotts.CreditCard{ + first_name: "Harry", + last_name: "Potter", + number: "4200000000000000", + year: 2099, month: 12, + verification_code: "123", + brand: "VISA" + } Gringotts.store(Gringotts.Gateways.XYZ, card, opts) """ def store(gateway, card, opts \\ []) do diff --git a/lib/gringotts/gateways/authorize_net.ex b/lib/gringotts/gateways/authorize_net.ex index 2612bb7a..ffa1396d 100644 --- a/lib/gringotts/gateways/authorize_net.ex +++ b/lib/gringotts/gateways/authorize_net.ex @@ -21,25 +21,25 @@ defmodule Gringotts.Gateways.AuthorizeNet do optional arguments for transactions with the Authorize.Net gateway. The following keys are supported: - | Key | Remarks | - | ---- | ------- | - | `customer` | | - | `invoice` | | - | `bill_to` | | - | `ship_to` | | - | `customer_ip` | | - | `order` | | - | `lineitems` | | - | `ref_id` | | - | `tax` | | - | `duty` | | - | `shipping` | | - | `po_number` | | - | `customer_type` | | - | `customer_profile_id` | | - | `profile` | | - - To know more about these keywords visit [Request and Response][req-resp] tabs for each + | Key | + | ---- | + | `customer` | + | `invoice` | + | `bill_to` | + | `ship_to` | + | `customer_ip` | + | `order` | + | `lineitems` | + | `ref_id` | + | `tax` | + | `duty` | + | `shipping` | + | `po_number` | + | `customer_type` | + | `customer_profile_id` | + | `profile` | + + To know more about these keywords check the [Request and Response][req-resp] tabs for each API method. [docs]: https://developer.authorize.net/api/reference/index.html @@ -48,10 +48,12 @@ defmodule Gringotts.Gateways.AuthorizeNet do ## Notes 1. Though Authorize.Net supports [multiple currencies][currencies] however, - multiple currencies in one account are not supported in _this_ module. A - merchant would need multiple Authorize.Net accounts, one for each chosen - currency. - 2. The responses of this module include a non-standard field: `:cavv_result`. + multiple currencies in one account is not supported. A merchant would need + multiple Authorize.Net accounts, one for each chosen currency. Please refer + the section on "Supported acquirers and currencies" [here][currencies]. + 2. _You, the merchant needs to be PCI-DSS Compliant if you wish to use this + module. Your server will recieve sensitive card and customer information._ + 3. The responses of this module include a non-standard field: `:cavv_result`. - `:cavv_result` is the "cardholder authentication verification response code". In case of Mastercard transactions, this field will always be `nil`. Please refer the "Response Format" section in the [docs][docs] for @@ -122,11 +124,7 @@ defmodule Gringotts.Gateways.AuthorizeNet do @aut_net_namespace "AnetApi/xml/v1/schema/AnetApiSchema.xsd" - alias Gringotts.{ - CreditCard, - Response, - Money - } + alias Gringotts.{CreditCard, Money, Response} @doc """ Transfers `amount` from the customer to the merchant. diff --git a/lib/gringotts/gateways/cams.ex b/lib/gringotts/gateways/cams.ex index 2f1d5805..1bcffb2c 100644 --- a/lib/gringotts/gateways/cams.ex +++ b/lib/gringotts/gateways/cams.ex @@ -120,7 +120,7 @@ defmodule Gringotts.Gateways.Cams do use Gringotts.Gateways.Base use Gringotts.Adapter, required_config: [:username, :password] - alias Gringotts.{CreditCard, Response, Money} + alias Gringotts.{CreditCard, Money, Response} alias Gringotts.Gateways.Cams.ResponseHandler, as: ResponseParser @live_url "https://secure.centralams.com/gw/api/transact.php" diff --git a/lib/gringotts/gateways/global_collect.ex b/lib/gringotts/gateways/global_collect.ex index 6075451d..41d69f3e 100644 --- a/lib/gringotts/gateways/global_collect.ex +++ b/lib/gringotts/gateways/global_collect.ex @@ -152,7 +152,7 @@ defmodule Gringotts.Gateways.GlobalCollect do import Poison, only: [decode: 1] - alias Gringotts.{Money, CreditCard, Response} + alias Gringotts.{CreditCard, Money, Response} @brand_map %{ VISA: "1", diff --git a/lib/gringotts/gateways/monei.ex b/lib/gringotts/gateways/monei.ex index 4d659656..b8a05c76 100644 --- a/lib/gringotts/gateways/monei.ex +++ b/lib/gringotts/gateways/monei.ex @@ -43,8 +43,6 @@ defmodule Gringotts.Gateways.Monei do | [`shipping`][sa] | Location of recipient of goods, for logistics. | | [`shipping_customer`][c] | Recipient details, could be different from `customer`. | - > These keys are being implemented, track progress in [issue #36][iss36]! - [extra-arg-docs]: https://docs.monei.net/reference/parameters [ba]: https://docs.monei.net/reference/parameters#billing-address [cart]: https://docs.monei.net/reference/parameters#cart @@ -54,7 +52,6 @@ defmodule Gringotts.Gateways.Monei do [m]: https://docs.monei.net/reference/parameters#merchant [t]: https://docs.monei.net/reference/parameters#tokenization [sa]: https://docs.monei.net/reference/parameters#shipping-address - [iss36]: https://github.com/aviabird/gringotts/issues/36 ## Registering your MONEI account at `Gringotts` @@ -81,6 +78,8 @@ defmodule Gringotts.Gateways.Monei do ## Scope of this module + * _You, the merchant needs to be PCI-DSS Compliant if you wish to use this + module. Your server will recieve sensitive card and customer information._ * MONEI does not process money in cents, and the `amount` is rounded to 2 decimal places. * Although MONEI supports payments from [various][all-card-list] @@ -148,7 +147,7 @@ defmodule Gringotts.Gateways.Monei do use Gringotts.Gateways.Base use Gringotts.Adapter, required_config: [:userId, :entityId, :password] import Poison, only: [decode: 1] - alias Gringotts.{CreditCard, Response, Money} + alias Gringotts.{CreditCard, Money, Response} @base_url "https://test.monei-api.net" @default_headers ["Content-Type": "application/x-www-form-urlencoded", charset: "UTF-8"] diff --git a/lib/gringotts/gateways/paymill.ex b/lib/gringotts/gateways/paymill.ex index e9ab3015..bb3dae13 100644 --- a/lib/gringotts/gateways/paymill.ex +++ b/lib/gringotts/gateways/paymill.ex @@ -43,9 +43,9 @@ defmodule Gringotts.Gateways.Paymill do * PAYMILL processes money in the sub-divided unit of currency (ie, in case of USD it works in cents). - * PAYMILL does not offer direct API integration for [PCI DSS][pci-dss] - compliant merchants, everyone must use PAYMILL as if they are not PCI - compliant. + * This module does not offer direct API integration for [PCI DSS][pci-dss] + compliant merchants. Hence, you can use this module even if your + infrastructure (servers) are not PCI-DSS compliant! * To use their product, a merchant (aka user of this library) would have to use their [Bridge (js integration)][bridge] (or equivalent) in your application frontend to collect Credit/Debit Card data. @@ -94,7 +94,7 @@ defmodule Gringotts.Gateways.Paymill do use Gringotts.Gateways.Base use Gringotts.Adapter, required_config: [:private_key, :public_key] - alias Gringotts.{Response, Money} + alias Gringotts.{Money, Response} @base_url "https://api.paymill.com/v2.1/" @headers [{"Content-Type", "application/x-www-form-urlencoded"}] diff --git a/lib/gringotts/gateways/stripe.ex b/lib/gringotts/gateways/stripe.ex index 94a8ce48..691d11b5 100644 --- a/lib/gringotts/gateways/stripe.ex +++ b/lib/gringotts/gateways/stripe.ex @@ -18,27 +18,48 @@ defmodule Gringotts.Gateways.Stripe do optional arguments for transactions with the Stripe gateway. The following keys are supported: - | Key | Remark | Status | - | ---- | --- | ---- | - | `currency` | | **Implemented** | - | `capture` | | **Implemented** | - | `description` | | **Implemented** | - | `metadata` | | **Implemented** | - | `receipt_email` | | **Implemented** | - | `shipping` | | **Implemented** | - | `customer` | | **Implemented** | - | `source` | | **Implemented** | - | `statement_descriptor` | | **Implemented** | - | `charge` | | **Implemented** | - | `reason` | | **Implemented** | - | `account_balance` | | Not implemented | - | `business_vat_id` | | Not implemented | - | `coupon` | | Not implemented | - | `default_source` | | Not implemented | - | `email` | | Not implemented | - | `shipping` | | Not implemented | + | Key | Status | + | ---- | ---- | + | `currency` | **Implemented** | + | `capture` | **Implemented** | + | `description` | **Implemented** | + | `metadata` | **Implemented** | + | `receipt_email` | **Implemented** | + | `shipping` | **Implemented** | + | `customer` | **Implemented** | + | `source` | **Implemented** | + | `statement_descriptor` | **Implemented** | + | `charge` | **Implemented** | + | `reason` | **Implemented** | + | `account_balance` | Not implemented | + | `business_vat_id` | Not implemented | + | `coupon` | Not implemented | + | `default_source` | Not implemented | + | `email` | Not implemented | + | `shipping` | Not implemented | + + ## Note + + _This module can be used by both PCI-DSS compliant as well as non-compliant + merchants!_ + + ### I'm not PCI-DSS compliant + + No worries, both `authorize/3` and `purchase/3` accept a + "payment-source-identifier" (a `string`) instead of a `CreditCard.t` + struct. You'll have to generate this identifier using [Stripe.js and + Elements][stripe-js] client-side. + + ### I'm PCI-DSS compliant + + In that case, you need not use [Stripe.js or Elements][stripe-js] and can + directly accept the client's card info and pass the `CreditCard.t` struct to + this module's functions. + + [stripe-js]: https://stripe.com/docs/sources/cards ## Registering your Stripe account at `Gringotts` + After [making an account on Stripe](https://stripe.com/), head to the dashboard and find your account `secrets` in the `API` section. @@ -59,11 +80,7 @@ defmodule Gringotts.Gateways.Stripe do use Gringotts.Gateways.Base use Gringotts.Adapter, required_config: [:secret_key] - alias Gringotts.{ - CreditCard, - Address, - Money - } + alias Gringotts.{Address, CreditCard, Money} @doc """ Performs a (pre) Authorize operation. @@ -72,15 +89,19 @@ defmodule Gringotts.Gateways.Stripe do places a hold on the transaction amount in the customer’s issuing bank and also triggers risk management. Funds are not transferred. - Stripe returns an `charge_id` which should be stored at your side and can be used later to: + Stripe returns an `charge_id` which should be stored at your side and can be + used later to: * `capture/3` an amount. * `void/2` a pre-authorization. ## Note - Uncaptured charges expire in 7 days. For more information, [see authorizing charges and settling later](https://support.stripe.com/questions/can-i-authorize-a-charge-and-then-wait-to-settle-it-later). + Uncaptured charges expire in 7 days. For more information, [see authorizing + charges and settling + later](https://support.stripe.com/questions/can-i-authorize-a-charge-and-then-wait-to-settle-it-later). ## Example - The following session shows how one would (pre) authorize a payment of $10 on a sample `card`. + The following session shows how one would (pre) authorize a payment of $10 on + a sample `card`. iex> card = %CreditCard{ first_name: "John", @@ -105,7 +126,7 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.authorize(Gringotts.Gateways.Stripe, amount, card, opts) """ @spec authorize(Money.t(), CreditCard.t() | String.t(), keyword) :: map - def authorize(amount, payment, opts \\ []) do + def authorize(amount, payment, opts) do params = create_params_for_auth_or_purchase(amount, payment, opts, false) commit(:post, "charges", params, opts) end @@ -143,7 +164,7 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.purchase(Gringotts.Gateways.Stripe, amount, card, opts) """ @spec purchase(Money.t(), CreditCard.t() | String.t(), keyword) :: map - def purchase(amount, payment, opts \\ []) do + def purchase(amount, payment, opts) do params = create_params_for_auth_or_purchase(amount, payment, opts) commit(:post, "charges", params, opts) end @@ -155,8 +176,9 @@ defmodule Gringotts.Gateways.Stripe do equal to the amount used in the pre-authorization referenced by `charge_id`. ## Note - Stripe allows partial captures and release the remaining amount back to the payment source. Thus, the same - pre-authorisation `charge_id` cannot be used to perform multiple captures. + Stripe allows partial captures and release the remaining amount back to the + payment source. Thus, the same pre-authorisation `charge_id` cannot be used to + perform multiple captures. ## Example The following session shows how one would (partially) capture a previously @@ -169,7 +191,7 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.capture(Gringotts.Gateways.Stripe, id, amount, opts) """ @spec capture(String.t(), Money.t(), keyword) :: map - def capture(id, amount, opts \\ []) do + def capture(id, amount, opts) do params = optional_params(opts) ++ amount_params(amount) commit(:post, "charges/#{id}/capture", params, opts) end @@ -202,7 +224,7 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.void(Gringotts.Gateways.Stripe, id, opts) """ @spec void(String.t(), keyword) :: map - def void(id, opts \\ []) do + def void(id, opts) do params = optional_params(opts) commit(:post, "charges/#{id}/refund", params, opts) end @@ -224,7 +246,7 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.refund(Gringotts.Gateways.Stripe, amount, id, opts) """ @spec refund(Money.t(), String.t(), keyword) :: map - def refund(amount, id, opts \\ []) do + def refund(amount, id, opts) do params = optional_params(opts) ++ amount_params(amount) commit(:post, "charges/#{id}/refund", params, opts) end @@ -232,8 +254,9 @@ defmodule Gringotts.Gateways.Stripe do @doc """ Stores the payment-source data for later use. - Stripe can store the payment-source details, for example card which can be used to effectively - to process One-Click and Recurring_ payments, and return a `customer_id` for reference. + Stripe can store the payment-source details, for example card which can be + used to effectively to process One-Click and Recurring_ payments, and return a + `customer_id` for reference. ## Example The following session shows how one would store a card (a payment-source) for @@ -261,7 +284,7 @@ defmodule Gringotts.Gateways.Stripe do iex> Gringotts.store(Gringotts.Gateways.Stripe, card, opts) """ @spec store(CreditCard.t() | String.t(), keyword) :: map - def store(payment, opts \\ []) do + def store(payment, opts) do params = optional_params(opts) ++ source_params(payment, opts) commit(:post, "customers", params, opts) end @@ -269,18 +292,19 @@ defmodule Gringotts.Gateways.Stripe do @doc """ Deletes previously stored payment-source data. - Deletes the already stored payment source, - so that it cannot be used again for capturing - payments. + Deletes the already stored payment source, so that it cannot be used again for + capturing payments. ## Examples - The following session shows how one would unstore a already stored payment source. + The following session shows how one would unstore a already stored payment + source. + iex> id = "cus_BwpLX2x4ecEUgD" iex> Gringotts.unstore(Gringotts.Gateways.Stripe, id, opts) """ - @spec unstore(String.t()) :: map - def unstore(id, opts \\ []), do: commit(:delete, "customers/#{id}", [], opts) + @spec unstore(String.t(), keyword) :: map + def unstore(id, opts), do: commit(:delete, "customers/#{id}", [], opts) # Private methods diff --git a/lib/gringotts/gateways/trexle.ex b/lib/gringotts/gateways/trexle.ex index a9c3d988..669e720d 100644 --- a/lib/gringotts/gateways/trexle.ex +++ b/lib/gringotts/gateways/trexle.ex @@ -14,6 +14,11 @@ defmodule Gringotts.Gateways.Trexle do | Refund | `refund/3` | | Store | `store/2` | + ## PCI compliance is mandatory! + + _You, the merchant needs to be PCI-DSS Compliant if you wish to use this + module! Your server will recieve sensitive card and customer information._ + ## The `opts` argument Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply @@ -32,7 +37,7 @@ defmodule Gringotts.Gateways.Trexle do After [creating your account][dashboard] successfully on Trexle, head to the dashboard and find your account "secrets" in the [`API keys`][keys] section. - Here's how the secrets map to the required configuration parameters for MONEI: + Here's how the secrets map to the required configuration parameters for Trexle: | Config parameter | Trexle secret | | ------- | ---- | @@ -83,7 +88,7 @@ defmodule Gringotts.Gateways.Trexle do use Gringotts.Gateways.Base use Gringotts.Adapter, required_config: [:api_key] import Poison, only: [decode: 1] - alias Gringotts.{Response, CreditCard, Address, Money} + alias Gringotts.{Address, CreditCard, Money, Response} @doc """ Performs a (pre) Authorize operation. @@ -92,7 +97,7 @@ defmodule Gringotts.Gateways.Trexle do places a hold on the transaction `amount` in the customer’s issuing bank and also triggers risk management. Funds are not transferred. - Trexle returns a "charge token", avaliable in the `Response.authorization` + Trexle returns a "charge token", avaliable in the `Response.id` field, which can be used in future to perform a `capture/3`. ### Example @@ -133,10 +138,10 @@ defmodule Gringotts.Gateways.Trexle do @doc """ Captures a pre-authorized `amount`. - `amount` is transferred to the merchant account by MONEI when it is smaller or + `amount` is transferred to the merchant account by Trexle when it is smaller or equal to the amount used in the pre-authorization referenced by `charge_token`. - Trexle returns a "charge token", avaliable in the `Response.authorization` + Trexle returns a "charge token", avaliable in the `Response.id` field, which can be used in future to perform a `refund/2`. ## Note @@ -210,7 +215,7 @@ defmodule Gringotts.Gateways.Trexle do Trexle processes a full or partial refund worth `amount`, referencing a previous `purchase/3` or `capture/3`. - Trexle returns a "refund token", avaliable in the `Response.authorization` + Trexle returns a "refund token", avaliable in the `Response.id` field. Multiple, partial refunds can be performed on the same "charge token" diff --git a/lib/gringotts/gateways/wire_card.ex b/lib/gringotts/gateways/wire_card.ex deleted file mode 100644 index 939a0fef..00000000 --- a/lib/gringotts/gateways/wire_card.ex +++ /dev/null @@ -1,405 +0,0 @@ -# call => Gringotts.Gateways.WireCard.authorize(100, creditcard, options) -import XmlBuilder - -defmodule Gringotts.Gateways.WireCard do - @moduledoc """ - WireCard System Plugins - """ - @test_url "https://c3-test.wirecard.com/secure/ssl-gateway" - @live_url "https://c3.wirecard.com/secure/ssl-gateway" - @homepage_url "http://www.wirecard.com" - - @doc """ - Wirecard only allows phone numbers with a format like this: +xxx(yyy)zzz-zzzz-ppp, where: - xxx = Country code - yyy = Area or city code - zzz-zzzz = Local number - ppp = PBX extension - For example, a typical U.S. or Canadian number would be "+1(202)555-1234-739" indicating PBX extension 739 at phone - number 5551234 within area code 202 (country code 1). - """ - @valid_phone_format ~r/\+\d{1,3}(\(?\d{3}\)?)?\d{3}-\d{4}-\d{3}/ - @default_currency "EUR" - @default_amount 100 - use Gringotts.Gateways.Base - use Gringotts.Adapter, required_config: [:login, :password, :signature] - - alias Gringotts.{ - CreditCard, - Address, - Response - } - - import Poison, only: [decode!: 1] - - @doc """ - Authorization - the second parameter may be a CreditCard or - a String which represents a GuWID reference to an earlier - transaction. If a GuWID is given, rather than a CreditCard, - then then the :recurring option will be forced to "Repeated" - =========================================================== - TODO: Mandatorily check for :login,:password, :signature in options - Note: payment_menthod for now is only credit_card and - TODO: change it so it can also have GuWID - ================================================ - E.g: => - creditcard = %CreditCard{ - number: "4200000000000000", - month: 12, - year: 2018, - first_name: "Longbob", - last_name: "Longsen", - verification_code: "123", - brand: "visa" - } - address = %{ - name: "Jim Smith", - address1: "456 My Street", - address2: "Apt 1", - company: "Widgets Inc", - city: "Ottawa", - state: "ON", - zip: "K1C2N6", - country: "CA", - phone: "(555)555-5555", - fax: "(555)555-6666" - } - options = [ - config: %{ - login: "00000031629CA9FA", - password: "TestXAPTER", - signature: "00000031629CAFD5", - }, - order_id: 1, - billing_address: address, - description: 'Wirecard remote test purchase', - email: "soleone@example.com", - ip: "127.0.0.1", - test: true - ] - """ - @spec authorize(Integer | Float, CreditCard.t() | String.t(), Keyword) :: {:ok, Map} - def authorize(money, payment_method, options \\ []) - - def authorize(money, %CreditCard{} = creditcard, options) do - options = Keyword.put(options, :credit_card, creditcard) - commit(:post, :preauthorization, money, options) - end - - def authorize(money, authorization, options) when is_binary(authorization) do - options = Keyword.put(options, :preauthorization, authorization) - commit(:post, :preauthorization, money, options) - end - - @doc """ - Capture - the first paramter here should be a GuWid/authorization. - Authorization is obtained by authorizing the creditcard. - """ - @spec capture(String.t(), Float, Keyword) :: {:ok, Map} - def capture(authorization, money, options \\ []) when is_binary(authorization) do - options = Keyword.put(options, :preauthorization, authorization) - commit(:post, :capture, money, options) - end - - @doc """ - Purchase - the second parameter may be a CreditCard or - a String which represents a GuWID reference to an earlier - transaction. If a GuWID is given, rather than a CreditCard, - then then the :recurring option will be forced to "Repeated" - """ - @spec purchase(Float | Integer, CreditCard | String.t(), Keyword) :: {:ok, Map} - def purchase(money, payment_method, options \\ []) - - def purchase(money, %CreditCard{} = creditcard, options) do - options = Keyword.put(options, :credit_card, creditcard) - commit(:post, :purchase, money, options) - end - - def purchase(money, authorization, options) when is_binary(authorization) do - options = Keyword.put(options, :preauthorization, authorization) - commit(:post, :purchase, money, options) - end - - @doc """ - Void - A credit card purchase that a seller cancels after it has - been authorized but before it has been settled. - A void transaction does not appear on the customer's - credit card statement, though it might appear in a list - of pending transactions when the customer checks their - account online. - ==== Parameters ====== - identification - The authorization string returned from the - initial authorization or purchase. - """ - @spec void(String.t(), Keyword) :: {:ok, Map} - def void(identification, options \\ []) when is_binary(identification) do - options = Keyword.put(options, :preauthorization, identification) - commit(:post, :reversal, nil, options) - end - - @doc """ - Performs a credit. - - This transaction indicates that money - should flow from the merchant to the customer. - ==== Parameters ==== - money -- The amount to be credited to the customer - as an Integer value in cents. - identification -- GuWID - """ - @spec refund(Float, String.t(), Keyword) :: {:ok, Map} - def refund(money, identification, options \\ []) when is_binary(identification) do - options = Keyword.put(options, :preauthorization, identification) - commit(:post, :bookback, money, options) - end - - @doc """ - Store card - Wirecard supports the notion of "Recurring - Transactions" by allowing the merchant to provide a reference - to an earlier transaction (the GuWID) rather than a credit - card. A reusable reference (GuWID) can be obtained by sending - a purchase or authorization transaction with the element - "RECURRING_TRANSACTION/Type" set to "Initial". Subsequent - transactions can then use the GuWID in place of a credit - card by setting "RECURRING_TRANSACTION/Type" to "Repeated". - - This implementation of card store utilizes a Wirecard - "Authorization Check" (a Preauthorization that is automatically - reversed). It defaults to a check amount of "100" (i.e. - $1.00) but this can be overriden (see below). - - IMPORTANT: In order to reuse the stored reference, the - +authorization+ from the response should be saved by - your application code. - - ==== Options specific to +store+ - - * :amount -- The amount, in cents, that should be - "validated" by the Authorization Check. This amount will - be reserved and then reversed. Default is 100. - - Note: This is not the only way to achieve a card store - operation at Wirecard. Any +purchase+ or +authorize+ - can be sent with +options[:recurring] = 'Initial'+ to make - the returned authorization/GuWID usable in later transactions - with +options[:recurring] = 'Repeated'+. - """ - @spec store(CreditCard.t(), Keyword) :: {:ok, Map} - def store(%CreditCard{} = creditcard, options \\ []) do - options = - options - |> Keyword.put(:credit_card, creditcard) - |> Keyword.put(:recurring, "Initial") - - money = options[:amount] || @default_amount - # Amex does not support authorization_check - case creditcard.brand do - "american_express" -> commit(:post, :preauthorization, money, options) - _ -> commit(:post, :authorization_check, money, options) - end - end - - # =================== Private Methods =================== - - # Contact WireCard, make the XML request, and parse the - # reply into a Response object. - defp commit(method, action, money, options) do - # TODO: validate and setup address hash as per AM - request = build_request(action, money, options) - - headers = %{ - "Content-Type" => "text/xml", - "Authorization" => - encoded_credentials( - options[:config][:login], - options[:config][:password] - ) - } - - method |> HTTPoison.request(base_url(options), request, headers) |> respond - end - - defp respond({:ok, %{status_code: 200, body: body}}) do - response = parse(body) - {:ok, response} - end - - defp respond({:ok, %{body: body, status_code: status_code}}) do - {:error, "Some Error Occurred: \n #{inspect(body)}"} - end - - # Read the XML message from the gateway and check if it was successful, - # and also extract required return values from the response - # TODO: parse XML Response - defp parse(data) do - XmlToMap.naive_map(data) - end - - # Generates the complete xml-message, that gets sent to the gateway - defp build_request(action, money, options) do - options = Keyword.put(options, :action, action) - - request = - doc( - element(:WIRECARD_BXML, [ - element(:W_REQUEST, [ - element(:W_JOB, [ - element(:JobID, ""), - element(:BusinessCaseSignature, options[:config][:signature]), - add_transaction_data(action, money, options) - ]) - ]) - ]) - ) - - request - end - - # Includes the whole transaction data (payment, creditcard, address) - # TODO: Add order_id to options if not present, see AM - # TOOD: Clean description before passing it to FunctionID, replace dummy - defp add_transaction_data(action, money, options) do - element("FNC_CC_#{atom_to_upcase_string(options[:action])}", [ - element(:FunctionID, "dummy_description"), - element( - :CC_TRANSACTION, - [ - element(:TransactionID, options[:order_id]), - element(:CommerceType, if(options[:commerce_type], do: options[:commerce_type])) - ] ++ add_action_data(action, money, options) ++ add_customer_data(options) - ) - ]) - end - - # Includes the IP address of the customer to the transaction-xml - defp add_customer_data(options) do - if options[:ip] do - [ - element(:CONTACT_DATA, [element(:IPAddress, options[:ip])]) - ] - end - end - - def add_action_data(action, money, options) do - case options[:action] do - # returns array of elements - action when action in [:preauthorization, :purchase, :authorization_check] -> - create_elems_for_preauth_or_purchase_or_auth_check(money, options) - - action when action in [:capture, :bookback] -> - create_elems_for_capture_or_bookback(money, options) - - action when action == :reversal -> - add_guwid(options[:preauthorization]) - end - end - - # Creates xml request elements if action is capture, bookback - defp create_elems_for_capture_or_bookback(money, options) do - add_guwid(options[:preauthorization]) ++ [add_amount(money, options)] - end - - # Creates xml request elements if action is preauth, purchase ir auth_check - # TODO: handle nil values if array not generated - defp create_elems_for_preauth_or_purchase_or_auth_check(money, options) do - # TODO: setup_recurring_flag - add_invoice(money, options) ++ - element_for_credit_card_or_guwid(options) ++ add_address(options[:billing_address]) - end - - defp add_address(address) do - if address do - [ - element(:CORPTRUSTCENTER_DATA, [ - element(:ADDRESS, [ - element(:Address1, address[:address1]), - element(:Address2, if(address[:address2], do: address[:address2])), - element(:City, address[:city]), - element(:Zip, address[:zip]), - add_state(address), - element(:Country, address[:country]), - element( - :Phone, - if(regex_match(@valid_phone_format, address[:phone]), do: address[:phone]) - ), - element(:Email, address[:email]) - ]) - ]) - ] - end - end - - defp add_state(address) do - if regex_match(~r/[A-Za-z]{2}/, address[:state]) && - regex_match(~r/^(us|ca)$/i, address[:country]) do - element(:State, String.upcase(address[:state])) - end - end - - defp element_for_credit_card_or_guwid(options) do - if options[:credit_card] do - add_creditcard(options[:credit_card]) - else - add_guwid(options[:preauthorization]) - end - end - - # Includes Guwid data to transaction-xml - defp add_guwid(preauth) do - [element(:GuWID, preauth)] - end - - # Includes the credit-card data to the transaction-xml - # TODO: Format Credit Card month, ref AM - defp add_creditcard(creditcard) do - [ - element(:CREDIT_CARD_DATA, [ - element(:CreditCardNumber, creditcard.number), - element(:CVC2, creditcard.verification_code), - element(:ExpirationYear, creditcard.year), - element(:ExpirationMonth, creditcard.month), - element(:CardHolderName, join_string([creditcard.first_name, creditcard.last_name], " ")) - ]) - ] - end - - # Includes the payment (amount, currency, country) to the transaction-xml - def add_invoice(money, options) do - [ - add_amount(money, options), - element(:Currency, currency(options)), - element(:CountryCode, options[:billing_address][:country]), - element(:RECURRING_TRANSACTION, [ - element(:Type, options[:recurring] || "Single") - ]) - ] - end - - # Include the amount in the transaction-xml - # TODO: check for localized currency or currency - # localized_amount(money, options[:currency] || currency(money)) - defp add_amount(money, options), do: element(:Amount, money) - - defp atom_to_upcase_string(atom) do - atom - |> to_string - |> String.upcase() - end - - # Encode login and password in Base64 to supply as HTTP header - # (for http basic authentication) - 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) - - defp regex_match(regex, string), do: Regex.match?(regex, string) - - defp base_url(opts), do: if(opts[:test], do: @test_url, else: @live_url) - - defp currency(opts), do: opts[:currency] || @default_currency -end diff --git a/lib/gringotts/money.ex b/lib/gringotts/money.ex index 304cc764..5e56a7f4 100644 --- a/lib/gringotts/money.ex +++ b/lib/gringotts/money.ex @@ -126,39 +126,6 @@ defprotocol Gringotts.Money do def to_string(money) end -# this implementation is used for dispatch on ex_money (and will also fire for -# money) -if Code.ensure_compiled?(Money) do - defimpl Gringotts.Money, for: Money do - def currency(money), do: money.currency |> Atom.to_string() - def value(money), do: money.amount - - def to_integer(money) do - {_, int_value, exponent, _} = Money.to_integer_exp(money) - {currency(money), int_value, exponent} - end - - def to_string(money) do - {:ok, currency_data} = Cldr.Currency.currency_for_code(currency(money)) - reduced = Money.reduce(money) - - { - currency(reduced), - value(reduced) - |> Decimal.round(currency_data.digits) - |> Decimal.to_string() - } - end - end -end - -if Code.ensure_compiled?(Monetized.Money) do - defimpl Gringotts.Money, for: Monetized.Money do - def currency(money), do: money.currency - def value(money), do: money.amount - end -end - # Assumes that the currency is subdivided into 100 units defimpl Gringotts.Money, for: Any do def currency(%{currency: currency}), do: currency diff --git a/mix.exs b/mix.exs index 589f379c..60056b6c 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Gringotts.Mixfile do def project do [ app: :gringotts, - version: "1.1.0", + version: "1.1.1-rc", description: description(), package: [ contributors: ["Aviabird Technologies"], @@ -12,7 +12,7 @@ defmodule Gringotts.Mixfile do licenses: ["MIT"], links: %{github: "https://github.com/aviabird/gringotts"} ], - elixir: ">= 1.3.0", + elixir: ">= 1.5.3", test_coverage: [ tool: ExCoveralls ], @@ -42,7 +42,7 @@ defmodule Gringotts.Mixfile do end # Specifies which paths to compile per environment. - defp elixirc_paths(:test), do: ["lib", "test/mocks"] + defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_), do: ["lib"] # Dependencies can be hex.pm packages: @@ -57,34 +57,32 @@ defmodule Gringotts.Mixfile do defp deps do [ {:poison, "~> 3.1.0"}, - {:httpoison, "~> 0.13"}, + {:httpoison, "~> 1.1"}, {:xml_builder, "~> 2.1"}, {:elixir_xml_to_map, "~> 0.1"}, - # Money related - {:decimal, "~> 1.0", optional: true}, - # ex_money is just needed for tests. - {:ex_money, "~> 1.1.0", only: [:dev, :test], optional: true}, + # money related + {:decimal, "~> 1.5"}, # docs and tests {:ex_doc, "~> 0.18", only: :dev, runtime: false}, {: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]}, + {:credo, "~> 0.9", only: [:dev, :test]}, {:inch_ex, "~> 0.5", only: :docs}, {:dialyxir, "~> 0.3", only: :dev}, - {:timex, "~> 3.2"}, - {:exvcr, "~> 0.10", only: :test} + {:timex, "~> 3.2"} ] end defp description do """ Gringotts is a payment processing library in Elixir integrating - various payment gateways, this draws motivation for shopify's + various payment gateways, and draws motivation from shopify's activemerchant ruby gem. """ end diff --git a/mix.lock b/mix.lock index 83a82360..704128b0 100644 --- a/mix.lock +++ b/mix.lock @@ -2,40 +2,41 @@ "abnf2": {:hex, :abnf2, "0.1.2", "6f8792b8ac3288dba5fc889c2bceae9fe78f74e1a7b36bea9726ffaa9d7bef95", [:mix], [], "hexpm"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "bypass": {:hex, :bypass, "0.8.1", "16d409e05530ece4a72fabcf021a3e5c7e15dcc77f911423196a0c551f2a15ca", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"}, + "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, - "credo": {:hex, :credo, "0.8.10", "261862bb7363247762e1063713bb85df2bbd84af8d8610d1272cd9c1943bba63", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.4.1", "ad9e501edf7322f122f7fc151cce7c2a0c9ada96f2b0155b8a09a795c2029770", [:mix], [], "hexpm"}, + "credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"}, - "elixir_xml_to_map": {:hex, :elixir_xml_to_map, "0.1.1", "57e924cd11731947bfd245ce57d0b8dd8b7168bf8edb20cd156a2982ca96fdfa", [:mix], [{:erlsom, "~>1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm"}, - "erlsom": {:hex, :erlsom, "1.4.1", "53dbacf35adfea6f0714fd0e4a7b0720d495e88c5e24e12c5dc88c7b62bd3e49", [:rebar3], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, + "elixir_xml_to_map": {:hex, :elixir_xml_to_map, "0.1.2", "e3d1bd2f6562711117ae209657f385a1c1c34c8c720c748eeba2e22815797071", [:mix], [{:erlsom, "~>1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm"}, + "erlsom": {:hex, :erlsom, "1.4.2", "5cddb82fb512f406f61162e511ae86582f824f0dccda788378b18a00d89c1b3f", [:rebar3], [], "hexpm"}, "ex_cldr": {:hex, :ex_cldr, "1.4.4", "654966e8724d607e5cf9ecd5509ffcf66868b17e479bbd22ab2e9123595f9103", [:mix], [{:abnf2, "~> 0.1", [hex: :abnf2, repo: "hexpm", optional: false]}, {:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 2.1 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"}, "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"}, + "excoveralls": {:hex, :excoveralls, "0.8.2", "b941a08a1842d7aa629e0bbc969186a4cefdd035bad9fe15d43aaaaaeb8fae36", [: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"}, + "exvcr": {:hex, :exvcr, "0.10.2", "a66a0fa86d03153e5c21e38b1320d10b537038d7bc7b10dcc1ab7f0343569822", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.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"}, - "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [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, "1.1.1", "96ed7ab79f78a31081bb523eefec205fd2900a02cda6dbc2300e7a1226219566", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "5.1.1", "cbc3b2fa1645113267cc59c760bafa64b2ea0334635ef06dbac8801e42f7279c", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"}, "meck": {:hex, :meck, "0.8.9", "64c5c0bd8bcca3a180b44196265c8ed7594e16bcc845d0698ec6b4e577f48188", [:rebar3], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, - "mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"}, + "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, - "plug": {:hex, :plug, "1.5.0", "224b25b4039bedc1eac149fb52ed456770b9678bbf0349cdd810460e1e09195b", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, + "plug": {:hex, :plug, "1.5.1", "1ff35bdecfb616f1a2b1c935ab5e4c47303f866cb929d2a76f0541e553a58165", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.3", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, - "timex": {:hex, :timex, "3.2.1", "639975eac45c4c08c2dbf7fc53033c313ff1f94fad9282af03619a3826493612", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, + "timex": {:hex, :timex, "3.3.0", "e0695aa0ddb37d460d93a2db34d332c2c95a40c27edf22fbfea22eb8910a9c8d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, "xml_builder": {:hex, :xml_builder, "2.1.0", "c249d5339427c13cae11e9d9d0e8b40d25d228b9ecc54029f24017385e60280b", [:mix], [], "hexpm"}, diff --git a/test/gateways/authorize_net_test.exs b/test/gateways/authorize_net_test.exs index 66ae689d..4047a1bc 100644 --- a/test/gateways/authorize_net_test.exs +++ b/test/gateways/authorize_net_test.exs @@ -1,7 +1,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do use ExUnit.Case, async: false alias Gringotts.Gateways.AuthorizeNetMock, as: MockResponse - alias Gringotts.CreditCard + alias Gringotts.{CreditCard, FakeMoney} alias Gringotts.Gateways.AuthorizeNet, as: ANet import Mock @@ -21,7 +21,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do verification_code: 123 } - @amount Money.new("2.99", :USD) + @amount FakeMoney.new("2.99", :USD) @opts [ config: @auth, @@ -32,17 +32,17 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do name: "vase", description: "Cannes logo", quantity: 18, - unit_price: Money.mult!(@amount, 18) + unit_price: FakeMoney.new("53.82", :USD) }, - tax: %{name: "VAT", amount: Money.new("0.1", :EUR), description: "Value Added Tax"}, + tax: %{name: "VAT", amount: FakeMoney.new("0.1", :EUR), description: "Value Added Tax"}, shipping: %{ name: "SAME-DAY-DELIVERY", - amount: Money.new("0.56", :EUR), + amount: FakeMoney.new("0.56", :EUR), description: "Zen Logistics" }, duty: %{ name: "import_duty", - amount: Money.new("0.25", :EUR), + amount: FakeMoney.new("0.25", :EUR), description: "Upon import of goods" } ] @@ -132,7 +132,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do post: fn _url, _body, _headers -> MockResponse.bad_card_purchase_response() end do - assert {:error, response} = ANet.purchase(@amount, @bad_card, @opts) + assert {:error, _response} = ANet.purchase(@amount, @bad_card, @opts) end end end @@ -152,7 +152,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do post: fn _url, _body, _headers -> MockResponse.bad_card_purchase_response() end do - assert {:error, response} = ANet.authorize(@amount, @bad_card, @opts) + assert {:error, _response} = ANet.authorize(@amount, @bad_card, @opts) end end end @@ -169,7 +169,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do test "with bad transaction id" do with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.bad_id_capture() end do - assert {:error, response} = ANet.capture(@capture_invalid_id, @amount, @opts) + assert {:error, _response} = ANet.capture(@capture_invalid_id, @amount, @opts) end end end @@ -186,14 +186,14 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do test "bad payment params" do with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.bad_card_refund() end do - assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund_bad_payment) + assert {:error, _response} = ANet.refund(@amount, @refund_id, @opts_refund_bad_payment) end end test "debit less than refund amount" do with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.debit_less_than_refund() end do - assert {:error, response} = ANet.refund(@amount, @refund_id, @opts_refund) + assert {:error, _response} = ANet.refund(@amount, @refund_id, @opts_refund) end end end @@ -208,7 +208,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do test "with bad transaction id" do with_mock HTTPoison, post: fn _url, _body, _headers -> MockResponse.void_non_existent_id() end do - assert {:error, response} = ANet.void(@void_invalid_id, @opts) + assert {:error, _response} = ANet.void(@void_invalid_id, @opts) end end end @@ -233,7 +233,7 @@ defmodule Gringotts.Gateways.AuthorizeNetTest do post: fn _url, _body, _headers -> MockResponse.store_without_profile_fields() end do - assert {:error, response} = ANet.store(@card, @opts_store_no_profile) + assert {:error, _response} = ANet.store(@card, @opts_store_no_profile) "Error" end diff --git a/test/gateways/bogus_test.exs b/test/gateways/bogus_test.exs index 9235bcf8..611c9696 100644 --- a/test/gateways/bogus_test.exs +++ b/test/gateways/bogus_test.exs @@ -5,7 +5,7 @@ defmodule Gringotts.Gateways.BogusTest do alias Gringotts.Gateways.Bogus, as: Gateway @some_id "some_arbitrary_id" - @amount Money.new(5, :USD) + @amount Gringotts.FakeMoney.new(5, :USD) test "authorize" do {:ok, %Response{id: id, success: success}} = Gateway.authorize(@amount, :card, []) diff --git a/test/gateways/cams_test.exs b/test/gateways/cams_test.exs index 43370865..db73b457 100644 --- a/test/gateways/cams_test.exs +++ b/test/gateways/cams_test.exs @@ -1,10 +1,7 @@ defmodule Gringotts.Gateways.CamsTest do use ExUnit.Case, async: false - alias Gringotts.{ - CreditCard, - Response - } + alias Gringotts.{CreditCard, Response} alias Gringotts.Gateways.CamsMock, as: MockResponse alias Gringotts.Gateways.Cams, as: Gateway @@ -48,10 +45,10 @@ defmodule Gringotts.Gateways.CamsTest do config: @auth ] - @money Money.new(:USD, 100) - @money_more Money.new(:USD, 101) - @money_less Money.new(:USD, 99) - @bad_currency Money.new(:INR, 100) + @money Gringotts.FakeMoney.new(100, :USD) + @money_more Gringotts.FakeMoney.new(101, :USD) + @money_less Gringotts.FakeMoney.new(99, :USD) + @bad_currency Gringotts.FakeMoney.new(100, :INR) @id "some_transaction_id" @bad_id "some_fake_transaction_id" diff --git a/test/gateways/global_collect_test.exs b/test/gateways/global_collect_test.exs index ea0f4742..ae8d504f 100644 --- a/test/gateways/global_collect_test.exs +++ b/test/gateways/global_collect_test.exs @@ -3,15 +3,14 @@ defmodule Gringotts.Gateways.GlobalCollectTest do alias Gringotts.Gateways.GlobalCollectMock, as: MockResponse alias Gringotts.Gateways.GlobalCollect - alias Gringotts.{ - CreditCard - } + alias Gringotts.CreditCard + alias Gringotts.FakeMoney import Mock - @amount Money.new("500", :USD) + @amount FakeMoney.new("500", :USD) - @bad_amount Money.new("50.3", :USD) + @bad_amount FakeMoney.new("50.3", :USD) @shipping_address %{ street: "Desertroad", @@ -68,13 +67,6 @@ defmodule Gringotts.Gateways.GlobalCollectTest do @invalid_token 30 - @invalid_config [ - config: %{ - secret_api_key: "some_secret_api_key", - api_key_id: "some_api_key_id" - } - ] - @options [ config: %{ secret_api_key: "some_secret_api_key", diff --git a/test/gateways/monei_test.exs b/test/gateways/monei_test.exs index e641d91b..0c4f3df3 100644 --- a/test/gateways/monei_test.exs +++ b/test/gateways/monei_test.exs @@ -2,15 +2,16 @@ defmodule Gringotts.Gateways.MoneiTest do use ExUnit.Case, async: true alias Gringotts.{ - CreditCard + CreditCard, + FakeMoney } alias Gringotts.Gateways.Monei, as: Gateway alias Plug.{Conn, Parsers} - @amount42 Money.new(42, :USD) - @amount3 Money.new(3, :USD) - @bad_currency Money.new(42, :INR) + @amount42 FakeMoney.new(42, :USD) + @amount3 FakeMoney.new(3, :USD) + @bad_currency FakeMoney.new(42, :INR) @card %CreditCard{ first_name: "Harry", diff --git a/test/gateways/paymill_test.exs b/test/gateways/paymill_test.exs index d830621c..a029f5a4 100644 --- a/test/gateways/paymill_test.exs +++ b/test/gateways/paymill_test.exs @@ -17,7 +17,7 @@ defmodule Gringotts.Gateways.PaymillTest do {:ok, bypass: bypass, opts: opts} end - @amount_42 Money.new(42, :EUR) + @amount_42 Gringotts.FakeMoney.new(42, :EUR) @valid_token "tok_d26e611c47d64693a281e8411934" @invalid_token "tok_d26e611c47d64693a281e841193" @@ -38,7 +38,7 @@ defmodule Gringotts.Gateways.PaymillTest do end) {:ok, response} = Gateway.authorize(@amount_42, @valid_token, config: opts) - assert response.gateway_code == 20000 + assert response.gateway_code == 20_000 end test "when paymill is down or unreachable", %{bypass: bypass, opts: opts} do @@ -75,7 +75,7 @@ defmodule Gringotts.Gateways.PaymillTest do end) {:ok, response} = Gateway.capture(@capture_preauth_id, @amount_42, config: opts) - assert response.gateway_code == 20000 + assert response.gateway_code == 20_000 end test "when preauthorization not found", %{bypass: bypass, opts: opts} do @@ -121,7 +121,7 @@ defmodule Gringotts.Gateways.PaymillTest do end) {:ok, response} = Gateway.purchase(@amount_42, @invalid_token, config: opts) - assert response.gateway_code == 20000 + assert response.gateway_code == 20_000 assert response.fraud_review == true assert response.status_code == 200 end @@ -156,7 +156,7 @@ defmodule Gringotts.Gateways.PaymillTest do end) {:ok, response} = Gateway.refund(@amount_42, @transaction_id, config: opts) - assert response.gateway_code == 20000 + assert response.gateway_code == 20_000 assert response.status_code == 200 end @@ -194,7 +194,7 @@ defmodule Gringotts.Gateways.PaymillTest do end) {:ok, response} = Gateway.void(@void_id, config: opts) - assert response.gateway_code == 50810 + assert response.gateway_code == 50_810 end test "when preauthorization used before", %{bypass: bypass, opts: opts} do diff --git a/test/gateways/trexle_test.exs b/test/gateways/trexle_test.exs index df74ff7d..65485fe8 100644 --- a/test/gateways/trexle_test.exs +++ b/test/gateways/trexle_test.exs @@ -1,13 +1,10 @@ defmodule Gringotts.Gateways.TrexleTest do use ExUnit.Case, async: false + + alias Gringotts.{Address, CreditCard, FakeMoney} alias Gringotts.Gateways.TrexleMock, as: MockResponse alias Gringotts.Gateways.Trexle - alias Gringotts.{ - CreditCard, - Address - } - import Mock @valid_card %CreditCard{ @@ -41,9 +38,9 @@ defmodule Gringotts.Gateways.TrexleTest do } # $2.99 - @amount Money.new("2.99", :USD) + @amount FakeMoney.new("2.99", :USD) # 50 US cents, trexle does not work with amount smaller than 50 cents. - @bad_amount Money.new("0.49", :USD) + @bad_amount FakeMoney.new("0.49", :USD) @valid_token "some_valid_token" @invalid_token "some_invalid_token" @@ -63,7 +60,7 @@ defmodule Gringotts.Gateways.TrexleTest do request: fn _method, _url, _body, _headers, _options -> MockResponse.test_for_purchase_with_valid_card() end do - assert {:ok, response} = Trexle.purchase(@amount, @valid_card, @opts) + assert {:ok, _response} = Trexle.purchase(@amount, @valid_card, @opts) end end diff --git a/test/gateways/wire_card_test.exs b/test/gateways/wire_card_test.exs deleted file mode 100644 index 61633735..00000000 --- a/test/gateways/wire_card_test.exs +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Gringotts.Gateways.WireCardTest do - use ExUnit.Case, async: false - - import Mock - - setup do - # TEST_AUTHORIZATION_GUWID = 'C822580121385121429927' - # TEST_PURCHASE_GUWID = 'C865402121385575982910' - # TEST_CAPTURE_GUWID = 'C833707121385268439116' - - # config = %{credentails: {'user', 'pass'}, default_currency: "EUR"} - :ok - end - - test "test_successful_authorization" do - assert 1 + 1 == 2 - end -end diff --git a/test/integration/gateways/monei_test.exs b/test/integration/gateways/monei_test.exs index 59bc9b88..a7a0deda 100644 --- a/test/integration/gateways/monei_test.exs +++ b/test/integration/gateways/monei_test.exs @@ -9,8 +9,8 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do @moduletag :integration - @amount Money.new(42, :EUR) - @sub_amount Money.new(21, :EUR) + @amount Gringotts.FakeMoney.new(42, :EUR) + @sub_amount Gringotts.FakeMoney.new(21, :EUR) @card %CreditCard{ first_name: "Harry", diff --git a/test/integration/gateways/paymill_test.exs b/test/integration/gateways/paymill_test.exs index f7919ac9..3e0db660 100644 --- a/test/integration/gateways/paymill_test.exs +++ b/test/integration/gateways/paymill_test.exs @@ -6,7 +6,7 @@ defmodule Gringotts.Integration.Gateways.PaymillTest do @moduletag integration: true - @amount Money.new(4200, :EUR) + @amount Gringotts.FakeMoney.new(4200, :EUR) @valid_token1 "tok_784c33eeb9a6adfc2bd3c21f95e6" @valid_token2 "tok_9e429fb2dc44bcf94bcd4e6e6ec5" @valid_token3 "tok_55b80f87f44f9328bee99360c4cc" @@ -31,7 +31,7 @@ defmodule Gringotts.Integration.Gateways.PaymillTest do test "with valid token and currency" do use_cassette "paymill/authorize with valid token and currency" do {:ok, response} = Gringotts.authorize(Gateway, @amount, @valid_token1) - assert response.gateway_code == 20000 + assert response.gateway_code == 20_000 assert response.status_code == 200 end end @@ -43,7 +43,7 @@ defmodule Gringotts.Integration.Gateways.PaymillTest do {:ok, response} = Gringotts.authorize(Gateway, @amount, @valid_token2) payment_id = response.id {:ok, response_cap} = Gringotts.capture(Gateway, payment_id, @amount) - assert response_cap.gateway_code == 20000 + assert response_cap.gateway_code == 20_000 assert response_cap.status_code == 200 end end @@ -53,7 +53,7 @@ defmodule Gringotts.Integration.Gateways.PaymillTest do test "with valid token currency" do use_cassette "paymill purchase with valid token currency" do {:ok, response} = Gringotts.purchase(Gateway, @amount, @valid_token3) - assert response.gateway_code == 20000 + assert response.gateway_code == 20_000 assert response.status_code == 200 end end @@ -65,7 +65,7 @@ defmodule Gringotts.Integration.Gateways.PaymillTest do {:ok, response} = Gringotts.purchase(Gateway, @amount, @valid_token4) trans_id = response.id {:ok, response_ref} = Gringotts.refund(Gateway, @amount, trans_id) - assert response_ref.gateway_code == 20000 + assert response_ref.gateway_code == 20_000 assert response_ref.status_code == 200 end end @@ -77,7 +77,7 @@ defmodule Gringotts.Integration.Gateways.PaymillTest do {:ok, response} = Gringotts.authorize(Gateway, @amount, @valid_token5) auth_id = response.id {:ok, response_void} = Gringotts.void(Gateway, auth_id) - assert response_void.gateway_code == 50810 + assert response_void.gateway_code == 50_810 assert response_void.status_code == 200 end end diff --git a/test/integration/gateways/stripe_test.exs b/test/integration/gateways/stripe_test.exs index e4d3e070..60dbefc5 100644 --- a/test/integration/gateways/stripe_test.exs +++ b/test/integration/gateways/stripe_test.exs @@ -1,16 +1,12 @@ defmodule Gringotts.Gateways.StripeTest do use ExUnit.Case + alias Gringotts.{Address, CreditCard} alias Gringotts.Gateways.Stripe - alias Gringotts.{ - CreditCard, - Address - } - @moduletag integration: true - @amount Money.new(5, :USD) + @amount Gringotts.FakeMoney.new(5, :USD) @card %CreditCard{ first_name: "John", last_name: "Smith", diff --git a/test/integration/money.exs b/test/integration/money.exs deleted file mode 100644 index e4aaa331..00000000 --- a/test/integration/money.exs +++ /dev/null @@ -1,61 +0,0 @@ -defmodule Gringotts.Integration.Gateways.MoneyTest do - use ExUnit.Case, async: true - - alias Gringotts.Money, as: MoneyProtocol - - @moduletag :integration - - @ex_money Money.new(42, :EUR) - @ex_money_long Money.new("42.126456", :EUR) - @ex_money_bhd Money.new(42, :BHD) - - @any %{value: Decimal.new(42), currency: "EUR"} - @any_long %{value: Decimal.new("42.126456"), currency: "EUR"} - @any_bhd %{value: Decimal.new("42"), currency: "BHD"} - - describe "ex_money" do - test "value is a Decimal.t" do - assert match?(%Decimal{}, MoneyProtocol.value(@ex_money)) - end - - test "currency is an upcase String.t" do - the_currency = MoneyProtocol.currency(@ex_money) - assert match?(currency when is_binary(currency), the_currency) - assert the_currency == String.upcase(the_currency) - end - - test "to_integer" do - assert match?({"EUR", 4200, -2}, MoneyProtocol.to_integer(@ex_money)) - assert match?({"BHD", 42_000, -3}, MoneyProtocol.to_integer(@ex_money_bhd)) - end - - test "to_string" do - assert match?({"EUR", "42.00"}, MoneyProtocol.to_string(@ex_money)) - assert match?({"EUR", "42.13"}, MoneyProtocol.to_string(@ex_money_long)) - assert match?({"BHD", "42.000"}, MoneyProtocol.to_string(@ex_money_bhd)) - end - end - - describe "Any" do - test "value is a Decimal.t" do - assert match?(%Decimal{}, MoneyProtocol.value(@any)) - end - - test "currency is an upcase String.t" do - the_currency = MoneyProtocol.currency(@any) - assert match?(currency when is_binary(currency), the_currency) - assert the_currency == String.upcase(the_currency) - end - - test "to_integer" do - assert match?({"EUR", 4200, -2}, MoneyProtocol.to_integer(@any)) - assert match?({"BHD", 4200, -2}, MoneyProtocol.to_integer(@any_bhd)) - end - - test "to_string" do - assert match?({"EUR", "42.00"}, MoneyProtocol.to_string(@any)) - assert match?({"EUR", "42.13"}, MoneyProtocol.to_string(@any_long)) - assert match?({"BHD", "42.00"}, MoneyProtocol.to_string(@any_bhd)) - end - end -end diff --git a/test/support/fake_money.ex b/test/support/fake_money.ex new file mode 100644 index 00000000..4f2437f1 --- /dev/null +++ b/test/support/fake_money.ex @@ -0,0 +1,37 @@ +defmodule Gringotts.FakeMoney do + @moduledoc """ + Defines a Gringotts.Money compliant money struct. + + Implements the gringotts protocol and does nothing else. + """ + + defstruct [:amount_field, :currency_field] + + def new(amount, currency) when is_atom(currency) do + struct(__MODULE__, amount_field: Decimal.new(amount), currency_field: currency) + end +end + +defimpl Gringotts.Money, for: Gringotts.FakeMoney do + def currency(money_struct), do: money_struct.currency_field + def value(money_struct), do: money_struct.amount_field + + def to_integer(money_struct) do + { + money_struct.currency_field, + money_struct.amount_field + |> Decimal.mult(100) + |> Decimal.to_integer(), + -2 + } + end + + def to_string(money_struct) do + { + Atom.to_string(money_struct.currency_field), + money_struct.amount_field + |> Decimal.round(2) + |> Decimal.to_string() + } + end +end diff --git a/test/mocks/authorize_net_mock.ex b/test/support/mocks/authorize_net_mock.ex similarity index 100% rename from test/mocks/authorize_net_mock.ex rename to test/support/mocks/authorize_net_mock.ex diff --git a/test/mocks/cams_mock.ex b/test/support/mocks/cams_mock.ex similarity index 100% rename from test/mocks/cams_mock.ex rename to test/support/mocks/cams_mock.ex diff --git a/test/mocks/global_collect_mock.ex b/test/support/mocks/global_collect_mock.ex similarity index 100% rename from test/mocks/global_collect_mock.ex rename to test/support/mocks/global_collect_mock.ex diff --git a/test/mocks/paymill_mock.ex b/test/support/mocks/paymill_mock.ex similarity index 100% rename from test/mocks/paymill_mock.ex rename to test/support/mocks/paymill_mock.ex diff --git a/test/mocks/trexle_mock.ex b/test/support/mocks/trexle_mock.ex similarity index 100% rename from test/mocks/trexle_mock.ex rename to test/support/mocks/trexle_mock.ex