POS软件是什么?你好意思吗,还在用老掉牙的Winform。
销售终端——POS(point of sale)是一种多功能终端,把它安装在信用卡的特约商户和受理网点中与计算机联成网络,就能实现电子资金自动转账,它具有支持消费、预授权、余额查询和转账等功能,使用起来安全、快捷、可靠。
POS软件大都长这样子:
不好意思了,我没有专门去收集POS软件的UI界面。感兴趣的朋友可以去澳大利亚知名POS软件供应商IDEALPOS的官网去围观:
POS Systems Australia - Software & Hardware - Idealpos
好久以前就想写这篇文章了,一是想要证明我还在程序员队伍里苟延残喘。二是这样的文章我从来就没看见过,写出来万一帮到其他人了呢。今天早上我查看请求的状态时豁然发现已经通过平台的审核了,接下来就是区域的审核:我们公司在FEIJI和SAMOA都有门店。
目前TaxCore提供的税务平台有四个国家(地区)在用:Samoa ,Fiji,USA, WA.目前TaxCore上备案通过的也没几家公司,我知道的就只要上面提到的澳大利亚IDEALPOS。
我的软件界面有参考它。在国内税务发票是使用针式打印机,套打发票,而国外的发票要求没这么高,用普通的热敏打印机打印就可以了,因为发票是可以在TaxCore的网站上面看到的。所以不能长期保存也没有问题。下面展示一张财务发票:TaxCore: Invoice Verification - TaxCore,点击一下页面的Journal超链接能看到财政发票的原始样子,大概就是这样子:
对于纳税人来说可以通过扫描发票上的二维码进行在线查看。对于商户来说,也可以进入到自己所在区域的税所的门户网站后台查看每一张发票,这是TaxCore的全球的沙盒环境的门户:TaxCore | Log off 。财政发票的从开始到结束部分是不运行有任何改动的。但商家可以在头尾加一点自己的东西比如:
TaxCore的审核是我见过最认真细致的,对发票的要求细致得很。我们软件来来回回经过十几轮审核修好终于才通过了。下面展示TaxCore对发票的要求:
乍一看,这基本上是不可能完成的任务吧,全英文的在线文档,没有人带,还好官方有个工单系统,有问题可以在上面提问,会得到很及时的很友好的回复(由于一般有时区的差异,工作时间不一样不要要求马上有答复)。关于官方的帮助文档的地址请点击这里:TaxCore - Help Viewer (revenue.gov.ws)
<!DOCTYPE html> <html> <head> <title>Online POS</title> <meta charset="utf-8"> </head> <body> <h1>Online POS</h1> <label for="invoiceRequest">Invoice request json</label> <textarea id="invoiceRequest" cols="100" rows="30" style="display:block"></textarea> <label for="taxcore_sign_element">Send Invoice Request:</label> <!--TaxCore HTML element--> <button id="taxcore_sign_element" data-taxcore-vsdc-url=" https://vsdc.sandbox.taxcore.online/" data-taxcore-input-id="invoiceRequest" data-taxcore-output-id="results" data-taxcore-invoice-request="" data-taxcore-debug="true" data-taxcore-signed-invoice-response="">Sign Invoice</button> <label for="results">Received Signed Invoice:</label> <textarea readonly id="results" cols="100" rows="30"></textarea> <!-- TAXCORE.JS --> <script src=" https://vsdc.sandbox.taxcore.online/onlinepos/v1/taxcore.min.js"></script> <!-- Custom script at Online POS --> <script> document.getElementById("invoiceRequest").innerHTML = JSON.stringify(CreateExampleInvoiceRequest(), undefined, 4); document.getElementById("taxcore_sign_element").dataset.taxcoreInvoiceRequest = JSON.stringify(CreateExampleInvoiceRequest()); // Listen to messages from TaxCore window.onmessage = function (e) { console.log(e.data); } function CreateExampleInvoiceRequest() { var invoiceRequest = { "DateAndTimeOfIssue": "2017-08-31T13:28:02.433Z", "Cashier": "John", "BD": null, "BuyerCostCenterId": null, "IT": "Normal", "TT": "Sale", "PaymentType": "Card", "InvoiceNumber": "31082017-2", "ReferentDocumentNumber": null, "PAC": null, "Options": { "OmitTextualRepresentation": 0, "OmitQRCodeGen": 0 }, "Items": [ { "GTIN": null, "Name": "Book", "Quantity": 1, "Labels": [ "A" ], "TotalAmount": 50 } ] }; return invoiceRequest; } </script> </body> </html>
在国内是不存在网络问题的,所以官方提供了HTML在线POS的示例代码。那么我认为有必要提一下为什么最终我选择了用Winform来做这个客户端程序。第一我有多年的CS程序开发经验,在这里3G和4G都满如蜗牛一样的网络条件下我首先想到的是自动实现离线和在线模式切换,先把交易数据存在本地的Sqlite中,一旦网络可用才上传到服务器,而且针对离线交易不能打印在线的财政发票的情况下TaxCore提供了半连接模式可以使用智能卡存储交易数据,一旦网络恢复则从卡里读取数据再上传,提供了一种本地审核的方式,就是离线可以打印财政发票。由于我对WPF等技术不太熟练,综合考虑于是就使用了WinForm来开发客户端程序。下面贴一点WinForm发起发票请求的代码:
/// <summary> /// 发起联网发票请求 /// </summary> /// <param name="bill">订单</param> public void RequestNormalInvoice(OrderTableEntity bill) { #region 构建请求json TaxCoreBobyRequest request = new TaxCoreBobyRequest(); request.Cashier = bill.casher; if (CurrentMemberObj != null) request.BD = string.IsNullOrEmpty(CurrentMemberObj.Buyer_Tin) == true ? null : CurrentMemberObj.Buyer_Tin; else request.BD = null; request.BuyerCostCenterId = null; request.IT = "Normal"; request.TT = "Sale"; request.InvoiceNumber = bill.orderNo; request.ReferentDocumentNumber = null; request.PAC = $"{ConfigObj.PAC}"; var s1 = new BaseRepository<BillPayDetailEntityTable>(); var s2 = new BaseRepository<BillConsumeDetailEntityTable>(); var s3 = new BaseRepository<Payment_method_tableEntity>(); var list1 = s1.GetList(p => p.order_No == bill.orderNo); var list2 = s2.GetList(p => p.order_No == bill.orderNo); List<GoodsItem> items = new List<GoodsItem>(); var goodsSvr = new BaseRepository<GoodsTableEntity>(); #region 商品明细 foreach (var item in list2) { var goods = goodsSvr.GetFirst(g => g.goodsNo == item.goods_No); items.Add(new GoodsItem { GTIN = null, Name = item.goods_Name, Quantity = item.qty, UnitPrice = item.sale_price - item.discountAmount, TotalAmount = item.qty * (item.sale_price - item.discountAmount), Labels = new string[] { goods == null ? ConfigObj.MISCELLANEOUS_Rate_Label : goods.taxLabels } }); } #endregion request.Items = items; var pInfo = s3.GetList(p => p.pm_Id == list1.FirstOrDefault().payment_method).FirstOrDefault(); if (pInfo.TaxcoreName != pInfo.pm_Name) request.PaymentType = pInfo.TaxcoreName; else request.PaymentType = pInfo.pm_Name; #endregion string invoiceRequest = JsonConvert.SerializeObject(request); var httpContent = new StringContent(invoiceRequest, Encoding.UTF8, "application/json"); HttpClient client; WebRequestHandler handler; GetClientAndHandler(out handler, out client); var response = client.PostAsync($"api/Sign/SignInvoice", httpContent).Result; if (response.StatusCode == HttpStatusCode.OK) { var svr = new BaseRepository<OrderTableEntity>(); bool b = invoiceRequest.Contains("'"); if (b) invoiceRequest = invoiceRequest.Replace("'", " "); string cmd = $"update orders set fiscalInvoiceRequest='{invoiceRequest}' where orderNo='{bill.orderNo}'"; svr.Context.Ado.ExecuteCommand(cmd); var jsonString = response.Content.ReadAsStringAsync(); jsonString.Wait(); var invoiceResponse = jsonString.Result; var responseObj = JsonConvert.DeserializeObject<TaxCoreResponse>(invoiceResponse); orderTableEntity = bill; PrintNormalInvoice(responseObj); responseObj.VerificationQRCode = ""; responseObj.ID = ""; responseObj.S = ""; responseObj.Journal = ""; string cmd_sql = $"update orders set UploadDateTime=null,fiscalInvoiceResponse='{JsonConvert.SerializeObject(responseObj)}' where orderNo='{bill.orderNo}'"; svr.Context.Ado.ExecuteCommand(cmd_sql); } else { MessageTip.ShowError($"Failed to request invoice:[{response.StatusCode}]"); throw new Exception($"Failed to request invoice:[{response.StatusCode}]"); } } /// <summary> /// 请求地址赋值 /// </summary> /// <param name="handler"></param> /// <param name="client"></param> public void GetClientAndHandler(out WebRequestHandler handler, out HttpClient client) { handler = CreateWebRequestHandler(); client = new HttpClient(handler); client.BaseAddress = new Uri($"{ConfigObj.RequstBaseURL}"); client.DefaultRequestHeaders.Accept.Clear(); } /// <summary> /// 请求加入证书 /// </summary> /// <returns></returns> private WebRequestHandler CreateWebRequestHandler() { var handler = new WebRequestHandler(); var cert = GetClientCertificate(); handler.ClientCertificateOptions = ClientCertificateOption.Manual; handler.ClientCertificates.Add(cert); return handler; } /// <summary> /// 读取证书 /// </summary> /// <returns></returns> private X509Certificate2 GetClientCertificate() { X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser); //store.Open(OpenFlags.ReadWrite); string certName = $"{ConfigObj.InvoiceCertificateName}"; store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); var certificates = store.Certificates; foreach (X509Certificate2 cert in certificates) { if (cert.Subject.Contains(certName)) { return cert; } } return null; }
关于打印的代码就有点冗长了,请无视我的复制和粘帖运用得如此炉火纯青。
/// <summary> /// 发票绘制 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void PdInvoice_PrintPage(object sender, PrintPageEventArgs e) { string[] journal = currentResponse.Journal.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); string pos_time = journal[9].Replace("POS Time: ", ""); if (!string.IsNullOrEmpty(orderTableEntity.member_No)) pos_time = journal[10].Replace("POS Time: ", ""); var s1 = new BaseRepository<BillConsumeDetailEntityTable>(); var s2 = new BaseRepository<BillPayDetailEntityTable>(); var list1 = s1.GetList(o => o.order_No == orderTableEntity.orderNo); var list2 = s2.GetList(o => o.order_No == orderTableEntity.orderNo); int right_space = 32; e.Graphics.Clear(Color.White); Rectangle rect = new Rectangle(0, 0, 300, 360); #region StringFormat StringFormat sf_center = new StringFormat(); sf_center.Alignment = StringAlignment.Center; sf_center.LineAlignment = StringAlignment.Center; StringFormat sf_left = new StringFormat(); sf_left.Alignment = StringAlignment.Near; sf_left.LineAlignment = StringAlignment.Center; StringFormat sf_right = new StringFormat(); sf_right.Alignment = StringAlignment.Far; sf_right.LineAlignment = StringAlignment.Center; #endregion Font tipsFont = new Font("微软雅黑", 9F, FontStyle.Bold); Font txtFont = new Font("微软雅黑", 9F); e.Graphics.DrawString($"{merchantStoreEntity.name}", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_center); rect.Y += 20; e.Graphics.DrawString($"===========================================", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 20; e.Graphics.DrawString($"PH#:{merchantStoreEntity.phone} MOB#:{merchantStoreEntity.mobile}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_center); if (!string.IsNullOrEmpty(merchantStoreEntity.store_advertising)) { rect.Y += 20; e.Graphics.DrawString($"{merchantStoreEntity.store_advertising}", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_center); } rect.Y += 20; e.Graphics.DrawString($"========= FISCAL INVOICE ===============", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 20; e.Graphics.DrawString($"TIN:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString(currentResponse.TIN, txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"Company:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{currentResponse.BusinessName}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"Store:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{merchantStoreEntity.name}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"Address:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{currentResponse.Address}", txtFont, Brushes.Black, new Rectangle(42, rect.Y, rect.Width - 76, 40), sf_right); rect.Y += 40; e.Graphics.DrawString($"District:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{currentResponse.District}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"Cashier TIN:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{CashierFlowContext.Instance.CurrentEmployee.employeeName}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"POS Number:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{orderTableEntity.orderNo}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"POS Time:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString(pos_time.Trim(), txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); if (!string.IsNullOrEmpty(orderTableEntity.member_No)) { rect.Y += 20; e.Graphics.DrawString($"Buyer TIN:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{orderTableEntity.member_No}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); } rect.Y += 20; e.Graphics.DrawString($"-----------------NORMAL SALE--------------------", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 20; e.Graphics.DrawString($"Items", txtFont, Brushes.Black, new Rectangle(4, rect.Y, rect.Width, 20), sf_center); rect.Y += 20; e.Graphics.DrawString($"===========================================", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 18; e.Graphics.DrawString($"Name Price Qty. Total ", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 16; var goodsSvr = new BaseRepository<GoodsTableEntity>(); foreach (var detailRow in list1) { var goods = goodsSvr.GetFirst(g => g.goodsNo == detailRow.goods_No); string label = goods == null ? ConfigObj.MISCELLANEOUS_Rate_Label : goods.taxLabels; e.Graphics.DrawString($"{detailRow.goods_Name} ({label})", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{detailRow.sale_price.ToString("C2").Substring(1)}", txtFont, Brushes.Black, new Rectangle(rect.Width / 2 - 86, rect.Y + 16, rect.Width, 20), sf_left); e.Graphics.DrawString($"{detailRow.qty}", txtFont, Brushes.Black, new Rectangle(rect.Width / 2 - 8, rect.Y + 16, rect.Width, 20), sf_left); e.Graphics.DrawString($"{detailRow.amount.ToString("C2").Substring(1)}", txtFont, Brushes.Black, new Rectangle(0, rect.Y + 16, rect.Width - right_space - 8, 20), sf_right); rect.Y += 34; } e.Graphics.DrawString($"Total Purchase:", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{orderTableEntity.payAmount.ToString("C2").Substring(1)}", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; var service = new BaseRepository<Payment_method_tableEntity>(); var p = service.GetFirst(pm => pm.pm_Id == list2.FirstOrDefault().payment_method); e.Graphics.DrawString($"Payment Method:", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); if (p.TaxcoreName != p.pm_Name) e.Graphics.DrawString(p.TaxcoreName, tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); else e.Graphics.DrawString($"{p.pm_Name}", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"===========================================", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 18; e.Graphics.DrawString($"Label Name Rate ", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString(" Tax ", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; foreach (var rate in currentResponse.TaxItems) { e.Graphics.DrawString($"{rate.Label} {rate.CategoryName} {rate.Rate}% ", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{rate.Amount.ToString("C2").Substring(1)} ", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; } e.Graphics.DrawString($"--------------------------------------------------", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 20; e.Graphics.DrawString($"Total Tax:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{currentResponse.TaxItems.Sum(t => t.Amount).ToString("C2").Substring(1)}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"===========================================", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 20; e.Graphics.DrawString($"SDC Time:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); string dt = Convert.ToDateTime(currentResponse.DT).ToString("dd/MM/yyyy HH:mm:ss"); if (ConfigObj.RequstBaseURL.Contains("sandbox")) { dt = Convert.ToDateTime(currentResponse.DT.AddHours(-12)).ToString("dd/MM/yyyy HH:mm:ss");//.ToString("dd/MM/yyyy HH:mm:ss"); } e.Graphics.DrawString($"{dt}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"SDC Invoice No: ", new Font("微软雅黑", 8.2F), Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_left); e.Graphics.DrawString($"{currentResponse.IN}", new Font("微软雅黑", 8.2F), Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"Invoice Counter: ", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_left); e.Graphics.DrawString($"{currentResponse.IC}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"===========================================", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 20; byte[] bytes = Convert.FromBase64String(currentResponse.VerificationQRCode); MemoryStream memStream = new MemoryStream(bytes); Image mImage = Image.FromStream(memStream); e.Graphics.DrawImage(mImage, new Rectangle(8, rect.Y, 266, 266)); rect.Y += 266; e.Graphics.DrawString($"====== END OF FISCAL INVOICE ==========", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 20; e.Graphics.DrawString($"Subtotal ({list1.Count()} items)", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{list1.Sum(o => o.amount).ToString("C2").Substring(1)}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); if (orderTableEntity.payMethodName.Contains("->(")) { rect.Y += 20; e.Graphics.DrawString($"===========Aggregated payment============", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); rect.Y += 20; e.Graphics.DrawString($"Payment method:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{orderTableEntity.payMethodName}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"Payment Amount:", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{orderTableEntity.payAmount.ToString("C2").Substring(1)}", txtFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); } if (p.is_cash.ToUpper().Equals("Y") && (list2.Any(o => o.exchangeAmount > 0))) { rect.Y += 20; e.Graphics.DrawString($"Collection:", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{(orderTableEntity.payAmount + orderTableEntity.payBackAmount).ToString("C2").Substring(1)}", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); rect.Y += 20; e.Graphics.DrawString($"Change:", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_left); e.Graphics.DrawString($"{orderTableEntity.payBackAmount.ToString("C2").Substring(1)}", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width - right_space, 20), sf_right); } if (!string.IsNullOrEmpty(merchantStoreEntity.store_advertising_footer)) { rect.Y += 22; e.Graphics.DrawString($"{merchantStoreEntity.store_advertising_footer}", tipsFont, Brushes.Black, new Rectangle(0, rect.Y, rect.Width, 20), sf_center); } }