-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introducing the Money protocol #71
Conversation
test/gateways/monei_test.exs
Outdated
@@ -196,6 +197,12 @@ defmodule Gringotts.Gateways.MoneiTest do | |||
then = Enum.map(all, &Gateway.respond({:ok, &1})) | |||
assert Keyword.keys(then) == [:ok, :error, :error, :error] | |||
end | |||
|
|||
defp amount(amount, currency \\ "USD") do | |||
%{amount: Decimal.new(amount), currency: currency} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oyeb instead of using a simple map, use ex_money
struct example here, put ex_money
as only a dependency for test environment.
Or you also write 3 test cases for testing 3 existing libs by passing money
structs of these libs showing how we accept all the libraries in elixir.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pkrawat1 agree with you on this.
@@ -62,4 +62,5 @@ defmodule Gringotts.Integration.Gateways.MoneiTest do | |||
assert config[:adapter] == Gringotts.Gateways.Monei | |||
end | |||
|
|||
defp amount(amount, currency \\ "EUR"), do: %{amount: Decimal.new(amount), currency: currency} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here as above comment, modify test cases to show it works with dependencies? what do you think?
Actually compatibility with external libs can be tested in a single file I am thinking.
Thoughts ? @kartikjagdale @arjun289 @oyeb
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not without adding them as dependencies that are available in test
environment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oyeb agree here with you...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So do we agree to add ex_money
and monteized
to our dependencies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, also keep the any
fallback just in case. We need more documentation stating out of box support for these libs. @oyeb
@@ -74,8 +75,14 @@ defmodule Gringotts.Mixfile do | |||
[ | |||
main: "Gringotts", | |||
logo: "images/lg.png", | |||
source_url: "https://github.com/aviabird/gringotts" | |||
source_url: "https://github.com/aviabird/gringotts", | |||
groups_for_modules: groups_for_modules() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cheers for this! Nothing to be done here 👍
lib/gringotts/gateways/monei.ex
Outdated
@@ -252,22 +242,18 @@ defmodule Gringotts.Gateways.Monei do | |||
The following session shows how one would process a payment in one-shot, | |||
without (pre) authorization. | |||
|
|||
iex> opts = [currency: "EUR"] # The default currency is EUR, and this is just for an example. | |||
iex> amount = %{amount: 40, currency: "EUR"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still don't understand why were we passing opts = [currency: "EUR"]
in the first place? It was available all along in the gateway configs and hence in the gateway files in the lib.
Thoughts: @oyeb @kartikjagdale @arjun289 @Zeus999 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ashish173 in gateway configs, it was the default_currency
, to fallback to this currency if someone doesn't specify currency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had implemented just 1 optional param, currency
. So it was the only thing that I could show an example for 😄
I've always been against having default_currency
in gateway/lib-global configs. Thankfully, with this PR all that will go away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oyeb why u r against with default_currency? what problems you find in that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pkrawat1 agree with @ashish173 here that user should be able to pass a default currency through options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kartikjagdale I don't think there is a need for that, if the user is passing a currency in Money
struct, then what is the use of of passing default currency
in options. it should be handled by our application only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All amounts should necessarily, explicitly have an associated currency. Pls explain the need for a default on #62.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, now we don't have the need for the default currency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I've read all the comments above and for the sake of organisation I am listing some points which would benefit from having default_currency
.
- In the application Instead of using
"USD"
strings in all the requests we can have a single source of truth for currency per gateway(assuming we have multiple gateways) or a global setting fordefault_currency
this way we will be able to use that to create our request params for lib.
This lead to better organisation per gateway. Please note that we don't use this config
directly in the library. We rely on what is send in the request.
@@ -30,7 +30,6 @@ defmodule Gringotts.Gateways.Monei do | |||
| ---- | --- | ---- | | |||
| `billing_address` | | Not implemented | | |||
| `cart` | | Not implemented | | |||
| `currency` | | **Implemented** | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oyeb why did we remove this one? these params show what goes in the payments gateway and this PR should not have any change in the effect? Am I missing something here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currency
is no longer an optional arg. It's explicitly available with the amount
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, I think I misunderstood that! 👍
lib/gringotts/gateways/monei.ex
Outdated
@@ -107,7 +106,7 @@ defmodule Gringotts.Gateways.Monei do | |||
aliases to it (to save some time): | |||
``` | |||
iex> alias Gringotts.{Response, CreditCard, Gateways.Monei} | |||
iex> opts = [currency: "EUR"] # The default currency is EUR, and this is just for an example. | |||
iex> amount = %{amount: 40, currency: "EUR"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing this is okay, for testing purpose developer can use a Map
but in case of code follow my comments in this PR it would awesome if we can somehow make it explicit that we support 2-3 money libs out there, maybe a bunch of grouped test cases just thinking?
Thoughts? @oyeb @kartikjagdale @arjun289 @SagarKarwande @Zeus999
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd love to do that! And I'd love to add integration tests that make sure ex_money
and monetized
are compliant.
All that's stopping me is that I'll have to add them as an optional
or only: :test
dependency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And if we are adding them as dependency, I suggest we get them as optional: :true
instead of only: :test
as that will let us explicitly implement the protocol for them (which is currently happening via the fall back to any
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oyeb it will awesome if we have integration test for ex_money and monetized scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oyeb Yes, let's add them as optional
and implement the protocol, make sure we have a fallback in any case.
lib/gringotts.ex
Outdated
@@ -92,10 +101,8 @@ defmodule Gringotts do | |||
country: "US", | |||
postal_code: "11111" | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
payment map is been divided into CreditCard
and Address
struct. so @oyeb this example needs to be change accordingly and now we are passing Address
into the opts keyword.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's a payment map? I'm not familiar with the recent changes in stripe (and also AFK), so I'll appreciate it if you can push a commit to this branch with the corrections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oyeb ok I will make the required changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Zeus999 I don't understand how is this change flowing backwards from gateway
to gringotts.ex
, care to give more info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pkrawat1 I think, the one who added the docs in gringotts.ex
copied the docs from stripe.ex
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pkrawat1 I am fixing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
lib/gringotts/gateways/monei.ex
Outdated
params = | ||
[ | ||
paymentType: "PA", | ||
amount: :erlang.float_to_binary(amount, decimals: 2), | ||
currency: currency(opts) | ||
amount: amount |> Money.amount |> Decimal.to_float |> :erlang.float_to_binary(decimals: 2), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could rename the protocol method Money.amount
to Money.value
perhaps, then this would read:
amount: amount |> Money.value |> Decimal.to_float |> :erlang.float_to_binary(decimals: 2)
Writing Money.value(amount)
is better than writing Money.amount(amount)
, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
Task 3I've hit a wall with writing integration tests for the money libs. The problem is that all these libs have a "naked" Moreover, there's no way for me to refer to the |
Task 4Because the module is naked, I can't implement the protocol for them in Gringotts. But we can send a PR to their projects, where they can implement our protocol. They won't have to add our lib as a dependency. It'll look something like: # Add this in their money.ex
if Code.ensure_compiled?(Gringotts.Money) do
defimpl value(money) do
...
end
defimpl currency(money) do
...
end
end If we ever change our protocol, we send PRs. If they make a breaking change later, it's their responsibility to mend it. |
184944c
to
d10e347
Compare
* Since amounts are Decimal.t, we depend on the decimal lib. - but it's marked as optional, so our users will have to explicitly add it to their deps * Added a group for gateways in the docs, for more features of ex_docs see how other use it: https://github.com/kipcole9/money/blob/v1.1.0/mix.exs
* no need of currency in opts, removed @default_currency * updates tests (even integration) to use a map for amount
There's much t0d0 in here. * Documented the amount section
* Changed `amount` -> `value`
* We'll just be keeping ex_money in tests, as all Money libs keep their modules "naked". See this thread: https://elixirforum.com/t/working-with-modules-with-same-name/11364
* Re-ordered methods, they are now alphabetic. * docs are not tied to any specific gateway.
lib/gringotts/money.ex
Outdated
end | ||
|
||
defimpl Gringotts.Money, for: Any do | ||
def currency(money), do: Map.get(money, :currency) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May I suggest calling to_string
on the map result which would then normalise from libs (like ex_money) that use an atom for currency code?
def currency(money), do: Map.get(money, :currency) |> to_string
or to ensure uppercase per your definitions:
def currency(money), do: Map.get(money, :currency) |> to_string |> String.upcase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is covered in 1076a9f (in this PR)
Both money
and ex_money
use upcase atom
s, so the implementation for Money
(not Any
) will fire and give us a string
. monetized
uses upcase strings, so there's no need to do an upcase
.
lib/gringotts/money.ex
Outdated
|
||
defimpl Gringotts.Money, for: Any do | ||
def currency(money), do: Map.get(money, :currency) | ||
def amount(money), do: Map.get(money, :amount) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps to enforce the use of Decimals this might be:
def amount(%{amount: %Decimal{} = amount}), do: amount
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll definitely introduce this when we add the to_float_string
and to_integer
methods to the protocol.
Building on the discussion in #62, this PR introduces a protocol to replace amount argument, and removes the need to specify currency in config or opts. Supporting `ex_money` and `monetized` money libs out of the box. Usage:- Money can now be passed like this while calling the public API methods. money = %{amount: Decimal.new(2017.18), currency: "USD"} Conversation for this happened here on elixir forum and https://elixirforum.com/t/gringotts-a-complete-payment-library-for-elixir-and-phoenix-framework/11054
Building on the discussion in #62, this PR introduces a protocol to replace
amount
argument, and removes the need to specifycurrency
in config oropts
.There's a fallback implementation for
Any
which enables us to supportex_money
andmonetized
right out of the box.Todo
Please push commits to resolve the following:
amount
tovalue
... as anamount
can be seen to have avalue
in a specificcurrency
.ex_money
as dependency and implement the protocol for it.