C井微信公众号支付接口对接

cbao 2018-11-03

这次是第二次对接微信公众号的支付了,第一次代码写的很乱,没有找现成的SDK,感觉里面乱七八糟的东西太多不够清爽,而我仅仅是做一个小功能,对接又不复杂干脆自己做。

不想重复做轮子的朋友可以参考一下:Senparc C# SDK

这里我只说一下公众号内支付,APP以及其他的支付,等我以后用得到的时候再总结。

简单的说对接过程分为4步

1、拿到用户的openid

2、使用微信接口下单获取preorder_id

3、前端H5调用微信支付对话框

4、接收微信支付结果的通知

好了,现在开始具体过程介绍

0.公众号信息准备

1、公众号的appid,appsecret

登录微信公众平台,在开发一栏里找到基本配置

C井微信公众号支付接口对接

C井微信公众号支付接口对接

2、微信支付平台的商户id(mch_id), 支付key(这个是自己设置的)

1.获取用户openid

每个用户在公众号里都有一个唯一的openid,公众号可以通过openid获取用户的信息,当然获取用户信息是需要用户同意的。但是只获取openid不需要用户同意。

这里没提到的内容可以参考官方文档,:微信网页授权

要获取openid,首先要获取code,然后根据code获取openid。这个过程是为了要用户授权,也就是下面的绿色页面。

获取code跳转到这个链接:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

参数

appid:是公众号的唯一标识

redirect_uri:是授权后重定向的回调链接地址,请使用urlEncode对链接进行处理

response_type:返回类型,请填写code

scope:是应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)

state:否 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节

#wechat_redirect 是 无论直接打开还是做页面302重定向时候,必须带此参数

在这里我们scope参数使用snsapi_base,使用snsapi_userinfo 会弹出绿色页面,但是结果是一样的。

如果没出错的话,页面会跳转到指定的redirect_uri页面,并附带上code和state参数。

获取code后,请求以下链接获取access_token:

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

参数

appid:公众号的唯一标识

secret:公众号的appsecret

code:填写第一步获取的code参数

grant_type:填写authorization_code

正确时返回的JSON数据包如下

{ "access_token":"ACCESS_TOKEN",

"expires_in":7200,

"refresh_token":"REFRESH_TOKEN",

"openid":"OPENID",

"scope":"SCOPE" }

1

2

3

4

5

看到这里面的openid这个参数没?这时就可以取用了,获取到的refresh_token可以保存起来,以后直接用refresh_token获取openid。

2.获取prepay_id

使用微信公众号支付必须在微信留个案底,需要把订单信息提交给微信,这时候就需要使用统一下单接口,官方文档

处于安全角度,使用这个接口需要对传输的信息加密,为了避免篡改,需要用到支付key,过程是这样的:

1、生成一个随机数:32位,可以创建一个Guid。

2、生成安全签名:把提交到微信的参数按照首字母字典排序(a-z),编辑成key=value&key=value…的形式,然后末尾加上key,用md5加个密。

3、创建提交的xml

4、使用post提交到微信接口

· 把要传的参数使用字典排序(除了sign):

SortedDictionary<String, String> param = new SortedDictionary<string, string>();

param.Add("appid", appid);

param.Add("attach", attach);

param.Add("body", body);

param.Add("mch_id", config.mchid);

param.Add("nonce_str", Guid.NewGuid().ToString().Replace("-", "").ToUpper());

param.Add("notify_url", notifyurl);

param.Add("openid", openid);

param.Add("out_trade_no", orderno);

param.Add("spbill_create_ip", System.Web.HttpContext.Current.Request.UserHostAddress);

param.Add("total_fee", ((Int32)(price * 100)).ToString());

param.Add("trade_type", "JSAPI");

1

2

3

4

5

6

7

8

9

10

11

12

· 参数生成字符串

private String GetUnEncyptKey(SortedDictionary<String, String> sort)

{

List<String> lstParam = new List<String>();

foreach (var item in sort)

{

lstParam.Add(String.Format("{0}={1}", item.Key, item.Value));

}

return String.Join("&", lstParam);

}

1

2

3

4

5

6

7

8

9

· MD5加密

/// <summary>

/// 签名字符串

/// </summary>

/// <param name="prestr">需要签名的字符串</param>

/// <param name="key">密钥</param>

