1.)微信开发一般就是看文档,按部就班调用API就行,这里推荐一些SDK
.NET
https://github.com/JeffreySu/WeiXinMPSDK
JAVA
http://git.oschina.net/pyinjava/fastweixin
NodeJS
https://github.com/node-weixin/node-weixin-api
Python
http://git.oschina.net/jeffkit/wechat
2.)在之前的文章微信支付[v3]中,说过V2升V3的一些记录,签名的方式JSSDK方式,现新的微信已经不需要依赖于JSSDK
微信消息配置
已.NET 为例子,WeiXinMPSDK支持公众号于企业号,实现起来也比较简单
/// <summary> /// 微信控制器 /// </summary> public class WeixinController : Controller { private static readonly Logger logger = LogManager.GetCurrentClassLogger(); public readonly string token = DbSetting.getAppText("Token"); public readonly string appId = DbSetting.getAppText("AppID"); public readonly string encodingAESKey = DbSetting.getAppText("EncodingAESKey"); /// <summary> /// 微信验签 /// </summary> /// <param name="signature"></param> /// <param name="timestamp"></param> /// <param name="nonce"></param> /// <param name="echostr"></param> /// <returns></returns> [HttpGet] public async Task<ActionResult> Index(string signature, string timestamp, string nonce, string echostr) { return await Task.Run(() => { if (!CheckSignature.Check(signature, timestamp, nonce, token)) { return "failed:" + signature + "," + CheckSignature.GetSignature(timestamp, nonce, token) + "。如果你在浏览器中看到这句话,说明此地址可以被作为微信公众账号后台的Url,请注意保持Token一致。"; } return echostr; }).ContinueWith(task => Content(task.Result)); } /// <summary> /// 处理微信消息 /// </summary> /// <param name="postModel">参数</param> /// <returns></returns> [HttpPost] public Task<ActionResult> Index(PostModel postModel) { return Task.Run<ActionResult>(() => { if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, token)) { return new WeixinResult("参数错误!"); } postModel.Token = token; postModel.EncodingAESKey = encodingAESKey; postModel.AppId = appId; var messageHandler = new MpWeixinMessageHandler(Request.InputStream, postModel); messageHandler.Execute(); return new FixWeixinBugWeixinResult(messageHandler); }).ContinueWith(task => task.Result); } }
消息重载
/// <summary> /// 处理微信消息 /// </summary> public class MpWeixinMessageHandler : MessageHandler<MessageContext<IRequestMessageBase, IResponseMessageBase>> { private static readonly Logger logger = LogManager.GetCurrentClassLogger(); public readonly string appId = DbSetting.getAppText("AppID"); public readonly string appSecret = DbSetting.getAppText("AppSecret"); /// <summary> /// 构造函数 /// </summary> /// <param name="inputStream"></param> /// <param name="postModel"></param> public MpWeixinMessageHandler(Stream inputStream, PostModel postModel) : base(inputStream, postModel) { WeixinContext.ExpireMinutes = 5; } /// <summary> /// 默认消息 [没有重写的OnXX方法,将默认返回DefaultResponseMessage中的结果] /// </summary> /// <param name="requestMessage"></param> /// <returns></returns> public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage) { //ResponseMessageText也可以是News等其他类型 var responseMessage = this.CreateResponseMessage<ResponseMessageText>(); responseMessage.Content = "这条消息来自DefaultResponseMessage。"; return responseMessage; } /// <summary> /// 关注微信公众号 /// </summary> /// <param name="requestMessage"></param> /// <returns></returns> public override IResponseMessageBase OnEvent_SubscribeRequest(RequestMessageEvent_Subscribe requestMessage) { var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageText>(requestMessage); var token = CommonApi.GetToken(appId, appSecret).access_token; var user = CommonApi.GetUserInfo(token, requestMessage.FromUserName); responseMessage.Content = "Hi " + user.nickname + " ,感谢您关注XXX!!!"; return responseMessage; } /// <summary> /// 取消关注公众号 /// </summary> /// <param name="requestMessage"></param> /// <returns></returns> public override IResponseMessageBase OnEvent_UnsubscribeRequest(RequestMessageEvent_Unsubscribe requestMessage) { return base.OnEvent_UnsubscribeRequest(requestMessage); } /// <summary> /// 发送文本消息 /// </summary> /// <param name="requestMessage"></param> /// <returns></returns> public override IResponseMessageBase OnTextRequest(RequestMessageText requestMessage) { var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageText>(requestMessage); responseMessage.Content = "感谢您关注XXX!!! "; return responseMessage; } }
微信支付
#region 创建订单与生成微信支付 JSAPPI 签名 /// <summary> /// 创建订单与生成微信支付 JSAPPI 签名 /// </summary> /// <param name="create">订餐参数</param> /// <returns></returns> [HttpPost] public async Task<ActionResult> Create(Create create) { try { logger.Info("orders create xml: " + create.ToXml()); if (!ModelState.IsValid) return Json(new { IsError = true, ErrorMsg = "创建订单参数异常!!!", Data = string.Empty }, JsonRequestBehavior.AllowGet); //取购物车缓存 var cart = CacheManger.Cache.Get<CartInfoModel>(create.OpenId); if (cart.IsNull() || cart.ShopId.IsNullOrEmpty() || cart.OpenID.IsNullOrEmpty() || cart.OpenID != create.OpenId || cart.CartItemList.IsNull() || cart.CartItemList.Count == 0) return Json(new { IsError = true, ErrorMsg = "创建订单参数异常!!!", Data = string.Empty }, JsonRequestBehavior.AllowGet); //控制库存与订单事物 using (var conn = new SqlConnection(DbSetting.App)) { await conn.OpenAsync(); var trans = conn.BeginTransaction(); //验证价格及库存[重要] //创建订单 string subject = @"xxx支付"; string orderNo = WxPayConfig.OutTradeNo; //!!! 1分钱测试 !!! decimal total_fee = 0.01m; var executeNum = await conn.ExecuteAsync(@"INSERT INTO Orders ( Origin, ShopId, OpenId, Name, PhoneNo, [Subject], OrderNo, [Status], TotalFee, Coupon, Memo, CreateTime ) VALUES ( @Origin, @ShopId, @OpenId, @Name, @PhoneNo, @Subject, @OrderNo, @Status, @TotalFee, @Coupon, @Memo, @CreateTime )", new { Origin = AbpConstants.WEIXIN_ORIGIN, OpenId = create.OpenId, ShopId = Guid.Parse(cart.ShopId), Name = create.UserName, PhoneNo = create.Phone, Subject = subject, OrderNo = orderNo, Status = "R", TotalFee = cart.TotalPrice, Coupon = "0", Memo = string.Empty, CreateTime = DateTime.Now }, trans); if (executeNum <= 0) { trans.Rollback(); return Json(new { IsError = true, ErrorMsg = "创建订单异常!!!", Data = string.Empty }, JsonRequestBehavior.AllowGet); } foreach (var item in cart.CartItemList.Where(t => t.CartNum != 0)) { executeNum = conn.Execute(@"INSERT INTO OrderDetail ( OrderId, ItemId, ItemName, Num, UnitPrice, TotalFee ) VALUES ( @OrderId, @ItemId, @ItemName, @Num, @UnitPrice, @TotalFee )", new { OrderId = orderNo, ItemId = item.ItemId, ItemName = item.ItemName, Num = item.CartNum, UnitPrice = item.Price, TotalFee = item.Price * item.CartNum }, trans); if (executeNum <= 0) { trans.Rollback(); return Json(new { IsError = true, ErrorMsg = "创建订单明细异常!!!", Data = string.Empty }, JsonRequestBehavior.AllowGet); } } //提交事物 trans.Commit(); var wxpay = await Order(WxPayConfig.APPID, create.OpenId, WxPayConfig.MCHID, orderNo, total_fee, WxPayConfig.NonceStr, subject, "没有优惠券", "XXX"); if (wxpay.IsError) { return Redirect(DbSetting.getAppText("Domain") + @"/oauth/1"); } var jsApiDict = new SortedDictionary<string, string>(); jsApiDict.Add("appId", appId); jsApiDict.Add("timeStamp", WxPayConfig.TimeStamp); jsApiDict.Add("nonceStr", WxPayConfig.NonceStr); jsApiDict.Add("package", "prepay_id=" + (wxpay.Data as SortedDictionary<string, string>)["prepay_id"]); jsApiDict.Add("signType", "MD5"); jsApiDict.Add("paySign", WxPayConfig.GenerateSign(jsApiDict)); return Json(new { IsError = false, ErrorMsg = string.Empty, Data = jsApiDict.ConvertDictionaryToJson() }, JsonRequestBehavior.AllowGet); } } catch (Exception ex) { logger.Info("orders create xml: " + create.ToXml() + " exception message: " + ex.Message); return Json(new { IsError = true, ErrorMsg = "创建订单异常!!!", Data = string.Empty }, JsonRequestBehavior.AllowGet); } } #endregion #region 微信支付签名 /// <summary> /// 微信支付签名 /// </summary> /// <param name="appid">公众账号ID</param> /// <param name="openid">微信唯一标识</param> /// <param name="mch_id">商户号</param> /// <param name="out_trade_no">商户订单号</param> /// <param name="total_fee">订单总金额,单位为分</param> /// <param name="nonce_str">随机字符串</param> /// <param name="body"></param> /// <param name="goods_tag"></param> /// <param name="attach"></param> /// <returns></returns> private async Task<WebAPIResponse> Order(string appid, string openid, string mch_id, string out_trade_no, decimal total_fee, string nonce_str, string body, string goods_tag, string attach) { try { //网页端调起支付API //https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 //统一下单 //https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 var dict = new SortedDictionary<string, string>(); dict.Add("appid", appid); dict.Add("openid", openid); dict.Add("mch_id", mch_id); dict.Add("nonce_str", nonce_str); dict.Add("out_trade_no", out_trade_no); //订单总金额,单位为分 dict.Add("total_fee", Convert.ToInt32(total_fee * 100).ToString()); //附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 dict.Add("attach", attach); dict.Add("body", body); dict.Add("trade_type", WxPayConfig.TRADETYPE); //终端IP dict.Add("spbill_create_ip", AbpConstants.Ip()); dict.Add("notify_url", DbSetting.getAppText("NOTIFY_URL")); //商品标记,代金券或立减优惠功能的参数 dict.Add("goods_tag", goods_tag); dict.Add("time_expire", DateTime.Now.AddMinutes(30).ToString("yyyyMMddHHmmss")); dict.Add("time_start", DateTime.Now.ToString("yyyyMMddHHmmss")); dict.Add("sign", WxPayConfig.GenerateSign(dict)); string xml = "<xml>"; foreach (var pair in dict) { if (pair.Value.GetType() == typeof(int)) { xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">"; } else if (pair.Value.GetType() == typeof(string)) { xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">"; } } xml += "</xml>"; logger.Info("OrdersController unifiedorder Req xml: " + xml); var data = await @"https://api.mch.weixin.qq.com/pay/unifiedorder".PostStringAsync(xml).ReceiveString(); logger.Info("OrdersController unifiedorder Resp xml: " + data); if (data.IsNotNullOrEmpty() && data.Contains("SUCCESS", StringComparison.OrdinalIgnoreCase)) { return new WebAPIResponse { IsError = false, Msg = "签名成功", Data = data.ConvertWeixinXmlToSortedDictionary() }; } return new WebAPIResponse { IsError = true, Msg = "微信支付签名失败" }; } catch (Exception ex) { logger.Error("OrdersController unifiedorder Exception :" + ex.Message); return new WebAPIResponse { IsError = true, Msg = "微信支付签名失败" }; } } #endregion #region 微信订单查询 /// <summary> /// 微信订单查询 /// </summary> /// <param name="appid">公众账号ID</param> /// <param name="mch_id">商户号</param> /// <param name="transaction_id">微信的订单号</param> /// <param name="out_trade_no">商户订单号</param> /// <param name="nonce_str">随机字符串</param> /// <param name="sign">签名</param> /// <returns></returns> private async Task<WebAPIResponse> Query(string appid, string mch_id, string transaction_id, string out_trade_no, string nonce_str, string sign) { if (appid.IsEmpty() || mch_id.IsEmpty() || transaction_id.IsEmpty()) { return new WebAPIResponse { IsError = true, Msg = "参数有误!!!" }; } try { var dict = new Dictionary<string, string>(); dict.Add("appid", appid); dict.Add("mch_id", mch_id); dict.Add("transaction_id", transaction_id); dict.Add("out_trade_no", out_trade_no); dict.Add("nonce_str", nonce_str); dict.Add("sign", sign); string xml = "<xml>"; foreach (var pair in dict) { if (pair.Value.GetType() == typeof(int)) { xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">"; } else if (pair.Value.GetType() == typeof(string)) { xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">"; } } xml += "</xml>"; logger.Info("OrdersController WeixinNotify Req xml: " + xml); var data = await @"https://api.mch.weixin.qq.com/pay/orderquery".PostStringAsync(xml).ReceiveString(); logger.Info("OrdersController WeixinNotify Resp xml: " + data); if (data.IsNotNullOrEmpty() && data.Contains("SUCCESS", StringComparison.OrdinalIgnoreCase)) { return new WebAPIResponse { IsError = false, Data = "查询订单成功!!!" }; } return new WebAPIResponse { IsError = true, Msg = "微信订单查询失败!!!" }; } catch (Exception ex) { logger.Error("OrdersController Exception :" + ex.Message); return new WebAPIResponse { IsError = true, Msg = ex.Message }; } } #endregion
JS签名调起支付窗口
<script type="text/javascript"> if (typeof WeixinJSBridge == "undefined") { if (document.addEventListener) { document.addEventListener(‘WeixinJSBridgeReady‘, onBridgeReady, false); } else if (document.attachEvent) { document.attachEvent(‘WeixinJSBridgeReady‘, onBridgeReady); document.attachEvent(‘onWeixinJSBridgeReady‘, onBridgeReady); } } else { onBridgeReady(); } @*@Html.Raw(Model.WeixinJsAPI)*@ function onBridgeReady() { WeixinJSBridge.invoke( ‘getBrandWCPayRequest‘, $.parseJSON(document.getElementById("WeixinJsAPI").value), function (data) { if (data.err_msg == ‘get_brand_wcpay_request:ok‘) { window.location.href = ‘ @Url.Content("~/oauth/orders")‘; } else { $(‘.confirmbtn‘).removeAttr(‘disabled‘).css(‘background-color‘, ‘#80D200‘); } } ); } </script>
异步消息
/// <summary> /// 微信支付异步通知 /// </summary> /// <returns></returns> public async Task<ActionResult> Notify() { int intLen = Convert.ToInt32(Request.InputStream.Length); if (intLen <= 0) { return Json(new { IsError = true, ErrorMsg = "没有微信支付异步参数!!! ", Data = string.Empty }, JsonRequestBehavior.AllowGet); } try { var dict = new SortedDictionary<string, string>(); //dict = @"appid=wx0ae16b319cd984cf&attach=XXX食堂&bank_type=CFT&cash_fee=1&fee_type=CNY&is_subscribe=Y&mch_id=1287151001&nonce_str=40f11a82cdde44588ec7d6b6bf9cca9d&openid=oxf8ns9AW1IwSwskzglgMH69t38o&out_trade_no=2015121621484780479309&result_code=SUCCESS&return_code=SUCCESS&sign=21C2A8727668E030BF801AB5B701BF93&time_end=20151216214906&total_fee=1&trade_type=JSAPI&transaction_id=1002230068201512162126471549".ConvertStringToSortedDictionary(); var xmlDoc = new XmlDocument(); xmlDoc.Load(Request.InputStream); var root = xmlDoc.SelectSingleNode("xml"); XmlNodeList xnl = root.ChildNodes; foreach (XmlNode xnf in xnl) { dict.Add(xnf.Name, xnf.InnerText); } if (dict.Count <= 0) { return Json(new { IsError = true, ErrorMsg = "没有异步参数!!! ", Data = string.Empty }, JsonRequestBehavior.AllowGet); } logger.Info("【 WeixinController Notify SDKUtil.ConvertDictionaryToString : 请求报文=[" + dict.ConvertDictionaryToString() + "]\n"); //验证签名 string signK = WxPayConfig.GenerateSign(dict); if (signK != dict["sign"]) { logger.Info("WeixinController Notify Verify WxSign Error : 请求报文=[signK " + signK + " : dict[‘sign‘] " + dict["sign"] + "]\n"); return Json(new { IsError = true, ErrorMsg = "验证签名失败 !!! ", Data = string.Empty }, JsonRequestBehavior.AllowGet); } //验证通信标识 string return_code = dict["return_code"]; if (!return_code.Equals("SUCCESS", StringComparison.OrdinalIgnoreCase)) { string return_msg = dict["return_msg"]; logger.Info("WeixinController Notify return_code Error : 请求报文=[" + return_code + " : " + return_msg + "]\n"); return Json(new { IsError = true, ErrorMsg = "验证通信标识失败 !!! ", Data = string.Empty }, JsonRequestBehavior.AllowGet); } //验证交易标识[重要!!!] string result_code = dict["result_code"]; if (!result_code.Equals("SUCCESS", StringComparison.OrdinalIgnoreCase)) { string err_code = dict["err_code"]; string err_code_des = dict["err_code_des"]; logger.Info("WeixinController Notify return_code Error : 请求报文=[" + err_code + " : " + err_code_des + "]\n"); return Json(new { IsError = true, ErrorMsg = "验证交易标识失败 !!! ", Data = string.Empty }, JsonRequestBehavior.AllowGet); } //公众账号ID string appid = dict["appid"]; //商户号 string mch_id = dict["mch_id"]; //随机字符串 string nonce_str = dict["nonce_str"]; //签名 string sign = dict["sign"]; //用户在商户appid 下的唯一标识 string openid = dict["openid"]; //用户是否关注公众账 string is_subscribe = dict["is_subscribe"]; //交易类型 string trade_type = dict["trade_type"]; //付款银行 string bank_type = dict["bank_type"]; //订单总金额[金额分转元] string total_fee = (float.Parse(dict["total_fee"]) / 100).ToString(); //商户订单号 string out_trade_no = dict["out_trade_no"]; //微信支付订单号 string transaction_id = dict["transaction_id"]; //商家数据包 string attach = dict["attach"]; //支付完成时间 string time_end = dict["time_end"]; #region 微信订单查询【使用签名验证废弃】 //var wxorder = await _weixinService.Query(WxPayConfig.KEY, WxPayConfig.APPID, WxPayConfig.MCHID, transaction_id, out_trade_no); #endregion //验证查询订单状态[R 预定状态 C 取消状态 P 支付完成 I 取单完成 D 送货状态] var order = await new SqlConnection(DbSetting.App).QueryAsync<Orders>(@"SELECT *FROM Orders WHERE OrderNo=@OrderNo", new { OrderNo = out_trade_no }).ContinueWith(t => t.Result.SingleOrDefault()); if (order.IsNull() || order.Status.Trim() != "R" || order.TradeNO.IsNotNullOrEmpty() || order.PayFee.IsNotNullOrEmpty()) { logger.Error("WeixinController Notify order Error xml : " + order.ToXml() + string.Format(" 订单: {0} 信息不正确,如有疑问请联系客服!", out_trade_no)); return Json(new { IsError = true, ErrorMsg = "验证查询订单状态失败 !!! ", Data = string.Empty }, JsonRequestBehavior.AllowGet); } using (var conn = new SqlConnection(DbSetting.App)) { await conn.OpenAsync(); var trans = conn.BeginTransaction(); //记录微信异步信息 var executeNum = await conn.ExecuteAsync(@"INSERT INTO PayNotify ( openid, is_subscribe, trade_type, bank_type, transaction_id, out_trade_no, total_fee, coupon_fee, attach, memo, time_end, createtime ) VALUES ( @openid, @is_subscribe, @trade_type, @bank_type, @transaction_id, @out_trade_no, @total_fee, @coupon_fee, @attach, @memo, @time_end, @createtime )", new { openid = openid, is_subscribe = is_subscribe, trade_type = trade_type, bank_type = bank_type, transaction_id = transaction_id, out_trade_no = out_trade_no, total_fee = total_fee, coupon_fee = 0.0m, attach = attach, memo = dict.ConvertDictionaryToJson(), time_end = time_end, createtime = DateTime.Now }, trans); if (executeNum <= 0) { trans.Rollback(); return Json(new { IsError = true, ErrorMsg = "记录微信异步信息异常!!!", Data = string.Empty }, JsonRequestBehavior.AllowGet); } //更新订单支付信息 executeNum = await conn.ExecuteAsync(@"UPDATE Orders SET Status = ‘P‘, TradeNO = @TradeNO, PayFee = @PayFee, PayTime = GETDATE()", new { TradeNO = transaction_id, PayFee = total_fee, }, trans); if (executeNum <= 0) { trans.Rollback(); return Json(new { IsError = true, ErrorMsg = "更新订单支付信息异常!!!", Data = string.Empty }, JsonRequestBehavior.AllowGet); } //!!! 扣菜品库存 trans.Commit(); } //构造成功XML var wxdict = new SortedDictionary<string, string>(); wxdict.Add("return_code", "SUCCESS"); wxdict.Add("return_msg", "PAY_SUCCESS"); string wxRXml = wxdict.ConvertWxDictToString(); logger.Info("WeixinController Notify Success wxRXml : " + wxRXml + " 】"); return Content(wxRXml); } catch (Exception ex) { logger.Fatal("WeixinController Notify Exception : " + ex.Message + " 】"); return Json(new { IsError = true, ErrorMsg = "微信支付异常失败 !!! ", Data = string.Empty }, JsonRequestBehavior.AllowGet); } }
!!!注意事项
- 支付授权目录与测试人的微信帐号白名单(出现access_denied或access_not_allow错误,请检查是否设置正确)
- 支付授权目录区分大小写
- 安全域名配置