using DSWeb.EntityDA; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Web; using System.Linq; using System.Text.RegularExpressions; using log4net; using Newtonsoft.Json.Linq; using DSWeb.Areas.Account.DAL.Chfee_Invoice_HangXin; using DSWeb.MvcShipping.DAL.MsCompanysDAL; using DSWeb.MvcShipping.DAL.MsSysParamSet; using DSWeb.Areas.CommMng.DAL; namespace DSWeb.Common.Helper { public class RuihongHelper { private const string apiUrlTest = "https://www.chinaeinv.com:943/igs/api/invoiceApi.jspa";//测试环境 private const string apiUrl = "https://www.chinaeinv.com/igs/api/invoiceApi.jspa";//正式环境 public RuihongHelper() { } /// /// 开启测试模式(使用测试环境) /// private bool IsTestMode = false; #region 对象属性 private string AppCode { get { if (IsTestMode) { return "PTTEST17"; } else { return "PT000147"; } } } private string KeyStore { get { if (IsTestMode) { return "PTTEST17.pfx"; } else { return "PT000147.pfx"; } } } private string KeyStorePsw { get { if (IsTestMode) { return "PTTEST17"; } else { return "vfrsedazsed"; } } } private string ApiUrl { get { if (IsTestMode) { return apiUrlTest; } else { return apiUrl; } } } #endregion #region 公共方法 #region 开票 /// /// 开票 /// /// /// public string PostInvoice(string strParams) { BasicDataRefDAL.SaveLog($"{strParams}","", "瑞宏开票", "发出"); var rtn = DoPostData(strParams, "chinaeinv.api.invoice.v3.kp_async"); BasicDataRefDAL.SaveLog($"{rtn}", "", "瑞宏开票", "返回"); return rtn; } /// /// 开票 /// /// /// public string PostInvoice(RuihongPostModel postModel) { var strJson = JsonConvert.SerializeObject(postModel); if (postModel.notices == null || postModel.notices.Count == 0) { JObject jobj = JObject.Parse(strJson); jobj.Remove("notices"); strJson = jobj.ToString(); } return PostInvoice(strJson); } /// /// 开票 /// /// /// public bool PostInvoiceRecord(string invId, out string msg) { var inv = ChinvoiceDAL.GetData("cm.GID='"+ invId + "'"); var invItems = ChinvoiceDAL.GetDetailList("PID='" + invId + "'"); RuihongPostModel postModel = new RuihongPostModel(); postModel.serialNo = inv.GID; postModel.postTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); postModel.order = new RuihongPostOrderModel() { orderNo = inv.GID }; if (inv.RECVCURR=="USD") { postModel.dynamicParams = new { dollarAmount = inv.OTCURRAMOUNT.ToString() }; } RuihongPostInvoiceModel postInv = new RuihongPostInvoiceModel(); if (IsTestMode) { postInv.taxpayerCode = "91370200264807TEST4A"; } else { //var company = MsCompanysDAL.GetNoPicData("GID='" + inv.COMPANYID + "'"); postInv.taxpayerCode = inv.TAXCODE; postInv.taxpayerName = inv.BILLRISES; postInv.taxpayerAddress = inv.ADDRESS; postInv.taxpayerTel = inv.OFFICEPHONE; } if (!string.IsNullOrEmpty(inv.PUSHEMAIL)) { postModel.notices = new List(); postModel.notices.Add(new RuihongPostNoticeModel() { type = "email", value= inv.PUSHEMAIL }); } //分拆银行和账号 for (int idx = inv.ACCOUNT.Length - 1; idx >= 0; idx--) { var idxStr = inv.ACCOUNT[idx].ToString(); if (Regex.IsMatch(idxStr, "[0-9\\s-]{1}")) { continue; } postInv.taxpayerBankName = inv.ACCOUNT.Substring(0, idx + 1); postInv.taxpayerBankAccount = inv.ACCOUNT.Substring(idx + 1); break; } postInv.customerCode = inv.CUSTRATENO; postInv.customerName = inv.INVOICECUSTNAME; postInv.customerAddress = inv.CUSTADDR; postInv.customerTel = inv.CUSTTEL; //分拆银行和账号 for (int idx = inv.CUSTBANK.Length - 1; idx >= 0; idx--) { var idxStr = inv.CUSTBANK[idx].ToString(); if (Regex.IsMatch(idxStr, "[0-9\\s-]{1}")) { continue; } postInv.customerBankName = inv.CUSTBANK.Substring(0, idx + 1); postInv.customerBankAccount = inv.CUSTBANK.Substring(idx + 1); break; } postInv.drawer = inv.OPERATORNAME; postInv.reviewer = inv.CHECKER; postInv.payee = inv.PAYEE; postInv.totalAmount = Math.Round(inv.INVAMOUNT, 2, MidpointRounding.AwayFromZero).ToString("#0.00"); postInv.remark = inv.REMARK; postModel.invoice = postInv; postInv.items = new List(); foreach (var item in invItems) { var goodsCode = item.GOODCODE == null ? string.Empty : item.GOODCODE; goodsCode = goodsCode.PadRight(19, '0'); //零税率标识。1:免税,2:不征税,3:普通零税率。税率为零的情况下,如果不传,则默认为1:免税。 var 零税率标识 = "1"; if (item.TAXRATE > 0) { 零税率标识 = null; } else { //if (item.ZTAXTYPE == "免税") 零税率标识 = "1"; //if (item.ZTAXTYPE == "不征税") 零税率标识 = "2"; //if (item.ZTAXTYPE == "普通零税率") 零税率标识 = "3"; 零税率标识= item.ZTAXTYPE ; } var taxrate = Math.Round(item.TAXRATE / 100, 2, MidpointRounding.AwayFromZero).ToString(); if (taxrate == "0.00" || taxrate == "0.0") taxrate = "0"; postInv.items.Add(new RuihongPostInvoiceItemModel() { type = "0", name = item.GOODSNAMEREF, spec = item.SPEC, uom = item.UNIT, quantity = item.PKGS, taxRate = taxrate, amount = Math.Round(item.AMOUNT + item.TAX, 2, MidpointRounding.AwayFromZero).ToString("#0.00"), price = item.PRICE, catalogCode = goodsCode, preferentialPolicyFlg = item.ISUSEPREF, addedValueTaxFlg = item.DEFREMARK, zeroTaxRateFlg = 零税率标识 }); } string rtn = PostInvoice(postModel); var rtnObj = JsonConvert.DeserializeObject(rtn); if (rtnObj.code == "0") { T_ALL_DA T_ALL_DA = new EntityDA.T_ALL_DA(); var blUpSQL = "update ch_fee_invoice set INVOICESERIALNUM='" + rtnObj.serialNo + "',EINVOICESTATE='1' where GID='" + invId + "' "; bool bl = T_ALL_DA.GetExecuteSqlCommand(blUpSQL); //inv.SerialNo = rtnObj.serialNo; //inv.Status = InvoiceRecord.StatusPost; //invDB.SaveChanges(); msg = rtnObj.message; return true; } else { T_ALL_DA T_ALL_DA = new EntityDA.T_ALL_DA(); var blUpSQL = "update ch_fee_invoice set EINVOICESTATE='1' where GID='" + invId + "' "; bool bl = T_ALL_DA.GetExecuteSqlCommand(blUpSQL); msg = rtnObj.message; return false; } } #endregion #region 查票 /// /// 查票 /// /// /// public string QueryInvoice(string strParams) { BasicDataRefDAL.SaveLog($"{strParams}", "", "瑞宏查票", "发出"); //logger.Debug($"瑞宏查票:{strParams}"); var rtn = DoPostData(strParams, "chinaeinv.api.invoice.v3.cx"); BasicDataRefDAL.SaveLog($"{rtn}", "", "瑞宏查票返回", "返回"); //logger.Debug($"瑞宏查票返回:{rtn}"); return rtn; } /// /// 根据操作流水号单个查询 /// /// 单个操作流水号 /// public string QueryInvoiceSerialNo(params string[] serNO) { if (serNO == null || serNO.Length == 0) { return null; } RuihongQueryModel reqModel = new RuihongQueryModel(); reqModel.serialNo = Guid.NewGuid().ToString(); reqModel.postTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); reqModel.criteria = new List(); foreach (var item in serNO) { reqModel.criteria.Add(new RuihongQueryInner() { name = "singlesSerialNo", value = item }); } return QueryInvoice(JsonConvert.SerializeObject(reqModel)); } /// /// 查询发票开出 /// /// /// /// public bool QueryInvoiceRecord(string invId,string INVOICESERIALNUM, out string msg) { //var inv = ChinvoiceDAL.GetData("cm.GID='" + invId + "'"); if (string.IsNullOrEmpty(INVOICESERIALNUM)) { msg = "还未成功进行平台开票,无可读数据"; return false; } string rtn = QueryInvoiceSerialNo(INVOICESERIALNUM); var rtnObj = JsonConvert.DeserializeObject(rtn); if (rtnObj.code == "0") { var InvCode = rtnObj.invoices[0].code.Substring(0, 12); var InvNum = rtnObj.invoices[0].code.Substring(12); var PdfUrl = rtnObj.invoices[0].pdfUnsignedUrl; PdfUrl = PdfUrl.Replace("Unsigned",""); var PictureUrl = rtnObj.invoices[0].viewUrl; var InvoiceDate = Convert.ToDateTime(rtnObj.invoices[0].generateTime).ToString("yyyy-MM-dd"); var needautolock = MsSysParamSetDAL.GetSysParam("INVOICEAUTOLOCK").isnullortrue(); T_ALL_DA T_ALL_DA = new EntityDA.T_ALL_DA(); var blUpSQL = "update ch_fee_invoice set INVOICECODE='" + InvCode + "',INVOICENO='" + InvNum + "',INVOICEINFOURL='" + PictureUrl + "',INVOICEPDFURL='" + PdfUrl + "',INVOICEMAKETIME='" + InvoiceDate + "',EINVOICESTATE='2' where GID='" + invId + "' "; if (needautolock) { blUpSQL = "update ch_fee_invoice set BILLSTATUS=1,INVOICECODE='" + InvCode + "',INVOICENO='" + InvNum + "',INVOICEINFOURL='" + PictureUrl + "',INVOICEPDFURL='" + PdfUrl + "',INVOICEMAKETIME='" + InvoiceDate + "',EINVOICESTATE='2' where GID='" + invId + "' "; } bool bl = T_ALL_DA.GetExecuteSqlCommand(blUpSQL); //读取成功后,发送邮件 //if (inv.PushMode == "0" && !string.IsNullOrEmpty(inv.Email)) //{ // MailHelper.SendMailInvoiceRecord(inv.GID); //} msg = rtnObj.message; return true; } else { msg = rtnObj.message; return false; } } #endregion #region 冲红 /// /// 冲红 /// /// /// public string RedInvoice(string strParams) { BasicDataRefDAL.SaveLog($"{strParams}", "", "瑞宏冲红", "发出"); var rtn = DoPostData(strParams, "chinaeinv.api.invoice.v3.ch_async"); BasicDataRefDAL.SaveLog($"{rtn}", "", "瑞宏冲红返回", "返回"); return rtn; } /// /// 冲红 /// /// /// public string RedInvoice(RuihongRedInvoiceModel redModel) { return RedInvoice(JsonConvert.SerializeObject(redModel)); } /// /// 冲红 /// /// /// public bool RedInvoiceRecord(string invId, out string msg) { var inv = ChinvoiceDAL.GetData("cm.GID='" + invId + "'"); //var invItems = ChinvoiceDAL.GetDetailList("PID='" + invId + "'"); RuihongRedInvoiceModel redModel = new RuihongRedInvoiceModel(); redModel.serialNo = inv.GID; redModel.postTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); redModel.originalCode = inv.REDCODE + inv.REDNUM; redModel.reason = "开票有误"; string rtn = RedInvoice(redModel); var rtnObj = JsonConvert.DeserializeObject(rtn); if (rtnObj.code == "0") { //inv.SerialNo = rtnObj.serialNo; //inv.Status = InvoiceRecord.StatusPost; //invDB.SaveChanges(); T_ALL_DA T_ALL_DA = new EntityDA.T_ALL_DA(); var blUpSQL = "update ch_fee_invoice set INVOICESERIALNUM='" + rtnObj.serialNo + "',EINVOICESTATE='2' where GID='" + invId + "' "; bool bl = T_ALL_DA.GetExecuteSqlCommand(blUpSQL); msg = rtnObj.message; return true; } else { msg = rtnObj.message; return false; } } #endregion public string DoPostData(string kpParams, string cmd) { //appCode string appcode = UrlEncode(AppCode, Encoding.UTF8); //cmdName string cmdName = UrlEncode(cmd, Encoding.UTF8); //sign string sign = UrlEncode(GetSign(kpParams), Encoding.UTF8); //URL StringBuilder Url = new StringBuilder(); Url.Append(ApiUrl); Url.Append("?sign=" + sign); Url.Append("&cmdName=" + cmdName); Url.Append("&appCode=" + appcode); //Post 请求URL HttpWebRequest request = null; HttpWebResponse response = null; try { //设置最大连接数 ServicePointManager.DefaultConnectionLimit = 200; //设置https验证方式 if (ApiUrl.StartsWith("https", StringComparison.OrdinalIgnoreCase)) { ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult); } request = WebRequest.Create(Url.ToString()) as HttpWebRequest; byte[] requestBytes = Encoding.GetEncoding("UTF-8").GetBytes(kpParams); request.Method = "POST"; request.Timeout = 180 * 1000; request.ContentType = "application/json;charset=UTF-8"; request.ContentLength = requestBytes.Length; using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(requestBytes, 0, requestBytes.Length); requestStream.Close(); } //获取响应报文 response = request.GetResponse() as HttpWebResponse; using (StreamReader reader = new StreamReader(request.GetResponse().GetResponseStream(), Encoding.GetEncoding("UTF-8"))) { return reader.ReadToEnd().Trim(); } } catch (Exception ex) { return ex.Message; } finally { //关闭连接和流 if (response != null) { response.Close(); } if (request != null) { request.Abort(); } } } #endregion #region 私有方法 private bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; } /// /// UrlEncode /// /// 要转码的文本 /// 编码 /// private string UrlEncode(string temp, Encoding encoding) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < temp.Length; i++) { string t = temp[i].ToString(); string k = HttpUtility.UrlEncode(t, encoding); if (t == k) { stringBuilder.Append(t); } else { stringBuilder.Append(k.ToUpper()); } } return stringBuilder.ToString(); } /// /// 数字签名 /// /// private string GetSign(string kpParams) { try { var keyStorePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, KeyStore); //logger.Debug($"keyStorePath:{keyStorePath}"); //logger.Debug($"KeyStorePsw:{KeyStorePsw}"); X509Certificate2 x = new X509Certificate2(keyStorePath, KeyStorePsw, X509KeyStorageFlags.Exportable); RSACryptoServiceProvider rsa = x.PrivateKey as RSACryptoServiceProvider; byte[] data = Encoding.UTF8.GetBytes(kpParams); RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider(); privateKey1.ImportParameters(rsa.ExportParameters(true)); byte[] sign = privateKey1.SignData(data, "MD5"); string strSign = Convert.ToBase64String(sign); string s1 = strSign.Substring(0, 76); string s2 = strSign.Substring(76 * 1, 76); string s3 = strSign.Substring(76 * 2); string resultSign = UrlEncode(s1, Encoding.UTF8) + "%0D%0A" + UrlEncode(s2, Encoding.UTF8) + "%0D%0A" + UrlEncode(s3, Encoding.UTF8); return resultSign; } catch (Exception ex) { var excep = ex; while (excep != null) { //logger.Error(excep.Message); //logger.Error(excep.StackTrace); excep = excep.InnerException; } return string.Empty; } } #endregion } #region 交互对象 #region 开票请求 public class RuihongPostModel { /// /// 是 50 操作流水号。传入重复的操作流水号则认为是重复操作。 /// public string serialNo { get; set; } /// /// 是 19 请求发送时间。格式为yyyy-MM-dd HH:mm:ss。 /// public string postTime { get; set; } /// /// 是 订单信息。 /// public RuihongPostOrderModel order { get; set; } /// /// 是 发票信息。 /// public RuihongPostInvoiceModel invoice { get; set; } /// /// 否 3 通知方式列表,最多3条。 /// public List notices { get; set; } /// /// 否 20 扩展参数。一组Key-Value形式的数据,会在响应报文中回传给调用方,由调用者和瑞宏网双方根据实际情况协商使用。 /// public object extendedParams { get; set; } /// /// 否 自定义参数。一组Key-Value形式的数据,key值:callbackUrl,Value为回调地址“http://wwww.***.com/test/invoice” /// public object dynamicParams { get; set; } } public class RuihongPostOrderModel { /// /// 是 50 订单编号。 /// public string orderNo { get; set; } /// /// 否 100 消费者的用户名。 /// public string account { get; set; } /// /// 否 250 货物配送地址。 /// public string address { get; set; } /// /// 否 消费者电话号码。手机号码或区号-固定电话号码。 /// public string tel { get; set; } /// /// 否 消费者电子邮件地址。电子邮件格式。 /// public string email { get; set; } } public class RuihongPostInvoiceItemModel { /// /// 是 1 发票行性质 0 正常行、1 折扣行、2 被 折扣行。 /// public string type { get; set; } /// /// 否 50 商品编码。 /// public string code { get; set; } /// /// 是 90 商品名称。可在每一行商品下加入折扣行,折扣行商品名称与被折扣商品名称一致,金额和税额栏以负数填写,税率与被折扣行商品税率相同,其它栏不填写。 /// public string name { get; set; } /// /// 否 40 规格型号。 /// public string spec { get; set; } /// /// 两者要么都为空,要么都不为空。 18(整)6(小) 商品单价。必须等于amount/quantity的四舍五入值。 /// public decimal price { get; set; } /// /// 两者要么都为空,要么都不为空。 18(整)6(小) 数量。必须大于等于0.000001。 /// public decimal quantity { get; set; } /// /// 否 20 单位。 /// public string uom { get; set; } /// /// 是 6(小) 税率。只能为0、 0.03、0.04、0.06、0.10、0.11、0.16、0.17。 /// public string taxRate { get; set; } /// /// 是 18(整)2(小) 税价合计金额。 /// public string amount { get; set; } /// /// 是 50 商品分类编码。目前的分类编码为19位,不足19位的在后面补0。 /// public string catalogCode { get; set; } /// /// 否 1 优惠政策标识。0:不使用,1:使用。 /// public string preferentialPolicyFlg { get; set; } /// /// 否 50 增值税特殊管理(当优惠政策标识为1时必填)。 /// public string addedValueTaxFlg { get; set; } /// /// 否 1 零税率标识。1:免税,2:不征税,3:普通零税率。税率为零的情况下,如果不传,则默认为3:普通零税率。 /// public string zeroTaxRateFlg { get; set; } } public class RuihongPostInvoiceModel { /// /// 是 20 销货方纳税人识别号。 /// public string taxpayerCode { get; set; } /// /// 否 100 销货方纳税人名称 /// public string taxpayerName { get; set; } /// /// 否 79 销货方地址。如果填写则使用填写的信息,否则使用纳税人注册时预留的信息。 /// public string taxpayerAddress { get; set; } /// /// 否 20 销货方电话。 /// public string taxpayerTel { get; set; } /// /// 否 69 销货方开户银行。 /// public string taxpayerBankName { get; set; } /// /// 否 30 销货方银行账号。 /// public string taxpayerBankAccount { get; set; } /// /// 是 100 购货方名称,即发票抬头。 /// public string customerName { get; set; } /// /// 否 20 购货方纳税人识别号或者个人身份证号 /// public string customerCode { get; set; } /// /// 否 79 购货方地址。 /// public string customerAddress { get; set; } /// /// 否 20 购货方电话。 /// public string customerTel { get; set; } /// /// 否 69 购货方开户银行。 /// public string customerBankName { get; set; } /// /// 否 30 购货方银行账号。 /// public string customerBankAccount { get; set; } /// /// 否 2 开票类型。p:电子增值税普通发票(默认) ps:电子收购发票 py:成品油 /// public string invoiceType { get; set; } /// /// 否 50 店铺编号 /// public string shopCode { get; set; } /// /// 否 50 店铺名称 /// public string shopName { get; set; } /// /// 否 50 支付方式 /// public string payType { get; set; } /// /// 否 50 支付流水号 /// public string payBillNo { get; set; } /// /// 是 8 开票人。 /// public string drawer { get; set; } /// /// 否 8 收款人。 /// public string payee { get; set; } /// /// 否 8 复核人。 /// public string reviewer { get; set; } /// /// 是 18(整)2(小) 税价合计金额。必须大于等于0.01元;必须等于发票明细合计金额;必须小于等于在税务局进行票种核定时确定的单张发票开票限额。 /// public string totalAmount { get; set; } /// /// 否 130 发票备注。 /// public string remark { get; set; } /// /// 是 100 发票项目明细列表。每张发票最多一百条。 /// public List items { get; set; } } public class RuihongPostNoticeModel { /// /// 是 通知类型。短信:sms;电子邮件:email。 /// public string type { get; set; } /// /// 是 通知类型为短信时,必须为手机号码;通知类型为电子邮件时,必须为邮件地址。 /// public string value { get; set; } } #endregion #region 查票请求 public class RuihongQueryModel { public string serialNo { get; set; } public string postTime { get; set; } public List criteria { get; set; } } public class RuihongQueryInner { public string name { get; set; } public string value { get; set; } } #endregion #region 冲红请求 public class RuihongRedInvoiceModel { /// /// 是 50 操作流水号。传入重复的操作流水号则认为是重复操作。 /// public string serialNo { get; set; } /// /// 是 19 请求发送时间。格式为yyyy-MM-dd HH:mm:ss。 /// public string postTime { get; set; } /// /// 是 20 原发票代码+原发票号码。 /// public string originalCode { get; set; } /// /// 是 250 冲红原因。 /// public string reason { get; set; } /// /// 否 3 通知方式列表。 /// public List notices { get; set; } /// /// 否 100 发票项目明细列表。每张发票最多一百条。如果为空则按照原发票金额和明细全额冲红。如果不为空则按照该明细和金额进行部分冲红。 /// public List items { get; set; } } #endregion #region 返回 public class RuihongRespCommon { public string serialNo { get; set; } public string postTime { get; set; } public string code { get; set; } public string message { get; set; } } public class RuihongRespQuery : RuihongRespCommon { public List invoices { get; set; } } public class RuihongRespQueryInner { public string orderNo { get; set; } public string code { get; set; } public string checkCode { get; set; } public string fiscalCode { get; set; } public string status { get; set; } public string generateTime { get; set; } public string pdfUnsignedUrl { get; set; } public string viewUrl { get; set; } public string relatedCode { get; set; } public string validReason { get; set; } public string validTime { get; set; } } #endregion #endregion }