defmodule WeChat.Utils do
@moduledoc false
@random_alphanumeric Enum.concat([?a..?z, ?A..?Z, 48..57])
alias WeChat.{JSSDKSignature, CardSignature}
def random_string(length) when length > 0 do
@random_alphanumeric
|> Enum.take_random(length)
|> List.to_string()
end
def now_unix() do
System.os_time(:second)
end
@doc """
To configure and load WeChat JSSDK in the target page's url properly, use `jsapi_ticket` and `url` to generate an signature for this scenario.
"""
@spec sign_jssdk(jsapi_ticket :: String.t(), url :: String.t()) :: JSSDKSignature.t()
def sign_jssdk(jsapi_ticket, url) do
url = String.replace(url, ~r/\#.*/, "")
noncestr = random_string(16)
timestamp = now_unix()
str_to_sign =
"jsapi_ticket=#{jsapi_ticket}&noncestr=#{noncestr}×tamp=#{timestamp}&url=#{url}"
signature = :crypto.hash(:sha, str_to_sign) |> Base.encode16(case: :lower)
%JSSDKSignature{value: signature, timestamp: timestamp, noncestr: noncestr}
end
@doc """
To initialize WeChat Card functions via JSSDK, use `wxcard_ticket`, `card_id` to generate an signature for this scenario.
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#65
"""
@spec sign_card(list :: [String.t()]) :: CardSignature.t()
@spec sign_card(wxcard_ticket :: String.t(), card_id :: String.t()) :: CardSignature.t()
@spec sign_card(wxcard_ticket :: String.t(), card_id :: String.t(), openid :: String.t()) ::
CardSignature.t()
def sign_card(wxcard_ticket, card_id), do: sign_card([wxcard_ticket, card_id])
def sign_card(wxcard_ticket, card_id, openid), do: sign_card([wxcard_ticket, card_id, openid])
def sign_card(list) do
noncestr = random_string(16)
timestamp = now_unix()
timestamp_str = Integer.to_string(timestamp)
str_to_sign = Enum.sort([timestamp_str, noncestr | list]) |> Enum.join()
signature = :crypto.hash(:sha, str_to_sign) |> Base.encode16(case: :lower)
%CardSignature{value: signature, timestamp: timestamp, noncestr: noncestr}
end
@doc """
Merges two keyword lists into one if the value to the matched key of the former(`keyword1`) is nil, will use the latter(`keyword2`)'s value to instead it.
"""
def merge_keyword(keyword1, keyword2) do
Keyword.merge(keyword1, keyword2, fn _k, v1, v2 ->
if v1 == nil, do: v2, else: v1
end)
end
def parse_uri(uri, opts \\ [])
def parse_uri(nil, _opts), do: nil
def parse_uri(uri, opts) do
URI.parse(uri)
|> format_uri_host(Keyword.get(opts, :host, "api.weixin.qq.com"))
|> format_uri_scheme(Keyword.get(opts, :scheme, "https"))
|> format_uri_port(Keyword.get(opts, :port, 443))
end
def json_decode(nil), do: nil
def json_decode(""), do: nil
def json_decode(input) do
case Jason.decode(input) do
{:ok, decoded} ->
decoded
{:error, _} ->
input
end
end
def as_error(%{body: %{"errcode" => errcode} = body, status: status}) do
# Transfer WeChat error response into `WeChat.Error` type.
%WeChat.Error{
errcode: errcode,
http_status: status,
message: Map.get(body, "errmsg")
}
end
def as_error(error) when is_map(error) do
%WeChat.Error{
message: Jason.encode(error)
}
end
def as_error(error) when is_atom(error) do
%WeChat.Error{
message: error
}
end
defp format_uri_host(%URI{host: nil} = uri, input_host) when is_bitstring(input_host) do
Map.put(uri, :host, input_host)
end
defp format_uri_host(uri, _) do
uri
end
defp format_uri_scheme(%URI{scheme: nil} = uri, input_scheme) when is_bitstring(input_scheme) do
Map.put(uri, :scheme, input_scheme)
end
defp format_uri_scheme(uri, _) do
uri
end
defp format_uri_port(%URI{port: nil} = uri, input_port) when is_integer(input_port) do
Map.put(uri, :port, input_port)
end
defp format_uri_port(uri, _) do
uri
end
end