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..796e0c7 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,7 +1,5 @@ import Config -# Sample configuration: -# -# config :ex_monobank, PrivateAPI, -# token: "your-personal-token", -# base_url: "https://api.monobank.ua" +config :tesla, adapter: Tesla.Adapter.Hackney + +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..cc0c257 --- /dev/null +++ b/config/test.exs @@ -0,0 +1,16 @@ +import Config + +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", + merchant_api: %{ + token: System.get_env("MONOBANK_API_TOKEN") + } diff --git a/fixture/vcr_cassettes/merchantapi/cancel_invoice_failure_with_invalid_id.json b/fixture/vcr_cassettes/merchantapi/cancel_invoice_failure_with_invalid_id.json new file mode 100644 index 0000000..384aa60 --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/cancel_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/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/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/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..5b6b92c --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/create_invoice_success_with_amount.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "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" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://api.monobank.ua/api/merchant/invoice/create" + }, + "response": { + "binary": false, + "body": "{\"invoiceId\":\"230605A3RNUJNt1oqoiY\",\"pageUrl\":\"https://pay.mbnk.biz/230605A3RNUJNt1oqoiY\"}", + "headers": { + "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": "fedf58314b5641523bdc9ac34be10ee3" + }, + "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/fixture/vcr_cassettes/merchantapi/get_invoice_status_failure_with_invalid_id.json b/fixture/vcr_cassettes/merchantapi/get_invoice_status_failure_with_invalid_id.json new file mode 100644 index 0000000..a73781b --- /dev/null +++ b/fixture/vcr_cassettes/merchantapi/get_invoice_status_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/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/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/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.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..7be2590 --- /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..63daf98 --- /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, :status, :amount, :ccy, :created_at, :modified_at] +end diff --git a/lib/ex_monobank/merchant_api.ex b/lib/ex_monobank/merchant_api.ex new file mode 100644 index 0000000..986e2cf --- /dev/null +++ b/lib/ex_monobank/merchant_api.ex @@ -0,0 +1,189 @@ +defmodule ExMonobank.MerchantAPI do + @moduledoc """ + Documentation for ExMonobank.MerchantAPI https://api.monobank.ua/docs/acquiring.html + """ + + use Tesla, only: [:get, :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, :merchant_api)[:token]} + ]) + + plug(Tesla.Middleware.JSON) + + if Mix.env() == :dev do + plug(Tesla.Middleware.Logger) + end + + @doc """ + Create invoice + https://api.monobank.ua/docs/acquiring.html#/paths/~1api~1merchant~1invoice~1create/post + """ + def create_invoice(%ExMonobank.Invoice{} = invoice) 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)} + + {:ok, %{body: %{"errText" => reason}}} -> + {:error, reason} + + {:error, reason} -> + {:error, reason} + 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 + """ + 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 + + @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 + + 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 + + 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 + + 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/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..488973a 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 @@ -30,9 +36,11 @@ defmodule ExMonobank.MixProject do defp deps do [ - {:tesla, "~> 1.4.0"}, + {:tesla, "~> 1.14.1"}, + {: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 end diff --git a/mix.lock b/mix.lock index 408ba95..e89fd5e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,19 +1,25 @@ %{ - "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.14.0", "ed3bef654e69cde5e6c022df8070a579a79e8ba2368a00acf3d75b82d9aceeed", [:rebar3], [], "hexpm", "ea59d87ef89da429b8e905264fdec3419f84f2215bb3d81e07a18aac919026c3"}, "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"}, - "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, - "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"}, - "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, - "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"}, - "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"}, + "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"}, + "hackney": {:hex, :hackney, "1.23.0", "55cc09077112bcb4a69e54be46ed9bc55537763a96cd4a80a221663a7eafd767", [:rebar3], [{:certifi, "~> 2.14.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.4.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", "6cd1c04cd15c81e5a493f167b226a15f0938a84fc8f0736ebe4ddcab65c0b44e"}, + "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.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"}, + "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, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "tesla": {:hex, :tesla, "1.14.1", "71c5b031b4e089c0fbfb2b362e24b4478465773ae4ef569760a8c2899ad1e73c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "c1dde8140a49a3bef5bb622356e77ac5a24ad0c8091f12c3b7fc1077ce797155"}, + "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..96487be --- /dev/null +++ b/test/ex_monobank/merchant_api_test.exs @@ -0,0 +1,176 @@ +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 == "230605A3RNUJNt1oqoiY" + assert invoice.page_url == "https://pay.mbnk.biz/230605A3RNUJNt1oqoiY" + end + end + + test "failure without amount" do + use_cassette "MerchantAPI/create_invoice_failure_without_amount" do + {: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, 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 + + 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 + + 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_invalid_id" do + {:error, error} = MerchantAPI.get_invoice_status("invalid") + assert error == "invoice not found" + end + 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 + {:ok, %ExMonobank.InvoiceInfo{} = invoice} = + MerchantAPI.cancel_invoice("230605DpurVjPDSmfzEP") + + 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 + + test "failure with invalid invoiceId" do + use_cassette "MerchantAPI/cancel_invoice_failure_with_invalid_id" do + {:error, error} = MerchantAPI.cancel_invoice("invalid") + assert error == "invoice not found" + 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 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()