-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
https://www.mercadopago.com/ * Implements `authorize/3`. * `@moduledoc` and docs added authorize --------- * Following optional arguments implemented: - `email`, `order_id`, `customer_id`, `order_type`, `installments` * Mercadopago provides an `access_token` and ` `public_key` for auth. * Mercadopago payments don't have any (writable) currency field, it seems that currency of the transaction is set by Mercadopago automatically from the merchant account.
- Loading branch information
Showing
2 changed files
with
274 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
defmodule Gringotts.Gateways.Mercadopago do | ||
@moduledoc """ | ||
[mercadopago][home] gateway implementation. | ||
For reference see [mercadopago documentation][docs]. | ||
The following features of mercadopago are implemented: | ||
| Action | Method | | ||
| ------ | ------ | | ||
| Pre-authorize | `authorize/3` | | ||
| Capture | `capture/3` | | ||
| Purchase | `purchase/3` | | ||
| Reversal | `void/2` | | ||
| Refund | `refund/3` | | ||
[home]: https://www.mercadopago.com/ | ||
[docs]: https://www.mercadopago.com.ar/developers/en/api-docs/ | ||
## The `opts` argument | ||
Most `Gringotts` API calls accept an optional `keyword` list `opts` to supply | ||
optional arguments for transactions with the mercadopago | ||
gateway. The following keys are supported: | ||
| Key | Remark | | ||
| ---- | --- | | ||
| `email` | Email of the customer. Type - string | | ||
| `order_id` | Order id issued by the merchant. Type- integer | | ||
| `customer_id` | Unique customer id issued by the gateway. For new customer it must skipped. Type- string| | ||
| `order_type` | `"mercadopago"` or `"mercadolibre"` as per the order. Type- string | | ||
| `installments` | No of installments for payment. Type- integer | | ||
## Registering your mercadopago account at `Gringotts` | ||
After [making an account on mercadopago][credentials], head to the credentials and find | ||
your account "secrets" in the `Checkout Transparent`. | ||
| Config parameter | MERCADOPAGO secret | | ||
| ------- | ---- | | ||
| `:access_token` | **Access Token** | | ||
| `:public_key` | **Public Key** | | ||
> Your Application config **must include the `[:public_key, :access_token]` field(s)** and would look | ||
> something like this: | ||
> | ||
> config :gringotts, Gringotts.Gateways.Mercadopago, | ||
> public_key: "your_secret_public_key" | ||
> access_token: "your_secret_access_token" | ||
[credentials]: https://www.mercadopago.com/mlb/account/credentials?type=basic | ||
## Note | ||
mercadopago processes money with upto two decimal places. | ||
## Supported currencies and countries | ||
mercadopago supports the currencies listed [here][currencies]. | ||
[currencies]: https://api.mercadopago.com/currencies | ||
## Following the examples | ||
1. First, set up a sample application and configure it to work with MERCADOPAGO. | ||
- You could do that from scratch by following our [Getting Started][gs] guide. | ||
- To save you time, we recommend [cloning our example | ||
repo][example] that gives you a pre-configured sample app ready-to-go. | ||
+ You could use the same config or update it the with your "secrets" | ||
as described [above](#module-registering-your-mercadopago-account-at-gringotts). | ||
2. Run an `iex` session with `iex -S mix` and add some variable bindings : | ||
``` | ||
iex> card = %CreditCard{first_name: "John", last_name: "Doe", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"} | ||
``` | ||
We'll be using these in the examples below. | ||
[gs]: https://github.com/aviabird/gringotts/wiki/ | ||
[home]: https://www.mercadopago.com | ||
[example]: https://github.com/aviabird/gringotts_example | ||
""" | ||
|
||
# The Base module has the (abstract) public API, and some utility | ||
# implementations. | ||
@base_url "https://api.mercadopago.com" | ||
use Gringotts.Gateways.Base | ||
alias Gringotts.CreditCard | ||
# The Adapter module provides the `validate_config/1` | ||
# Add the keys that must be present in the Application config in the | ||
# `required_config` list | ||
use Gringotts.Adapter, required_config: [:public_key, :access_token] | ||
|
||
alias Gringotts.{CreditCard, Response} | ||
|
||
@doc """ | ||
Performs a (pre) Authorize operation. | ||
The authorization validates the `card` details with the banking network, | ||
places a hold on the transaction `amount` in the customer’s issuing bank. | ||
mercadoapgo's `authorize` returns customer id(available in `Response.token`) and authorization id(available in the `Response.id` field) : | ||
* `capture/3` _an_ amount. | ||
* `void/2` a pre-authorization. | ||
## Note | ||
For a new customer, `customer_id` field should be ignored. Otherwise it should be provided. | ||
## Example | ||
### Authorization for new customer. | ||
The following example shows how one would (pre) authorize a payment of 42 BRL on a sample `card`. | ||
Ignore `customer_id`. | ||
iex> amount = Money.new(42, :BRL) | ||
iex> card = %Gringotts.CreditCard{first_name: "Lord", last_name: "Voldemort", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"} | ||
iex> opts = [email: "[email protected]", order_id: 123123, payment_method_id: "visa"] | ||
iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Mercadopago, amount, card, opts) | ||
iex> auth_result.id # This is the authorization ID | ||
iex> auth_result.token # This is the customer ID/token | ||
### Authorization for old customer. | ||
The following example shows how one would (pre) authorize a payment of 42 BRL on a sample `card`. | ||
Mention `customer_id`. | ||
iex> amount = Money.new(42, :BRL) | ||
iex> card = %Gringotts.CreditCard{first_name: "Hermione", last_name: "Granger", number: "4509953566233704", year: 2099, month: 12, verification_code: "123", brand: "VISA"} | ||
iex> opts = [email: "[email protected]", order_id: 123125, customer_id: "hermoine's customer id", payment_method_id: "visa"] | ||
iex> {:ok, auth_result} = Gringotts.authorize(Gringotts.Gateways.Mercadopago, amount, card, opts) | ||
iex> auth_result.id # This is the authorization ID | ||
iex> auth_result.token # This is the customer ID/token | ||
""" | ||
|
||
@spec authorize(Money.t(), CreditCard.t(), keyword) :: {:ok | :error, Response} | ||
def authorize(amount, %CreditCard{} = card, opts) do | ||
with {:ok, customer_id} <- create_customer(opts), | ||
{:ok, card_token} <- create_token(card, opts) do | ||
{_, value, _, _} = Money.to_integer_exp(amount) | ||
url_params = [access_token: opts[:config][:access_token]] | ||
|
||
params = [ | ||
authorize_params(value, opts, card_token, false, card), | ||
customer_params(card, customer_id, opts) | ||
] | ||
|
||
body = | ||
params | ||
|> Enum.reduce(&Map.merge/2) | ||
|> Poison.encode!() | ||
|
||
commit(:post, "/v1/payments", body, opts, params: url_params) | ||
end | ||
end | ||
|
||
############################################################################### | ||
# PRIVATE METHODS # | ||
############################################################################### | ||
|
||
# Makes the request to mercadopago's network. | ||
# For consistency with other gateway implementations, make your (final) | ||
# network request in here, and parse it using another private method called | ||
# `respond`. | ||
@spec commit(atom, String.t(), String.t(), keyword, keyword) :: {:ok | :error, Response.t()} | ||
defp commit(method, path, body, opts, url_params) do | ||
headers = [{"content-type", "application/json"}, {"accept", "application/json"}] | ||
res = HTTPoison.request(method, "#{@base_url}#{path}", body, headers, url_params) | ||
respond(res, opts) | ||
end | ||
|
||
# Parses mercadopago's response and returns a `Gringotts.Response` struct | ||
# in a `:ok`, `:error` tuple. | ||
|
||
defp create_customer(opts) do | ||
if Keyword.has_key?(opts, :customer_id) do | ||
{:ok, opts[:customer_id]} | ||
else | ||
url_params = [access_token: opts[:config][:access_token]] | ||
body = %{email: opts[:email]} |> Poison.encode!() | ||
{state, res} = commit(:post, "/v1/customers", body, opts, params: url_params) | ||
|
||
if state == :error do | ||
{state, res} | ||
else | ||
{state, res.id} | ||
end | ||
end | ||
end | ||
|
||
defp token_params(%CreditCard{} = card) do | ||
%{ | ||
expirationYear: card.year, | ||
expirationMonth: card.month, | ||
cardNumber: card.number, | ||
securityCode: card.verification_code, | ||
cardholder: %{name: CreditCard.full_name(card)} | ||
} | ||
end | ||
|
||
defp create_token(%CreditCard{} = card, opts) do | ||
url_params = [public_key: opts[:config][:public_key]] | ||
body = Poison.encode!(token_params(card)) | ||
|
||
{state, res} = | ||
commit(:post, "/v1/card_tokens/#{opts[:customer_id]}", body, opts, params: url_params) | ||
|
||
case state do | ||
:error -> {state, res} | ||
_ -> {state, res.id} | ||
end | ||
end | ||
|
||
defp authorize_params(value, opts, token_id, capture, %CreditCard{} = card) do | ||
%{ | ||
installments: opts[:installments] || 1, | ||
transaction_amount: value, | ||
payment_method_id: String.downcase(card.brand), | ||
token: token_id, | ||
capture: capture | ||
} | ||
end | ||
|
||
defp customer_params(%CreditCard{} = card, customer_id, opts) do | ||
%{ | ||
payer: %{ | ||
type: "customer", | ||
id: customer_id, | ||
first_name: card.first_name, | ||
last_name: card.last_name | ||
}, | ||
order: %{ | ||
type: opts[:order_type], | ||
id: opts[:order_id] | ||
} | ||
} | ||
end | ||
|
||
defp success_body(body, status_code, opts) do | ||
%Response{ | ||
success: true, | ||
id: body["id"], | ||
token: opts[:customer_id], | ||
status_code: status_code, | ||
message: body["status"] | ||
} | ||
end | ||
|
||
defp error_body(body, status_code, opts) do | ||
%Response{ | ||
success: false, | ||
token: opts[:customer_id], | ||
status_code: status_code, | ||
message: body["message"] | ||
} | ||
end | ||
|
||
defp respond({:ok, %HTTPoison.Response{body: body, status_code: status_code}}, opts) do | ||
body = body |> Poison.decode!() | ||
|
||
case body["cause"] do | ||
nil -> {:ok, success_body(body, status_code, opts)} | ||
_ -> {:error, error_body(body, status_code, opts)} | ||
end | ||
end | ||
|
||
defp respond({:error, %HTTPoison.Error{} = error}, _) do | ||
{ | ||
:error, | ||
Response.error( | ||
reason: "network related failure", | ||
message: "HTTPoison says '#{error.reason}' [ID: #{error.id || "nil"}]" | ||
) | ||
} | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.