首页 > 代码库 > 公众号微信支付

公众号微信支付

1、概要

     公众号是以微信用户的一个联系人形式存在的,支付是微信服务号的核心一环。

     本篇主要介绍微信支付这一功能,避免大家再跳微信支付的坑。

1.1 关于Magicodes.WeChat.SDK

      MAGICODES.WECHAT.SDK为心莱团队封装的轻量级微信SDK,现已全部开源,开源库地址为:https://github.com/xin-lai/Magicodes.WeChat.SDK

      更多介绍,请关注后续博客。

2、微信公众号支付

     用户已有商城网址,用户通过微信消息、微信扫描二维码、微信自定义菜单等操作在微信内打开网页时,可以调用微信支付完成下单购买流程。

2.1    支付流程

  2.1.1    微信网页支付流程

      技术分享

 

备注:实际流程其实非常简单

  ① 用户支付之前,程序生成订单并调用统一下单API()

  ② 微信系统返回预付单信息

  ③ 根据信息生成JSAPI页面调用的支付参数并签名,jssdk调用

  ④ 用户支付,jsapi向微信系统发送请求

  ⑤ 微信系统返回支付结果给用户,同时异步发送结果给程序后台(程序没有收到通知,可以调用查询接口)

  ⑥ 支付完成,用户界面根据结果做相关页面跳转或提示处理,程序后台根据通知做订单状态变更等逻辑处理。

2.1.2    刷卡支付

    后续更新

2.1.3    扫码支付

     后续更新

2.1.4    app支付

     后续更新 

2.2    注意事项

2.3    开发实践 

   2.3.1    开发配置

   1、设置测试目录

在微信公众平台设置。支付测试状态下,设置测试目录,测试人的微信号添加到白名单,发起支付的页面目录必须与设置的精确匹配。并将支付链接发到对应的公众号会话窗口中才能正常发起支付测试。注意正式目录一定不能与测试目录设置成一样,否则支付会出错。

       技术分享

 

友情提示:如果是使用测试目录的地址,一定要记得把个人测试微信号添加到白名单。

   2、设置正式支付目录

根据图中栏目顺序进入修改栏目,勾选JSAPI网页支付开通该权限,并配置好支付授权目录,该目录必须是发起支付的页面的精确目录,子目录下无法正常调用支付。具体界面如图:

      技术分享

 

友情提示:注意红色框框里面的说明,一不小心会很容易进坑的。

   2.3.2    开发程序

            直接看代码吧

微信支付业务类

