-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from feng19/pay
Support Wechat Pay
- Loading branch information
Showing
34 changed files
with
1,204 additions
and
366 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,125 @@ | ||
defmodule WeChat.Builder.Pay do | ||
@moduledoc false | ||
alias WeChat.Builder.Utils | ||
|
||
defmacro __using__(options \\ []) do | ||
options = Map.new(options) | ||
client = __CALLER__.module | ||
check_options!(options, client) | ||
options = options |> Macro.prewalk(&Macro.expand(&1, __CALLER__)) |> Map.new() | ||
requester = Map.get(options, :requester, WeChat.Requester.Pay) | ||
storage = Map.get(options, :storage, WeChat.Storage.PayFile) | ||
public_key = WeChat.Pay.Utils.decode_key(options.client_cert) | ||
# private_key = WeChat.Pay.Utils.decode_key(options.client_key) | ||
private_key = WeChat.Pay.Crypto.load_pem!(options.client_key) | ||
public_key = private_key |> X509.PublicKey.derive() |> Macro.escape() | ||
private_key = Macro.escape(private_key) | ||
|
||
api_secret_key = | ||
with :not_handle <- Utils.handle_env_option(client, :api_secret_key, options.api_secret_key) do | ||
quote do | ||
def api_secret_key, do: unquote(options.api_secret_key) | ||
end | ||
end | ||
|
||
quote do | ||
use Supervisor | ||
|
||
@spec start_link(WeChat.Pay.start_options()) :: Supervisor.on_start() | ||
def start_link(opts) do | ||
opts = Map.new(opts) | ||
requester_a = WeChat.Pay.get_requester_spec(:A, __MODULE__, opts.cacerts) | ||
requester_b = WeChat.Pay.get_requester_spec(:B, __MODULE__, opts.cacerts) | ||
WeChat.Pay.put_requester_opts(__MODULE__, :A, opts.serial_no) | ||
Supervisor.start_link(__MODULE__, Map.new(opts), name: :"#{__MODULE__}.Supervisor") | ||
end | ||
|
||
@impl true | ||
def init(opts) do | ||
refresher = Map.get(opts, :refresher, WeChat.Refresher.Pay) | ||
children = [{refresher, {__MODULE__, opts}}, requester_a, requester_b] | ||
opts = [strategy: :one_for_one, name: :"#{__MODULE__}.Supervisor"] | ||
Supervisor.start_link(children, opts) | ||
|
||
Map.get_lazy(opts, :cacerts, fn -> | ||
# Load Cacerts From Storage | ||
{:ok, cacerts} = unquote(storage).restore(unquote(options.mch_id), :cacerts) | ||
cacerts | ||
end) | ||
|> WeChat.Pay.Certificates.put_certs(__MODULE__) | ||
|
||
children = [ | ||
{refresher, Map.put(opts, :client, __MODULE__)}, | ||
WeChat.Pay.get_requester_spec(__MODULE__) | ||
] | ||
|
||
Supervisor.init(children, strategy: :one_for_one) | ||
end | ||
|
||
@spec get(url :: binary) :: WeChat.response() | ||
def get(url), do: get(url, []) | ||
|
||
@spec get(url :: binary, opts :: keyword) :: WeChat.response() | ||
def get(url, opts) do | ||
%{name: name, serial_no: serial_no} = WeChat.Pay.get_requester_opts(__MODULE__) | ||
|
||
__MODULE__ | ||
|> unquote(requester).new(__MODULE__, name, serial_no) | ||
unquote(requester).new(__MODULE__) | ||
|> Tesla.get(url, opts) | ||
end | ||
|
||
@spec post(url :: binary, body :: any) :: WeChat.response() | ||
def post(url, body), do: post(url, body, []) | ||
|
||
@spec post(url :: binary, body :: any, opts :: keyword) :: WeChat.response() | ||
def post(url, body, opts) do | ||
%{name: name, serial_no: serial_no} = WeChat.Pay.get_requester_opts(__MODULE__) | ||
|
||
__MODULE__ | ||
|> unquote(requester).new(__MODULE__, name, serial_no) | ||
unquote(requester).new(__MODULE__) | ||
|> Tesla.post(url, body, opts) | ||
end | ||
|
||
@spec mch_id() :: WeChat.Pay.mch_id() | ||
def mch_id, do: unquote(options.mch_id) | ||
def api_secret_key, do: unquote(options.api_secret_key) | ||
@spec api_secret_key() :: WeChat.Pay.api_secret_key() | ||
unquote(api_secret_key) | ||
@spec client_serial_no() :: WeChat.Pay.client_serial_no() | ||
def client_serial_no, do: unquote(options.client_serial_no) | ||
@spec storage() :: WeChat.Storage.Adapter.t() | ||
def storage, do: unquote(storage) | ||
@spec client_cert() :: WeChat.Pay.client_cert() | ||
def client_cert, do: unquote(options.client_cert) | ||
@spec client_key() :: WeChat.Pay.client_key() | ||
def client_key, do: unquote(options.client_key) | ||
@doc false | ||
def public_key, do: unquote(public_key) | ||
# def private_key, do: unquote(private_key) | ||
@doc false | ||
def private_key, do: unquote(private_key) | ||
|
||
@doc "加密敏感信息" | ||
def encrypt_secret_data(data) do | ||
WeChat.Pay.Crypto.encrypt_secret_data(data, unquote(public_key)) | ||
end | ||
|
||
@doc "解密敏感信息" | ||
def decrypt_secret_data(cipher_text) do | ||
WeChat.Pay.Crypto.decrypt_secret_data(cipher_text, unquote(private_key)) | ||
end | ||
end | ||
end | ||
|
||
defp check_options!(options, client) do | ||
unless Keyword.get(options, :mch_id) |> is_binary() do | ||
raise ArgumentError, "please set mch_id option for #{inspect(client)}" | ||
end | ||
|
||
unless Keyword.get(options, :client_serial_no) |> is_binary() do | ||
raise ArgumentError, "please set client_serial_no option for #{inspect(client)}" | ||
end | ||
|
||
api_secret_key = Keyword.get(options, :api_secret_key) | ||
|
||
unless is_binary(api_secret_key) or Utils.check_env_option?(api_secret_key) do | ||
raise ArgumentError, "please set api_secret_key option for #{inspect(client)}" | ||
end | ||
|
||
unless Keyword.get(options, :client_cert) |> check_pem_file?() do | ||
raise ArgumentError, "please set client_cert option for #{inspect(client)}" | ||
end | ||
|
||
unless Keyword.get(options, :client_key) |> check_pem_file?() do | ||
raise ArgumentError, "please set client_key option for #{inspect(client)}" | ||
end | ||
end | ||
|
||
defp check_pem_file?({:app_dir, app, path}) when is_atom(app) and is_binary(path), | ||
do: Application.app_dir(app, path) |> File.exists?() | ||
|
||
defp check_pem_file?({:file, path}) when is_binary(path), do: File.exists?(path) | ||
defp check_pem_file?(_), do: false | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
defmodule WeChat.Pay.AuthorizationMiddleware do | ||
import WeChat.Utils, only: [pay_doc_link_prefix: 0] | ||
|
||
@moduledoc """ | ||
微信支付 V3 Authorization 签名生成 | ||
Tesla Middleware | ||
- [如何生成请求签名](#{pay_doc_link_prefix()}/merchant/development/interface-rules/signature-generation.html){:target="_blank"} | ||
- [签名相关问题](#{pay_doc_link_prefix()}/merchant/development/interface-rules/signature-faqs.html){:target="_blank"} | ||
""" | ||
@behaviour Tesla.Middleware | ||
alias WeChat.Pay.Crypto | ||
|
||
@impl Tesla.Middleware | ||
def call(env, next, client) do | ||
token = gen_token(client.mch_id(), client.client_serial_no(), client.private_key(), env) | ||
|
||
env | ||
|> Tesla.put_headers([{"authorization", "WECHATPAY2-SHA256-RSA2048 #{token}"}]) | ||
|> Tesla.run(next) | ||
end | ||
|
||
def gen_token(mch_id, serial_no, private_key, env) do | ||
timestamp = WeChat.Utils.now_unix() | ||
nonce_str = :crypto.strong_rand_bytes(16) |> Base.encode16() | ||
signature = Crypto.sign(env, timestamp, nonce_str, private_key) | ||
|
||
~s(mchid="#{mch_id}",nonce_str="#{nonce_str}",timestamp="#{timestamp}",serial_no="#{serial_no}",signature="#{signature}") | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
defmodule WeChat.Pay.Bill do | ||
@moduledoc "微信支付-交易账单" | ||
import Jason.Helpers | ||
import WeChat.Utils, only: [pay_doc_link_prefix: 0] | ||
|
||
@doc """ | ||
申请交易账单 - | ||
[官方文档](#{pay_doc_link_prefix()}/merchant/apis/bill-download/trade-bill/get-trade-bill.html){:target="_blank"} | ||
""" | ||
def tradebill(client, bill_date, bill_type \\ "ALL", zip? \\ false) | ||
|
||
def tradebill(client, bill_date, bill_type, false) do | ||
client.post( | ||
"/v3/bill/tradebill", | ||
json_map(bill_date: bill_date, bill_type: bill_type, tar_type: "GZIP") | ||
) | ||
end | ||
|
||
def tradebill(client, bill_date, bill_type, true) do | ||
client.post("/v3/bill/tradebill", json_map(bill_date: bill_date, bill_type: bill_type)) | ||
end | ||
end |
Oops, something went wrong.