Erlang服务端与微信登录和微信支付接口的交互
最近因为项目需要做微信支付的功能。分享下心得和代码(本人测试成功)。因为erlang的开源资源很少,也没找到合适的方法解析微信开放平台发送的XML数据。所以将自己造的轮子分享下。下面详细介绍下erlang后端和微信开发平台交互的步骤。全是自己一字一字码上去的,转载请注明转自Zmyths 加地址,谢谢。
1. 先看看微信开放平台的官方开发文档:https://open.weixin.qq.com/
如图:
官方的微信登录和微信支付的API,给定的参数很详细。
2. 微信支付API的文档链接:
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
微信登录API的文档链接:
3. http的几种请求方式
Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE。URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而HTTP中的GET,POST,PUT,DELETE就对应着对这个资源的查,改,增,删4个操作。到这里,大家应该有个大概的了解了,GET一般用于获取/查询资源信息,而POST一般用于更新资源信息。
Get 和Post区别:
简单说:Get是向服务器发索取数据的一种请求,而Post是向服务器提交数据的一种请求。
Get是获取信息,而不是修改信息,类似数据库查询功能一样,数据不会被修改。Get请求的参数会跟在url后进行传递,请求的数据会附在URL之后,以?分割URL和传输数据,参数之间以&相连,%XX中的XX为该符号以16进制表示的ASCII,如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密。
Get传输的数据有大小限制,因为GET是通过URL提交数据,那么GET可提交的数据量就跟URL的长度有直接关系了,不同的浏览器对URL的长度的限制是不同的。
Post请求则作为http消息的实际内容发送给web服务器,数据放置在HTML Header内提交,Post没有限制提交的数据。Post比Get安全,当数据是中文或者不敏感的数据,则用get,因为使用get,参数会显示在地址,对于敏感数据和不是中文字符的数据,则用post。
从底层说:
GET产生一个TCP数据包;POST产生两个TCP数据包。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
这里主要用到的是两种,微信登录用的是 get 请求方式,带上参数请求token,
而微信支付用的post 请求方式,将XML数据post给服务器。
4.具体demo:
微信登录:
1. 服务端通过客户端获取的code去向微信开发平台请求,获取token :
https://api.weixin.qq.com/sns/oauth2/access_token"++
"?appid="++?AppId++
"&secret="++?Secret++
"&code="++Code++
"&grant_type=authorization_code",
fun_http:async_http_request(get,{Url,[]},{?MODULE, getTokenCb, {}}).
2. 以上demo是url所带的参数,注意:url和传输数据之间要用?分割 ,请求方式是get,最后加上自己的异步回调函数,带着token 再去授权:
Url="https://api.weixin.qq.com/sns/auth"++
"?access_token="++Token++
"&openid="++OpenId,
fun_http:async_http_request(get,{Url,[]}, {?MODULE,authCb, {Token,OpenId}}).
3. 授权之后再去获取个人信息:
Url="https://api.weixin.qq.com/sns/userinfo"++
"?access_token="++Token++
"&openid="++OpenId,
fun_http:async_http_request(get,{Url,[]}, {?MODULE,getUserInfoCb, {}}).
至此微信登录就完成了。因为微信平台回复的都是json字符串,很好解析,就不详细说了。
下面重点说的是:微信支付
微信支付,从下单开始:
1. 服务端向微信请求预支付订单号,再发给前端。
这里官方要的是XML数据,所以用post方式将XML数据发给微信的服务器。Erlang中封装xml的方式,最简单暴力的就是将XML数据封装成一个字符串,发过去就可以,实践证明可行的:
getPrepayId(TradeId,Ip,Price)->
Str=get_randStr(), %%生成随机串,自己实现
StrA="appid="++?AppId++
"&body=APP"++
"&mch_id="++?Mch_id++
"&nonce_str="++Str++
"¬ify_url=www.baidu.com"++
"&out_trade_no="++TradeId++
"&spbill_create_ip="++ip2str(Ip)++
"&total_fee="++util:to_list(Price*100)++
"&trade_type=APP",
StrSignTemp=StrA++"&key=rtyuiophjklyhjkyhjkjkljk",
Sign=string:to_upper(util:md5(StrSignTemp)),
Url="https://api.mch.weixin.qq.com/pay/unifiedorder",
Data="<xml><appid>"++?AppId++"</appid>"++
"<body>APP</body>"++
"<mch_id>"++?Mch_id++"</mch_id>"++
"<nonce_str>"++Str++"</nonce_str>"++
"<notify_url> www.baidu.com </notify_url>"++
"<out_trade_no>"++TradeId++"</out_trade_no>"++
"<spbill_create_ip>"++ip2str(Ip)++"</spbill_create_ip>"++
"<total_fee>"++util:to_list(Price*100)++"</total_fee>"++
"<trade_type>APP</trade_type>"++
"<sign>"++Sign++"</sign>"++
"</xml>",
fun_http:async_http_request(post,{Url,[],"text/xml",Data},{?MODULE, getPrepayIdCb,{TradeId,Price}}).
如上demo 可以看到,先将需要发送的参数,拼一起成strA,再strA++"&key=rtyuiophjklyhjkyhjkjkljk"= StrSignTemp,再将这个字符串先MD5加密,再执行一次转换大写的操作,签名就生成了。这时候再将这些参数按照标签的形式<appid>"++?AppId++"</appid>" 拼一起,最后封装成一个XML数据的字符串data,再向官方的Url postdata.这时候请求就成功了。注意:Data中的每一个参数和上面生成签名的每一个同名参数得一致,签名不能错误,方式是post data.这些都正确就可以收到微信回复的XML数据,得到预支付订单号prepay_id。。
2. Erlang中xmerl的方法可以解析XML,但网上少有可以完成解析XML的demo.下面是自己解析的方法(究其原理,就是一层一层的将节点剥离出来,最后得到其中的一层的某个字段):
-record(xmlNamespace,{k1=[],k2=[]}).
-record(xmlElement,{
name, % atom()
expanded_name= [], % string() | {URI,Local} |{"xmlns",Local}
nsinfo= [], % {Prefix, Local} | []
namespace=#xmlNamespace{},
parents= [], % [{atom(),integer()}]
pos, % integer()
attributes= [],%[#xmlAttribute()],
content= [],
language= "", % string()
xmlbase="", % string() XML Base path, forrelative URI:s
elementdef=undeclared% atom(), one of [undeclared | prolog | external | element]
}).
-record(xmlText,{parents=[],pos=0,attributes=[],value="",name}).
getPrepayIdCb({_StatusLine,Body},{TradeId,Price})->
?log("!!!!!!~p",[Body]),
Str=util:to_list(Body),
{XmlElt,_}=xmerl_scan:string(Str),
Items= xmerl_xpath:string("/xml", XmlElt),
Prepay_id= lists:foldl(fun(Item, T) ->
[#xmlElement{content=Content}]=xmerl_xpath:string("/xml/prepay_id", Item),
case Content of
[#xmlText{value=Prepay_id1}]->Prepay_id1++T;
_->T
end end,"",Items).
如上,需要定义三个record。字段可以自己命名,Body就是解析http请求回复的数据,然后先将binary文件转化成list, 再通过xmerl_scan:string(Str)方法,提取出XML的节点元素。再通过xmerl_xpath:string("/xml", XmlElt)方法,提取出XML节点下的所有数据。 再通过下面的[#xmlElement{content=Content}]=xmerl_xpath:string("/xml/prepay_id", Item)
方法,找到prepay_id 这个标签的节点数据,和#xmlElement 匹配,其中的content就是我们要的,而content去和xmlText 记录匹配,其中的value字段就是我们要的prepay_id的值。其他字段同理可以获得。
注意:只要理解了erlang的匹配原理,record对应的字段都是自己命名,自己可以任意取出来的。
如客户端支付成功后,服务端去验证是否成功,需要获取的字段trade_state的值是否为SUCCESS
Flag= lists:foldl(fun(Item, T) ->
[#xmlElement{content=Content}]=xmerl_xpath:string("/xml/trade_state", Item),
case Content of
[#xmlText{value=Va}]-> Va++T;
_->T
end end,"", Items).
以上是个人做微信接口交互的时候的心得,只想分享一些成功的可以实用的方法,erlang开源东西太少,找到可用的方法太少。欢迎分享,指教,转载注出处,谢谢。