Current section
29 Versions
Jump to
Current section
29 Versions
Compare versions
19
files changed
+332
additions
-167
deletions
| @@ -7,15 +7,23 @@ Wechat API wrapper in Elixir. | |
| 7 7 |  |
| 8 8 | |
| 9 9 | ## Installation |
| 10 | + |
| 10 11 | ```elixir |
| 11 12 | def deps do |
| 12 13 | [{:wechat, "~> 0.4.0"}] |
| 13 14 | end |
| 14 15 | ``` |
| 15 16 | |
| 16 | - Warning: 0.4.0 is a rewrite for APIs, don't upgrade if you are using 0.3.0. |
| 17 | + ## Configuration (optional) |
| 18 | + |
| 19 | + ```elixir |
| 20 | + config :wechat, |
| 21 | + adapter_opts: {Wechat.Adapters.Redis, ["redis://localhost:6379/0"]}, |
| 22 | + httpoison_opts: [recv_timeout: 300_000] |
| 23 | + ``` |
| 24 | + |
| 25 | + ## Create a client to call APIs |
| 17 26 | |
| 18 | - ## Usage |
| 19 27 | ```elixir |
| 20 28 | iex(1)> client = Wechat.Client.new(%{appid: "WECHAT_APPID", secret: "WECHAT_SECRET"}) |
| 21 29 | %Wechat.Client{ |
| @@ -23,92 +31,138 @@ iex(1)> client = Wechat.Client.new(%{appid: "WECHAT_APPID", secret: "WECHAT_SECR | |
| 23 31 | secret: "WECHAT_SECRET", |
| 24 32 | endpoint: "https://api.weixin.qq.com/" |
| 25 33 | } |
| 34 | + |
| 26 35 | iex(2)> Wechat.User.get(client) |
| 27 36 | {:ok, |
| 28 | - %{ |
| 29 | - "count" => 1, |
| 30 | - "data" => %{"openid" => ["oi00OuKAhA8bm5okpaIDs7WmUZr4"]}, |
| 31 | - "next_openid" => "oi00OuKAhA8bm5okpaIDs7WmUZr4", |
| 32 | - "total" => 1 |
| 33 | - }} |
| 37 | + %{ |
| 38 | + "count" => 1, |
| 39 | + "data" => %{"openid" => ["oi00OuKAhA8bm5okpaIDs7WmUZr4"]}, |
| 40 | + "next_openid" => "oi00OuKAhA8bm5okpaIDs7WmUZr4", |
| 41 | + "total" => 1 |
| 42 | + }} |
| 34 43 | ``` |
| 35 44 | |
| 36 | - ## Config |
| 45 | + ## Create a Wechat implementation |
| 46 | + |
| 47 | + You can implement the `Wechat` module to simplify the usage. |
| 48 | + |
| 49 | + First, create an implementation by `use Wechat` : |
| 50 | + |
| 37 51 | ```elixir |
| 38 | - config :wechat, |
| 39 | - adapter_opts: {Wechat.Adapters.Redis, ["redis://localhost:6379/0"]}, |
| 40 | - httpoison_opts: [recv_timeout: 300_000] |
| 52 | + defmodule MyApp.Wechat do |
| 53 | + use Wechat, otp_app: :my_app |
| 54 | + |
| 55 | + def users do |
| 56 | + client() |> Wechat.User.get() |
| 57 | + end |
| 58 | + end |
| 41 59 | ``` |
| 42 60 | |
| 43 | - ## Plugs |
| 61 | + Config the implementation with Wechat credentials: |
| 44 62 | |
| 45 | - ### Parse wechat message (in Phonenix controller) |
| 63 | + ```elixir |
| 64 | + config :my_app, MyApp.Wechat, |
| 65 | + appid: "APP_ID", |
| 66 | + secret: "APP_SECRET", |
| 67 | + token: "TOKEN", |
| 68 | + encoding_aes_key: "ENCODING_AES_KEY" # Required if you enabled the encrypt mode |
| 69 | + ``` |
| 46 70 | |
| 47 | - * config.exs |
| 71 | + ## Wechat implementation examples |
| 48 72 | |
| 49 | - ```elixir |
| 50 | - config :my_app, MyApp.Wechat, |
| 51 | - appid: "APP_ID", |
| 52 | - secret: "APP_SECRET", |
| 53 | - token: "TOKEN", |
| 54 | - encoding_aes_key: "ENCODING_AES_KEY" # Required if you enabled the encrypt mode |
| 55 | - ``` |
| 73 | + ### JS-SDK |
| 56 74 | |
| 57 | - * my_app/wechat.ex |
| 75 | + [https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html) |
| 58 76 | |
| 59 | - ```elixir |
| 60 | - defmodule MyApp.Wechat do |
| 61 | - use Wechat, otp_app: :beaver |
| 77 | + ```html |
| 78 | + <script type="text/javascript" src="//res.wx.qq.com/open/js/jweixin-1.4.0.js"></script> |
| 79 | + <%= raw MyApp.Wechat.wechat_config_js(@conn, debug: false, api: ~w(previewImage closeWindow)) %> |
| 62 80 | |
| 63 | - def users do |
| 64 | - client() |> Wechat.User.get() |
| 81 | + <script> |
| 82 | + $(function() { |
| 83 | + var urls = []; |
| 84 | + $('img').map(function(){ |
| 85 | + url = window.location.origin + $(this).attr('src'), |
| 86 | + urls.push(url); |
| 87 | + }); |
| 88 | + |
| 89 | + $('img').click(function(e) { |
| 90 | + wx.previewImage({ |
| 91 | + current: window.location.origin + $(this).attr('src'), |
| 92 | + urls: urls |
| 93 | + }); |
| 94 | + }) |
| 95 | + }); |
| 96 | + </script> |
| 97 | + ``` |
| 98 | + |
| 99 | + ### Process message in Phoenix |
| 100 | + |
| 101 | + [https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html](https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html) |
| 102 | + |
| 103 | + - router.ex |
| 104 | + |
| 105 | + ```elixir |
| 106 | + defmodule MyApp.Router do |
| 107 | + scope "/wechat", MyApp do |
| 108 | + resources "/", WechatController, [:index, :create] |
| 109 | + end |
| 110 | + end |
| 111 | + ``` |
| 112 | + |
| 113 | + - wechat_controller.ex |
| 114 | + |
| 115 | + ```elixir |
| 116 | + defmodule MyApp.WechatController do |
| 117 | + use MyApp.Web, :controller |
| 118 | + |
| 119 | + # Validate signature param |
| 120 | + plug Wechat.Plugs.RequestValidator, module: MyApp.Wechat |
| 121 | + |
| 122 | + # Parse message |
| 123 | + plug Wechat.Plugs.MessageParser, [module: MyApp.Wechat] when action in [:create] |
| 124 | + |
| 125 | + def index(conn, %{"echostr" => echostr}) do |
| 126 | + text conn, echostr |
| 127 | + end |
| 128 | + |
| 129 | + def create(conn, _params) do |
| 130 | + %{"ToUserName" => to, "FromUserName" => from, "Content" => content} = conn.body_params |
| 131 | + reply = %{from: to, to: from, content: content} |
| 132 | + |
| 133 | + msg = Phoenix.View.render_to_string(EvercamWechatWeb.WechatView, "text.xml", reply: reply) |
| 134 | + |
| 135 | + # Return encrypted message if possible |
| 136 | + case Wechat.encrypt_message(msg) do |
| 137 | + {:ok, reply} -> |
| 138 | + render(conn, "encrypt.xml", reply: reply) |
| 139 | + |
| 140 | + {:error, _} -> |
| 141 | + text(conn, msg) |
| 65 142 | end |
| 66 143 | end |
| 67 | - ``` |
| 144 | + end |
| 145 | + ``` |
| 68 146 | |
| 69 | - * router.ex |
| 147 | + - text.xml.eex |
| 70 148 | |
| 71 | - ```elixir |
| 72 | - defmodule MyApp.Router do |
| 73 | - scope "/wechat", MyApp do |
| 74 | - resources "/", WechatController, [:index, :create] |
| 75 | - end |
| 76 | - end |
| 77 | - ``` |
| 149 | + ```xml |
| 150 | + <xml> |
| 151 | + <MsgType><![CDATA[text]]></MsgType> |
| 152 | + <Content><![CDATA[<%= @reply.content %>]]></Content> |
| 153 | + <ToUserName><![CDATA[<%= @reply.to %>]]></ToUserName> |
| 154 | + <FromUserName><![CDATA[<%= @reply.from %>]]></FromUserName> |
| 155 | + <CreateTime><%= DateTime.to_unix(DateTime.utc_now) %></CreateTime> |
| 156 | + </xml> |
| 157 | + ``` |
| 78 158 | |
| 79 | - * wechat_controller.ex |
| 159 | + - encrypt.xml.eex |
| 80 160 | |
| 81 | - ```elixir |
| 82 | - defmodule MyApp.WechatController do |
| 83 | - use MyApp.Web, :controller |
| 84 | - |
| 85 | - plug Wechat.Plugs.RequestValidator, module: MyApp.Wechat |
| 86 | - plug Wechat.Plugs.MessageParser, [module: MyApp.Wechat] when action in [:create] |
| 87 | - |
| 88 | - def index(conn, %{"echostr" => echostr}) do |
| 89 | - text conn, echostr |
| 90 | - end |
| 91 | - |
| 92 | - def create(conn, _params) do |
| 93 | - msg = conn.body_params |
| 94 | - reply = echo_reply(msg, msg["Content"]) |
| 95 | - render conn, "text.xml", reply: reply |
| 96 | - end |
| 97 | - |
| 98 | - defp echo_reply(%{"ToUserName" => to, "FromUserName" => from}, content) do |
| 99 | - %{from: to, to: from, content: content} |
| 100 | - end |
| 101 | - end |
| 102 | - ``` |
| 103 | - |
| 104 | - * text.xml.eex |
| 105 | - |
| 106 | - ```xml |
| 107 | - <xml> |
| 108 | - <MsgType><![CDATA[text]]></MsgType> |
| 109 | - <Content><![CDATA[<%= @reply.content %>]]></Content> |
| 110 | - <ToUserName><![CDATA[<%= @reply.to %>]]></ToUserName> |
| 111 | - <FromUserName><![CDATA[<%= @reply.from %>]]></FromUserName> |
| 112 | - <CreateTime><%= DateTime.to_unix(DateTime.utc_now) %></CreateTime> |
| 113 | - </xml> |
| 114 | - ``` |
| 161 | + ```xml |
| 162 | + <xml> |
| 163 | + <Encrypt><![CDATA[<%= @reply.msg_encrypt %>]]></Encrypt> |
| 164 | + <MsgSignature><![CDATA[<%= @reply.msg_signature %>]]></MsgSignature> |
| 165 | + <TimeStamp><%= @reply.timestamp %></TimeStamp> |
| 166 | + <Nonce><![CDATA[<%= @reply.nonce %>]]></Nonce> |
| 167 | + </xml> |
| 168 | + ``` |
| @@ -1,7 +1,7 @@ | |
| 1 1 | {<<"app">>,<<"wechat">>}. |
| 2 2 | {<<"build_tools">>,[<<"mix">>]}. |
| 3 3 | {<<"description">>,<<"Wechat API wrapper in Elixir.">>}. |
| 4 | - {<<"elixir">>,<<"~> 1.6">>}. |
| 4 | + {<<"elixir">>,<<"~> 1.7">>}. |
| 5 5 | {<<"files">>, |
| 6 6 | [<<"lib">>,<<"lib/wechat">>,<<"lib/wechat/adapter.ex">>, |
| 7 7 | <<"lib/wechat/util.ex">>,<<"lib/wechat/client.ex">>, |
| @@ -15,9 +15,9 @@ | |
| 15 15 | <<"lib/wechat/utils/message_encryptor.ex">>,<<"lib/wechat/adapters">>, |
| 16 16 | <<"lib/wechat/adapters/sandbox.ex">>,<<"lib/wechat/adapters/redis.ex">>, |
| 17 17 | <<"lib/wechat/plugs">>,<<"lib/wechat/plugs/request_validator.ex">>, |
| 18 | - <<"lib/wechat/plugs/message_parser.ex">>,<<"lib/wechat/base.ex">>, |
| 19 | - <<"lib/wechat/menu.ex">>,<<"lib/wechat/application.ex">>, |
| 20 | - <<"lib/wechat/helpers">>,<<"lib/wechat.ex">>,<<"lib/wechat_bypass">>, |
| 18 | + <<"lib/wechat/plugs/message_parser.ex">>,<<"lib/wechat/menu.ex">>, |
| 19 | + <<"lib/wechat/application.ex">>,<<"lib/wechat/helpers">>, |
| 20 | + <<"lib/wechat.ex">>,<<"lib/wechat_bypass">>, |
| 21 21 | <<"lib/wechat_bypass/assertion.ex">>,<<"lib/wechat_bypass/case.ex">>, |
| 22 22 | <<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE.txt">>]}. |
| 23 23 | {<<"licenses">>,[<<"MIT">>]}. |
| @@ -44,4 +44,4 @@ | |
| 44 44 | {<<"optional">>,true}, |
| 45 45 | {<<"repository">>,<<"hexpm">>}, |
| 46 46 | {<<"requirement">>,<<">= 0.0.0">>}]]}. |
| 47 | - {<<"version">>,<<"0.4.5">>}. |
| 47 | + {<<"version">>,<<"0.4.6">>}. |
| @@ -21,7 +21,7 @@ defmodule Wechat do | |
| 21 21 | end |
| 22 22 | """ |
| 23 23 | |
| 24 | - alias Wechat.Client |
| 24 | + alias Wechat.{Client, Request} |
| 25 25 | |
| 26 26 | @callback client() :: Client.t() |
| 27 27 | |
| @@ -55,31 +55,24 @@ defmodule Wechat do | |
| 55 55 | |
| 56 56 | if Code.ensure_loaded?(Phoenix.Controller) do |
| 57 57 | def wechat_config_js(conn, opts \\ []) do |
| 58 | - client = client() |
| 59 | - |
| 60 58 | import Phoenix.Controller, only: [current_url: 1] |
| 61 | - page_url = current_url(conn) |
| 62 59 | |
| 63 | - debug = Keyword.get(opts, :debug, false) |
| 64 | - js_api_list = opts |> Keyword.get(:api, []) |> Enum.join(",") |
| 65 | - |
| 66 | - %{timestamp: timestamp, noncestr: nonce, signature: signature} = |
| 67 | - Wechat.Client.sign_jsapi(client, page_url) |
| 68 | - |
| 69 | - """ |
| 70 | - <script type="text/javascript"> |
| 71 | - wx.config({ |
| 72 | - debug: #{debug}, |
| 73 | - jsApiList: ['#{js_api_list}'], |
| 74 | - appId: '#{client.appid}', |
| 75 | - timestamp: '#{timestamp}', |
| 76 | - nonceStr: '#{nonce}', |
| 77 | - signature: '#{signature}' |
| 78 | - }); |
| 79 | - </script> |
| 80 | - """ |
| 60 | + Wechat.Client.wechat_config_js(client(), current_url(conn), opts) |
| 81 61 | end |
| 82 62 | end |
| 83 63 | end |
| 84 64 | end |
| 65 | + |
| 66 | + @doc """ |
| 67 | + Get access token. |
| 68 | + """ |
| 69 | + def token(client) do |
| 70 | + params = [ |
| 71 | + appid: client.appid, |
| 72 | + secret: client.secret, |
| 73 | + grant_type: :client_credential |
| 74 | + ] |
| 75 | + |
| 76 | + Request.raw_get(client, "cgi-bin/token", params: params) |
| 77 | + end |
| 85 78 | end |
| @@ -1,15 +0,0 @@ | |
| 1 | - defmodule Wechat.Base do |
| 2 | - @moduledoc false |
| 3 | - |
| 4 | - alias Wechat.Request |
| 5 | - |
| 6 | - def token(client) do |
| 7 | - params = [ |
| 8 | - appid: client.appid, |
| 9 | - secret: client.secret, |
| 10 | - grant_type: :client_credential |
| 11 | - ] |
| 12 | - |
| 13 | - Request.raw_get(client, "cgi-bin/token", params: params) |
| 14 | - end |
| 15 | - end |
| @@ -3,7 +3,7 @@ defmodule Wechat.Client do | |
| 3 3 | `Client` represents an API client. |
| 4 4 | """ |
| 5 5 | |
| 6 | - alias Wechat.{AccessToken, Base, Config, Error} |
| 6 | + alias Wechat.{AccessToken, Config, Error} |
| 7 7 | alias Wechat.Utils.{MessageEncryptor, SignatureVerifier} |
| 8 8 | |
| 9 9 | @endpoint "https://api.weixin.qq.com/" |
| @@ -28,7 +28,7 @@ defmodule Wechat.Client do | |
| 28 28 | @doc """ |
| 29 29 | Create client to consume Wechat API with config. |
| 30 30 | |
| 31 | - ## Examples |
| 31 | + ## Example |
| 32 32 | |
| 33 33 | iex> Wechat.Client.new(appid: "my_appid", secret: "my_secret") |
| 34 34 | %Wechat.Client{ |
| @@ -65,7 +65,7 @@ defmodule Wechat.Client do | |
| 65 65 | |
| 66 66 | @spec get_token(t) :: {:ok, t} | {:error, Error.t()} |
| 67 67 | def get_token(client) do |
| 68 | - case Base.token(client) do |
| 68 | + case Wechat.token(client) do |
| 69 69 | {:ok, body} -> |
| 70 70 | token = AccessToken.new(body) |
| 71 71 | :ok = Config.adapter().write_token(client.appid, token) |
| @@ -140,4 +140,26 @@ defmodule Wechat.Client do | |
| 140 140 | {:error, msg} |
| 141 141 | end |
| 142 142 | end |
| 143 | + |
| 144 | + @doc """ |
| 145 | + Configure JS-SDK. |
| 146 | + """ |
| 147 | + def wechat_config_js(client, url, opts) do |
| 148 | + debug = Keyword.get(opts, :debug, false) |
| 149 | + js_api_list = opts |> Keyword.get(:api, []) |> Enum.join(",") |
| 150 | + %{timestamp: timestamp, noncestr: nonce, signature: signature} = sign_jsapi(client, url) |
| 151 | + |
| 152 | + """ |
| 153 | + <script type="text/javascript"> |
| 154 | + wx.config({ |
| 155 | + debug: #{debug}, |
| 156 | + jsApiList: ['#{js_api_list}'], |
| 157 | + appId: '#{client.appid}', |
| 158 | + timestamp: '#{timestamp}', |
| 159 | + nonceStr: '#{nonce}', |
| 160 | + signature: '#{signature}' |
| 161 | + }); |
| 162 | + </script> |
| 163 | + """ |
| 164 | + end |
| 143 165 | end |
Loading more files…