技术分享
  1 /// <summary>  2 /// 微信支付接口,官方API:https://mp.weixin.qq.com/paymch/readtemplate?t=mp/business/course2_tmpl&lang=zh_CN&token=25857919#4  3   4     /// </summary>  5     public class TenPayV3 : PayBase  6   7     {  8         public UnifiedorderResult Unifiedorder(UnifiedorderRequest model)  9  10         { 11  12             var url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; 13  14   15  16   17  18             UnifiedorderResult result = null; 19  20   21  22             model.AppId = WeiChatConfig.AppId; 23  24             model.MchId = PayConfig.MchId; 25  26             if (model.NotifyUrl == null) 27  28                 model.NotifyUrl = PayConfig.Notify; 29  30             Dictionary<string, string> dictionary = PayUtil.GetAuthors<UnifiedorderRequest>(model); 31  32             model.Sign = PayUtil.CreateMd5Sign(dictionary, PayConfig.TenPayKey);//生成Sign 33  34             Dictionary<string, string> dict = PayUtil.GetAuthors<UnifiedorderRequest>(model); 35  36             result = PostXML<UnifiedorderResult>(url, model); 37  38             return result; 39  40         }       41  42   43  44         /// <summary> 45  46         /// 订单查询接口 47  48         /// </summary> 49  50         /// <param name="data"></param> 51  52         /// <returns></returns> 53  54         public static string OrderQuery(string data) 55  56         { 57  58             var urlFormat = "https://api.mch.weixin.qq.com/pay/orderquery"; 59  60   61  62             var formDataBytes = data =http://www.mamicode.com/= null ? new byte[0] : Encoding.UTF8.GetBytes(data); 63  64             using (MemoryStream ms = new MemoryStream()) 65  66             { 67  68                 ms.Write(formDataBytes, 0, formDataBytes.Length); 69  70                 ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置 71  72                 return RequestUtility.HttpPost(urlFormat, null, ms); 73  74             } 75  76         } 77  78   79  80         /// <summary> 81  82         /// 关闭订单接口 83  84         /// </summary> 85  86         /// <param name="data">关闭订单需要post的xml数据</param> 87  88         /// <returns></returns> 89  90         public static string CloseOrder(string data) 91  92         { 93  94             var urlFormat = "https://api.mch.weixin.qq.com/pay/closeorder"; 95  96   97  98             var formDataBytes = data =http://www.mamicode.com/= null ? new byte[0] : Encoding.UTF8.GetBytes(data); 99 100             using (MemoryStream ms = new MemoryStream())101 102             {103 104                 ms.Write(formDataBytes, 0, formDataBytes.Length);105 106                 ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置107 108                 return RequestUtility.HttpPost(urlFormat, null, ms);109 110             }111 112         }113 114  115 116       117 118  119 120         /// <summary>121 122         /// 退款查询接口123 124         /// </summary>125 126         /// <param name="data"></param>127 128         /// <returns></returns>129 130         public static string RefundQuery(string data)131 132         {133 134             var urlFormat = "https://api.mch.weixin.qq.com/pay/refundquery";135 136  137 138             var formDataBytes = data =http://www.mamicode.com/= null ? new byte[0] : Encoding.UTF8.GetBytes(data);139 140             using (MemoryStream ms = new MemoryStream())141 142             {143 144                 ms.Write(formDataBytes, 0, formDataBytes.Length);145 146                 ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置147 148                 return RequestUtility.HttpPost(urlFormat, null, ms);149 150             }151 152         }153 154  155 156         /// <summary>157 158         /// 对账单接口159 160         /// </summary>161 162         /// <param name="data"></param>163 164         /// <returns></returns>165 166         public static string DownloadBill(string data)167 168         {169 170             var urlFormat = "https://api.mch.weixin.qq.com/pay/downloadbill";171 172  173 174             var formDataBytes = data =http://www.mamicode.com/= null ? new byte[0] : Encoding.UTF8.GetBytes(data);175 176             using (MemoryStream ms = new MemoryStream())177 178             {179 180                 ms.Write(formDataBytes, 0, formDataBytes.Length);181 182                 ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置183 184                 return RequestUtility.HttpPost(urlFormat, null, ms);185 186             }187 188         }189 190  191 192         /// <summary>193 194         /// 短链接转换接口195 196         /// </summary>197 198         /// <param name="data"></param>199 200         /// <returns></returns>201 202         public static string ShortUrl(string data)203 204         {205 206             var urlFormat = "https://api.mch.weixin.qq.com/tools/shorturl";207 208  209 210             var formDataBytes = data =http://www.mamicode.com/= null ? new byte[0] : Encoding.UTF8.GetBytes(data);211 212             using (MemoryStream ms = new MemoryStream())213 214             {215 216                 ms.Write(formDataBytes, 0, formDataBytes.Length);217 218                 ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置219 220                 return RequestUtility.HttpPost(urlFormat, null, ms);221 222             }223 224         }225 226         /// <summary>227 228         ///229 230         /// </summary>231 232         /// <param name="page"></param>233 234         /// <returns></returns>235 236         public NotifyResult Notify(Stream inputStream)237 238         {239 240             NotifyResult result = null;241 242             string data =http://www.mamicode.com/ PayUtil.PostInput(inputStream);243 244             result = XmlHelper.DeserializeObject<NotifyResult>(data);245 246             return result;247 248         }249 250         /// <summary>251 252         /// 通知并返回处理XML253 254         /// </summary>255 256         /// <param name="inputStream">输入流</param>257 258         /// <param name="successAction">成功处理逻辑回调函数</param>259 260         /// <param name="failAction">失败处理逻辑回调函数</param>261 262         /// <param name="successMsg">成功返回消息</param>263 264         /// <param name="errorMsg">失败返回消息</param>265 266         /// <param name="isSync">是否异步执行相关处理逻辑</param>267 268         /// <returns></returns>269 270         public string NotifyAndReurnResult(Stream inputStream, Action<NotifyResult> successAction, Action<NotifyResult> failAction, string successMsg = "OK", string errorMsg = "FAIL", bool isSync = true)271 272         {273 274             var result = Notify(inputStream);275 276             var request = new NotifyRequest();277 278             request.ReturnCode = "FAIL";279 280             if (result.IsSuccess())281 282             {283 284                 if (isSync)285 286                     Task.Run(() => successAction(result));287 288                 else289 290                     successAction.Invoke(result);291 292                 //交易成功293 294                 request.ReturnCode = "SUCCESS";295 296                 request.ReturnMsg = successMsg;297 298                 return XmlHelper.SerializeObject(request);299 300             }301 302             else303 304             {305 306                 if (isSync)307 308                     Task.Run(() => failAction(result));309 310                 else311 312                     failAction.Invoke(result);313 314                 request.ReturnMsg = errorMsg;315 316                 return XmlHelper.SerializeObject(request);317 318             }319 320         }321 322 }323 324 }
