lib/wechat/pay/pay.ex

defmodule WeChat.Pay do
  @moduledoc """
  微信支付(V3版)

  ** 注意 ** 未经上线测试,请谨慎使用

  ## 引入 x509 依赖

      def deps do
        [
          {:wechat, "~> x.x", hex: :wechat_sdk},
          {:x509, "~> x.x"}
        ]
      end

  ## 定义 Client 模块

      defmodule YourApp.WeChatAppCodeName do
        @moduledoc "CodeName"
        use WeChat.Pay,
          mch_id: "mch_id",
          api_secret_key: "api_secret_key",
          client_serial_no: "client_serial_no",
          client_key: "client_key"
      end

  定义参数说明请看 `t:options/0`

  ## 启动支付 Client 进程

      defmodule YourApp.Application do
        def start(_type, _args) do
          children = [
            # ...
            YourApp.WeChatAppCodeName,
            # or
            {YourApp.WeChatAppCodeName, start_options},
            # ...
          ]


          Supervisor.start_link(children, strategy: :one_for_one, name: YourApp.Supervisor)
        end
      end

  启动参数说明请看 `t:start_options/0`

  ## 处理回调消息

  详情请看 `WeChat.Pay.EventHandler`
  """
  require Logger
  import WeChat.Utils, only: [pay_doc_link_prefix: 0]
  alias WeChat.Pay.Certificates

  @typedoc "商户号"
  @type mch_id :: binary
  @typedoc """
  平台 证书序列号 -
  [官方文档](#{pay_doc_link_prefix()}/merchant/development/interface-rules/certificate-faqs.html){:target="_blank"}
  """
  @type platform_serial_no :: serial_no
  @typedoc """
  商户API 证书序列号 -
  [官方文档](#{pay_doc_link_prefix()}/merchant/development/interface-rules/certificate-faqs.html){:target="_blank"}
  """
  @type client_serial_no :: serial_no
  @typedoc "证书的序列号"
  @type serial_no :: binary
  @typedoc """
  平台证书列表 -
  [官方文档](#{pay_doc_link_prefix()}/merchant/development/interface-rules/wechatpay-certificates.html){:target="_blank"}
  """
  @type cacerts :: list(binary)
  @typedoc """
  商户 API 私钥 -
  [官方文档](#{pay_doc_link_prefix()}/merchant/development/interface-rules/privatekey-and-certificate.html){:target="_blank"}
  """
  @type client_key :: pem_file
  @typedoc """
  API v3密钥 -
  [官方文档](#{pay_doc_link_prefix()}/merchant/development/interface-rules/apiv3key.html){:target="_blank"}
  """
  @type api_secret_key :: binary | WeChat.env_option()
  @type client :: module
  @typep pem_file :: {:file, Path.t()} | {:app_dir, Application.app(), Path.t()}

  @typedoc """
  构建参数

  ## 参数说明

  - `mch_id`: `t:mch_id/0` - 必填
  - `api_secret_key`: `t:binary/0` - 必填
  - `client_serial_no`: `t:client_serial_no/0` - 必填
  - `client_key`: `t:client_key/0` - 必填
  - `storage`: `t:WeChat.Storage.Adapter.t()`
  - `requester`: 请求客户端 - `t:module/0`

  ## 默认参数:

  - `storage`: `WeChat.Storage.PayFile`
  - `requester`: `WeChat.Requester.Pay`
  """
  @type options :: [
          mch_id: mch_id,
          api_secret_key: binary,
          client_serial_no: client_serial_no,
          client_key: client_key,
          requester: module,
          storage: module
        ]

  @typedoc """
  启动参数

  - `refresher`: 刷新器 - `t:module/0`, 可选, 默认值: `WeChat.Refresher.Pay`
  """
  @type start_options :: [refresher: module]
  @type requester_id :: :A | :B
  @type requester_opts :: %{id: requester_id, name: atom}

  @doc false
  defmacro __using__(options \\ []) do
    quote do
      use WeChat.Builder.Pay, unquote(options)
    end
  end

  @doc "动态构建 client"
  @spec build_client(client, options) :: {:ok, client}
  def build_client(client, options) do
    with {:module, module, _binary, _term} <-
           Module.create(
             client,
             quote do
               @moduledoc false
               use WeChat.Builder.Pay, unquote(Macro.escape(options))
             end,
             Macro.Env.location(__ENV__)
           ) do
      {:ok, module}
    end
  end

  @doc false
  def finch_name(client), do: :"#{client}.Finch"

  def get_requester_spec(client) do
    name = finch_name(client)
    # todo config finch_pool
    finch_pool = Application.get_env(:wechat, :finch_pool, size: 32, count: 8)
    options = [name: name, pools: %{:default => finch_pool}]
    spec = Finch.child_spec(options)
    %{spec | id: Finch}
  end

  @doc "初始化平台证书"
  @spec init_cacerts(client) :: {:ok, cacerts :: list(map)}
  def init_cacerts(client) do
    with {:ok, cacerts} when is_list(cacerts) <- Certificates.certificates(client, true) do
      Certificates.put_certs(cacerts, client)
      storage = client.storage()
      result = storage.store(client.mch_id(), :cacerts, cacerts)
      Logger.info("store cacerts for mch_id:#{client.mch_id()} result: #{inspect(result)}.")
      {:ok, cacerts}
    else
      error ->
        Logger.warning("Init certificates error: #{inspect(error)}.")
        error
    end
  end
end