├── .gitignore ├── Nop.Plugin.Payments.Weixin ├── Controllers │ └── PaymentWeixinController.cs ├── DependencyRegistrar.cs ├── Description.txt ├── Models │ ├── ConfigurationModel.cs │ ├── PaymentInfoModel.cs │ ├── PrepayInfoModel.cs │ └── WxData.cs ├── Nop.Plugin.Payments.Weixin.csproj ├── Notes.txt ├── Properties │ └── AssemblyInfo.cs ├── RouteProvider.cs ├── Scripts │ └── qrcode.min.js ├── Services │ └── WeixinService.cs ├── Utils.cs ├── Validatiors │ └── ConfigurationValidator.cs ├── Views │ └── PaymentWeixin │ │ ├── Configure.cshtml │ │ ├── JsapiPrepay.cshtml │ │ ├── NativePayment.cshtml │ │ └── PaymentInfo.cshtml ├── WeixinPaymentProcessor.cs ├── WeixinPaymentSetting.cs ├── app.config ├── logo.jpg ├── packages.config └── web.config └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj 2 | [Bb]in 3 | *.user 4 | *.suo 5 | *.[Cc]ache 6 | *.bak 7 | *.ncb 8 | *.log 9 | *.DS_Store 10 | [Tt]humbs.db 11 | _ReSharper.* 12 | *.resharper 13 | Ankh.NoLoad 14 | *.VisualState.xml 15 | TestResult.xml 16 | testresult.xml 17 | test-results 18 | *.pidb 19 | *.userprefs 20 | packages/ 21 | *.nupkg 22 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Controllers/PaymentWeixinController.cs: -------------------------------------------------------------------------------- 1 | using Nop.Core; 2 | using Nop.Plugin.Payments.Weixin.Models; 3 | using Nop.Services.Configuration; 4 | using Nop.Services.Localization; 5 | using Nop.Services.Logging; 6 | using Nop.Services.Orders; 7 | using Nop.Services.Payments; 8 | using Nop.Web.Framework.Controllers; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Globalization; 12 | using System.Linq; 13 | using System.Net; 14 | using System.Web.Mvc; 15 | using System.Web.Script.Serialization; 16 | using System.Xml.Linq; 17 | using Nop.Plugin.Payments.Weixin.Services; 18 | 19 | namespace Nop.Plugin.Payments.Weixin.Controllers 20 | { 21 | public class PaymentWeixinController : BasePaymentController 22 | { 23 | private readonly ISettingService _settingService; 24 | private readonly IPaymentService _paymentService; 25 | private readonly IOrderService _orderService; 26 | private readonly ILogger _logger; 27 | private readonly ILocalizationService _localizationService; 28 | private readonly IOrderProcessingService _orderProcessingService; 29 | private readonly WeixinPaymentSetting _WeixinPaymentSetting; 30 | private readonly IWebHelper _webHelper; 31 | private readonly IStoreContext _storeContext; 32 | private readonly IWeixinPaymentService _weixinPaymentService; 33 | 34 | public PaymentWeixinController(ISettingService settingService, IPaymentService paymentService, 35 | IOrderService orderService, ILogger logger, ILocalizationService localizationService, 36 | IWebHelper webHelper, IOrderProcessingService orderProcessingService, 37 | WeixinPaymentSetting WeixinPaymentSetting, IStoreContext storeContext, 38 | IWeixinPaymentService weixinPaymentService) 39 | { 40 | this._settingService = settingService; 41 | this._paymentService = paymentService; 42 | this._orderService = orderService; 43 | this._webHelper = webHelper; 44 | this._logger = logger; 45 | this._localizationService = localizationService; 46 | this._orderProcessingService = orderProcessingService; 47 | this._WeixinPaymentSetting = WeixinPaymentSetting; 48 | this._storeContext = storeContext; 49 | this._weixinPaymentService = weixinPaymentService; 50 | } 51 | 52 | [AdminAuthorize] 53 | [ChildActionOnly] 54 | public ActionResult Configure() 55 | { 56 | var model = new ConfigurationModel(); 57 | model.AppId = _WeixinPaymentSetting.AppId; 58 | model.AppSecret = _WeixinPaymentSetting.AppSecret; 59 | model.MchId = _WeixinPaymentSetting.MchId; 60 | model.MchKey = _WeixinPaymentSetting.MchKey; 61 | model.AdditionalFee = _WeixinPaymentSetting.AdditionalFee; 62 | 63 | return View("~/Plugins/Payments.Weixin/Views/PaymentWeixin/Configure.cshtml", model); 64 | } 65 | 66 | [HttpPost] 67 | [AdminAuthorize] 68 | [ChildActionOnly] 69 | public ActionResult Configure(ConfigurationModel model) 70 | { 71 | if (!ModelState.IsValid) 72 | return Configure(); 73 | 74 | // save settings 75 | _WeixinPaymentSetting.AppId = model.AppId; 76 | _WeixinPaymentSetting.AppSecret = model.AppSecret; 77 | _WeixinPaymentSetting.MchId = model.MchId; 78 | _WeixinPaymentSetting.MchKey = model.MchKey; 79 | _WeixinPaymentSetting.AdditionalFee = model.AdditionalFee; 80 | _settingService.SaveSetting(_WeixinPaymentSetting); 81 | 82 | SuccessNotification(_localizationService.GetResource("Admin.Plugins.Saved")); 83 | 84 | return Configure(); 85 | } 86 | 87 | [ChildActionOnly] 88 | public ActionResult PaymentInfo() 89 | { 90 | var model = new PaymentInfoModel(); 91 | return View("~/Plugins/Payments.Weixin/Views/PaymentWeixin/PaymentInfo.cshtml", model); 92 | } 93 | 94 | [NonAction] 95 | public override IList ValidatePaymentForm(FormCollection form) 96 | { 97 | var warnings = new List(); 98 | return warnings; 99 | } 100 | 101 | [NonAction] 102 | public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) 103 | { 104 | var paymentInfo = new ProcessPaymentRequest(); 105 | return paymentInfo; 106 | } 107 | 108 | [ValidateInput(false)] 109 | public ActionResult Notify() 110 | { 111 | var processor = _paymentService.LoadPaymentMethodBySystemName("Payments.Weixin") as WeixinPaymentProcessor; 112 | if (processor == null || !processor.PluginDescriptor.Installed) 113 | throw new NopException("Weixin module can not be loaded"); 114 | 115 | string status_msg = String.Empty; 116 | // string v_oid = Request["v_oid"]; // 订单编号 117 | return Content(""); 118 | } 119 | 120 | [ValidateInput(false)] 121 | public ActionResult NativePayment(int orderId, string codeUrl) 122 | { 123 | var model = new PrepayInfoModel() 124 | { 125 | OrderId = orderId, 126 | WeixinNativePaymentUrl = codeUrl, 127 | }; 128 | 129 | return View("Plugin.Payments.Weixin.NativePayment", model); 130 | } 131 | 132 | [ValidateInput(false)] 133 | public ActionResult AuthNotify(int orderId, string ip, string code, string state) 134 | { 135 | _logger.Information( 136 | $"收到微信验证通知:orderId=[{orderId}], ip=[{ip}], code=[{code}], state=[{state}]"); 137 | var processor = _paymentService.LoadPaymentMethodBySystemName("Payments.Weixin") as WeixinPaymentProcessor; 138 | if (processor == null || !processor.PluginDescriptor.Installed) 139 | { 140 | throw new NopException("Weixin module can not be loaded"); 141 | } 142 | 143 | if (!string.IsNullOrEmpty(code)) 144 | { 145 | var authResult = this.RequestWeixinOauth2(code); 146 | var values = new Dictionary(); 147 | var notifyUrl = _webHelper.GetStoreLocation(false) + $"Plugins/PaymentWeixin/PrepayNotify"; 148 | var order = _orderService.GetOrderById(orderId); 149 | values["notify_url"] = notifyUrl; 150 | values["spbill_create_ip"] = ip; 151 | values["trade_type"] = "JSAPI"; 152 | values["openid"] = authResult["openid"]; 153 | 154 | var returnWeixinData = this._weixinPaymentService.UnifiedOrder(order, values); 155 | var resultCode = returnWeixinData.GetValue("result_code"); 156 | var err_code = returnWeixinData.GetValue("err_code"); 157 | if (resultCode != null && resultCode == "SUCCESS") 158 | { 159 | PrepayInfoModel model = PreparePrepayInfo(returnWeixinData.GetValue("prepay_id"), orderId); 160 | _logger.Information($"进入微信支付界面..."); 161 | return View("~/Plugins/Payments.Weixin/Views/PaymentWeixin/JsapiPrepay.cshtml", model); 162 | } 163 | else if (err_code == "ORDERPAID") 164 | { 165 | _weixinPaymentService.ProcessOrderPaid(order); 166 | return Redirect($"~/OrderDetails/{orderId}"); 167 | } 168 | else 169 | { 170 | return Redirect($"~/OrderDetails/{orderId}"); 171 | } 172 | } 173 | else { 174 | _logger.Error("微信支付失败:微信没有返回合适的 code"); 175 | throw new NopException("支付失败"); 176 | } 177 | } 178 | 179 | private PrepayInfoModel PreparePrepayInfo(string prepayId, int orderId) 180 | { 181 | var payData = new WxPayData(_WeixinPaymentSetting.MchKey); 182 | payData.SetValue("appId", _WeixinPaymentSetting.AppId); 183 | payData.SetValue("timeStamp", WxPayData.GenerateTimeStamp()); 184 | payData.SetValue("nonceStr", WxPayData.GenerateNonceStr()); 185 | payData.SetValue("package", $"prepay_id={prepayId}"); 186 | payData.SetValue("signType", "MD5"); 187 | payData.SetValue("paySign", payData.MakeSign()); 188 | var model = new PrepayInfoModel() 189 | { 190 | AppId = payData.GetValue("appId"), 191 | TimeStamp = payData.GetValue("timeStamp"), 192 | NonceStr = payData.GetValue("nonceStr"), 193 | Package = payData.GetValue("package"), 194 | SignType = payData.GetValue("signType"), 195 | PaySign = payData.GetValue("paySign"), 196 | PrepayId = prepayId, 197 | OrderId = orderId, 198 | }; 199 | return model; 200 | } 201 | 202 | [ValidateInput(false)] 203 | public ActionResult CheckPaymentResult(int orderId) 204 | { 205 | var order = _orderService.GetOrderById(orderId); 206 | var success = _weixinPaymentService.OrderQuery(order); 207 | if (success) 208 | { 209 | _weixinPaymentService.ProcessOrderPaid(order); 210 | } 211 | 212 | return Redirect($"~/OrderDetails/{orderId}"); 213 | } 214 | 215 | [ValidateInput(false)] 216 | public ActionResult PrepayNotify() 217 | { 218 | using (var r = new System.IO.StreamReader(Request.InputStream)) 219 | { 220 | _logger.Information($"通知查询参数:[{Request.QueryString.ToString()}]"); 221 | _logger.Information($"通知内容:[{r.ReadToEnd()}]"); 222 | } 223 | return Content(""); 224 | } 225 | 226 | [ValidateInput(false)] 227 | public ActionResult Return() 228 | { 229 | var processor = _paymentService.LoadPaymentMethodBySystemName("Payments.Weixin") as WeixinPaymentProcessor; 230 | if (processor == null || !processor.PluginDescriptor.Installed) 231 | throw new NopException("Weixin module can not be loaded"); 232 | 233 | string status_msg = String.Empty; 234 | string v_oid = Request["v_oid"]; // 订单编号 235 | string v_pstatus = Request["v_pstatus"]; // 支付状态 236 | string v_pstring = Request["v_pstring"]; // 支付结果信息 237 | string v_pmode = Request["v_pmode"]; // 支付银行 238 | string v_md5str = Request["v_md5str"]; // 订单MD5校验码 239 | string v_amount = Request["v_amount"]; // 订单实际支付金额 240 | string v_moneytype = Request["v_moneytype"]; // 订单实际支付币种 241 | string remark1 = Request["remark1"]; // 备注字段1 242 | string remark2 = Request["remark2"]; // 备注字段2,返回通知结果地址 243 | 244 | // return RedirectToRoute("CheckoutCompleted", new { orderId = 0 }); 245 | //自定义错误页面,需要商户开发页面 246 | return RedirectToAction("Index", "Home", new { area = "" }); 247 | } 248 | 249 | private Dictionary RequestWeixinOauth2(string code) 250 | { 251 | if (string.IsNullOrEmpty(code)) 252 | { 253 | throw new ArgumentNullException(code); 254 | } 255 | var appid = _WeixinPaymentSetting.AppId; 256 | var secret = _WeixinPaymentSetting.AppSecret; 257 | var url = $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={appid}&secret={secret}&code={code}&grant_type=authorization_code"; 258 | 259 | var jsonText = this.DoHttpRequest(url); 260 | var jss = new JavaScriptSerializer(); 261 | var dict = jss.Deserialize>(jsonText); 262 | return dict; 263 | } 264 | 265 | private string DoHttpRequest(string url) 266 | { 267 | using (var wb = new WebClient()) 268 | { 269 | var response = wb.DownloadString(url); 270 | return response; 271 | } 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/DependencyRegistrar.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Nop.Core.Configuration; 3 | using Nop.Core.Infrastructure; 4 | using Nop.Core.Infrastructure.DependencyManagement; 5 | using Nop.Plugin.Payments.Weixin.Services; 6 | 7 | namespace Nop.Plugin.Payments.Weixin 8 | { 9 | /// 10 | /// Dependency registrar 11 | /// 12 | public class DependencyRegistrar : IDependencyRegistrar 13 | { 14 | /// 15 | /// Register services and interfaces 16 | /// 17 | /// Container builder 18 | /// Type finder 19 | /// Config 20 | public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config) 21 | { 22 | builder.RegisterType().As().InstancePerLifetimeScope(); 23 | 24 | 25 | } 26 | 27 | /// 28 | /// Order of this dependency registrar implementation 29 | /// 30 | public int Order 31 | { 32 | get { return 1; } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Description.txt: -------------------------------------------------------------------------------- 1 | Group: Payment methods 2 | FriendlyName: 微信支付 3 | SystemName: Payments.Weixin 4 | Version: 1.00 5 | SupportedVersions: 3.70 6 | Author: 李维@云南伴星科技有限公司 7 | DisplayOrder: 1 8 | FileName: Nop.Plugin.Payments.Weixin.dll 9 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Models/ConfigurationModel.cs: -------------------------------------------------------------------------------- 1 | using Nop.Web.Framework; 2 | using Nop.Web.Framework.Mvc; 3 | using System; 4 | using FluentValidation.Attributes; 5 | using Nop.Plugin.Payments.Weixin.Validatiors; 6 | 7 | namespace Nop.Plugin.Payments.Weixin.Models 8 | { 9 | [Validator(typeof(ConfigurationValidator))] 10 | public class ConfigurationModel : BaseNopModel 11 | { 12 | [NopResourceDisplayName("Plugins.Payments.Weixin.AppId")] 13 | public string AppId { get; set; } 14 | 15 | [NopResourceDisplayName("Plugins.Payments.Weixin.AppSecret")] 16 | public string AppSecret { get; set; } 17 | 18 | [NopResourceDisplayName("Plugins.Payments.Weixin.MchId")] 19 | public string MchId { get; set; } 20 | 21 | [NopResourceDisplayName("Plugins.Payments.Weixin.MchKey")] 22 | public string MchKey { get; set; } 23 | 24 | [NopResourceDisplayName("Plugins.Payments.Weixin.AdditionalFee")] 25 | public decimal AdditionalFee { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Models/PaymentInfoModel.cs: -------------------------------------------------------------------------------- 1 | using Nop.Web.Framework.Mvc; 2 | 3 | namespace Nop.Plugin.Payments.Weixin.Models 4 | { 5 | public class PaymentInfoModel : BaseNopModel 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Models/PrepayInfoModel.cs: -------------------------------------------------------------------------------- 1 | using Nop.Web.Framework.Mvc; 2 | 3 | namespace Nop.Plugin.Payments.Weixin.Models { 4 | public class PrepayInfoModel : BaseNopModel { 5 | 6 | /* 7 | "appId": "wx2421b1c4370ec43b", //公众号名称,由商户传入 8 | "timeStamp": " 1395712654", //时间戳,自1970年以来的秒数 9 | "nonceStr": "e61463f8efa94090b1f366cccfbbb444", //随机串 10 | "package": "prepay_id=u802345jgfjsdfgsdg888", 11 | "signType": "MD5", //微信签名方式: 12 | "paySign": "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 13 | */ 14 | public string AppId { get; set; } 15 | public string TimeStamp { get; set; } 16 | public string NonceStr { get; set; } 17 | public string Package { get; set; } 18 | public string SignType { get; set; } 19 | public string PaySign { get; set; } 20 | public int OrderId { get; set; } 21 | public string PrepayId { get; set; } 22 | public string WeixinNativePaymentUrl { get; set; } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Models/WxData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web.Script.Serialization; 7 | using System.Xml; 8 | using System.Security.Cryptography; 9 | 10 | namespace Nop.Plugin.Payments.Weixin.Models { 11 | /// 12 | /// 微信支付协议接口数据类,所有的API接口通信都依赖这个数据结构, 13 | /// 在调用接口之前先填充各个字段的值,然后进行接口通信, 14 | /// 这样设计的好处是可扩展性强,用户可随意对协议进行更改而不用重新设计数据结构, 15 | /// 还可以随意组合出不同的协议数据包,不用为每个协议设计一个数据包结构 16 | /// 17 | public class WxPayData { 18 | private readonly string _appSecret; 19 | 20 | public WxPayData(string appSecret) { 21 | this._appSecret = appSecret.Trim(); 22 | } 23 | 24 | //采用排序的Dictionary的好处是方便对数据包进行签名,不用再签名之前再做一次排序 25 | private SortedDictionary m_values = new SortedDictionary(); 26 | 27 | /** 28 | * 设置某个字段的值 29 | * @param key 字段名 30 | * @param value 字段值 31 | */ 32 | public void SetValue(string key, string value) { 33 | m_values[key] = value; 34 | } 35 | 36 | /** 37 | * 根据字段名获取某个字段的值 38 | * @param key 字段名 39 | * @return key对应的字段值 40 | */ 41 | public string GetValue(string key) { 42 | string o = null; 43 | m_values.TryGetValue(key, out o); 44 | return o; 45 | } 46 | 47 | /** 48 | * 判断某个字段是否已设置 49 | * @param key 字段名 50 | * @return 若字段key已被设置,则返回true,否则返回false 51 | */ 52 | public bool IsSet(string key) { 53 | string o = null; 54 | m_values.TryGetValue(key, out o); 55 | if (null != o) 56 | return true; 57 | else 58 | return false; 59 | } 60 | 61 | /** 62 | * @将Dictionary转成xml 63 | * @return 经转换得到的xml串 64 | * @throws ApplicationException 65 | **/ 66 | public string ToXml() { 67 | //数据为空时不能转化为xml格式 68 | if (0 == m_values.Count) { 69 | throw new ApplicationException("WxPayData数据为空!"); 70 | } 71 | 72 | string xml = ""; 73 | foreach (KeyValuePair pair in m_values) { 74 | //字段值不能为null,会影响后续流程 75 | if (pair.Value == null) { 76 | throw new ApplicationException("WxPayData内部含有值为null的字段!"); 77 | } 78 | 79 | if (pair.Value.GetType() == typeof(int)) { 80 | xml += "<" + pair.Key + ">" + pair.Value + ""; 81 | } else if (pair.Value.GetType() == typeof(string)) { 82 | xml += "<" + pair.Key + ">" + ""; 83 | } else//除了string和int类型不能含有其他数据类型 84 | { 85 | throw new ApplicationException("WxPayData字段数据类型错误!"); 86 | } 87 | } 88 | xml += ""; 89 | return xml; 90 | } 91 | 92 | /** 93 | * @将xml转为WxPayData对象并返回对象内部的数据 94 | * @param string 待转换的xml串 95 | * @return 经转换得到的Dictionary 96 | * @throws ApplicationException 97 | */ 98 | public SortedDictionary FromXml(string xml) { 99 | if (string.IsNullOrEmpty(xml)) { 100 | throw new ApplicationException("将空的xml串转换为WxPayData不合法!"); 101 | } 102 | 103 | XmlDocument xmlDoc = new XmlDocument(); 104 | xmlDoc.LoadXml(xml); 105 | XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点 106 | XmlNodeList nodes = xmlNode.ChildNodes; 107 | foreach (XmlNode xn in nodes) { 108 | XmlElement xe = (XmlElement)xn; 109 | m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中 110 | } 111 | 112 | try { 113 | //2015-06-29 错误是没有签名 114 | if (m_values["return_code"] != "SUCCESS") { 115 | return m_values; 116 | } 117 | CheckSign();//验证签名,不通过会抛异常 118 | } catch (ApplicationException ex) { 119 | throw new ApplicationException(ex.Message); 120 | } 121 | 122 | return m_values; 123 | } 124 | 125 | /** 126 | * @Dictionary格式转化成url参数格式 127 | * @ return url格式串, 该串不包含sign字段值 128 | */ 129 | public string ToUrl() { 130 | string buff = ""; 131 | foreach (KeyValuePair pair in m_values) { 132 | if (pair.Value == null) { 133 | throw new ApplicationException("WxPayData内部含有值为null的字段!"); 134 | } 135 | 136 | if (pair.Key != "sign" && pair.Value.ToString() != "") { 137 | buff += pair.Key + "=" + pair.Value + "&"; 138 | } 139 | } 140 | buff = buff.Trim('&'); 141 | return buff; 142 | } 143 | 144 | 145 | /** 146 | * @Dictionary格式化成Json 147 | * @return json串数据 148 | */ 149 | public string ToJson() { 150 | var jss = new JavaScriptSerializer(); 151 | var jsonStr = jss.Serialize(m_values); 152 | return jsonStr; 153 | } 154 | 155 | /** 156 | * @values格式化成能在Web页面上显示的结果(因为web页面上不能直接输出xml格式的字符串) 157 | */ 158 | public string ToPrintStr() { 159 | string str = ""; 160 | foreach (KeyValuePair pair in m_values) { 161 | if (pair.Value == null) { 162 | throw new ApplicationException("WxPayData内部含有值为null的字段!"); 163 | } 164 | 165 | str += string.Format("{0}={1}
", pair.Key, pair.Value.ToString()); 166 | } 167 | return str; 168 | } 169 | 170 | /** 171 | * @生成签名,详见签名生成算法 172 | * @return 签名, sign字段不参加签名 173 | */ 174 | public string MakeSign() { 175 | //转url格式 176 | string str = ToUrl(); 177 | //在string后加入API KEY 178 | str += "&key=" + _appSecret; 179 | //MD5加密 180 | var md5 = MD5.Create(); 181 | var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str)); 182 | var sb = new StringBuilder(); 183 | foreach (byte b in bs) { 184 | sb.Append(b.ToString("x2")); 185 | } 186 | //所有字符转为大写 187 | return sb.ToString().ToUpper(); 188 | } 189 | 190 | /** 191 | * 192 | * 检测签名是否正确 193 | * 正确返回true,错误抛异常 194 | */ 195 | public bool CheckSign() { 196 | //如果没有设置签名,则跳过检测 197 | if (!IsSet("sign")) { 198 | throw new ApplicationException("WxPayData签名存在但不合法!"); 199 | } 200 | //如果设置了签名但是签名为空,则抛异常 201 | else if (GetValue("sign") == null || GetValue("sign").ToString() == "") { 202 | throw new ApplicationException("WxPayData签名存在但不合法!"); 203 | } 204 | 205 | //获取接收到的签名 206 | string return_sign = GetValue("sign").ToString(); 207 | 208 | //在本地计算新的签名 209 | string cal_sign = MakeSign(); 210 | 211 | if (cal_sign == return_sign) { 212 | return true; 213 | } 214 | 215 | throw new ApplicationException("WxPayData签名验证错误!"); 216 | } 217 | 218 | /** 219 | * @获取Dictionary 220 | */ 221 | public SortedDictionary GetValues() { 222 | return m_values; 223 | } 224 | 225 | /** 226 | * 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数 227 | * @return 时间戳 228 | */ 229 | public static string GenerateTimeStamp() { 230 | TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); 231 | return Convert.ToInt64(ts.TotalSeconds).ToString(); 232 | } 233 | 234 | /** 235 | * 生成随机串,随机串包含字母或数字 236 | * @return 随机串 237 | */ 238 | public static string GenerateNonceStr() { 239 | return Guid.NewGuid().ToString().Replace("-", ""); 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Nop.Plugin.Payments.Weixin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E1BBA3FD-1931-47D0-B7E6-B05C010B3B9B} 8 | Library 9 | Properties 10 | Nop.Plugin.Payments.Weixin 11 | Nop.Plugin.Payments.Weixin 12 | v4.5.1 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | ..\..\Presentation\Nop.Web\Plugins\Payments.Weixin\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | ..\..\Presentation\Nop.Web\Plugins\Payments.Weixin\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | False 36 | ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll 37 | False 38 | 39 | 40 | ..\..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll 41 | False 42 | 43 | 44 | False 45 | 46 | 47 | False 48 | 49 | 50 | False 51 | 52 | 53 | 54 | False 55 | ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll 56 | False 57 | 58 | 59 | False 60 | ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll 61 | False 62 | 63 | 64 | False 65 | ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll 66 | False 67 | 68 | 69 | False 70 | ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll 71 | False 72 | 73 | 74 | False 75 | 76 | 77 | False 78 | 79 | 80 | False 81 | 82 | 83 | False 84 | 85 | 86 | False 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | PreserveNewest 108 | 109 | 110 | PreserveNewest 111 | 112 | 113 | PreserveNewest 114 | 115 | 116 | PreserveNewest 117 | 118 | 119 | PreserveNewest 120 | 121 | 122 | PreserveNewest 123 | 124 | 125 | 126 | 127 | PreserveNewest 128 | 129 | 130 | Always 131 | 132 | 133 | 134 | 135 | 136 | {6bda8332-939f-45b7-a25e-7a797260ae59} 137 | Nop.Core 138 | False 139 | 140 | 141 | {210541ad-f659-47da-8763-16f36c5cd2f4} 142 | Nop.Services 143 | False 144 | 145 | 146 | {75fd4163-333c-4dd5-854d-2ef294e45d94} 147 | Nop.Web.Framework 148 | False 149 | 150 | 151 | 152 | 159 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Notes.txt: -------------------------------------------------------------------------------- 1 | 'Copy local' property of the referenced assemblies are set to 'false'. 2 | We know that they're referenced by the main web applications. So there's no need to deploy them. 3 | It can dramatically reduce package size. 4 | 5 | 6 | Set project output path to ..\..\Presentation\Nop.Web\Plugins\{PluginName}\ (both 'Release' and 'Debug' configurations) 7 | 8 | 9 | All views (cshtml files) and web.config file should have "Build action" set to "Content" and "Copy to output directory" set to "Copy if newer" -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的常规信息通过以下 6 | // 特性集控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("Nop.Plugin.Payments.Weixin")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("昆明维智众源企业管理咨询有限公司")] 12 | [assembly: AssemblyProduct("Nop.Plugin.Payments.Weixin")] 13 | [assembly: AssemblyCopyright("Copyright © 昆明维智众源企业管理咨询有限公司")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // 将 ComVisible 设置为 false 使此程序集中的类型 18 | // 对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | // 则将该类型上的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("1525be1d-076b-4fc8-b999-3cfee6386125")] 24 | 25 | // 程序集的版本信息由下面四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | // 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/RouteProvider.cs: -------------------------------------------------------------------------------- 1 | using Nop.Web.Framework.Mvc.Routes; 2 | using System.Web.Mvc; 3 | using System.Web.Routing; 4 | 5 | namespace Nop.Plugin.Payments.Weixin 6 | { 7 | public class RouteProvider : IRouteProvider 8 | { 9 | public void RegisterRoutes(RouteCollection routes) 10 | { 11 | // PaymentInfo 12 | routes.MapRoute("Plugin.Payments.Weixin.PaymentInfo", 13 | "Plugins/PaymentPay/Weixin/PaymentInfo", 14 | new { controller = "PaymentsWeixin", action = "PaymentInfo" }, 15 | new[] { "Nop.Plugin.Payments.Weixin.Controllers" }); 16 | 17 | // Configure 18 | routes.MapRoute("Plugin.Payments.Weixin.Configure", 19 | "Plugins/PaymentPay/Weixin/Configure", 20 | new { controller = "PaymentsWeixin", action = "Configure" }, 21 | new[] { "Nop.Plugin.Payments.Weixin.Controllers" }); 22 | 23 | //Notify 24 | routes.MapRoute("Plugin.Payments.Weixin.Notify", 25 | "Plugins/PaymentWeixin/Notify", 26 | new { controller = "PaymentWeixin", action = "Notify" }, 27 | new[] { "Nop.Plugin.Payments.Weixin.Controllers" }); 28 | 29 | //NotifyAuth 30 | routes.MapRoute("Plugin.Payments.Weixin.AuthNotify", 31 | "Plugins/PaymentWeixin/AuthNotify", 32 | new { controller = "PaymentWeixin", action = "AuthNotify" }, 33 | new[] { "Nop.Plugin.Payments.Weixin.Controllers" }); 34 | 35 | //NotifyAuth 36 | routes.MapRoute("Plugin.Payments.Weixin.PrepayNotify", 37 | "Plugins/PaymentWeixin/PrepayNotify", 38 | new { controller = "PaymentWeixin", action = "PrepayNotify" }, 39 | new[] { "Nop.Plugin.Payments.Weixin.Controllers" }); 40 | 41 | //NativePayment 42 | routes.MapRoute("Plugin.Payments.Weixin.NativePayment", 43 | "Plugins/PaymentWeixin/NativePayment", 44 | new { controller = "PaymentWeixin", action = "NativePayment" }, 45 | new[] { "Nop.Plugin.Payments.Weixin.Controllers" }); 46 | 47 | //NotifyAuth 48 | routes.MapRoute("Plugin.Payments.Weixin.CheckPaymentResult", 49 | "Plugins/PaymentWeixin/CheckPaymentResult", 50 | new { controller = "PaymentWeixin", action = "CheckPaymentResult" }, 51 | new[] { "Nop.Plugin.Payments.Weixin.Controllers" }); 52 | 53 | //Return 54 | routes.MapRoute("Plugin.Payments.Weixin.Return", 55 | "Plugins/PaymentWeixin/Return", 56 | new { controller = "PaymentWeixin", action = "Return" }, 57 | new[] { "Nop.Plugin.Payments.Weixin.Controllers" }); 58 | 59 | } 60 | 61 | public int Priority 62 | { 63 | get { return 0; } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Scripts/qrcode.min.js: -------------------------------------------------------------------------------- 1 | var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Services/WeixinService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Nop.Services.Logging; 7 | using Nop.Services.Localization; 8 | using Nop.Services.Orders; 9 | using Nop.Core; 10 | using Nop.Core.Domain.Orders; 11 | 12 | using Nop.Plugin.Payments.Weixin.Models; 13 | using System.Net; 14 | 15 | namespace Nop.Plugin.Payments.Weixin.Services 16 | { 17 | public interface IWeixinPaymentService 18 | { 19 | WxPayData PostApiRequest(string url, IDictionary values); 20 | bool OrderQuery(Order order); 21 | WxPayData UnifiedOrder(Order order, IDictionary values); 22 | void ProcessOrderPaid(Order order); 23 | } 24 | 25 | public class WeixinPaymentService : IWeixinPaymentService 26 | { 27 | private readonly IOrderService _orderService; 28 | private readonly ILogger _logger; 29 | private readonly ILocalizationService _localizationService; 30 | private readonly IOrderProcessingService _orderProcessingService; 31 | private readonly WeixinPaymentSetting _WeixinPaymentSetting; 32 | private readonly IWebHelper _webHelper; 33 | private readonly IStoreContext _storeContext; 34 | 35 | public const string WeixinUnifiedOrderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; 36 | public const string WeixinOrderQueryUrl = "https://api.mch.weixin.qq.com/pay/orderquery"; 37 | 38 | public WeixinPaymentService(IOrderService orderService, 39 | ILogger logger, 40 | ILocalizationService localizationService, 41 | IOrderProcessingService orderProcessingService, 42 | WeixinPaymentSetting weixinPaymentSetting, 43 | IWebHelper webHelper, 44 | IStoreContext storeContext) 45 | { 46 | this._logger = logger; 47 | this._localizationService = localizationService; 48 | this._orderProcessingService = orderProcessingService; 49 | this._WeixinPaymentSetting = weixinPaymentSetting; 50 | this._webHelper = webHelper; 51 | this._storeContext = storeContext; 52 | } 53 | 54 | public WxPayData PostApiRequest(string url, IDictionary values) 55 | { 56 | if (values == null) 57 | { 58 | throw new ArgumentNullException(nameof(values)); 59 | } 60 | 61 | var wxdata = new WxPayData(_WeixinPaymentSetting.MchKey); 62 | foreach (var p in values) 63 | { 64 | wxdata.SetValue(p.Key, p.Value); 65 | } 66 | wxdata.SetValue("appid", _WeixinPaymentSetting.AppId); 67 | wxdata.SetValue("mch_id", _WeixinPaymentSetting.MchId); 68 | wxdata.SetValue("nonce_str", WxPayData.GenerateNonceStr()); 69 | wxdata.SetValue("sign", wxdata.MakeSign()); 70 | 71 | if (!wxdata.CheckSign()) 72 | { 73 | throw new NopException("在请求之前发现签名校验错误!"); 74 | } 75 | 76 | using (var wc = new WebClient()) 77 | { 78 | wc.Encoding = System.Text.Encoding.UTF8; 79 | _logger.Information($"Post 微信支付API调用:URL=[{url}], DATA=[{wxdata.ToXml()}]"); 80 | var result = wc.UploadString(url, "POST", wxdata.ToXml()); 81 | _logger.Information($"微信支付调用返回数据:[{result}]"); 82 | var returnWeixinData = new WxPayData(this._WeixinPaymentSetting.MchKey); 83 | returnWeixinData.FromXml(result); 84 | var returnCode = returnWeixinData.GetValue("return_code"); 85 | if (returnCode != null && returnCode == "SUCCESS") 86 | { 87 | return returnWeixinData; 88 | } 89 | else { 90 | var msg = returnWeixinData.GetValue("return_msg"); 91 | throw new NopException($"微信统一下单接口返回错误:[{msg}]"); 92 | } 93 | } 94 | } 95 | 96 | public bool OrderQuery(Order order) 97 | { 98 | if (order == null) 99 | { 100 | throw new ArgumentNullException(nameof(order)); 101 | } 102 | 103 | var values = new Dictionary(); 104 | 105 | values["out_trade_no"] = order.Id.ToString(); 106 | 107 | var returnPayData = this.PostApiRequest(WeixinOrderQueryUrl, values); 108 | var returnCode = returnPayData.GetValue("return_code"); 109 | var tradeState = returnPayData.GetValue("trade_state"); 110 | return returnCode != null && returnCode == "SUCCESS" 111 | && tradeState != null && tradeState == "SUCCESS"; 112 | } 113 | 114 | public void ProcessOrderPaid(Order order) 115 | { 116 | if (order == null) 117 | { 118 | throw new ArgumentNullException(nameof(order)); 119 | } 120 | 121 | if (_orderProcessingService.CanMarkOrderAsPaid(order)) 122 | { 123 | _orderProcessingService.MarkOrderAsPaid(order); 124 | } 125 | } 126 | 127 | public WxPayData UnifiedOrder(Order order, IDictionary values) 128 | { 129 | var fullValues = new Dictionary(values); 130 | var bodyText = $"支付【{_storeContext.CurrentStore.Name}】订单 #{order.Id}"; 131 | fullValues["body"] = bodyText; 132 | fullValues["attach"] = _storeContext.CurrentStore.Name; 133 | fullValues["device_info"] = "WEB"; 134 | fullValues["out_trade_no"] = order.Id.ToString(); 135 | fullValues["total_fee"] = Math.Round(order.OrderTotal * 100).ToString(); 136 | return this.PostApiRequest(WeixinUnifiedOrderUrl, fullValues); 137 | } 138 | 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web; 7 | 8 | namespace Nop.Plugin.Payments.Weixin 9 | { 10 | public static class HttpContextExtensions 11 | { 12 | public static bool IsInWeixinBrowser(this HttpRequestBase request) 13 | { 14 | if(request == null) 15 | { 16 | throw new ArgumentNullException(nameof(request)); 17 | } 18 | return request.UserAgent.ToLowerInvariant().Contains("micromessenger"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Validatiors/ConfigurationValidator.cs: -------------------------------------------------------------------------------- 1 | using Nop.Plugin.Payments.Weixin.Models; 2 | using Nop.Services.Localization; 3 | using Nop.Web.Framework.Validators; 4 | using FluentValidation; 5 | 6 | namespace Nop.Plugin.Payments.Weixin.Validatiors 7 | { 8 | public class ConfigurationValidator : BaseNopValidator 9 | { 10 | public ConfigurationValidator(ILocalizationService localizationService) 11 | { 12 | RuleFor(x => x.AppId).NotEmpty().WithMessage(localizationService.GetResource("Plugins.Payments.Weixin.AppIdRequired")); 13 | RuleFor(x => x.AppSecret).NotEmpty().WithMessage(localizationService.GetResource("Plugins.Payments.Weixin.AppSecretRequired")); 14 | RuleFor(x => x.MchId).NotEmpty().WithMessage(localizationService.GetResource("Plugins.Payments.Weixin.MchIdRequired")); 15 | RuleFor(x => x.AdditionalFee).GreaterThanOrEqualTo(0).WithMessage(localizationService.GetResource("Plugins.Payments.Weixin.AdditionalFeeRequired")); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Views/PaymentWeixin/Configure.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = ""; 3 | } 4 | @model ConfigurationModel 5 | @using Nop.Plugin.Payments.Weixin.Models; 6 | @using Nop.Web.Framework; 7 | 8 | @using (Html.BeginForm()) { 9 | @Html.AntiForgeryToken() 10 | 11 | 12 | 15 | 19 | 20 | 21 | 24 | 28 | 29 | 30 | 33 | 37 | 38 | 39 | 42 | 46 | 47 | 48 | 51 | 55 | 56 | 57 | 60 | 61 |
13 | @Html.NopLabelFor(model => model.AppId): 14 | 16 | @Html.EditorFor(model => model.AppId) 17 | @Html.ValidationMessageFor(model => model.AppId) 18 |
22 | @Html.NopLabelFor(model => model.AppSecret): 23 | 25 | @Html.EditorFor(model => model.AppSecret) 26 | @Html.ValidationMessageFor(model => model.AppSecret) 27 |
31 | @Html.NopLabelFor(model => model.MchId): 32 | 34 | @Html.EditorFor(model => model.MchId) 35 | @Html.ValidationMessageFor(model => model.MchId) 36 |
40 | @Html.NopLabelFor(model => model.MchKey): 41 | 43 | @Html.EditorFor(model => model.MchKey) 44 | @Html.ValidationMessageFor(model => model.MchKey) 45 |
49 | @Html.NopLabelFor(model => model.AdditionalFee): 50 | 52 | @Html.EditorFor(model => model.AdditionalFee) 53 | @Html.ValidationMessageFor(model => model.AdditionalFee) 54 |
58 | 59 |
62 | } -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Views/PaymentWeixin/JsapiPrepay.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = ""; 3 | } 4 | @model Nop.Plugin.Payments.Weixin.Models.PrepayInfoModel 5 | 6 | 7 | 8 | 9 |

正在启动微信支付....

10 | 11 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Views/PaymentWeixin/NativePayment.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = ""; 3 | } 4 | @model Nop.Plugin.Payments.Weixin.Models.PrepayInfoModel 5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 |
13 |

