C#微信开发之旅(九):JSAPI支付(V3)(相关代码待补全)

微信开发遇到最复杂的就是支付了,无论V2还是V3。这篇文章将给出全套的V3版本JSAPI支付代码,包括预支付->支付->订单查询->通知->退款,其中前三步已经上线应用,退款只是简单测试了一下,大家要用的话需要谨慎。。。

一、预支付&支付

实际就是讲订单信息交给微信端,返回给我们一个预支付id(与V2app支付相似),支付时将预支付id交给微信处理。注意:预支付id 需存储,每个out_trade_no(我们自己的订单号)只能对应一个预支付id。代码奉上:(mvc demo 最后会一并发出)

 1         public ActionResult Pay()
 2         {
 3             string code = "";//网页授权获得的code
 4             string orderNo = ""; //文档中的out_trade_no
 5             string description = ""; //商品描述
 6             string totalFee = "1";//订单金额(单位:分)
 7             string createIp = "127.0.0.1";
 8             string notifyUrl = ""; //通知url
 9             string openId = WeiXinHelper.GetUserOpenId(code);//通过网页授权code获取用户openid(或者之前有存储用户的openid 也可以直接拿来用)
10
11             //prepayid 只有第一次支付时生成,如果需要再次支付,必须拿之前生成的prepayid。
12             //也就是说一个orderNo 只能对应一个prepayid
13             string prepayid = string.Empty;
14
15             #region 之前生成过 prepayid,此处可略过
16
17             //创建Model
18             UnifiedWxPayModel model = UnifiedWxPayModel.CreateUnifiedModel(WeiXinConst.AppId, WeiXinConst.PartnerId, WeiXinConst.PartnerKey);
19
20             //预支付
21             UnifiedPrePayMessage result = WeiXinHelper.UnifiedPrePay(model.CreatePrePayPackage(description, orderNo, totalFee, createIp, notifyUrl, openId));
22
23             if (result == null
24                     || !result.ReturnSuccess
25                     || !result.ResultSuccess
26                     || string.IsNullOrEmpty(result.Prepay_Id))
27             {
28                 throw new Exception("获取PrepayId 失败");
29             }
30
31             //预支付订单
32             prepayid = result.Prepay_Id;
33
34             #endregion
35
36             //JSAPI 支付参数的Model
37             PayModel payModel = new PayModel()
38             {
39                 AppId = model.AppId,
40                 Package = string.Format("prepay_id={0}", prepayid),
41                 Timestamp = ((DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000).ToString(),
42                 Noncestr = CommonUtil.CreateNoncestr(),
43             };
44
45             Dictionary<string, string> nativeObj = new Dictionary<string, string>();
46             nativeObj.Add("appId", payModel.AppId);
47             nativeObj.Add("package", payModel.Package);
48             nativeObj.Add("timeStamp", payModel.Timestamp);
49             nativeObj.Add("nonceStr", payModel.Noncestr);
50             nativeObj.Add("signType", payModel.SignType);
51             payModel.PaySign = model.GetCftPackage(nativeObj); //生成JSAPI 支付签名
52
53
54             return View(payModel);
55         }

UnifiedWxPayModel 为V3统一支付帮助类,包括V3相关接口参数生成及签名的实现:

这里用到的 生成预支付请求参数Xml:

 1         #region 生成 预支付 请求参数(XML)
 2         /// <summary>
 3         /// 生成 预支付 请求参数(XML)
 4         /// </summary>
 5         /// <param name="description"></param>
 6         /// <param name="tradeNo"></param>
 7         /// <param name="totalFee"></param>
 8         /// <param name="createIp"></param>
 9         /// <param name="notifyUrl"></param>
10         /// <param name="openid"></param>
11         /// <returns></returns>
12         public string CreatePrePayPackage(string description, string tradeNo, string totalFee, string createIp, string notifyUrl, string openid)
13         {
14             Dictionary<string, string> nativeObj = new Dictionary<string, string>();
15
16             nativeObj.Add("appid", AppId);
17             nativeObj.Add("mch_id", PartnerId);
18             nativeObj.Add("nonce_str", CommonUtil.CreateNoncestr());
19             nativeObj.Add("body", description);
20             nativeObj.Add("out_trade_no", tradeNo);
21             nativeObj.Add("total_fee", totalFee); //todo:写死为1
22             nativeObj.Add("spbill_create_ip", createIp);
23             nativeObj.Add("notify_url", notifyUrl);
24             nativeObj.Add("trade_type", "JSAPI");
25             nativeObj.Add("openid", openid);
26             nativeObj.Add("sign", GetCftPackage(nativeObj));
27
28             return DictionaryToXmlString(nativeObj);
29         }
30
31         #endregion

预支付请求在WeiXinHelper中,实现方式与前几篇中相似,这里就不上代码了。

二、订单查询

JSAPI返回支付成功,我们需要到后台查询下订单状态以确定支付是否成功,如果后台未接到通知,则要到微信服务器查询订单状态;最后才能展示给用户支付的结果:

 1         /// <summary>
 2         /// 到微信服务器查询 订单支付的结果 (jsapi支付返回ok,我们要判断下服务器支付状态,如果没有支付成功,到微信服务器查询)
 3         /// </summary>
 4         /// <param name="orderNo"></param>
 5         public bool QueryOrder(string orderNo)
 6         {
 7             //这里应先判断服务器 订单支付状态,如果接到通知,并已经支付成功,就不用 执行下面的查询了
 8             UnifiedWxPayModel model = UnifiedWxPayModel.CreateUnifiedModel(WeiXinConst.AppId, WeiXinConst.PartnerId, WeiXinConst.PartnerKey);
 9             UnifiedOrderQueryMessage message = WeiXinHelper.UnifiedOrderQuery(model.CreateOrderQueryXml(orderNo));
10             //此处主动查询的结果,只做查询用(不能作为支付成功的依据)
11             return message.Success;
12         }

三、通知

微信支付通知以Post Xml方式:

 1         /// <summary>
 2         /// 微信支付通知(貌似比较臃肿,待优化)
 3         /// </summary>
 4         /// <returns></returns>
 5         public void Notify()
 6         {
 7             ReturnMessage returnMsg = new ReturnMessage() { Return_Code = "SUCCESS", Return_Msg = "" };
 8             string xmlString = GetXmlString(Request);
 9             NotifyMessage message = null;
10             try
11             {
12                 //此处应记录日志
13                 message = HttpClientHelper.XmlDeserialize<NotifyMessage>(xmlString);
14
15                 #region 验证签名并处理通知
16                 XmlDocument doc = new XmlDocument();
17                 doc.LoadXml(xmlString);
18
19                 Dictionary<string, string> dic = new Dictionary<string, string>();
20                 string sign = string.Empty;
21                 foreach (XmlNode node in doc.FirstChild.ChildNodes)
22                 {
23                     if (node.Name.ToLower() != "sign")
24                         dic.Add(node.Name, node.InnerText);
25                     else
26                         sign = node.InnerText;
27                 }
28
29                 UnifiedWxPayModel model = UnifiedWxPayModel.CreateUnifiedModel(WeiXinConst.AppId, WeiXinConst.PartnerId, WeiXinConst.PartnerKey);
30                 if (model.ValidateMD5Signature(dic, sign))
31                 {
32                     //处理通知
33                 }
34                 else
35                 {
36                     throw new Exception("签名未通过!");
37                 }
38
39                 #endregion
40
41             }
42             catch (Exception ex)
43             {
44                 //此处记录异常日志
45                 returnMsg.Return_Code = "FAIL";
46                 returnMsg.Return_Msg = ex.Message;
47             }
48             Response.Write(returnMsg.ToXmlString());
49             Response.End();
50         }
51
52         /// <summary>
53         /// 获取Post Xml数据
54         /// </summary>
55         /// <param name="request"></param>
56         /// <returns></returns>
57         private string GetXmlString(HttpRequestBase request)
58         {
59             using (System.IO.Stream stream = request.InputStream)
60             {
61                 Byte[] postBytes = new Byte[stream.Length];
62                 stream.Read(postBytes, 0, (Int32)stream.Length);
63                 return System.Text.Encoding.UTF8.GetString(postBytes);
64             }
65         }

四、退款

退款需要用到证书,配置WeiXinConst内证书相关常量再使用:

 1         /// <summary>
 2         /// 订单退款
 3         /// </summary>
 4         /// <param name="transaction_Id">微信交易单号</param>
 5         /// <param name="orderNo">我们自己的单号</param>
 6         /// <param name="totalFee">订单金额(分)</param>
 7         /// <param name="refundNo">退款单号(我们自己定义)</param>
 8         /// <param name="refundFee">退款金额(分)</param>
 9         /// <returns></returns>
10         public bool UnifiedOrderRefund(string transaction_Id,string orderNo,string totalFee, string refundNo,string refundFee)
11         {
12             UnifiedWxPayModel model = UnifiedWxPayModel.CreateUnifiedModel(WeiXinConst.AppId, WeiXinConst.PartnerId, WeiXinConst.PartnerKey);
13             string postData = model.CreateOrderRefundXml(orderNo, transaction_Id, totalFee, refundNo, refundFee);
14             //退款需要用到证书, 要配置WeiXineConst CertPath 和 CertPwd
15             return WeiXinHelper.Refund(postData, WeiXinConst.CertPath, WeiXinConst.CertPwd);
16         }

私有的方法:

  1. Dictionary<string,string>转为XmlDocument

     1         /// <summary>
     2         /// dictionary转为xml 字符串
     3         /// </summary>
     4         /// <param name="dic"></param>
     5         /// <returns></returns>
     6         private static string DictionaryToXmlString(Dictionary<string, string> dic)
     7         {
     8             StringBuilder xmlString = new StringBuilder();
     9             xmlString.Append("<xml>");
    10             foreach (string key in dic.Keys)
    11             {
    12                 xmlString.Append(string.Format("<{0}><![CDATA[{1}]]></{0}>", key, dic[key]));
    13             }
    14             xmlString.Append("</xml>");
    15             return xmlString.ToString();
    16         }

  2. XmlDocument转为Dictionary<string,string> 

     1         /// <summary>
     2         /// xml字符串 转换为  dictionary
     3         /// </summary>
     4         /// <param name="document"></param>
     5         /// <returns></returns>
     6         public static Dictionary<string, string> XmlToDictionary(string xmlString)
     7         {
     8             System.Xml.XmlDocument document = new System.Xml.XmlDocument();
     9             document.LoadXml(xmlString);
    10
    11             Dictionary<string, string> dic = new Dictionary<string, string>();
    12
    13             var nodes = document.FirstChild.ChildNodes;
    14
    15             foreach (System.Xml.XmlNode item in nodes)
    16             {
    17                 dic.Add(item.Name, item.InnerText);
    18             }
    19             return dic;
    20         }

时间: 2024-12-21 08:11:08

C#微信开发之旅(九):JSAPI支付(V3)(相关代码待补全)的相关文章

C#微信开发之旅(三):AccessToken获取及全局管理

由于AccessToken有效期为2小时,并且接口调用有数量限制,所以开始时选择用WCF做了全局管理(项目中要到AccessToken的地方太多了,支付相关.生成二维码.获取用户信息.菜单操作等等) 下面是AccessToken全局管理的单例类,(原理:通过微信接口获取AccessToken,存储在内存中,当其他项目调用时,会判断是否过期,过期去拿新Token再返回): 1 /// <summary> 2 /// AccessToken类,公众号通过此token 获取相关信息 (单例类) 3

C#微信开发之旅(十三):V2订单查询&amp;退款(完结)

订单查询 用处同V3订单查询,直接上代码: 1 /// <summary> 2 /// V2订单查询 3 /// </summary> 4 public void QueryOrder() 5 { 6 string orderNo = string.Empty; 7 8 WxPayModel model = WxPayModel.Create(orderNo); 9 OrderQueryMessage message = WeiXinHelper.OrderQuery(model.

ADT开发中的一些优化设置:代码背景色、代码字体大小、代码自动补全

初学Android开发,在网上找到一些ADT工具的优化,自己设置好了,截图保存下来.免得以后忘了. 1. 设置背景颜色: 色调85.饱和度90.亮度205 RGB:199.237.204 2. 设置代码的字体 设置JAVA文件代码的字体: 设置XML文件中代码的字体: 3. 设置自动补全代码 刚刚学Android,有很多变量和方法 都不熟悉.需要有提示,才更加方便. 快捷方式:Alt + /    可以出现代码提示. 默认的只有输入“ .” 以后才会有代码补全提示,可作如下设置: 在Auto a

微信支付服务商模式(受理机构模式)开发注意事项,jsapi支付

1.首先下载的demo,一般都是有些bug的,先要改一下. 2.微信貌似没有为服务商模式单独开发demo,下载的也都是普通商户的支付demo,其实这里没有必要单独写,因为他们区别就是几个参数的区别. (0)demo里设置的参数全部都要填服务商的,而不是子商户的. (1)第一个区别是openid,demo里直接传的openid,服务商模式需要传sub_openid,获取的方式就不说明了,总之获取所需都是子商户即特约商户的appid,appsecret. (2)需要在传入页面多传一个sub_mch_

C#微信开发之旅(十):APP预支付及支付参数生成(V2)

App支付流程: 本篇随笔只实现红框内的两个功能:生成预支付Id,生成app支付参数 1 /// <summary> 2 /// App 预支付 3 /// </summary> 4 /// <returns></returns> 5 public ActionResult AppPrePay() 6 { 7 string orderNo = ""; //订单编号,文档中的out_trade_no 8 string description

C#微信开发之旅(三):基础类之WeiXinConst

开发过程中需要用的的公众号信息在这里配置,此外需要用到的Url信息无需更改. /// <summary> /// 微信 需要用到的Url.Json常量 /// </summary> public class WeiXinConst { #region Value Const /// <summary> /// 微信开发者 AppId /// </summary> public const string AppId = "你的AppId";

C#微信开发之旅(十一):V2发货接口

用户支付完成后,V2版本微信支付需要调用发货接口,否则微信会告警并且用户也可以进行维权,总之会有灰常多的事情: 1 public void DeliverNotify() 2 { 3 string openId = string.Empty; //买家openid 4 string transactionId = string.Empty;//微信交易单号 5 string orderNo = string.Empty;//我们自己的订单号,文档中的out_trade_no 6 7 8 WxPa

C#微信开发之旅(二):基础类之HttpClientHelper

包含通过HttpClient发起get或post请求的方法,所有调用微信接口的操作都通过此类.话不多说,直接上代码: 1 public class HttpClientHelper 2 { 3 /// <summary> 4 /// get请求 5 /// </summary> 6 /// <param name="url"></param> 7 /// <returns></returns> 8 public s

C#微信开发之旅(七):根据经纬度获取地址(百度地图Api)

开发过程中遇到这样的需求,根据用户的地理位置不同,显示不同区域的产品. 这里用到了微信:获取用户地理位置 的功能,(每隔5秒上报 或 进入回话时上报一次),我们根据微信推送过来的经纬度,来转换成实际地址,这里用到的是百度地图Api(要用的话先申请百度ak). PS:微信的这个功能很不稳定,靠它不靠谱,经常不推送...(后来加了手动定位,百度地图Web定位组件 还不错,不是广告!0.0) #region 根据经纬度 获取地址信息 BaiduApi /// <summary> /// 根据经纬度