public async Task <(string SessionKey, string OpenId)> CodeToSession(string code) { try { // https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code // {"session_key":"aNpIMqbx2dh4s4zikIBKGg==","expires_in":7200,"openid":"o21z-0E5BiJ6QE1QPlo-cyU-TJ78"} var html = await _client.GetStringAsync($"https://api.weixin.qq.com/sns/jscode2session?appid={_wxOptions.WxAppId}&secret={_wxOptions.WxSecretKey}&js_code={code}&grant_type=authorization_code"); var sessionKey = _sessionKeyPattern.Match(html).Groups[1].Value; // // The input is not a valid Base-64 string as it contains a non-base 64 character, sometimes wexin session key returned contains some illegal base 64 character, fixed in sql. var openId = _openIdPattern.Match(html).Groups[1].Value; if (string.IsNullOrEmpty(openId) || string.IsNullOrEmpty(sessionKey)) { _logger.EnqueueMessage($"{_wxOptions.WxAppId} {_wxOptions.WxSecretKey} {nameof(CodeToSession)}: {html}"); } // Weixin bug fix, founded 2019.5.11 // The input is not a valid Base-64 string as it contains a non-base 64 character // fixed in sql //if(sessionKey.IndexOf("\\", StringComparison.Ordinal) != -1) sessionKey = sessionKey.Replace("\\", ""); return(sessionKey, openId); } catch (Exception ex) { _logger.EnqueueMessage($"{nameof(global::WeixinPay.Services.WeixinService)}.{nameof(CodeToSession)} error. message: {ex.Message} stackTrace: {ex.StackTrace}"); } // If error, just return empty return(string.Empty, string.Empty); }
public async Task <(bool IsCompleted, string Result)> SendAsync(string receiver, string paramString, string templateCode) { if (string.IsNullOrEmpty(receiver) || !RegexCache.MobilePattern.IsMatch(receiver)) { throw new ArgumentException("Invalid receiver number", nameof(receiver)); } if (string.IsNullOrEmpty(paramString)) { throw new ArgumentNullException(nameof(paramString)); } if (string.IsNullOrEmpty(templateCode)) { throw new ArgumentNullException(nameof(templateCode)); } var timestamp = DateTime.Now.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"); var nonce = Guid.NewGuid().ToString(); StringBuilder builder = new StringBuilder(); // system and business parameters, already order by asc. builder.Append($"AccessKeyId={_options.AliSmsAccessKeyId}&"); builder.Append("Action=SendSms&"); builder.Append("Format=XML&"); builder.Append($"PhoneNumbers={PercentEncode(receiver)}&"); builder.Append($"RegionId={PercentEncode("cn-hangzhou")}&"); builder.Append($"SignName={PercentEncode(_options.AliSmsSignName)}&"); builder.Append("SignatureMethod=HMAC-SHA1&"); builder.Append($"SignatureNonce={nonce}&"); builder.Append("SignatureVersion=1.0&"); builder.Append($"TemplateCode={PercentEncode(templateCode)}&"); builder.Append($"TemplateParam={PercentEncode(paramString)}&"); builder.Append($"Timestamp={PercentEncode(timestamp)}&"); builder.Append("Version=2017-05-25"); // 计算签名 var signString = $"GET&%2F&{Regex.Replace(Encode(builder.ToString()), @"%[a-f\d]{2}", m => m.Value.ToUpperInvariant())}"; HMACSHA1 hmacsha1 = new HMACSHA1(Encoding.UTF8.GetBytes(_options.AliSmsAccessKeySecret + "&")); var sign = Convert.ToBase64String(hmacsha1.ComputeHash(Encoding.UTF8.GetBytes(signString))); try { var url = $"http://dysmsapi.aliyuncs.com/?AccessKeyId={Encode(_options.AliSmsAccessKeyId)}&Action=SendSms&Format=XML&PhoneNumbers={Encode(receiver)}&RegionId={Encode("cn-hangzhou")}&SignatureMethod=HMAC-SHA1&SignatureNonce={Encode(nonce)}&SignatureVersion=1.0&SignName={Encode(_options.AliSmsSignName)}&TemplateCode={Encode(templateCode)}&TemplateParam={Encode(paramString)}&Timestamp={Encode(timestamp)}&Version={Encode("2017-05-25")}&Signature={Encode(sign)}"; var result = await _client.GetStringAsync(url); return(result.EndsWith("<Code>OK</Code></SendSmsResponse>", StringComparison.OrdinalIgnoreCase), result); } catch (Exception ex) { _logger.EnqueueMessage($"{nameof(AliSmsSender)}.{nameof(SendAsync)} error. message: {ex.Message} stackTrace: {ex.StackTrace}"); } return(false, "error"); }
public async Task <bool> SendEmailAsync(string to, string subject, string body, IEnumerable <string> cc = null) { // Verify if (string.IsNullOrEmpty(to)) { throw new ArgumentNullException(nameof(to)); } if (string.IsNullOrEmpty(subject)) { throw new ArgumentNullException(nameof(subject)); } if (string.IsNullOrEmpty(body)) { throw new ArgumentNullException(nameof(body)); } var message = new MailMessage(new MailAddress(_options.AliMailAccount, _options.AliMailDisplayName), new MailAddress(to)) { Subject = subject, Body = body, IsBodyHtml = true }; if (cc != null) { foreach (string s in cc) { message.CC.Add(s); } } try { using (var smtpClient = new SmtpClient(_options.AliMailSmtp, _options.AliMailPort) { Credentials = new NetworkCredential(_options.AliMailAccount, _options.AliMailPassword), DeliveryMethod = SmtpDeliveryMethod.Network, EnableSsl = _options.AliMailEnableSsl }) { //smtpClient.Send(message); await smtpClient.SendMailAsync(message); } return(true); } catch (Exception ex) { _logger.EnqueueMessage($"{nameof(AliMailSender)}.{nameof(SendEmailAsync)} error. details: {ex.Message} stackTrace: {ex.StackTrace}"); } return(false); }
/// <summary> /// Get weixin prepayId /// </summary> /// <param name="nonceStr"></param> /// <param name="orderPaymentId"></param> /// <param name="amount"></param> /// <param name="wxOpenId"></param> /// <param name="attach"></param> /// <param name="billCreationIp"></param> /// <returns></returns> private async Task <string> UnifiedOrder(string nonceStr, string orderPaymentId, decimal amount, string wxOpenId, string billCreationIp, string attach) { UnifiedOrder unifiedOrder = new UnifiedOrder { MchId = _options.WxMchId, AppId = _options.WxAppId, NotifyUrl = _options.WxPaymentNotifyUrl, SignType = "MD5", TradeType = "JSAPI", Body = $"{_options.WxPlatformName}订单", NonceStr = nonceStr, OutTradeNo = orderPaymentId, TotalFee = Math.Round(amount * 100).ToString(), // unit: fen max scale is 2 SPBillCreateIP = billCreationIp, OpenId = wxOpenId, Attach = attach }; // Sign unifiedOrder.Sign = BuildSignString(unifiedOrder); // Xml string requestXml = BuildRequestXml(unifiedOrder); if (_options.WxIsDebug) { _logger.EnqueueMessage($"UnifiedOrder: {requestXml}"); } try { HttpContent content = new ByteArrayContent(Encoding.UTF8.GetBytes(requestXml)); var res = await _client.PostAsync("https://api.mch.weixin.qq.com/pay/unifiedorder", content); string xml = await res.Content.ReadAsStringAsync(); if (_options.WxIsDebug) { _logger.EnqueueMessage(xml); } //bool isReturnSuccess = xml.IndexOf("<return_code><![CDATA[SUCCESS]]></return_code>", StringComparison.Ordinal) != -1; bool isReturnSuccess = xml.StartsWith("<xml><return_code><![CDATA[SUCCESS]]></return_code>", StringComparison.Ordinal); if (!isReturnSuccess) { return(string.Empty); } bool isResultSuccess = xml.IndexOf("<result_code><![CDATA[SUCCESS]]></result_code>", StringComparison.Ordinal) != -1; return(isResultSuccess ? PrepayIdPattern.Match(xml).Groups[1].Value : string.Empty /*error*/); } catch (Exception ex) { _logger.EnqueueMessage($"{nameof(WeixinPay)}.{nameof(UnifiedOrder)} error. Message: {ex.Message} InnerMessage: {ex.InnerException?.Message} StackTrace: {ex.StackTrace}"); } return(string.Empty); string BuildRequestXml(UnifiedOrder order) { StringBuilder xmlBuilder = new StringBuilder("<xml>"); xmlBuilder.Append($"<appid>{order.AppId}</appid>"); if (!string.IsNullOrEmpty(order.Attach)) { xmlBuilder.Append($"<attach>{order.Attach}</attach>"); } if (!string.IsNullOrEmpty(order.Body)) { xmlBuilder.Append($"<body>{order.Body}</body>"); } if (!string.IsNullOrEmpty(order.Detail)) { xmlBuilder.Append($"<detail>{order.Detail}</detail>"); } if (!string.IsNullOrEmpty(order.DeviceInfo)) { xmlBuilder.Append($"<device_info>{order.DeviceInfo}</device_info>"); } if (!string.IsNullOrEmpty(order.FeeType)) { xmlBuilder.Append($"<fee_type>{order.FeeType}</fee_type>"); } if (!string.IsNullOrEmpty(order.GoodsTag)) { xmlBuilder.Append($"<goods_tag>{order.GoodsTag}</goods_tag>"); } if (!string.IsNullOrEmpty(order.LimitPay)) { xmlBuilder.Append($"<limit_pay>{order.LimitPay}</limit_pay>"); } if (!string.IsNullOrEmpty(order.MchId)) { xmlBuilder.Append($"<mch_id>{order.MchId}</mch_id>"); } if (!string.IsNullOrEmpty(order.NonceStr)) { xmlBuilder.Append($"<nonce_str>{order.NonceStr}</nonce_str>"); } if (!string.IsNullOrEmpty(order.NotifyUrl)) { xmlBuilder.Append($"<notify_url>{order.NotifyUrl}</notify_url>"); } if (!string.IsNullOrEmpty(order.OpenId)) { xmlBuilder.Append($"<openid>{order.OpenId}</openid>"); } if (!string.IsNullOrEmpty(order.OutTradeNo)) { xmlBuilder.Append($"<out_trade_no>{order.OutTradeNo}</out_trade_no>"); } if (!string.IsNullOrEmpty(order.ProductId)) { xmlBuilder.Append($"<product_id>{order.ProductId}</product_id>"); } if (!string.IsNullOrEmpty(order.Receipt)) { xmlBuilder.Append($"<receipt>{order.Receipt}</receipt>"); } if (!string.IsNullOrEmpty(order.SceneInfo)) { xmlBuilder.Append($"<scene_info>{order.SceneInfo}</scene_info>"); } if (!string.IsNullOrEmpty(order.Sign)) { xmlBuilder.Append($"<sign>{order.Sign}</sign>"); } if (!string.IsNullOrEmpty(order.SignType)) { xmlBuilder.Append($"<sign_type>{order.SignType}</sign_type>"); } if (!string.IsNullOrEmpty(order.SPBillCreateIP)) { xmlBuilder.Append($"<spbill_create_ip>{order.SPBillCreateIP}</spbill_create_ip>"); } if (!string.IsNullOrEmpty(order.TimeExpire)) { xmlBuilder.Append($"<time_expire>{order.TimeExpire}</time_expire>"); } if (!string.IsNullOrEmpty(order.TimeStart)) { xmlBuilder.Append($"<time_start>{order.TimeStart}</time_start>"); } if (!string.IsNullOrEmpty(order.TotalFee)) { xmlBuilder.Append($"<total_fee>{order.TotalFee}</total_fee>"); } if (!string.IsNullOrEmpty(order.TradeType)) { xmlBuilder.Append($"<trade_type>{order.TradeType}</trade_type>"); } return(xmlBuilder.Append("</xml>").ToString()); } string BuildSignString(UnifiedOrder order) { StringBuilder signBuilder = new StringBuilder(); signBuilder.Append($"appid={order.AppId}"); if (!string.IsNullOrEmpty(order.Attach)) { signBuilder.Append($"&attach={order.Attach}"); } if (!string.IsNullOrEmpty(order.Body)) { signBuilder.Append($"&body={order.Body}"); } if (!string.IsNullOrEmpty(order.Detail)) { signBuilder.Append($"&detail={order.Detail}"); } if (!string.IsNullOrEmpty(order.DeviceInfo)) { signBuilder.Append($"&device_info={order.DeviceInfo}"); } if (!string.IsNullOrEmpty(order.FeeType)) { signBuilder.Append($"&fee_type={order.FeeType}"); } if (!string.IsNullOrEmpty(order.GoodsTag)) { signBuilder.Append($"&goods_tag={order.GoodsTag}"); } if (!string.IsNullOrEmpty(order.LimitPay)) { signBuilder.Append($"&limit_pay={order.LimitPay}"); } if (!string.IsNullOrEmpty(order.MchId)) { signBuilder.Append($"&mch_id={order.MchId}"); } if (!string.IsNullOrEmpty(order.NonceStr)) { signBuilder.Append($"&nonce_str={order.NonceStr}"); } if (!string.IsNullOrEmpty(order.NotifyUrl)) { signBuilder.Append($"¬ify_url={order.NotifyUrl}"); } if (!string.IsNullOrEmpty(order.OpenId)) { signBuilder.Append($"&openid={order.OpenId}"); } if (!string.IsNullOrEmpty(order.OutTradeNo)) { signBuilder.Append($"&out_trade_no={order.OutTradeNo}"); } if (!string.IsNullOrEmpty(order.ProductId)) { signBuilder.Append($"&product_id={order.ProductId}"); } if (!string.IsNullOrEmpty(order.Receipt)) { signBuilder.Append($"&receipt={order.Receipt}"); } if (!string.IsNullOrEmpty(order.SceneInfo)) { signBuilder.Append($"&scene_info={order.SceneInfo}"); } if (!string.IsNullOrEmpty(order.SignType)) { signBuilder.Append($"&sign_type={order.SignType}"); } if (!string.IsNullOrEmpty(order.SPBillCreateIP)) { signBuilder.Append($"&spbill_create_ip={order.SPBillCreateIP}"); } if (!string.IsNullOrEmpty(order.TimeExpire)) { signBuilder.Append($"&time_expire={order.TimeExpire}"); } if (!string.IsNullOrEmpty(order.TimeStart)) { signBuilder.Append($"&time_start={order.TimeStart}"); } if (!string.IsNullOrEmpty(order.TotalFee)) { signBuilder.Append($"&total_fee={order.TotalFee}"); } if (!string.IsNullOrEmpty(order.TradeType)) { signBuilder.Append($"&trade_type={order.TradeType}"); } signBuilder.Append($"&key={_options.WxMchKey}"); return(signBuilder.ToString().GetHashUTF8().ToUpperInvariant()); } }
/// <summary> /// Weixin refund /// </summary> /// <param name="device_info1">设备号 否 终端设备号</param> /// <param name="out_refund_no1"> /// 商户退款单号 out_refund_no 是 String(64) 1217752501201407033233368018 /// 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。 /// </param> /// <param name="out_trade_no1"> /// 商户订单号 out_trade_no String(32) 1217752501201407033233368018 /// 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。 /// </param> /// <param name="refund_account1"> /// 退款资金来源 refund_account 否 String(30) REFUND_SOURCE_RECHARGE_FUNDS /// 仅针对老资金流商户使用 /// REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款) /// REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款 /// </param> /// <param name="refund_desc1"> /// 退款原因 refund_desc 否 String(80) 商品已售完 /// 若商户传入,会在下发给用户的退款消息中体现退款原因 /// 注意:若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因 /// </param> /// <param name="refund_fee1">退款金额 refund_fee 是 Int 100 退款总金额,订单总金额,单位为分,只能为整数,详见支付金额</param> /// <param name="refund_fee_type1">货币种类 refund_fee_type 否 String(8) CNY 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型</param> /// <param name="total_fee1">订单金额 total_fee 是 Int 100 订单总金额,单位为分,只能为整数,详见支付金额</param> /// <param name="transaction_id1">微信订单号 transaction_id 二选一 String(32) 1217752501201407033233368018 微信生成的订单号,在支付通知中有返回</param> /// <returns>微信退款单号</returns> public async Task <string> Refund(string out_refund_no1, string out_trade_no1, string total_fee1, string transaction_id1, string refund_fee1, string refund_account1 = "", string refund_desc1 = "", string refund_fee_type1 = "CNY", string device_info1 = "") { string nonce_str1 = out_refund_no1.GetHashUTF8(); // 随机字符串,不长于32位 var hash = BuildSignString(_options.WxAppId, device_info1, _options.WxMchId, nonce_str1, _options.WxRefundNotifyUrl, out_refund_no1, out_trade_no1, refund_account1, refund_desc1, refund_fee1, refund_fee_type1, "MD5", total_fee1, transaction_id1); var requestXml = BuildRequestXml(_options.WxAppId, device_info1, _options.WxMchId, nonce_str1, _options.WxRefundNotifyUrl, out_refund_no1, out_trade_no1, refund_account1, refund_desc1, refund_fee1, refund_fee_type1, hash, "MD5", total_fee1, transaction_id1); if (_options.WxIsDebug) { _logger.EnqueueMessage($"WxRefundXml: {requestXml}"); } try { HttpContent content = new ByteArrayContent(Encoding.UTF8.GetBytes(requestXml)); var res = await _client.PostAsync("https://api.mch.weixin.qq.com/secapi/pay/refund", content); var xml = await res.Content.ReadAsStringAsync(); if (_options.WxIsDebug) { _logger.EnqueueMessage($"WxRefundRes: {xml}"); } // no sign validation note: must be validated before using bool isReturnSuccess = xml.IndexOf("<return_code><![CDATA[SUCCESS]]></return_code>", StringComparison.Ordinal) != -1; //bool isReturnSuccess = xml.StartsWith("<xml><return_code><![CDATA[SUCCESS]]></return_code>", StringComparison.Ordinal); if (!isReturnSuccess) { return(string.Empty); } bool isResultSuccess = xml.IndexOf("<result_code><![CDATA[SUCCESS]]></result_code>", StringComparison.Ordinal) != -1; return(isResultSuccess ? RefundIdPattern.Match(xml).Groups[1].Value : string.Empty /*error*/); } catch (Exception ex) { _logger.EnqueueMessage($"{nameof(WeixinPay)}.{nameof(Refund)} error. Message: {ex.Message} InnerMessage: {ex.InnerException?.Message} StackTrace: {ex.StackTrace}"); } return(string.Empty); string BuildSignString(string appid, string device_info, string mch_id, string nonce_str, string notify_url, string out_refund_no, string out_trade_no, string refund_account, string refund_desc, string refund_fee, string refund_fee_type, string sign_type, string total_fee, string transaction_id) { StringBuilder signBuilder = new StringBuilder(); signBuilder.Append($"appid={appid}"); if (!string.IsNullOrEmpty(device_info)) { signBuilder.Append($"&device_info={device_info}"); } if (!string.IsNullOrEmpty(mch_id)) { signBuilder.Append($"&mch_id={mch_id}"); } if (!string.IsNullOrEmpty(nonce_str)) { signBuilder.Append($"&nonce_str={nonce_str}"); } if (!string.IsNullOrEmpty(notify_url)) { signBuilder.Append($"¬ify_url={notify_url}"); } if (!string.IsNullOrEmpty(out_refund_no)) { signBuilder.Append($"&out_refund_no={out_refund_no}"); } if (!string.IsNullOrEmpty(out_trade_no)) { signBuilder.Append($"&out_trade_no={out_trade_no}"); } if (!string.IsNullOrEmpty(refund_account)) { signBuilder.Append($"&refund_account={refund_account}"); } if (!string.IsNullOrEmpty(refund_desc)) { signBuilder.Append($"&refund_desc={refund_desc}"); } if (!string.IsNullOrEmpty(refund_fee)) { signBuilder.Append($"&refund_fee={refund_fee}"); } if (!string.IsNullOrEmpty(refund_fee_type)) { signBuilder.Append($"&refund_fee_type={refund_fee_type}"); } if (!string.IsNullOrEmpty(sign_type)) { signBuilder.Append($"&sign_type={sign_type}"); } if (!string.IsNullOrEmpty(total_fee)) { signBuilder.Append($"&total_fee={total_fee}"); } if (!string.IsNullOrEmpty(transaction_id)) { signBuilder.Append($"&transaction_id={transaction_id}"); } signBuilder.Append($"&key={_options.WxMchKey}"); return(signBuilder.ToString().GetHashUTF8().ToUpperInvariant()); } string BuildRequestXml(string appid, string device_info, string mch_id, string nonce_str, string notify_url, string out_refund_no, string out_trade_no, string refund_account, string refund_desc, string refund_fee, string refund_fee_type, string sign, string sign_type, string total_fee, string transaction_id) { StringBuilder xmlBuilder = new StringBuilder("<xml>"); xmlBuilder.Append($"<appid>{appid}</appid>"); if (!string.IsNullOrEmpty(device_info)) { xmlBuilder.Append($"<device_info>{device_info}</device_info>"); } if (!string.IsNullOrEmpty(mch_id)) { xmlBuilder.Append($"<mch_id>{mch_id}</mch_id>"); } if (!string.IsNullOrEmpty(nonce_str)) { xmlBuilder.Append($"<nonce_str>{nonce_str}</nonce_str>"); } if (!string.IsNullOrEmpty(notify_url)) { xmlBuilder.Append($"<notify_url>{notify_url}</notify_url>"); } if (!string.IsNullOrEmpty(out_refund_no)) { xmlBuilder.Append($"<out_refund_no>{out_refund_no}</out_refund_no>"); } if (!string.IsNullOrEmpty(out_trade_no)) { xmlBuilder.Append($"<out_trade_no>{out_trade_no}</out_trade_no>"); } if (!string.IsNullOrEmpty(refund_account)) { xmlBuilder.Append($"<refund_account>{refund_account}</refund_account>"); } if (!string.IsNullOrEmpty(refund_desc)) { xmlBuilder.Append($"<refund_desc>{refund_desc}</refund_desc>"); } if (!string.IsNullOrEmpty(refund_fee)) { xmlBuilder.Append($"<refund_fee>{refund_fee}</refund_fee>"); } if (!string.IsNullOrEmpty(refund_fee_type)) { xmlBuilder.Append($"<refund_fee_type>{refund_fee_type}</refund_fee_type>"); } if (!string.IsNullOrEmpty(sign)) { xmlBuilder.Append($"<sign>{sign}</sign>"); } if (!string.IsNullOrEmpty(sign_type)) { xmlBuilder.Append($"<sign_type>{sign_type}</sign_type>"); } if (!string.IsNullOrEmpty(total_fee)) { xmlBuilder.Append($"<total_fee>{total_fee}</total_fee>"); } if (!string.IsNullOrEmpty(transaction_id)) { xmlBuilder.Append($"<transaction_id>{transaction_id}</transaction_id>"); } return(xmlBuilder.Append("</xml>").ToString()); } }