Current section
Files
Jump to
Current section
Files
lib/wechat_pay/plug/refund.ex
defmodule WechatPay.Plug.Refund do
@moduledoc """
Plug behaviour to handle **Refund** Notification from Wechat's Payment Gateway.
Official document: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=9
## Example
### Define a handler
See `WechatPay.Plug.Handler` for how to implement a handler.
```elixir
defmodule MyApp.WechatHandler do
use WechatPay.Plug.Handler
@impl WechatPay.Plug.Handler
def handle_data(conn, _data) do
:ok
end
end
```
### Plug in
In your app's `lib/my_app_web/router.ex`:
```elixir
post "/wechat_pay/notification/refund", WechatPay.Plug.Refund, [handler: MyApp.WechatHandler, api_key: "my-api-key"]
```
"""
alias WechatPay.Utils.XMLParser
alias WechatPay.Error
import Plug.Conn
@spec init(keyword()) :: [{:api_key, any()} | {:handler, any()}]
def init(opts) do
handler = Keyword.get(opts, :handler)
api_key = Keyword.get(opts, :api_key)
[handler: handler, api_key: api_key]
end
@spec call(Plug.Conn.t(), [{:api_key, any()} | {:handler, any()}]) :: Plug.Conn.t()
def call(conn, handler: handler_module, api_key: api_key) do
{:ok, body, conn} = Plug.Conn.read_body(conn)
with {:ok, data} <- XMLParser.parse(body),
:ok <- process_data(conn, data, handler_module, api_key) do
response_with_success_info(conn)
else
{:error, %Error{reason: reason}} ->
conn
|> send_resp(:unprocessable_entity, reason)
end
end
defp response_with_success_info(conn) do
body = ~s"""
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
"""
conn
|> put_resp_content_type("application/xml")
|> send_resp(:ok, body)
end
defp process_data(conn, data, handler_module, api_key) do
with {:ok, data} <- process_return_field(data),
{:ok, decrypted_data} <- decrypt_data(data, api_key),
{:ok, map} <- XMLParser.parse(decrypted_data, "root"),
:ok <- apply(handler_module, :handle_data, [conn, map]) do
:ok
else
{:error, %Error{} = error} ->
maybe_handle_error(handler_module, conn, error, data)
{:error, error}
end
end
defp process_return_field(%{return_code: "SUCCESS"} = data) do
{:ok, data}
end
defp process_return_field(%{return_code: "FAIL", return_msg: reason}) do
{:error, %Error{reason: reason, type: :failed_return}}
end
defp maybe_handle_error(handler_module, conn, error, data) do
handler_module.handle_error(conn, error, data)
end
defp decrypt_data(%{req_info: encrypted_data}, api_key) do
key =
:md5
|> :crypto.hash(api_key)
|> Base.encode16(case: :lower)
{:ok, data} =
encrypted_data
|> Base.decode64()
try do
xml_string = :crypto.block_decrypt(:aes_ecb, key, data)
{:ok, xml_string}
rescue
ArgumentError ->
{:error, %Error{reason: "Fail to decrypt req_info", type: :fail_to_decrypt_req_info}}
end
end
defp decrypt_data(_, _api_key) do
{:error,
%Error{reason: "Missing the encrypted `req_info` in response data", type: :missing_req_info}}
end
end