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 == 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 == 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 == 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 == 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 == 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 = PayUtil.PostInput(inputStream); 243 244 result = XmlHelper.DeserializeObject<NotifyResult>(data); 245 246 return result; 247 248 } 249 250 /// <summary> 251 252 /// 通知并返回处理XML 253 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 else 289 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 else 303 304 { 305 306 if (isSync) 307 308 Task.Run(() => failAction(result)); 309 310 else 311 312 failAction.Invoke(result); 313 314 request.ReturnMsg = errorMsg; 315 316 return XmlHelper.SerializeObject(request); 317 318 } 319 320 } 321 322 } 323 324 }
把返回参数和请求参数,序列化成对象,方便我们在编写我们本身逻辑的时候调用
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标准的三位字母代码,默认人民币:CNY 120 /// </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 /// 用户端实际ip 130 /// </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秒表示为20091227091010 140 /// </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)公众号内支付填JSAPI 155 /// </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 : PayResult 167 { 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/FAIL 195 /// </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、APP 220 /// </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_$0 265 /// </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 NotifyRequest 302 { 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 }
这些地方是获取微信支付的配置信息,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 : PayResult 16 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 : PayResult 52 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 }
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 else 142 143 { 144 145 string res; 146 147 148 149 try 150 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 = (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) != 0 368 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 PayResult 442 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 }
支付完成之后,异步回掉的处理
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 else 52 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 }
当前是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 try 20 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 #endregion 70 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 }
这也是一个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 = ‘@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 }
注意这里,这里先是通过ajax请求到我们前面所写的微信统一下单那个action,然后获取到返回的那个对象
关于ko.toJS(),这是knockout里面的方法,作用是把result处理成json对象。
好了,到此微信支付已经处理完了,详细代码请移步http://github.com/xin-lai下载最新源码。