请使用微信扫描下面的二维码以便完成支付

14 |
15 |
16 |
17 |
18 | 已完成付款 19 |
20 |
21 |
22 | 23 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/Views/PaymentWeixin/PaymentInfo.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = ""; 3 | } 4 | @model Nop.Plugin.Payments.Weixin.Models.PaymentInfoModel 5 | 6 | 7 | 8 | 11 | 12 |
9 | @T("Plugins.Payments.Weixin.RedirectionTip") 10 |
-------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/WeixinPaymentProcessor.cs: -------------------------------------------------------------------------------- 1 | using Nop.Core.Domain; 2 | using Nop.Core.Domain.Orders; 3 | using Nop.Core.Domain.Payments; 4 | using Nop.Core.Plugins; 5 | using Nop.Plugin.Payments.Weixin.Controllers; 6 | using Nop.Services.Configuration; 7 | using Nop.Services.Orders; 8 | using Nop.Services.Payments; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using Nop.Services.Localization; 13 | using System.Web.Routing; 14 | using Nop.Web.Framework; 15 | using Nop.Core; 16 | using System.Globalization; 17 | using System.Net; 18 | using System.Web; 19 | using Nop.Services.Logging; 20 | using Nop.Plugin.Payments.Weixin.Models; 21 | using Nop.Plugin.Payments.Weixin.Services; 22 | 23 | namespace Nop.Plugin.Payments.Weixin 24 | { 25 | public class WeixinPaymentProcessor : BasePlugin, IPaymentMethod 26 | { 27 | private readonly HttpContextBase _httpContext; 28 | private readonly WeixinPaymentSetting _WeixinPaymentSetting; 29 | private readonly StoreInformationSettings _storeInformationSetting; 30 | private readonly IWebHelper _webHelper; 31 | private readonly ISettingService _settingService; 32 | private readonly IOrderTotalCalculationService _orderTotalCalculationService; 33 | private readonly IStoreContext _storeContext; 34 | private readonly IOrderService _orderService; 35 | private readonly ILogger _logger; 36 | private readonly IWeixinPaymentService _weixinPaymentService; 37 | 38 | public WeixinPaymentProcessor( 39 | ILogger logger, 40 | HttpContextBase httpContext, 41 | WeixinPaymentSetting WeixinPaymentSetting, 42 | StoreInformationSettings storeInformationSettings, IWebHelper webHelper, 43 | ISettingService settingService, 44 | IOrderService orderService, 45 | IStoreContext storeContext, 46 | IWeixinPaymentService weixinPaymentService, 47 | IOrderTotalCalculationService orderTotalCalculationService) 48 | { 49 | this._logger = logger; 50 | this._httpContext = httpContext; 51 | this._WeixinPaymentSetting = WeixinPaymentSetting; 52 | this._storeInformationSetting = storeInformationSettings; 53 | this._webHelper = webHelper; 54 | this._settingService = settingService; 55 | this._orderService = orderService; 56 | this._storeContext = storeContext; 57 | this._weixinPaymentService = weixinPaymentService; 58 | this._orderTotalCalculationService = orderTotalCalculationService; 59 | } 60 | 61 | public decimal GetAdditionalHandlingFee(IList cart) 62 | { 63 | var result = this.CalculateAdditionalFee(_orderTotalCalculationService, cart, 64 | _WeixinPaymentSetting.AdditionalFee, false); 65 | return result; 66 | } 67 | 68 | public CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) 69 | { 70 | var result = new CapturePaymentResult(); 71 | result.AddError("Capture method not supported"); 72 | return result; 73 | } 74 | 75 | public RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest) 76 | { 77 | var result = new RefundPaymentResult(); 78 | result.AddError("Refund method not supported"); 79 | return result; 80 | } 81 | 82 | public VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) 83 | { 84 | var result = new VoidPaymentResult(); 85 | result.AddError("Void method not supported"); 86 | return result; 87 | } 88 | 89 | public ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentRequest processPaymentRequest) 90 | { 91 | var result = new ProcessPaymentResult(); 92 | result.AddError("Recurring payment not supported"); 93 | return result; 94 | } 95 | 96 | public CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurringPaymentRequest cancelPaymentRequest) 97 | { 98 | var result = new CancelRecurringPaymentResult(); 99 | result.AddError("Recurring payment not supported"); 100 | return result; 101 | } 102 | 103 | public bool CanRePostProcessPayment(Order order) 104 | { 105 | if (order == null) 106 | throw new ArgumentNullException("order"); 107 | 108 | if (order.PaymentStatus != PaymentStatus.Pending) 109 | return false; 110 | 111 | if ((DateTime.UtcNow - order.CreatedOnUtc).TotalMinutes < 1) 112 | return false; 113 | 114 | return true; 115 | } 116 | 117 | public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) 118 | { 119 | actionName = "Configure"; 120 | controllerName = "PaymentWeixin"; 121 | routeValues = new RouteValueDictionary() { { "Namespaces", "Nop.Plugin.Payments.Weixin.Controllers" }, { "area", null } }; 122 | } 123 | 124 | public void GetPaymentInfoRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) 125 | { 126 | actionName = "PaymentInfo"; 127 | controllerName = "PaymentWeixin"; 128 | routeValues = new RouteValueDictionary() { { "Namespaces", "Nop.Plugin.Payments.Weixin.Controllers" }, { "area", null } }; 129 | } 130 | 131 | public Type GetControllerType() 132 | { 133 | return typeof(PaymentWeixinController); 134 | } 135 | 136 | /// 137 | /// Returns a value indicating whether payment method should be hidden during checkout 138 | /// 139 | /// Shoping cart 140 | /// true - hide; false - display. 141 | public bool HidePaymentMethod(IList cart) 142 | { 143 | //you can put any logic here 144 | //for example, hide this payment method if all products in the cart are downloadable 145 | //or hide this payment method if current customer is from certain country 146 | if (this._httpContext.Request.UserAgent.ToLowerInvariant().Contains("micromessenger")) 147 | { 148 | return false; 149 | } 150 | else 151 | { 152 | return true; 153 | } 154 | } 155 | 156 | public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) 157 | { 158 | var result = new ProcessPaymentResult(); 159 | result.NewPaymentStatus = PaymentStatus.Pending; 160 | return result; 161 | } 162 | 163 | public void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) 164 | { 165 | if (this._weixinPaymentService.OrderQuery(postProcessPaymentRequest.Order)) 166 | { 167 | _weixinPaymentService.ProcessOrderPaid(postProcessPaymentRequest.Order); 168 | _httpContext.Response.Redirect($"~/OrderDetails/{postProcessPaymentRequest.Order.Id}"); 169 | } 170 | else 171 | { 172 | var isInWeixinBrowser = this._httpContext.Request.IsInWeixinBrowser(); 173 | if (isInWeixinBrowser) 174 | { 175 | this.PostPRocessPaymentInWeixinBrowser(postProcessPaymentRequest); 176 | } 177 | else 178 | { 179 | var clientIp = _webHelper.GetCurrentIpAddress() ?? ""; 180 | PostWeixinQrcodeOrder(postProcessPaymentRequest.Order, clientIp); 181 | } 182 | } 183 | } 184 | 185 | /// 186 | /// 在微信浏览器内支付则先请求微信认证,获取 code, openid 之类 187 | /// 188 | /// 189 | private void PostPRocessPaymentInWeixinBrowser(PostProcessPaymentRequest postProcessPaymentRequest) 190 | { 191 | var appid = this._WeixinPaymentSetting.AppId; 192 | var clientIp = _webHelper.GetCurrentIpAddress() ?? ""; 193 | var orderId = postProcessPaymentRequest.Order.Id; 194 | var authNotifyUrl = 195 | _webHelper.GetStoreLocation(false) + 196 | $"Plugins/PaymentWeixin/AuthNotify?orderId={orderId}&ip={clientIp}"; 197 | var redirectUri = WebUtility.UrlEncode(authNotifyUrl); 198 | var url = $"https://open.weixin.qq.com/connect/oauth2/authorize?" + 199 | $"appid={appid}&redirect_uri={redirectUri}&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect"; 200 | _httpContext.Response.Redirect(url); 201 | } 202 | 203 | /// 204 | /// 在微信浏览器外支付则直接调用统一下单接口下单,然后生成 URL 的二维码,用户用微信扫描后付款 205 | /// 206 | /// 207 | /// 208 | /// 209 | private void PostWeixinQrcodeOrder(Order order, string ip) 210 | { 211 | var wxdata = new WxPayData(_WeixinPaymentSetting.MchKey); 212 | var notifyUrl = _webHelper.GetStoreLocation(false) + $"Plugins/PaymentWeixin/PrepayNotify"; 213 | var values = new Dictionary(); 214 | values["notify_url"] = notifyUrl; 215 | values["spbill_create_ip"] = ip; 216 | values["trade_type"] = "NATIVE"; 217 | values["product_id"] = order.Id.ToString(); 218 | 219 | var returnWeixinData = _weixinPaymentService.UnifiedOrder(order, values); 220 | var resultCode = returnWeixinData.GetValue("result_code"); 221 | var codeUrl = WebUtility.UrlEncode(returnWeixinData.GetValue("code_url")); 222 | var redirectUrl = _webHelper.GetStoreLocation(false) + 223 | $"Plugins/PaymentWeixin/NativePayment?orderId={order.Id}&codeUrl={codeUrl}"; 224 | _httpContext.Response.Redirect(redirectUrl); 225 | } 226 | 227 | public override void Install() 228 | { 229 | var settings = new WeixinPaymentSetting() 230 | { 231 | AppId = "", 232 | AppSecret = "", 233 | MchId = "", 234 | MchKey = "", 235 | AdditionalFee = 0 236 | }; 237 | _settingService.SaveSetting(settings); 238 | 239 | //locales 240 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.RedirectionTip", "将本次将使用微信支付!"); 241 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.AppId", "AppID"); 242 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.AppSecret", "AppSecret"); 243 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.MchKey", "商户密钥"); 244 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.MchId", "商户号"); 245 | //this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.Vmid.Hint", "请输入商户号"); 246 | //this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.Key.Hint", "请输入商户Key"); 247 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.AdditionalFee", "额外费用"); 248 | //this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.AdditionalFee.Hint", "请输入额外费用,没有则输0"); 249 | // 验证 250 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.AppIdRequired", "请输入 AppID"); 251 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.AppSecretRequired", "请输入 AppSecret"); 252 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.MchIdRequired", "请输入商户号"); 253 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.MchKeyRequired", "请输入商户密钥"); 254 | this.AddOrUpdatePluginLocaleResource("Plugins.Payments.Weixin.AdditionalFeeRequired", "请输入额外费用,没有则输0"); 255 | 256 | base.Install(); 257 | } 258 | 259 | public override void Uninstall() 260 | { 261 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.RedirectionTip"); 262 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.AppId"); 263 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.AppId.Hint"); 264 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.AppSecret"); 265 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.AppSecret.Hint"); 266 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.MchId"); 267 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.MchId.Hint"); 268 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.MchKey"); 269 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.MchKey.Hint"); 270 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.AdditionalFee"); 271 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.AdditionalFee.Hint"); 272 | // 验证 273 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.AppIdRequired"); 274 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.AppSecretRequired"); 275 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.MchIdRequired"); 276 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.MchKeyRequired"); 277 | this.DeletePluginLocaleResource("Plugins.Payments.Weixin.AdditionalFeeRequired"); 278 | 279 | base.Uninstall(); 280 | } 281 | 282 | public bool SupportCapture 283 | { 284 | get 285 | { 286 | return false; 287 | } 288 | } 289 | 290 | public bool SupportPartiallyRefund 291 | { 292 | get 293 | { 294 | return false; 295 | } 296 | } 297 | 298 | public bool SupportRefund 299 | { 300 | get 301 | { 302 | return false; 303 | } 304 | } 305 | 306 | public bool SupportVoid 307 | { 308 | get 309 | { 310 | return false; 311 | } 312 | } 313 | 314 | public RecurringPaymentType RecurringPaymentType 315 | { 316 | get 317 | { 318 | return RecurringPaymentType.NotSupported; 319 | } 320 | } 321 | 322 | public PaymentMethodType PaymentMethodType 323 | { 324 | get 325 | { 326 | return PaymentMethodType.Redirection; 327 | } 328 | } 329 | 330 | public bool SkipPaymentInfo 331 | { 332 | get { return false; } 333 | } 334 | 335 | 336 | 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/WeixinPaymentSetting.cs: -------------------------------------------------------------------------------- 1 | using Nop.Core.Configuration; 2 | 3 | namespace Nop.Plugin.Payments.Weixin 4 | { 5 | public class WeixinPaymentSetting : ISettings 6 | { 7 | /// 8 | /// AppId 9 | /// 10 | public string AppId { get; set; } 11 | 12 | /// 13 | /// Secret 14 | /// 15 | public string AppSecret { get; set; } 16 | 17 | /// 18 | /// 商户号 19 | /// 20 | public string MchId { get; set; } 21 | 22 | /// 23 | /// 商户密钥 24 | /// 25 | public string MchKey { get; set; } 26 | 27 | /// 28 | /// 额外费用 29 | /// 30 | public decimal AdditionalFee { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oldrev/NopPaymentWeixin/204322d277da26109166c247ae292713fe5f48a4/Nop.Plugin.Payments.Weixin/logo.jpg -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Nop.Plugin.Payments.Weixin/web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nop-Commerce 的微信支付插件 2 | 3 | 4 | 授权方式:MIT 5 | 6 | 版权所有 (C) 2017-TODAY 云南伴星科技有限公司 7 | --------------------------------------------------------------------------------