View Code

把返回参数和请求参数,序列化成对象,方便我们在编写我们本身逻辑的时候调用

技术分享
  1  [XmlRoot("xml")]  2     [Serializable()]  3     public class Result : PayResult  4     {  5         /// <summary>  6         /// 微信分配的公众账号ID  7         /// </summary>  8         [XmlElement("appid")]  9         public string AppId { get; set; } 10         /// <summary> 11         /// 微信支付分配的商户号 12         /// </summary> 13         [XmlElement("mch_id")] 14         public string Mch_Id { get; set; } 15         /// <summary> 16         /// 微信支付分配的终端设备号 17         /// </summary> 18         [XmlElement("device_info")] 19         public string Device_Info { get; set; } 20         /// <summary> 21         /// 随机字符串,不长于32 位 22         /// </summary> 23         [XmlElement("nonce_str")] 24         public string NonceStr { get; set; } 25         /// <summary> 26         /// 签名 27         /// </summary> 28         [XmlElement("sign")] 29         public string Sign { get; set; } 30         /// <summary> 31         /// SUCCESS/FAIL 32         /// </summary> 33         [XmlElement("result_code")] 34         public string ResultCode { get; set; } 35         [XmlElement("err_code")] 36         public string ErrCode { get; set; } 37         [XmlElement("err_code_des")] 38         public string ErrCodeDes { get; set; } 39     } 40  41     [XmlRoot("xml")] 42     [Serializable()] 43     public class UnifiedorderResult : Result 44     { 45         /// <summary> 46         /// 交易类型:JSAPI、NATIVE、APP 47         /// </summary> 48         [XmlElement("trade_type")] 49         public string TradeType { get; set; } 50         /// <summary> 51         /// 微信生成的预支付ID,用于后续接口调用中使用 52         /// </summary> 53         [XmlElement("prepay_id")] 54         public string PrepayId { get; set; } 55         /// <summary> 56         /// trade_type为NATIVE时有返回,此参数可直接生成二维码展示出来进行扫码支付 57         /// </summary> 58         [XmlElement("code_url")] 59         public string CodeUrl { get; set; } 60     } 61  62  63     [XmlRoot("xml")] 64     [Serializable()] 65     public class UnifiedorderRequest 66     { 67         /// <summary> 68         /// OpenId 69         /// </summary> 70         [XmlElement("openid")] 71         public string OpenId { get; set; } 72         /// <summary> 73         /// 【不用填写】微信开放平台审核通过的应用APPID 74         /// </summary> 75         [XmlElement("appid")] 76         public string AppId { get; set; } 77         /// <summary> 78         /// 【不用填写】微信支付分配的商户号 79         /// </summary> 80         [XmlElement("mch_id")] 81         public string MchId { get; set; } 82  83         /// <summary> 84         /// 终端设备号(门店号或收银设备ID),默认请传"WEB" 85         /// </summary> 86         [XmlElement("device_info")] 87         public string DeviceInfo { get; set; } 88         /// <summary> 89         /// 随机字符串,不长于32位 90         /// </summary> 91         [XmlElement("nonce_str")] 92         public string NonceStr { get; set; } 93         /// <summary> 94         /// 【不用填写】签名 95         /// </summary> 96         [XmlElement("sign")] 97         public string Sign { get; set; } 98         /// <summary> 99         /// 商品描述交易字段格式根据不同的应用场景按照以下格式: APP——需传入应用市场上的APP名字-实际商品名称,天天爱消除-游戏充值。100         /// </summary>101         [XmlElement("body")]102         public string Body { get; set; }103         /// <summary>104         /// 商品名称明细列表105         /// </summary>106         [XmlElement("detail")]107         public string Detail { get; set; }108         /// <summary>109         /// 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据110         /// </summary>111         [XmlElement("attach")]112         public string Attach { get; set; }113         /// <summary>114         /// 商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号115         /// </summary>116         [XmlElement("out_trade_no")]117         public string OutTradeNo { get; set; }118         /// <summary>119         /// 符合ISO 4217标准的三位字母代码,默认人民币:CNY120         /// </summary>121         [XmlElement("fee_type")]122         public string FeeType { get; set; }123         /// <summary>124         /// 订单总金额,单位为分125         /// </summary>126         [XmlElement("total_fee")]127         public string TotalFee { get; set; }128         /// <summary>129         /// 用户端实际ip130         /// </summary>131         [XmlElement("spbill_create_ip")]132         public string SpbillCreateIp { get; set; }133         /// <summary>134         /// 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。135         /// </summary>136         [XmlElement("time_start")]137         public string TimeStart { get; set; }138         /// <summary>139         /// 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010140         /// </summary>141         [XmlElement("time_expire")]142         public string TimeExpire { get; set; }143         /// <summary>144         /// 商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠145         /// </summary>146         [XmlElement("goods_tag")]147         public string GoodsTag { get; set; }148         /// <summary>149         /// 【不用填写】接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数150         /// </summary>151         [XmlElement("notify_url")]152         public string NotifyUrl { get; set; }153         /// <summary>154         /// 支付类型(JSAPI,NATIVE,APP)公众号内支付填JSAPI155         /// </summary>156         [XmlElement("trade_type")]157         public string TradeType { get; set; }158         /// <summary>159         /// no_credit--指定不能使用信用卡支付160         /// </summary>161         [XmlElement("limit_pay")]162         public string LimitPay { get; set; }163     }164     [XmlRoot("xml")]165     [Serializable()]166     public class NotifyResult : PayResult167     {168         /// <summary>169         /// 微信分配的公众账号ID(企业号corpid即为此appId)170         /// </summary>171         [XmlElement("appid")]172         public string AppId { get; set; }173         /// <summary>174         /// 微信支付分配的商户号175         /// </summary>176         [XmlElement("mch_id")]177         public string MchId { get; set; }178         /// <summary>179         /// 微信支付分配的终端设备号180         /// </summary>181         [XmlElement("device_info")]182         public string DeviceInfo { get; set; }183         /// <summary>184         /// 随机字符串,不长于32位185         /// </summary>186         [XmlElement("nonce_str")]187         public string NonceStr { get; set; }188         /// <summary>189         /// 签名190         /// </summary>191         [XmlElement("sign")]192         public string Sign { get; set; }193         /// <summary>194         /// 业务结果,SUCCESS/FAIL195         /// </summary>196         [XmlElement("result_code")]197         public string ResultCode { get; set; }198         /// <summary>199         /// 错误返回的信息描述200         /// </summary>201         [XmlElement("err_code")]202         public string ErrCode { get; set; }203         /// <summary>204         /// 错误返回的信息描述205         /// </summary>206         [XmlElement("err_code_des")]207         public string ErrCodeDes { get; set; }208         /// <summary>209         /// 用户在商户appid下的唯一标识210         /// </summary>211         [XmlElement("openid")]212         public string OpenId { get; set; }213         /// <summary>214         /// 用户是否关注公众账号,Y-关注,N-未关注,仅在公众账号类型支付有效215         /// </summary>216         [XmlElement("is_subscribe")]217         public string IsSubscribe { get; set; }218         /// <summary>219         /// 交易类型,JSAPI、NATIVE、APP220         /// </summary>221         [XmlElement("trade_type")]222         public string TradeType { get; set; }223         /// <summary>224         /// 银行类型,采用字符串类型的银行标识,银行类型见银行列表225         /// </summary>226         [XmlElement("bank_type")]227         public string BankType { get; set; }228         /// <summary>229         /// 订单总金额,单位为分230         /// </summary>231         [XmlElement("total_fee")]232         public string TotalFee { get; set; }233         /// <summary>234         /// 应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额235         /// </summary>236         [XmlElement("settlement_total_fee")]237         public string SettlementTotalFee { get; set; }238         /// <summary>239         /// 货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型240         /// </summary>241         [XmlElement("fee_type")]242         public string FeeType { get; set; }243         /// <summary>244         /// 货币类型现金支付金额订单现金支付金额,详见支付金额245         /// </summary>246         [XmlElement("cash_fee")]247         public string CashFee { get; set; }248         /// <summary>249         /// 货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型250         /// </summary>251         [XmlElement("cash_fee_type")]252         public string CashFeeType { get; set; }253         /// <summary>254         /// 代金券金额<=订单金额,订单金额-代金券金额=现金支付金额,详见支付金额]255         /// </summary>256         [XmlElement("coupon_fee")]257         public string CouponFee { get; set; }258         /// <summary>259         /// 代金券使用数量260         /// </summary>261         [XmlElement("coupon_count")]262         public string CouponCount { get; set; }263         /// <summary>264         /// CASH--充值代金券         NO_CASH---非充值代金券        订单使用代金券时有返回(取值:CASH、NO_CASH)。$n为下标,从0开始编号,举例:coupon_type_$0265         /// </summary>266         [XmlElement("coupon_type_$n")]267         public string CouponTypeN { get; set; }268         /// <summary>269         /// 代金券ID,$n为下标,从0开始编号270         /// </summary>271         [XmlElement("coupon_id_$n")]272         public string CouponIdN { get; set; }273         /// <summary>274         /// 单个代金券支付金额,$n为下标,从0开始编号275         /// </summary>276         [XmlElement("coupon_fee_$n")]277         public string CouponFeeN { get; set; }278         /// <summary>279         /// 微信支付订单号280         /// </summary>281         [XmlElement("transaction_id")]282         public string TransactionId { get; set; }283         /// <summary>284         /// 商户系统的订单号,与请求一致285         /// </summary>286         [XmlElement("out_trade_no")]287         public string OutTradeNo { get; set; }288         /// <summary>289         /// 商家数据包,原样返回290         /// </summary>291         [XmlElement("attach")]292         public string Attach { get; set; }293         /// <summary>294         /// 支付完成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则295         /// </summary>296         [XmlElement("time_end")]297         public string TimeEnd { get; set; }298     }299     [XmlRoot("xml")]300     [Serializable()]301     public class NotifyRequest302     {303         /// <summary>304         /// SUCCESS/FAIL SUCCESS表示商户接收通知成功并校验成功305         /// </summary>306         [XmlElement("return_code")]307         public string ReturnCode { get; set; }308         /// <summary>309         /// 返回信息,如非空,为错误原因:签名失败 参数格式校验错误310         /// </summary>311         [XmlElement("return_msg")]312         public string ReturnMsg { get; set; }313     }
View Code 

这些地方是获取微信支付的配置信息,appid,machid,密钥等信息。由于我们项目本身涉及到了多租户功能,所以Key是租户Id,这里通过Key获取不能租户的配置信息,如果不要实现多租户,默认不填写就OK,然后我们也可以通过把配置信息封装成一个对象写死在这里。具体源码可以去http:githup.com/xin-lai下载。全部功能已开源。

技术分享
 1 /// <summary> 2  3         /// POST提交请求,返回ApiResult对象 4  5         /// </summary> 6  7         /// <typeparam name="T">ApiResult对象</typeparam> 8  9         /// <param name="url">请求地址</param>10 11         /// <param name="obj">提交的数据对象</param>12 13         /// <returns>ApiResult对象</returns>14 15         protected T PostXML<T>(string url, object obj, Func<string, string> serializeStrFunc = null) where T : PayResult16 17         {18 19             var wr = new WeChatApiWebRequestHelper();20 21             string resultStr = null;22 23             var result = wr.HttpPost<T>(url, obj, out resultStr, serializeStrFunc, inputDataType: WebRequestDataTypes.XML, outDataType: WebRequestDataTypes.XML);24 25             if (result != null)26 27             {28 29                 result.DetailResult = resultStr;30 31             }32 33             return result;34 35         }36 37         /// <summary>38 39         /// POST提交请求,带证书,返回ApiResult对象40 41         /// </summary>42 43         /// <typeparam name="T">ApiResult对象</typeparam>44 45         /// <param name="url">请求地址</param>46 47         /// <param name="obj">提交的数据对象</param>48 49         /// <returns>ApiResult对象</returns>50 51         protected T PostXML<T>(string url, object obj, X509Certificate2 cer, Func<string, string> serializeStrFunc = null) where T : PayResult52 53         {54 55             var wr = new WeChatApiWebRequestHelper();56 57             string resultStr = null;58 59             var result = wr.HttpPost<T>(url, obj,cer, out resultStr, serializeStrFunc, inputDataType: WebRequestDataTypes.XML, outDataType: WebRequestDataTypes.XML);60 61             if (result != null)62 63             {64 65                 result.DetailResult = resultStr;66 67             }68 69             return result;70 71         }72 73     }
View Code

PayUtiy类,封装了一些公共方法

技术分享
  1 public static class PayUtil  2   3     {  4   5         /// <summary>  6   7         /// 随机生成Noncestr  8   9         /// </summary> 10  11         /// <returns></returns> 12  13         public static string GetNoncestr() 14  15         { 16  17             Random random = new Random(); 18  19             return MD5UtilHelper.GetMD5(random.Next(1000).ToString(), "GBK"); 20  21         } 22  23         /// <summary> 24  25         /// 根据当前系统时间加随机序列来生成订单号 26  27         /// </summary> 28  29         /// <returns>订单号</returns> 30  31         public static string GenerateOutTradeNo() 32  33         { 34  35             var ran = new Random(); 36  37             return string.Format("{0}{1}", UnixStamp(), ran.Next(999)); 38  39         } 40  41         /// <summary> 42  43         /// 获取时间戳 44  45         /// </summary> 46  47         /// <returns></returns> 48  49         public static string GetTimestamp() 50  51         { 52  53             TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); 54  55             return Convert.ToInt64(ts.TotalSeconds).ToString(); 56  57         } 58  59   60  61         /// <summary> 62  63         /// 对字符串进行URL编码 64  65         /// </summary> 66  67         /// <param name="instr"></param> 68  69         /// <param name="charset"></param> 70  71         /// <returns></returns> 72  73         public static string UrlEncode(string instr, string charset) 74  75         { 76  77             //return instr; 78  79             if (instr == null || instr.Trim() == "") 80  81                 return ""; 82  83             else 84  85             { 86  87                 string res; 88  89   90  91                 try 92  93                 { 94  95                     res = System.Web.HttpUtility.UrlEncode(instr, Encoding.GetEncoding(charset)); 96  97   98  99                 }100 101                 catch (Exception ex)102 103                 {104 105                     res = System.Web.HttpUtility.UrlEncode(instr, Encoding.GetEncoding("GB2312"));106 107                 }108 109  110 111  112 113                 return res;114 115             }116 117         }118 119  120 121         /// <summary>122 123         /// 对字符串进行URL解码124 125         /// </summary>126 127         /// <param name="instr"></param>128 129         /// <param name="charset"></param>130 131         /// <returns></returns>132 133         public static string UrlDecode(string instr, string charset)134 135         {136 137             if (instr == null || instr.Trim() == "")138 139                 return "";140 141             else142 143             {144 145                 string res;146 147  148 149                 try150 151                 {152 153                     res = System.Web.HttpUtility.UrlDecode(instr, Encoding.GetEncoding(charset));154 155  156 157                 }158 159                 catch (Exception ex)160 161                 {162 163                     res = System.Web.HttpUtility.UrlDecode(instr, Encoding.GetEncoding("GB2312"));164 165                 }166 167  168 169  170 171                 return res;172 173  174 175             }176 177         }178 179  180 181  182 183         /// <summary>184 185         /// 取时间戳生成随即数,替换交易单号中的后10位流水号186 187         /// </summary>188 189         /// <returns></returns>190 191         public static UInt32 UnixStamp()192 193         {194 195             TimeSpan ts = DateTime.Now - TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));196 197             return Convert.ToUInt32(ts.TotalSeconds);198 199         }200 201         /// <summary>202 203         /// 取随机数204 205         /// </summary>206 207         /// <param name="length"></param>208 209         /// <returns></returns>210 211         public static string BuildRandomStr(int length)212 213         {214 215             Random rand = new Random();216 217  218 219             int num = rand.Next();220 221  222 223             string str = num.ToString();224 225  226 227             if (str.Length > length)228 229             {230 231                 str = str.Substring(0, length);232 233             }234 235             else if (str.Length < length)236 237             {238 239                 int n = length - str.Length;240 241                 while (n > 0)242 243                 {244 245                     str.Insert(0, "0");246 247                     n--;248 249                 }250 251             }252 253  254 255             return str;256 257         }258 259         /// <summary>260 261         /// 循环获取一个实体类每个字段的XmlAttribute属性的值262 263         /// </summary>264 265         /// <typeparam name="T"></typeparam>266 267         /// <returns></returns>268 269         public static Dictionary<string, string> GetAuthors<T>(T model)270 271         {272 273             Dictionary<string, string> _dict = new Dictionary<string, string>();274 275  276 277             Type type = model.GetType(); //获取类型278 279  280 281             PropertyInfo[] props = typeof(T).GetProperties();282 283             foreach (PropertyInfo prop in props)284 285             {286 287                 object[] attrs = prop.GetCustomAttributes(true);288 289                 foreach (object attr in attrs)290 291                 {292 293                     XmlElementAttribute authAttr = attr as XmlElementAttribute;294 295                     if (authAttr != null)296 297                     {298 299                         string auth = authAttr.ElementName;300 301  302 303                         PropertyInfo property = type.GetProperty(prop.Name);304 305                         string value = http://www.mamicode.com/(string)property.GetValue(model, null); //获取属性值306 307  308 309                         _dict.Add(auth, value);310 311                     }312 313                 }314 315             }316 317             return _dict;318 319         }320 321  322 323         /// <summary>324 325         /// 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名326 327         /// </summary>328 329         /// <param name="key">参数名</param>330 331         /// <param name="value">参数值</param>332 333         /// key和value通常用于填充最后一组参数334 335         /// <returns></returns>336 337         public static string CreateMd5Sign(Dictionary<string, string> dict, string value)338 339         {340 341             ArrayList akeys = new ArrayList();342 343             foreach (var x in dict)344 345             {346 347                 if ("sign".CompareTo(x.Key) == 0)348 349                     continue;350 351                 akeys.Add(x.Key);352 353             }354 355             StringBuilder sb = new StringBuilder();356 357             akeys.Sort();358 359  360 361             foreach (string k in akeys)362 363             {364 365                 string v = (string)dict[k];366 367                 if (null != v && "".CompareTo(v) != 0368 369                     && "sign".CompareTo(k) != 0 && "key".CompareTo(k) != 0)370 371                 {372 373                     sb.Append(k + "=" + v + "&");374 375                 }376 377             }378 379             sb.Append("key=" + value);380 381             var md5 = MD5.Create();382 383             var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString()));384 385             var sbuilder = new StringBuilder();386 387             foreach (byte b in bs)388 389             {390 391                 sbuilder.Append(b.ToString("x2"));392 393             }394 395             //所有字符转为大写396 397             return sbuilder.ToString().ToUpper();398 399         }400 401         /// <summary>402 403         /// 接收post数据404 405         /// </summary>406 407         /// <param name="context"></param>408 409         /// <returns></returns>410 411         public static string PostInput(Stream stream)412 413         {414 415             int count = 0;416 417             byte[] buffer = new byte[1024];418 419             StringBuilder builder = new StringBuilder();420 421             while ((count = stream.Read(buffer, 0, 1024)) > 0)422 423             {424 425                 builder.Append(Encoding.UTF8.GetString(buffer, 0, count));426 427             }428 429             return builder.ToString();430 431         }432 433     }434 435 PayResult类,请求参数基类436 437 [XmlRoot("xml")]438 439     [Serializable()]440 441     public class PayResult442 443     {444 445         public virtual bool IsSuccess()446 447         {448 449             return this.ReturnCode == "SUCCESS";450 451         }452 453         /// <summary>454 455         /// 返回状态码456 457         /// SUCCESS/FAIL,此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断458 459         /// </summary>460 461         [XmlElement("return_code")]462 463         public string ReturnCode { get; set; }464 465  466 467         /// <summary>468 469         /// 返回信息,返回信息,如非空,为错误原因,签名失败,参数格式校验错误470 471         /// </summary>472 473         [XmlElement("return_msg")]474 475         public string Message { get; set; }476 477  478 479         /// <summary>480 481         /// 详细内容482 483         /// </summary>484 485         [XmlIgnore]486 487         public string DetailResult { get; set; }488 489     }
View Code

支付完成之后,异步回掉的处理

技术分享
 1 /// <summary> 2  3         /// 微信支付回调地址 4  5         /// </summary> 6  7         /// <param name="tenantId"></param> 8  9         /// <returns></returns>10 11         [Route("PayNotify/{tenantId}")]12 13         [AllowAnonymous]14 15         public ActionResult PayNotify(int tenantId)16 17         {18 19             Action<NotifyResult> successAction = (result) =>20 21             {22 23                 using (var context = new AppDbContext())24 25                 {26 27                     var order = context.Order_Infos.FirstOrDefault(o => o.Code == result.OutTradeNo);28 29                     if (null != order)30 31                     {32 33                         //修改订单状态34 35                         order.State = EnumOrderStatus.Overhang;36 37                         order.ThirdPayType = EnumThirdPayType.WX;38 39                         order.PaymentOn = DateTime.Now;40 41                         order.UpdateTime = DateTime.Now;42 43                         context.SaveChanges();44 45  46 47  48 49                     }50 51                     else52 53                     {54 55                         logger.Log(LoggerLevels.Debug, "Order information does not exist!");56 57                     }58 59                 }60 61                 //此处编写成功处理逻辑62 63                 logger.Log(LoggerLevels.Debug, "Success: JSON:" + JsonConvert.SerializeObject(result));64 65             };66 67             Action<NotifyResult> failAction = (result) =>68 69             {70 71                 //此处编写失败处理逻辑72 73                 logger.Log(LoggerLevels.Debug, "Fail: JSON:" + JsonConvert.SerializeObject(result));74 75             };76 77             return Content(WeiChatApisContext.Current.TenPayV3Api.NotifyAndReurnResult(Request.InputStream, successAction, failAction));78 79         }
View Code

当前是mvc项目,所以我建了一个webapi控制器,路由地址是http://xx.com/api/ PayNotify/{tenantId},tenantId是租户id,非必填,但是如果没有,要记得把路由里面的参数也去掉。这个地址就是本文前面配置的回调地址,一定要配置正确

然后是调用微信的统一下单的方法

技术分享
 1 /// <summary> 2  3         /// 微信支付(统一下单) 4  5         /// </summary> 6  7         /// <param name="id"></param> 8  9         /// <returns></returns>10 11         [HttpGet]12 13         [Route("Pay/{id}")]14 15         public IHttpActionResult WechatPay(Guid id)16 17         {18 19             try20 21             {22 23                 //查询订单24 25                 var order = db.Order_Infos.SingleOrDefault(o => o.Id == id && o.OpenId == WeiChatApplicationContext.Current.WeiChatUser.OpenId);26 27                 if (null == order)28 29                     return BadRequest("订单信息不存在");30 31                 #region 统一下单32 33                 var model = new UnifiedorderRequest();34 35                 model.OpenId = WeiChatApplicationContext.Current.WeiChatUser.OpenId;36 37                 model.SpbillCreateIp = "8.8.8.8";38 39                 model.OutTradeNo = order.Code;40 41                 model.TotalFee = Convert.ToInt32((order.TotalPrice + order.Shipping) * 100).ToString();42 43                 model.NonceStr = PayUtil.GetNoncestr();44 45                 model.TradeType = "JSAPI";46 47                 model.Body = "购买商品";48 49                 model.DeviceInfo = "WEB";50 51                 var result = WeiChatApisContext.Current.TenPayV3Api.Unifiedorder(model);52 53  54 55                 Dictionary<string, string> _dict = new Dictionary<string, string>();56 57                 _dict.Add("appId", result.AppId);58 59                 _dict.Add("timeStamp", PayUtil.GetTimestamp());60 61                 _dict.Add("nonceStr", result.NonceStr);62 63                 _dict.Add("package", "prepay_id=" + result.PrepayId);64 65                 _dict.Add("signType", "MD5");66 67                 _dict.Add("paySign", PayUtil.CreateMd5Sign(_dict, WeiChatConfigManager.Current.GetPayConfig().TenPayKey));68 69                 #endregion70 71                 return Ok(_dict);72 73             }74 75             catch (Exception ex)76 77             {78 79                 log.Log(LoggerLevels.Error, "WechatPay:" + ex.Message);80 81             }82 83             return BadRequest("操作失败,请联系管理员!");84 85         }
View Code

这也是一个webapi或mvc控制器,给前台页面调用,作用是通过微信的统一下单方法获取微信的预付单,然后再生成一个供给jssdk调用的对象。

 

页面上的jssdk调用方法

技术分享
 1 function onBridgeReady(data) { 2  3             WeixinJSBridge.invoke(getBrandWCPayRequest, data, function (res) { 4  5                 is_suc = true; 6  7                 if (res.err_msg == "get_brand_wcpay_request:ok") { //支付成功后 8  9                     location.href = http://www.mamicode.com/@Url.TenantAction("PaySuccess", "Personal");10 11                 } else {12 13  14 15                 }16 17             });18 19         }20 21         function CallPay(data) {22 23             if (typeof WeixinJSBridge == "undefined") {24 25                 if (document.addEventListener) {26 27                     document.addEventListener(WeixinJSBridgeReady, onBridgeReady(data), false);28 29                 } else if (document.attachEvent) {30 31                     document.attachEvent(WeixinJSBridgeReady, onBridgeReady(data));32 33                     document.attachEvent(onWeixinJSBridgeReady, onBridgeReady(data));34 35                 }36 37             } else {38 39                 onBridgeReady(data);40 41             }42 43         }44 45  46 47 this.toPay = function () {48 49                 wc.restApi.get({50 51                     isBlockUI: false,52 53                     url: /api/MyOrder/Pay/ + _orderid,54 55                     success: function (result) {56 57                         CallPay(ko.toJS(result));58 59                     }60 61                 });62 63             }
View Code

注意这里,这里先是通过ajax请求到我们前面所写的微信统一下单那个action,然后获取到返回的那个对象

关于ko.toJS(),这是knockout里面的方法,作用是把result处理成json对象。

 

好了,到此微信支付已经处理完了,详细代码请移步http://github.com/xin-lai下载最新源码。

 

公众号微信支付