From e44bf8739483fae5e824ca6309717b42275d487e Mon Sep 17 00:00:00 2001 From: Volodya Sveredyuk Date: Mon, 5 Jun 2023 20:03:42 +0300 Subject: [PATCH 01/10] ExMonobank.MerchantAPI.create_invoice/1 --- .gitignore | 1 + config/config.exs | 6 +- config/dev.exs | 1 + config/test.exs | 18 +++++ ...e_invoice_failure_with_invalid_amount.json | 30 ++++++++ ...create_invoice_failure_without_amount.json | 30 ++++++++ .../create_invoice_success_with_amount.json | 30 ++++++++ ...eate_invoice_success_with_full_params.json | 30 ++++++++ lib/ex_monobank.ex | 3 + lib/ex_monobank/invoice.ex | 19 +++++ lib/ex_monobank/invoice_info.ex | 4 + lib/ex_monobank/merchant_api.ex | 53 ++++++++++++++ lib/ex_monobank/personal_api.ex | 37 ++++++---- lib/ex_monobank/public_api.ex | 7 +- mix.exs | 12 ++- mix.lock | 21 ++++-- test/ex_monobank/merchant_api_test.exs | 73 +++++++++++++++++++ test/test_helper.exs | 1 + 18 files changed, 342 insertions(+), 34 deletions(-) create mode 100644 config/dev.exs create mode 100644 config/test.exs create mode 100644 fixture/vcr_cassettes/merchantapi/create_invoice_failure_with_invalid_amount.json create mode 100644 fixture/vcr_cassettes/merchantapi/create_invoice_failure_without_amount.json create mode 100644 fixture/vcr_cassettes/merchantapi/create_invoice_success_with_amount.json create mode 100644 fixture/vcr_cassettes/merchantapi/create_invoice_success_with_full_params.json create mode 100644 lib/ex_monobank/invoice.ex create mode 100644 lib/ex_monobank/invoice_info.ex create mode 100644 lib/ex_monobank/merchant_api.ex create mode 100644 test/ex_monobank/merchant_api_test.exs create mode 100644 test/test_helper.exs diff --git a/.gitignore b/.gitignore index 2dcaa3d..8ad73ff 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ erl_crash.dump ex_monobank-*.tar /demo +.env diff --git a/config/config.exs b/config/config.exs index b706af9..871a3d1 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,7 +1,3 @@ import Config -# Sample configuration: -# -# config :ex_monobank, PrivateAPI, -# token: "your-personal-token", -# base_url: "https://api.monobank.ua" +import_config "#{Mix.env()}.exs" diff --git a/config/dev.exs b/config/dev.exs new file mode 100644 index 0000000..becde76 --- /dev/null +++ b/config/dev.exs @@ -0,0 +1 @@ +import Config diff --git a/config/test.exs b/config/test.exs new file mode 100644 index 0000000..277d55f --- /dev/null +++ b/config/test.exs @@ -0,0 +1,18 @@ +import Config + +config :tesla, adapter: Tesla.Adapter.Hackney + +config :exvcr, + vcr_cassette_library_dir: "fixture/vcr_cassettes", + filter_sensitive_data: [ + [pattern: ".+", placeholder: "PASSWORD_PLACEHOLDER"] + ], + filter_url_params: false, + filter_request_headers: ["X-Token"], + response_headers_blacklist: [] + +config :ex_monobank, + base_url: "https://api.monobank.ua", + private_api: %{ + token: System.get_env("MONOBANK_API_TOKEN") + } diff --git a/fixture/vcr_cassettes/merchantapi/create_invoice_failure_with_invalid_amount.json b/fixture/vcr_cassettes/merchantapi/create_invoice_failure_with_invalid_amount.json new file mode 100644 index 0000000..131bfb8 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/create_invoice_failure_with_invalid_amount.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"amount\":\"amount\",\"ccy\":null,\"merchant_paym_info\":null,\"payment_type\":null,\"qr_id\":null,\"redirect_url\":null,\"save_card\":null,\"validity\":null,\"webhook_url\":null}", + "headers": { + "X-Token": "***", + "content-type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/create" + }, + "response": { + "binary": false, + "body": "{\"errCode\":\"BAD_REQUEST\",\"errText\":\"json unmarshal: : json: cannot unmarshal string into Go struct field InvoiceCreateRequest.amount of type int64\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 16:49:28 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "148", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "61895d4c6139a769df80f390860c0a5f" + }, + "status_code": 400, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/merchantapi/create_invoice_failure_without_amount.json b/fixture/vcr_cassettes/merchantapi/create_invoice_failure_without_amount.json new file mode 100644 index 0000000..e7a9f8a --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/create_invoice_failure_without_amount.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"amount\":null,\"ccy\":null,\"merchant_paym_info\":null,\"payment_type\":null,\"qr_id\":null,\"redirect_url\":null,\"save_card\":null,\"validity\":null,\"webhook_url\":null}", + "headers": { + "X-Token": "***", + "content-type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/create" + }, + "response": { + "binary": false, + "body": "{\"errCode\":\"1001\",\"errText\":\"invalid 'amount'\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 16:44:37 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "47", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "92d8434a8f38222e69d1eb73a33d1025" + }, + "status_code": 400, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_amount.json b/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_amount.json new file mode 100644 index 0000000..90df3e6 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_amount.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"amount\":100,\"ccy\":null,\"merchant_paym_info\":null,\"payment_type\":null,\"qr_id\":null,\"redirect_url\":null,\"save_card\":null,\"validity\":null,\"webhook_url\":null}", + "headers": { + "X-Token": "***", + "content-type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/create" + }, + "response": { + "binary": false, + "body": "{\"invoiceId\":\"230605DpurVjPDSmfzEP\",\"pageUrl\":\"https://pay.mbnk.biz/230605DpurVjPDSmfzEP\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 16:38:48 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "90", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "9b87b195820e462bbc783b59958780b5" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_full_params.json b/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_full_params.json new file mode 100644 index 0000000..5e4df03 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_full_params.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"amount\":100,\"ccy\":980,\"merchantPaymInfo\":{\"basketOrder\":[{\"name\":\"TestItem\",\"qty\":2,\"sum\":100}],\"destination\":\"destination_info\",\"reference\":\"reference_id\"},\"paymentType\":\"debit\",\"qrId\":null,\"redirectUrl\":\"https://localhost:4000/monopay/success\",\"saveCardData\":null,\"validity\":3600,\"webhookUrl\":\"https://localhost:4000/api/v1/monopay/webhook\"}", + "headers": { + "X-Token": "***", + "content-type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/create" + }, + "response": { + "binary": false, + "body": "{\"invoiceId\":\"2306058TJ5EYzZxFNuvv\",\"pageUrl\":\"https://pay.mbnk.biz/2306058TJ5EYzZxFNuvv\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 16:57:58 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "90", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "b74df652f9fc0fce4466babf6f745135" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/lib/ex_monobank.ex b/lib/ex_monobank.ex index 24aaf7d..7b6a127 100644 --- a/lib/ex_monobank.ex +++ b/lib/ex_monobank.ex @@ -1,2 +1,5 @@ defmodule ExMonobank do + @default_url "https://api.monobank.ua" + + def default_url, do: @default_url end diff --git a/lib/ex_monobank/invoice.ex b/lib/ex_monobank/invoice.ex new file mode 100644 index 0000000..f2211e4 --- /dev/null +++ b/lib/ex_monobank/invoice.ex @@ -0,0 +1,19 @@ +defmodule ExMonobank.Invoice do + @derive Jason.Encoder + @enforce_keys [:amount] + defstruct [ + :amount, + :ccy, + :merchantPaymInfo, + :redirectUrl, + :webhookUrl, + :validity, + :paymentType, + :qrId, + :saveCardData + ] + + def new(attrs) do + struct(__MODULE__, attrs) + end +end diff --git a/lib/ex_monobank/invoice_info.ex b/lib/ex_monobank/invoice_info.ex new file mode 100644 index 0000000..512ecf3 --- /dev/null +++ b/lib/ex_monobank/invoice_info.ex @@ -0,0 +1,4 @@ +defmodule ExMonobank.InvoiceInfo do + @enforce_keys [:id, :page_url] + defstruct [:id, :page_url] +end diff --git a/lib/ex_monobank/merchant_api.ex b/lib/ex_monobank/merchant_api.ex new file mode 100644 index 0000000..f27976e --- /dev/null +++ b/lib/ex_monobank/merchant_api.ex @@ -0,0 +1,53 @@ +defmodule ExMonobank.MerchantAPI do + @moduledoc """ + Documentation for ExMonobank.MerchantAPI + """ + + use Tesla, only: [:post], docs: false + + plug( + Tesla.Middleware.BaseUrl, + Application.get_env( + :ex_monobank, + :base_url, + ExMonobank.default_url() + ) + ) + + plug(Tesla.Middleware.Headers, [ + {"X-Token", Application.get_env(:ex_monobank, :private_api)[:token]} + ]) + + plug(Tesla.Middleware.JSON) + + if Mix.env() == :dev do + plug(Tesla.Middleware.Logger) + end + + @invoice_create_endpoint "/api/merchant/invoice/create" + + @doc """ + Create invoice for payment + attrs = %{ + amount: 100 + } + """ + def create_invoice(%ExMonobank.Invoice{} = invoice) do + post(@invoice_create_endpoint, invoice) + |> case do + {:ok, %{status: status, body: body}} when status >= 200 and status < 400 -> + {:ok, map_to_invoice_info(body)} + + {:ok, %{body: %{"errText" => reason}}} -> + {:error, reason} + + {:error, reason} -> + reason |> dbg() + {:error, reason} + end + end + + defp map_to_invoice_info(%{"invoiceId" => id, "pageUrl" => url}) do + struct(ExMonobank.InvoiceInfo, %{id: id, page_url: url}) + end +end diff --git a/lib/ex_monobank/personal_api.ex b/lib/ex_monobank/personal_api.ex index d3d3010..b855b10 100644 --- a/lib/ex_monobank/personal_api.ex +++ b/lib/ex_monobank/personal_api.ex @@ -9,8 +9,9 @@ defmodule ExMonobank.PersonalAPI do Tesla.Middleware.BaseUrl, Application.get_env( :ex_monobank, - :private_api - )[:base_url] || "https://api.monobank.ua" + :base_url, + ExMonobank.default_url() + ) ) plug(Tesla.Middleware.Headers, [ @@ -50,7 +51,8 @@ defmodule ExMonobank.PersonalAPI do Get client's account statements for a period from given date to now """ def statement(account, from) do - map_fn = if account == 0, do: &map_to_statement_info/1, else: &map_to_statement_info(&1, account) + map_fn = + if account == 0, do: &map_to_statement_info/1, else: &map_to_statement_info(&1, account) case get("/personal/statement/#{account}/#{DateTime.to_unix(from)}") do {:ok, %{status: status, body: body}} when status >= 200 and status < 400 -> @@ -180,19 +182,22 @@ defmodule ExMonobank.PersonalAPI do ) end - defp map_to_statement_info(%{ - "id" => id, - "amount" => amount, - "balance" => balance, - "cashbackAmount" => cashback_amount, - "commissionRate" => commission_rate, - "currencyCode" => currency_code, - "description" => description, - "hold" => hold, - "mcc" => mcc, - "operationAmount" => operation_amount, - "time" => time - }, account_id) do + defp map_to_statement_info( + %{ + "id" => id, + "amount" => amount, + "balance" => balance, + "cashbackAmount" => cashback_amount, + "commissionRate" => commission_rate, + "currencyCode" => currency_code, + "description" => description, + "hold" => hold, + "mcc" => mcc, + "operationAmount" => operation_amount, + "time" => time + }, + account_id + ) do struct( ExMonobank.StatementInfo, %{ diff --git a/lib/ex_monobank/public_api.ex b/lib/ex_monobank/public_api.ex index 188cab5..dc9ad04 100644 --- a/lib/ex_monobank/public_api.ex +++ b/lib/ex_monobank/public_api.ex @@ -9,13 +9,14 @@ defmodule ExMonobank.PublicAPI do Tesla.Middleware.BaseUrl, Application.get_env( :ex_monobank, - :base_url - ) || "https://api.monobank.ua" + :base_url, + ExMonobank.default_url() + ) ) plug(Tesla.Middleware.JSON) - if Mix.env == :dev do + if Mix.env() == :dev do plug(Tesla.Middleware.Logger) end diff --git a/mix.exs b/mix.exs index 5881a2d..7c60fba 100644 --- a/mix.exs +++ b/mix.exs @@ -8,7 +8,13 @@ defmodule ExMonobank.MixProject do elixir: "~> 1.10", start_permanent: Mix.env() == :prod, package: package(), - deps: deps() + deps: deps(), + preferred_cli_env: [ + vcr: :test, + "vcr.delete": :test, + "vcr.check": :test, + "vcr.show": :test + ] ] end @@ -31,8 +37,10 @@ defmodule ExMonobank.MixProject do defp deps do [ {:tesla, "~> 1.4.0"}, + {:hackney, "~> 1.18"}, {:jason, ">= 1.0.0"}, - {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} + {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, + {:exvcr, "~> 0.11", only: :test} ] end end diff --git a/mix.lock b/mix.lock index 408ba95..b417df2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,19 +1,24 @@ %{ - "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"}, "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, - "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm", "1222419f706e01bfa1095aec9acf6421367dcfab798a6f67c54cf784733cd6b5"}, + "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, + "exvcr": {:hex, :exvcr, "0.14.1", "d9aacec631ed379e366bce5b3c68f6ec5b92b89142f9379425854e80a28c1107", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "5f1e0854fbad0c4bb64ae8cdd289eeace90b8a0a209788c2f840d70f86a2d63c"}, + "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, + "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/test/ex_monobank/merchant_api_test.exs b/test/ex_monobank/merchant_api_test.exs new file mode 100644 index 0000000..fc6ff86 --- /dev/null +++ b/test/ex_monobank/merchant_api_test.exs @@ -0,0 +1,73 @@ +defmodule ExMonobank.MerchantAPITest do + use ExUnit.Case, async: true + use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney + + alias ExMonobank.MerchantAPI + + describe "create_invoice/1" do + test "success with amount" do + use_cassette "MerchantAPI/create_invoice_success_with_amount" do + {:ok, %ExMonobank.InvoiceInfo{} = invoice} = + %{amount: 100} + |> ExMonobank.Invoice.new() + |> MerchantAPI.create_invoice() + + assert invoice.id == "230605DpurVjPDSmfzEP" + assert invoice.page_url == "https://pay.mbnk.biz/230605DpurVjPDSmfzEP" + end + end + + test "failure without amount" do + use_cassette "MerchantAPI/create_invoice_failure_without_amount" do + {:error, "invalid 'amount'"} = + %{} + |> ExMonobank.Invoice.new() + |> MerchantAPI.create_invoice() + end + end + + test "failure with non-integer amount" do + use_cassette "MerchantAPI/create_invoice_failure_with_invalid_amount" do + {:error, + "json unmarshal: : json: cannot unmarshal string into Go struct field InvoiceCreateRequest.amount of type int64"} == + %{amount: "amount"} + |> ExMonobank.Invoice.new() + |> MerchantAPI.create_invoice() + end + end + + test "success with full params" do + use_cassette "MerchantAPI/create_invoice_success_with_full_params" do + {:ok, %ExMonobank.InvoiceInfo{} = invoice} = + %{ + amount: 100, + ccy: 980, + merchantPaymInfo: %{ + reference: "reference_id", + destination: "destination_info", + basketOrder: [ + %{ + name: "TestItem", + qty: 2, + sum: 100 + } + ] + }, + redirectUrl: "https://localhost:4000/monopay/success", + webhookUrl: "https://localhost:4000/api/v1/monopay/webhook", + validity: 3600, + paymentType: "debit", + saveCard: %{ + saveCard: true, + walletId: "ExMonobankWalletId" + } + } + |> ExMonobank.Invoice.new() + |> MerchantAPI.create_invoice() + + assert invoice.id == "2306058TJ5EYzZxFNuvv" + assert invoice.page_url == "https://pay.mbnk.biz/2306058TJ5EYzZxFNuvv" + end + end + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() From 2b6f62b556e43d6399fa7933fe8bb595785cbfd8 Mon Sep 17 00:00:00 2001 From: Volodya Sveredyuk Date: Mon, 5 Jun 2023 20:40:42 +0300 Subject: [PATCH 02/10] ExMonobank.MerchantAPI.get_invoice_status/1 --- ...voice_status_failure_with_inbvalid_id.json | 29 ++++++++++ ..._invoice_status_success_with_valid_id.json | 29 ++++++++++ lib/ex_monobank/invoice_info.ex | 2 +- lib/ex_monobank/merchant_api.ex | 54 +++++++++++++++---- mix.exs | 2 +- mix.lock | 11 ++-- test/ex_monobank/merchant_api_test.exs | 33 ++++++++++-- 7 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 fixture/vcr_cassettes/merchantapi/get_invoice_status_failure_with_inbvalid_id.json create mode 100644 fixture/vcr_cassettes/merchantapi/get_invoice_status_success_with_valid_id.json diff --git a/fixture/vcr_cassettes/merchantapi/get_invoice_status_failure_with_inbvalid_id.json b/fixture/vcr_cassettes/merchantapi/get_invoice_status_failure_with_inbvalid_id.json new file mode 100644 index 0000000..a73781b --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/get_invoice_status_failure_with_inbvalid_id.json @@ -0,0 +1,29 @@ +[ + { + "request": { + "body": "", + "headers": { + "X-Token": "***" + }, + "method": "get", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/status?invoiceId=invalid" + }, + "response": { + "binary": false, + "body": "{\"errCode\":\"1004\",\"errText\":\"invoice not found\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 17:38:08 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "48", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "c4381cba68a97a67caf7b73542a1e1d9" + }, + "status_code": 404, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/merchantapi/get_invoice_status_success_with_valid_id.json b/fixture/vcr_cassettes/merchantapi/get_invoice_status_success_with_valid_id.json new file mode 100644 index 0000000..fdbf323 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/get_invoice_status_success_with_valid_id.json @@ -0,0 +1,29 @@ +[ + { + "request": { + "body": "", + "headers": { + "X-Token": "***" + }, + "method": "get", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/status?invoiceId=230605DpurVjPDSmfzEP" + }, + "response": { + "binary": false, + "body": "{\"invoiceId\":\"230605DpurVjPDSmfzEP\",\"status\":\"created\",\"amount\":100,\"ccy\":980,\"createdDate\":\"2023-06-05T16:38:48Z\",\"modifiedDate\":\"2023-06-05T16:38:48Z\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 17:15:54 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "153", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "80c43a91813277182f0b8b807d98c0c5" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/lib/ex_monobank/invoice_info.ex b/lib/ex_monobank/invoice_info.ex index 512ecf3..63daf98 100644 --- a/lib/ex_monobank/invoice_info.ex +++ b/lib/ex_monobank/invoice_info.ex @@ -1,4 +1,4 @@ defmodule ExMonobank.InvoiceInfo do @enforce_keys [:id, :page_url] - defstruct [:id, :page_url] + defstruct [:id, :page_url, :status, :amount, :ccy, :created_at, :modified_at] end diff --git a/lib/ex_monobank/merchant_api.ex b/lib/ex_monobank/merchant_api.ex index f27976e..a727369 100644 --- a/lib/ex_monobank/merchant_api.ex +++ b/lib/ex_monobank/merchant_api.ex @@ -1,9 +1,9 @@ defmodule ExMonobank.MerchantAPI do @moduledoc """ - Documentation for ExMonobank.MerchantAPI + Documentation for ExMonobank.MerchantAPI https://api.monobank.ua/docs/acquiring.html """ - use Tesla, only: [:post], docs: false + use Tesla, only: [:get, :post], docs: false plug( Tesla.Middleware.BaseUrl, @@ -24,17 +24,29 @@ defmodule ExMonobank.MerchantAPI do plug(Tesla.Middleware.Logger) end - @invoice_create_endpoint "/api/merchant/invoice/create" - @doc """ - Create invoice for payment - attrs = %{ - amount: 100 - } + Create invoice + https://api.monobank.ua/docs/acquiring.html#/paths/~1api~1merchant~1invoice~1create/post """ def create_invoice(%ExMonobank.Invoice{} = invoice) do - post(@invoice_create_endpoint, invoice) - |> case do + case post("/api/merchant/invoice/create", invoice) do + {:ok, %{status: status, body: body}} when status >= 200 and status < 400 -> + {:ok, map_to_invoice_info(body)} + + {:ok, %{body: %{"errText" => reason}}} -> + {:error, reason} + + {:error, reason} -> + {:error, reason} + end + end + + @doc """ + Get invoice status + https://api.monobank.ua/docs/acquiring.html#/paths/~1api~1merchant~1invoice~1status?invoiceId=%7BinvoiceId%7D/get + """ + def get_invoice_status(id) do + case get("/api/merchant/invoice/status", query: %{invoiceId: id}) do {:ok, %{status: status, body: body}} when status >= 200 and status < 400 -> {:ok, map_to_invoice_info(body)} @@ -42,7 +54,6 @@ defmodule ExMonobank.MerchantAPI do {:error, reason} {:error, reason} -> - reason |> dbg() {:error, reason} end end @@ -50,4 +61,25 @@ defmodule ExMonobank.MerchantAPI do defp map_to_invoice_info(%{"invoiceId" => id, "pageUrl" => url}) do struct(ExMonobank.InvoiceInfo, %{id: id, page_url: url}) end + + defp map_to_invoice_info(%{ + "invoiceId" => id, + "status" => status, + "amount" => amount, + "ccy" => ccy, + "createdDate" => created_date, + "modifiedDate" => modified_date + }) do + {:ok, created_at, 0} = DateTime.from_iso8601(created_date) + {:ok, modified_at, 0} = DateTime.from_iso8601(modified_date) + + struct(ExMonobank.InvoiceInfo, %{ + id: id, + status: status, + amount: amount, + ccy: ccy, + created_at: created_at, + modified_at: modified_at + }) + end end diff --git a/mix.exs b/mix.exs index 7c60fba..10dac0d 100644 --- a/mix.exs +++ b/mix.exs @@ -39,7 +39,7 @@ defmodule ExMonobank.MixProject do {:tesla, "~> 1.4.0"}, {:hackney, "~> 1.18"}, {:jason, ">= 1.0.0"}, - {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, + {:ex_doc, "~> 0.29.4", only: :dev, runtime: false}, {:exvcr, "~> 0.11", only: :test} ] end diff --git a/mix.lock b/mix.lock index b417df2..b363632 100644 --- a/mix.lock +++ b/mix.lock @@ -1,8 +1,8 @@ %{ "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, - "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"}, + "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm", "1222419f706e01bfa1095aec9acf6421367dcfab798a6f67c54cf784733cd6b5"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, "exvcr": {:hex, :exvcr, "0.14.1", "d9aacec631ed379e366bce5b3c68f6ec5b92b89142f9379425854e80a28c1107", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "5f1e0854fbad0c4bb64ae8cdd289eeace90b8a0a209788c2f840d70f86a2d63c"}, @@ -10,13 +10,14 @@ "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"}, - "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"}, diff --git a/test/ex_monobank/merchant_api_test.exs b/test/ex_monobank/merchant_api_test.exs index fc6ff86..b1af6f7 100644 --- a/test/ex_monobank/merchant_api_test.exs +++ b/test/ex_monobank/merchant_api_test.exs @@ -19,20 +19,24 @@ defmodule ExMonobank.MerchantAPITest do test "failure without amount" do use_cassette "MerchantAPI/create_invoice_failure_without_amount" do - {:error, "invalid 'amount'"} = + {:error, error} = %{} |> ExMonobank.Invoice.new() |> MerchantAPI.create_invoice() + + assert error == "invalid 'amount'" end end test "failure with non-integer amount" do use_cassette "MerchantAPI/create_invoice_failure_with_invalid_amount" do - {:error, - "json unmarshal: : json: cannot unmarshal string into Go struct field InvoiceCreateRequest.amount of type int64"} == + {:error, error} = %{amount: "amount"} |> ExMonobank.Invoice.new() |> MerchantAPI.create_invoice() + + assert error == + "json unmarshal: : json: cannot unmarshal string into Go struct field InvoiceCreateRequest.amount of type int64" end end @@ -70,4 +74,27 @@ defmodule ExMonobank.MerchantAPITest do end end end + + describe "get_invoice_status/1" do + test "success with valid invoiceId" do + use_cassette "MerchantAPI/get_invoice_status_success_with_valid_id" do + {:ok, %ExMonobank.InvoiceInfo{} = invoice} = + MerchantAPI.get_invoice_status("230605DpurVjPDSmfzEP") + + assert invoice.id == "230605DpurVjPDSmfzEP" + assert invoice.status == "created" + assert invoice.amount == 100 + assert invoice.ccy == 980 + assert invoice.created_at == ~U[2023-06-05 16:38:48Z] + assert invoice.modified_at == ~U[2023-06-05 16:38:48Z] + end + end + + test "failure with invalid invoiceId" do + use_cassette "MerchantAPI/get_invoice_status_failure_with_inbvalid_id" do + {:error, error} = MerchantAPI.get_invoice_status("invalid") + assert error == "invoice not found" + end + end + end end From 35db9c693b7849b054aae514a95b7f71f2707d5b Mon Sep 17 00:00:00 2001 From: Volodya Sveredyuk Date: Mon, 5 Jun 2023 20:57:01 +0300 Subject: [PATCH 03/10] ExMonobank.MerchantAPI.cancel_invoice/1 --- ...ncel_invoice_failure_with_inbvalid_id.json | 30 +++++++++++++++++++ lib/ex_monobank/merchant_api.ex | 17 +++++++++++ test/ex_monobank/merchant_api_test.exs | 22 ++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 fixture/vcr_cassettes/merchantapi/cancel_invoice_failure_with_inbvalid_id.json diff --git a/fixture/vcr_cassettes/merchantapi/cancel_invoice_failure_with_inbvalid_id.json b/fixture/vcr_cassettes/merchantapi/cancel_invoice_failure_with_inbvalid_id.json new file mode 100644 index 0000000..384aa60 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/cancel_invoice_failure_with_inbvalid_id.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"invoiceId\":\"invalid\"}", + "headers": { + "X-Token": "***", + "content-type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/cancel" + }, + "response": { + "binary": false, + "body": "{\"errCode\":\"1004\",\"errText\":\"invoice not found\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 17:56:20 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "48", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "43de06cedd872e972ebfbbeb5403301d" + }, + "status_code": 404, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/lib/ex_monobank/merchant_api.ex b/lib/ex_monobank/merchant_api.ex index a727369..77dc04c 100644 --- a/lib/ex_monobank/merchant_api.ex +++ b/lib/ex_monobank/merchant_api.ex @@ -58,6 +58,23 @@ defmodule ExMonobank.MerchantAPI do end end + @doc """ + Cancel invoice + https://api.monobank.ua/docs/acquiring.html#/paths/~1api~1merchant~1invoice~1cancel/post + """ + def cancel_invoice(id, extra \\ %{}) do + case post("/api/merchant/invoice/cancel", Map.merge(%{invoiceId: id}, extra)) do + {:ok, %{status: status, body: body}} when status >= 200 and status < 400 -> + {:ok, map_to_invoice_info(body)} + + {:ok, %{body: %{"errText" => reason}}} -> + {:error, reason} + + {:error, reason} -> + {:error, reason} + end + end + defp map_to_invoice_info(%{"invoiceId" => id, "pageUrl" => url}) do struct(ExMonobank.InvoiceInfo, %{id: id, page_url: url}) end diff --git a/test/ex_monobank/merchant_api_test.exs b/test/ex_monobank/merchant_api_test.exs index b1af6f7..d91d889 100644 --- a/test/ex_monobank/merchant_api_test.exs +++ b/test/ex_monobank/merchant_api_test.exs @@ -97,4 +97,26 @@ defmodule ExMonobank.MerchantAPITest do end end end + + describe "cancel_invoice/1" do + # TODO: How to cancel test order? + # test "success with valid invoiceId" do + # use_cassette "MerchantAPI/cancel_invoice_success_with_valid_id" do + # {:ok, %ExMonobank.InvoiceInfo{} = invoice} = + # MerchantAPI.cancel_invoice("230605DpurVjPDSmfzEP") + + # assert invoice.status == "processing" + # assert invoice.created_at == ~U[2023-06-05 16:38:48Z] + # assert invoice.modified_at == ~U[2023-06-05 16:38:48Z] + # end + # end + + @tag :focus + test "failure with invalid invoiceId" do + use_cassette "MerchantAPI/cancel_invoice_failure_with_inbvalid_id" do + {:error, error} = MerchantAPI.cancel_invoice("invalid") + assert error == "invoice not found" + end + end + end end From 1b4f8976b6d70e6d32a18c5a1951f2ad1fd0cd68 Mon Sep 17 00:00:00 2001 From: Volodya Sveredyuk Date: Mon, 5 Jun 2023 21:02:02 +0300 Subject: [PATCH 04/10] ExMonobank.MerchantAPI.cancel_invoice/1 +success test --- .../cancel_invoice_success_with_valid_id.json | 30 +++++++++++++++++++ lib/ex_monobank/merchant_api.ex | 15 ++++++++++ test/ex_monobank/merchant_api_test.exs | 19 ++++++------ 3 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 fixture/vcr_cassettes/merchantapi/cancel_invoice_success_with_valid_id.json diff --git a/fixture/vcr_cassettes/merchantapi/cancel_invoice_success_with_valid_id.json b/fixture/vcr_cassettes/merchantapi/cancel_invoice_success_with_valid_id.json new file mode 100644 index 0000000..0de9603 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/cancel_invoice_success_with_valid_id.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"invoiceId\":\"230605DpurVjPDSmfzEP\"}", + "headers": { + "X-Token": "***", + "content-type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/cancel" + }, + "response": { + "binary": false, + "body": "{\"status\":\"success\",\"createdDate\":\"2023-06-05T17:59:28Z\",\"modifiedDate\":\"2023-06-05T17:59:28Z\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 17:59:28 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "95", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "39c7df33208ce1ba96f347eb788ab34e" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/lib/ex_monobank/merchant_api.ex b/lib/ex_monobank/merchant_api.ex index 77dc04c..d426ca3 100644 --- a/lib/ex_monobank/merchant_api.ex +++ b/lib/ex_monobank/merchant_api.ex @@ -99,4 +99,19 @@ defmodule ExMonobank.MerchantAPI do modified_at: modified_at }) end + + defp map_to_invoice_info(%{ + "status" => status, + "createdDate" => created_date, + "modifiedDate" => modified_date + }) do + {:ok, created_at, 0} = DateTime.from_iso8601(created_date) + {:ok, modified_at, 0} = DateTime.from_iso8601(modified_date) + + struct(ExMonobank.InvoiceInfo, %{ + status: status, + created_at: created_at, + modified_at: modified_at + }) + end end diff --git a/test/ex_monobank/merchant_api_test.exs b/test/ex_monobank/merchant_api_test.exs index d91d889..8aada4c 100644 --- a/test/ex_monobank/merchant_api_test.exs +++ b/test/ex_monobank/merchant_api_test.exs @@ -100,16 +100,17 @@ defmodule ExMonobank.MerchantAPITest do describe "cancel_invoice/1" do # TODO: How to cancel test order? - # test "success with valid invoiceId" do - # use_cassette "MerchantAPI/cancel_invoice_success_with_valid_id" do - # {:ok, %ExMonobank.InvoiceInfo{} = invoice} = - # MerchantAPI.cancel_invoice("230605DpurVjPDSmfzEP") + @tag :focus + test "success with valid invoiceId" do + use_cassette "MerchantAPI/cancel_invoice_success_with_valid_id" do + {:ok, %ExMonobank.InvoiceInfo{} = invoice} = + MerchantAPI.cancel_invoice("230605DpurVjPDSmfzEP") - # assert invoice.status == "processing" - # assert invoice.created_at == ~U[2023-06-05 16:38:48Z] - # assert invoice.modified_at == ~U[2023-06-05 16:38:48Z] - # end - # end + assert invoice.status == "success" + assert invoice.created_at == ~U[2023-06-05 17:59:28Z] + assert invoice.modified_at == ~U[2023-06-05 17:59:28Z] + end + end @tag :focus test "failure with invalid invoiceId" do From 2d5b396f1a52fc14530059cf47152da6d6107231 Mon Sep 17 00:00:00 2001 From: Volodya Sveredyuk Date: Mon, 5 Jun 2023 21:16:33 +0300 Subject: [PATCH 05/10] ExMonobank.MerchantAPI.remove_invoice/1 --- .../create_invoice_success_with_amount.json | 8 ++--- ...emove_invoice_failure_with_invalid_id.json | 30 +++++++++++++++++ ..._invoice_failure_with_paid_invoice_id.json | 30 +++++++++++++++++ ...invoice_success_with_valid_invoice_id.json | 30 +++++++++++++++++ lib/ex_monobank/merchant_api.ex | 21 ++++++++++++ test/ex_monobank/merchant_api_test.exs | 32 ++++++++++++++++--- 6 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 fixture/vcr_cassettes/merchantapi/remove_invoice_failure_with_invalid_id.json create mode 100644 fixture/vcr_cassettes/merchantapi/remove_invoice_failure_with_paid_invoice_id.json create mode 100644 fixture/vcr_cassettes/merchantapi/remove_invoice_success_with_valid_invoice_id.json diff --git a/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_amount.json b/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_amount.json index 90df3e6..5b6b92c 100644 --- a/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_amount.json +++ b/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_amount.json @@ -1,7 +1,7 @@ [ { "request": { - "body": "{\"amount\":100,\"ccy\":null,\"merchant_paym_info\":null,\"payment_type\":null,\"qr_id\":null,\"redirect_url\":null,\"save_card\":null,\"validity\":null,\"webhook_url\":null}", + "body": "{\"amount\":100,\"ccy\":null,\"merchantPaymInfo\":null,\"paymentType\":null,\"qrId\":null,\"redirectUrl\":null,\"saveCardData\":null,\"validity\":null,\"webhookUrl\":null}", "headers": { "X-Token": "***", "content-type": "application/json" @@ -13,15 +13,15 @@ }, "response": { "binary": false, - "body": "{\"invoiceId\":\"230605DpurVjPDSmfzEP\",\"pageUrl\":\"https://pay.mbnk.biz/230605DpurVjPDSmfzEP\"}", + "body": "{\"invoiceId\":\"230605A3RNUJNt1oqoiY\",\"pageUrl\":\"https://pay.mbnk.biz/230605A3RNUJNt1oqoiY\"}", "headers": { - "Date": "Mon, 05 Jun 2023 16:38:48 GMT", + "Date": "Mon, 05 Jun 2023 18:12:48 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "90", "Connection": "keep-alive", "Access-Control-Allow-Origin": "*", "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", - "Trace-Id": "9b87b195820e462bbc783b59958780b5" + "Trace-Id": "fedf58314b5641523bdc9ac34be10ee3" }, "status_code": 200, "type": "ok" diff --git a/fixture/vcr_cassettes/merchantapi/remove_invoice_failure_with_invalid_id.json b/fixture/vcr_cassettes/merchantapi/remove_invoice_failure_with_invalid_id.json new file mode 100644 index 0000000..37ebcc2 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/remove_invoice_failure_with_invalid_id.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"invoiceId\":\"invalid\"}", + "headers": { + "X-Token": "***", + "content-type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/remove" + }, + "response": { + "binary": false, + "body": "{\"errCode\":\"1004\",\"errText\":\"invoice not found\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 18:14:21 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "48", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "3f59e316eb13eefedd7087e04868e00e" + }, + "status_code": 404, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/merchantapi/remove_invoice_failure_with_paid_invoice_id.json b/fixture/vcr_cassettes/merchantapi/remove_invoice_failure_with_paid_invoice_id.json new file mode 100644 index 0000000..878c966 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/remove_invoice_failure_with_paid_invoice_id.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"invoiceId\":\"230605DpurVjPDSmfzEP\"}", + "headers": { + "X-Token": "***", + "content-type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/remove" + }, + "response": { + "binary": false, + "body": "{\"errCode\":\"INVOICE_ALREADY_USED\",\"errText\":\"invoice not active\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 18:14:20 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "65", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "c11c87927cbf4f08d4f5074fcbc26651" + }, + "status_code": 400, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/merchantapi/remove_invoice_success_with_valid_invoice_id.json b/fixture/vcr_cassettes/merchantapi/remove_invoice_success_with_valid_invoice_id.json new file mode 100644 index 0000000..da72018 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/remove_invoice_success_with_valid_invoice_id.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"invoiceId\":\"230605A3RNUJNt1oqoiY\"}", + "headers": { + "X-Token": "***", + "content-type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/remove" + }, + "response": { + "binary": false, + "body": "{\"status\":\"success\"}", + "headers": { + "Date": "Mon, 05 Jun 2023 18:14:20 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "20", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "83048e17bb9a58a75bac25c586987261" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/lib/ex_monobank/merchant_api.ex b/lib/ex_monobank/merchant_api.ex index d426ca3..eca1ce1 100644 --- a/lib/ex_monobank/merchant_api.ex +++ b/lib/ex_monobank/merchant_api.ex @@ -75,6 +75,23 @@ defmodule ExMonobank.MerchantAPI do end end + @doc """ + Remove invoice + https://api.monobank.ua/docs/acquiring.html#/paths/~1api~1merchant~1invoice~1remove/post + """ + def remove_invoice(id) do + case post("/api/merchant/invoice/remove", %{invoiceId: id}) do + {:ok, %{status: status, body: body}} when status >= 200 and status < 400 -> + {:ok, map_to_invoice_info(body)} + + {:ok, %{body: %{"errText" => reason}}} -> + {:error, reason} + + {:error, reason} -> + {:error, reason} + end + end + defp map_to_invoice_info(%{"invoiceId" => id, "pageUrl" => url}) do struct(ExMonobank.InvoiceInfo, %{id: id, page_url: url}) end @@ -114,4 +131,8 @@ defmodule ExMonobank.MerchantAPI do modified_at: modified_at }) end + + defp map_to_invoice_info(%{"status" => status}) do + struct(ExMonobank.InvoiceInfo, %{status: status}) + end end diff --git a/test/ex_monobank/merchant_api_test.exs b/test/ex_monobank/merchant_api_test.exs index 8aada4c..954c096 100644 --- a/test/ex_monobank/merchant_api_test.exs +++ b/test/ex_monobank/merchant_api_test.exs @@ -12,8 +12,8 @@ defmodule ExMonobank.MerchantAPITest do |> ExMonobank.Invoice.new() |> MerchantAPI.create_invoice() - assert invoice.id == "230605DpurVjPDSmfzEP" - assert invoice.page_url == "https://pay.mbnk.biz/230605DpurVjPDSmfzEP" + assert invoice.id == "230605A3RNUJNt1oqoiY" + assert invoice.page_url == "https://pay.mbnk.biz/230605A3RNUJNt1oqoiY" end end @@ -99,8 +99,6 @@ defmodule ExMonobank.MerchantAPITest do end describe "cancel_invoice/1" do - # TODO: How to cancel test order? - @tag :focus test "success with valid invoiceId" do use_cassette "MerchantAPI/cancel_invoice_success_with_valid_id" do {:ok, %ExMonobank.InvoiceInfo{} = invoice} = @@ -112,7 +110,6 @@ defmodule ExMonobank.MerchantAPITest do end end - @tag :focus test "failure with invalid invoiceId" do use_cassette "MerchantAPI/cancel_invoice_failure_with_inbvalid_id" do {:error, error} = MerchantAPI.cancel_invoice("invalid") @@ -120,4 +117,29 @@ defmodule ExMonobank.MerchantAPITest do end end end + + describe "remove_invoice/1" do + test "success with valid invoiceId" do + use_cassette "MerchantAPI/remove_invoice_success_with_valid_invoice_id" do + {:ok, invoice} = MerchantAPI.remove_invoice("230605A3RNUJNt1oqoiY") + + assert invoice.status == "success" + end + end + + test "failure with valid invoiceId" do + use_cassette "MerchantAPI/remove_invoice_failure_with_paid_invoice_id" do + {:error, error} = MerchantAPI.remove_invoice("230605DpurVjPDSmfzEP") + + assert error == "invoice not active" + end + end + + test "failure with invalid invoiceId" do + use_cassette "MerchantAPI/remove_invoice_failure_with_invalid_id" do + {:error, error} = MerchantAPI.remove_invoice("invalid") + assert error == "invoice not found" + end + end + end end From 1c9fd37fe4e1018d21552821c4e5bd1f3cf351f7 Mon Sep 17 00:00:00 2001 From: Volodya Sveredyuk Date: Mon, 5 Jun 2023 21:21:12 +0300 Subject: [PATCH 06/10] Move tesla config into config.exs --- config/config.exs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/config.exs b/config/config.exs index 871a3d1..796e0c7 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,3 +1,5 @@ import Config +config :tesla, adapter: Tesla.Adapter.Hackney + import_config "#{Mix.env()}.exs" From 7517ac3a4f8d5a2e4d5f79fa5a3141181f3a9f7c Mon Sep 17 00:00:00 2001 From: Volodya Sveredyuk Date: Wed, 7 Jun 2023 14:30:43 +0300 Subject: [PATCH 07/10] Fix cassets name typo --- config/test.exs | 2 -- ...id_id.json => cancel_invoice_failure_with_invalid_id.json} | 0 ...d.json => get_invoice_status_failure_with_invalid_id.json} | 0 test/ex_monobank/merchant_api_test.exs | 4 ++-- 4 files changed, 2 insertions(+), 4 deletions(-) rename fixture/vcr_cassettes/merchantapi/{cancel_invoice_failure_with_inbvalid_id.json => cancel_invoice_failure_with_invalid_id.json} (100%) rename fixture/vcr_cassettes/merchantapi/{get_invoice_status_failure_with_inbvalid_id.json => get_invoice_status_failure_with_invalid_id.json} (100%) diff --git a/config/test.exs b/config/test.exs index 277d55f..2b1d2c0 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,7 +1,5 @@ import Config -config :tesla, adapter: Tesla.Adapter.Hackney - config :exvcr, vcr_cassette_library_dir: "fixture/vcr_cassettes", filter_sensitive_data: [ diff --git a/fixture/vcr_cassettes/merchantapi/cancel_invoice_failure_with_inbvalid_id.json b/fixture/vcr_cassettes/merchantapi/cancel_invoice_failure_with_invalid_id.json similarity index 100% rename from fixture/vcr_cassettes/merchantapi/cancel_invoice_failure_with_inbvalid_id.json rename to fixture/vcr_cassettes/merchantapi/cancel_invoice_failure_with_invalid_id.json diff --git a/fixture/vcr_cassettes/merchantapi/get_invoice_status_failure_with_inbvalid_id.json b/fixture/vcr_cassettes/merchantapi/get_invoice_status_failure_with_invalid_id.json similarity index 100% rename from fixture/vcr_cassettes/merchantapi/get_invoice_status_failure_with_inbvalid_id.json rename to fixture/vcr_cassettes/merchantapi/get_invoice_status_failure_with_invalid_id.json diff --git a/test/ex_monobank/merchant_api_test.exs b/test/ex_monobank/merchant_api_test.exs index 954c096..f2964e5 100644 --- a/test/ex_monobank/merchant_api_test.exs +++ b/test/ex_monobank/merchant_api_test.exs @@ -91,7 +91,7 @@ defmodule ExMonobank.MerchantAPITest do end test "failure with invalid invoiceId" do - use_cassette "MerchantAPI/get_invoice_status_failure_with_inbvalid_id" do + use_cassette "MerchantAPI/get_invoice_status_failure_with_invalid_id" do {:error, error} = MerchantAPI.get_invoice_status("invalid") assert error == "invoice not found" end @@ -111,7 +111,7 @@ defmodule ExMonobank.MerchantAPITest do end test "failure with invalid invoiceId" do - use_cassette "MerchantAPI/cancel_invoice_failure_with_inbvalid_id" do + use_cassette "MerchantAPI/cancel_invoice_failure_with_invalid_id" do {:error, error} = MerchantAPI.cancel_invoice("invalid") assert error == "invoice not found" end From 361c7b0d48d943089cf3f7fe96a3b5e19e4f280b Mon Sep 17 00:00:00 2001 From: Volodya Sveredyuk Date: Sat, 10 Jun 2023 14:25:47 +0300 Subject: [PATCH 08/10] Fix Invoice.webHookUrl --- lib/ex_monobank/invoice.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ex_monobank/invoice.ex b/lib/ex_monobank/invoice.ex index f2211e4..7be2590 100644 --- a/lib/ex_monobank/invoice.ex +++ b/lib/ex_monobank/invoice.ex @@ -6,7 +6,7 @@ defmodule ExMonobank.Invoice do :ccy, :merchantPaymInfo, :redirectUrl, - :webhookUrl, + :webHookUrl, :validity, :paymentType, :qrId, From c242d017452d20563825f2225e8721bf6c515007 Mon Sep 17 00:00:00 2001 From: Volodya Sveredyuk Date: Sat, 10 Jun 2023 14:56:44 +0300 Subject: [PATCH 09/10] Update config to use merchant_api namespace --- config/test.exs | 2 +- lib/ex_monobank/merchant_api.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/test.exs b/config/test.exs index 2b1d2c0..cc0c257 100644 --- a/config/test.exs +++ b/config/test.exs @@ -11,6 +11,6 @@ config :exvcr, config :ex_monobank, base_url: "https://api.monobank.ua", - private_api: %{ + merchant_api: %{ token: System.get_env("MONOBANK_API_TOKEN") } diff --git a/lib/ex_monobank/merchant_api.ex b/lib/ex_monobank/merchant_api.ex index eca1ce1..36f949a 100644 --- a/lib/ex_monobank/merchant_api.ex +++ b/lib/ex_monobank/merchant_api.ex @@ -15,7 +15,7 @@ defmodule ExMonobank.MerchantAPI do ) plug(Tesla.Middleware.Headers, [ - {"X-Token", Application.get_env(:ex_monobank, :private_api)[:token]} + {"X-Token", Application.get_env(:ex_monobank, :merchant_api)[:token]} ]) plug(Tesla.Middleware.JSON) From 4160a92604f826583be3432dd30ccb640bd0c476 Mon Sep 17 00:00:00 2001 From: Volodya Sveredyuk Date: Sat, 10 Jun 2023 19:13:06 +0300 Subject: [PATCH 10/10] ExMonobank.MerchantAPI.get_payment_info/1 --- ..._payment_info_failure_with_invalid_id.json | 29 +++++++++++ ...et_payment_info_success_with_valid_id.json | 29 +++++++++++ lib/ex_monobank/merchant_api.ex | 51 +++++++++++++++++++ lib/ex_monobank/payment_info.ex | 30 +++++++++++ test/ex_monobank/merchant_api_test.exs | 31 +++++++++++ 5 files changed, 170 insertions(+) create mode 100644 fixture/vcr_cassettes/merchantapi/get_payment_info_failure_with_invalid_id.json create mode 100644 fixture/vcr_cassettes/merchantapi/get_payment_info_success_with_valid_id.json create mode 100644 lib/ex_monobank/payment_info.ex diff --git a/fixture/vcr_cassettes/merchantapi/get_payment_info_failure_with_invalid_id.json b/fixture/vcr_cassettes/merchantapi/get_payment_info_failure_with_invalid_id.json new file mode 100644 index 0000000..715e7e6 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/get_payment_info_failure_with_invalid_id.json @@ -0,0 +1,29 @@ +[ + { + "request": { + "body": "", + "headers": { + "X-Token": "***" + }, + "method": "get", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/payment-info?invoiceId=invalid" + }, + "response": { + "binary": false, + "body": "{\"errCode\":\"1004\",\"errText\":\"invoice not found\"}", + "headers": { + "Date": "Sat, 10 Jun 2023 16:12:30 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "48", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "a233d0efe47b76bc05b753322ed5a59b" + }, + "status_code": 404, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/merchantapi/get_payment_info_success_with_valid_id.json b/fixture/vcr_cassettes/merchantapi/get_payment_info_success_with_valid_id.json new file mode 100644 index 0000000..4d9842b --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/get_payment_info_success_with_valid_id.json @@ -0,0 +1,29 @@ +[ + { + "request": { + "body": "", + "headers": { + "X-Token": "***" + }, + "method": "get", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/payment-info?invoiceId=230605DpurVjPDSmfzEP" + }, + "response": { + "binary": false, + "body": "{\"maskedPan\":\"53754114******22\",\"approvalCode\":\"563594\",\"rrn\":\"073687715731\",\"createdDate\":\"2023-06-05T17:59:05Z\",\"terminal\":\"MI000000\",\"paymentScheme\":\"full\",\"paymentMethod\":\"pan\",\"fee\":1,\"domesticCard\":true,\"country\":\"804\",\"amount\":100,\"ccy\":980,\"finalAmount\":0,\"cancelList\":[{\"status\":\"success\",\"amount\":100,\"ccy\":980,\"createdDate\":\"2023-06-05T17:59:28Z\",\"modifiedDate\":\"2023-06-05T17:59:28Z\",\"approvalCode\":\"563594\",\"rrn\":\"073687715731\"}]}", + "headers": { + "Date": "Sat, 10 Jun 2023 16:05:50 GMT", + "Content-Type": "application/json; charset=utf-8", + "Content-Length": "443", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload", + "Trace-Id": "687d4279d364320310215bbf574613d0" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/lib/ex_monobank/merchant_api.ex b/lib/ex_monobank/merchant_api.ex index 36f949a..986e2cf 100644 --- a/lib/ex_monobank/merchant_api.ex +++ b/lib/ex_monobank/merchant_api.ex @@ -58,6 +58,23 @@ defmodule ExMonobank.MerchantAPI do end end + @doc """ + Get payment extra info + https://api.monobank.ua/docs/acquiring.html#/paths/~1api~1merchant~1invoice~1payment-info?invoiceId=%7BinvoiceId%7D/get + """ + def get_payment_info(id) do + case get("/api/merchant/invoice/payment-info", query: %{invoiceId: id}) do + {:ok, %{status: status, body: body}} when status >= 200 and status < 400 -> + {:ok, map_to_payment_info(body)} + + {:ok, %{body: %{"errText" => reason}}} -> + {:error, reason} + + {:error, reason} -> + {:error, reason} + end + end + @doc """ Cancel invoice https://api.monobank.ua/docs/acquiring.html#/paths/~1api~1merchant~1invoice~1cancel/post @@ -135,4 +152,38 @@ defmodule ExMonobank.MerchantAPI do defp map_to_invoice_info(%{"status" => status}) do struct(ExMonobank.InvoiceInfo, %{status: status}) end + + defp map_to_payment_info(%{ + "amount" => amount, + "approvalCode" => approval_code, + "ccy" => ccy, + "country" => country, + "createdDate" => created_date, + "domesticCard" => domestic_card, + "fee" => fee, + "finalAmount" => final_amount, + "maskedPan" => masked_pan, + "paymentMethod" => payment_method, + "paymentScheme" => payment_scheme, + "rrn" => rrn, + "terminal" => terminal + }) do + {:ok, created_at, 0} = DateTime.from_iso8601(created_date) + + struct(ExMonobank.PaymentInfo, %{ + masked_pan: masked_pan, + amount: amount, + approval_code: approval_code, + ccy: ccy, + country: country, + created_at: created_at, + domestic_card: domestic_card, + fee: fee, + final_amount: final_amount, + payment_method: payment_method, + payment_scheme: payment_scheme, + rrn: rrn, + terminal: terminal + }) + end end diff --git a/lib/ex_monobank/payment_info.ex b/lib/ex_monobank/payment_info.ex new file mode 100644 index 0000000..bd2f878 --- /dev/null +++ b/lib/ex_monobank/payment_info.ex @@ -0,0 +1,30 @@ +defmodule ExMonobank.PaymentInfo do + @enforce_keys [ + :masked_pan, + :approval_code, + :rrn, + :amount, + :ccy, + :final_amount, + :terminal, + :payment_scheme, + :payment_method, + :domestic_card, + :country + ] + defstruct [ + :masked_pan, + :approval_code, + :rrn, + :amount, + :ccy, + :final_amount, + :created_at, + :terminal, + :payment_scheme, + :payment_method, + :fee, + :domestic_card, + :country + ] +end diff --git a/test/ex_monobank/merchant_api_test.exs b/test/ex_monobank/merchant_api_test.exs index f2964e5..96487be 100644 --- a/test/ex_monobank/merchant_api_test.exs +++ b/test/ex_monobank/merchant_api_test.exs @@ -98,6 +98,37 @@ defmodule ExMonobank.MerchantAPITest do end end + describe "get_payment_info/1" do + test "success with valid invoiceId" do + use_cassette "MerchantAPI/get_payment_info_success_with_valid_id" do + {:ok, %ExMonobank.PaymentInfo{} = payment} = + MerchantAPI.get_payment_info("230605DpurVjPDSmfzEP") + + assert payment.masked_pan == "53754114******22" + assert payment.approval_code == "563594" + assert payment.rrn == "073687715731" + assert payment.amount == 100 + assert payment.ccy == 980 + assert payment.final_amount == 0 + assert payment.created_at == ~U[2023-06-05 17:59:05Z] + assert payment.terminal == "MI000000" + assert payment.payment_scheme == "full" + assert payment.payment_method == "pan" + assert payment.fee == 1 + assert payment.domestic_card == true + assert payment.country == "804" + end + end + + @tag :focus + test "failure with invalid invoiceId" do + use_cassette "MerchantAPI/get_payment_info_failure_with_invalid_id" do + {:error, error} = MerchantAPI.get_payment_info("invalid") + assert error == "invoice not found" + end + end + end + describe "cancel_invoice/1" do test "success with valid invoiceId" do use_cassette "MerchantAPI/cancel_invoice_success_with_valid_id" do