/// <param name="_input_charset">编码格式</param>

/// <returns>签名结果</returns>

public static string Sign(string prestr, string key, string _input_charset)

{

StringBuilder sb = new StringBuilder(32);

prestr = prestr +"&key="+ key;

MD5 md5 = new MD5CryptoServiceProvider();

byte[] t = md5.ComputeHash(Encoding.GetEncoding(_input_charset).GetBytes(prestr));

for (int i = 0; i < t.Length; i++)

{

sb.Append(t[i].ToString("x").PadLeft(2, '0'));

}

return sb.ToString();

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

· 把刚才生成的sign加入到SortedDictionary中然后生成xml:

param.Add("sign", "签名值");

1

/// <summary>

/// 把提交的参数参数转换为xml

/// </summary>

/// <param name="requestInfo"></param>

/// <returns></returns>

public static String GetPostXML(SortedDictionary<String, String> requestInfo)

{

List<String> xml = new List<String>();

xml.Add("<xml>");

SortedDictionary<String, String> post_param = ExcludeEmpty(requestInfo);

foreach (var item in post_param)

{

if (item.Key == "detail")

{

xml.Add(String.Format("<{0}><![CDATA[{1}]]</{0}>", item.Key, item.Value));

}

else {

xml.Add(String.Format("<{0}>{1}</{0}>", item.Key, item.Value));

}

}

xml.Add("</xml>");

return String.Join("", xml);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

(微信的detail 是使用CDATA防止xml转义的,其他参数不能采用)

好了,把XML使用POST提交到接口吧,地址是:https://api.mch.weixin.qq.com/pay/unifiedorder

返回结果也是xml的,可以返序列化。使用这段代码可以直接反序列化成实体类:

/// <summary>

/// 反序列化xml

/// </summary>

/// <param name="xml">XML字符串</param>

/// <returns></returns>

public static T Deserialize<T>(string xml)

{

try

{

String typename=typeof(T).Name;

xml = xml.Replace("<xml>", "<" + typename + ">").Replace("</xml>", "</" + typename + ">");

using (StringReader sr = new StringReader(xml))

{

XmlSerializer xmldes = new XmlSerializer(typeof(T));

return (T)xmldes.Deserialize(sr);

}

}

catch (Exception e)

{

return default(T);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

好了,正常情况下就可以获取到prepay_id了。

3.前端H5调用微信支付对话框

请参考官方文档

对接过统一下单接口,这个过程就很简单了。按照这个文档做就好了。这里的随机字符串和签名都是重新生成的,生成方式和前面说的一样。

前端页面JS:

function onBridgeReady() {

WeixinJSBridge.invoke(

'getBrandWCPayRequest', {

"appId": "wx8888888888888888", //公众号名称,由商户传入

"timeStamp": "1414561699", //时间戳,自1970年以来的秒数

"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS", //随机串

"package": "prepay_id=123456789",

"signType": "MD5", //微信签名方式:

"paySign": "C380BEC2BFD727A4B6845133519F3AD6" //微信签名

},

function (res) {

if (res.err_msg == "get_brand_wcpay_request:ok") {

//支付成功,后续自行处理

}

else

{

//支付取消,或者其他错误,自行处理

}

});

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

好了,到现在支付就完成了,下面还需要获取一下支付通知,最终确认一下支付状态。

4.接收支付通知

请自行阅读一下,官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7

简单说明一下文档中return_code 和result_code 分别表示通信结果和业务(支付)执行结果。收到参数后自己记录一下信息,尤其是支付单号transaction_id,然后再按照约定格式返回一个xml表示成功就可以了。

必须要注意的是,一定要对收到的参数进行签名,验证收到的消息来源是否来自微信,千万不可懒。因为加密所需的key只有你和微信知道,所以加密结果可能能对起来。如果key泄漏了会很危险,因为其他人可能会伪造一个结果,告诉你支付成功了。key可以在微信支付平台中修改。

最后

这次对接过程还比较顺利,没有遇到什么坑,记得第一次做微信接口的时候,不知道是文档我没理解,还是其他什么原因,用了很长事件。当然只按照上面说的是不行的,这里只是提供一个思路和几个辅助的代码段,还需要按照你自己的业务逻辑进行改造,至于其他接口大同小异,不在叙述了。

相关推荐