# 商家 App:电子发票打印接入(商米 V3) 后端在 ezPay 开票成功落库后,后台自动准备 PDF 和打印图片。**App 不需要再排版或把 PDF 转图片**;使用商米原生 SDK 的 `printBitmap` 打印 PNG。平台继续查看 PDF。 这是电子发票证明联,与 `/getOrderPrintInfo` 餐点小票不同。App 代码由前端团队接入,本次后端不修改 App。 接口前缀:`/system/orderShOprate`,请求头传商家 `token`;`orderId` 使用 `pos_order.id`。普通商家、夜市、摊位、授权子账号沿用订单权限。 ## 1. 查看明细及打印次数 `GET /getInvoicePrintDetail?orderId=3173` 返回 `invoiceNumber`、`printCount`、`fileName`、`contentType`、`pdfBase64`。此 PDF 仅交易明细,无兑奖二维码,重复查看不占首次资格。 - `printCount=0` 显示“打印发票”;大于0显示“申请补印”。仅统计本系统。 - 自动预生成文件、平台查看文件均不增加次数。 ## 2. 用户点击打印时申请文件 `POST /printInvoice` ```json { "orderId": 3173, "requestId": "78490c8a-6755-4a49-bd36-2b661f46f653", "reprint": false } ``` `requestId` 是本次打印申请 UUID,调用前保存。超时、断网、响应丢失时使用同一值和同一参数重试,不能每次产生新编号。按钮提交中禁用。 成功响应示意(图片数量随页长变化,以下仅列一段): ```json { "code": 200, "msg": "操作成功", "data": { "printJobId": 123, "invoiceNumber": "AB12345678", "printSequence": 1, "reprint": false, "fileName": "AB12345678-1.pdf", "contentType": "application/pdf", "pdfBase64": "JVBERi0...", "reversePdfBase64": "JVBERi0...", "printerProfile": "SUNMI_58_384", "dotsPerMm": 8, "images": [ { "pageNumber": 1, "partNumber": 1, "width": 384, "height": 720, "lastPart": true, "separateAfter": true, "pngBase64": "iVBORw0..." } ] } } ``` 新增字段: | 字段 | 用法 | |---|---| | `printerProfile` | 当前为 `SUNMI_58_384`,面向58mm纸卷、384点有效打印区 | | `dotsPerMm` | 8点/mm;按原像素输出,禁止再次缩放 | | `images[]` | PNG分段,必须按数组原顺序连续输出 | | `pageNumber` / `partNumber` | 从1开始;同页可能有多段 | | `width` / `height` | 图片实际像素尺寸,宽固定384,单段高最多2048 | | `lastPart` | 是否当前PDF页的最后一段 | | `separateAfter` | 此段后需要将证明联与明细分离;不是每段都切纸 | | `pngBase64` | 无Data URL前缀的PNG Base64 | 保留的 `pdfBase64` 用于预览/兼容;打印以 `images` 为准。PDF纸面仍为57mm宽,PNG只包含居中的48mm有效打印区,已经去除白边,没有把整张PDF缩小。 ## 3. 商米 V3 打印步骤 1. App 运行在 V3 上时,连接商米内置打印服务。uni-app 通过 Android 原生插件或 UTS 对接商米 SDK。 2. 校验实际设备有效宽度为384点,再使用本打印配置;不同打印宽度不能强行缩放套用。 3. 将 `pngBase64` 解码为 PNG,再解码为 Bitmap;关闭资源密度自动缩放,确认 Bitmap 宽度为384。 4. 按 `images` 顺序调用 `printBitmap`,处理异步回调,不能并发发送导致乱序。**同页分段之间不额外换行、走纸或裁切**。 5. 遇到 `separateAfter=true`,按设备能力分离/提示撕纸后继续。个人票证明联与明细分开;企业票是同一长页,中途不能分离。V3是否具备切刀以实机为准,不能无条件调用切刀接口。 6. 正常结束后处理设备出纸结果。`onRunResult`只表示指令执行情况;事务打印的`onPrintResult`用于确认打印结果。缺纸、开盖等由App提示,不能因回调不明确就自动重复整张原件。 `reversePdfBase64`是背面参考,不属于`images`,不要接在正面后打印。使用符合要求的预印背面纸卷;背面及纸张仍需实际验收。 ## 4. 文件未准备好与重试 - 后台每10秒扫描,包含已开纸本存量;每轮最多10张。积压或ezPay故障时可能更久,不能把10秒当成完成承诺。 - 文件未就绪返回业务错误,`msg`为“发票打印文件正在准备,请稍后重试”(支持国际化)。本次不新增打印记录、不占次数;不要自动换成补印。 - 后台失败按1/5/15/60分钟退避,此后每小时重试;不影响已成功的开票状态。 - 实际申请打印仍会查询ezPay确认发票有效,因此网络故障时仍可能等待或失败;App按返回`msg`提示。 - 旧版本已保存的打印申请可能返回`images=[]`,保留原PDF供查看;不要把旧PDF任意缩放或裁边输出。需要再次出纸时进入补印流程,使用新的requestId。 ## 5. 补印 ```json { "orderId": 3173, "requestId": "a25ece72-8030-475d-97c9-3c49ce308d26", "reprint": true, "reprintReason": "原证明联字迹不清,已核对内容一致", "originalAvailable": true, "contentsVerified": true } ``` 原因最多200字,两个声明必须为true。成功后`printSequence>=2`,图片和PDF已经带“補印”,App不用拼字。仅适用于已确认的补印情形,需要保留原联。 首次/补印资格按**成功受理打印申请**记录,不是按预生成文件记录,也不是按设备出纸回执记录。原requestId恢复响应不会新增次数;不能用缓存原件反复打印。作废后拒绝打印,重开后的新发票重新计数并使用新requestId。 ## 6. 平台查看与上线 - 平台现有“查看PDF”接口不变:已有打印记录看最新记录;没有打印记录时看已预生成原件。因此商家尚未打印,平台也可以看到PDF。 - 先手动执行`updatesql/sql.md`中Phase12建表及“2026-11-02 开票后预生成PDF及商米V3图片”迁移,再发布后端;不需要配置若依sys_job。 - 联调:新开纸本自动生成但次数为0、首次、重复请求、补印、缺纸/失败、作废/重开、越权、B2C/B2B长明细、重启后失败任务恢复。 - 正式使用前需在真实V3上检查纸张尺寸、文字、Code39和两个二维码;后端本地图片回读不等于真实热敏打印验收或官方认可。 商米SDK参考:[内置打印机开发者文档](https://cdn.sunmi.com/public/generalfile/mgt-document/cb26f141d70b49f592a098db9e8a6114.pdf)。 ### 2026-11-02 字体修复说明 后端字体改为完整 Noto Sans CJK TC(固定 Regular 字重),随 JAR 打包。支持简繁中文、英文、数字与常用标点混排,App 不安装字体、不转换文字,打印仍使用后端图片。历史“测试门店”缺字导致的失败文件,更新后端后会在原定重试时间重新生成;不需要重新开票,本次无新增 SQL。此前 Phase12/13 迁移仍须已执行。特殊 Emoji 等不保证覆盖,遇到不支持的字形保留服务端错误原因,不静默丢字。 ## 订单列表、详情的打印入口状态(2026-11-03) 请求参数不变,以下位置新增字段: | 接口 | 返回位置 | | --- | --- | | `/system/orderShOprate/orderList` | `data.records[]` 每条订单 | | `/system/order/getorderxq` | `data` 订单详情 | ```json { "canPrintInvoice": true, "invoicePrintCount": 0 } ``` - `canPrintInvoice=false`:不展示可用的发票打印入口。包括无发票、未开立、开立失败、作废、载具、捐赠及文件未就绪。 - `canPrintInvoice=true` 且 `invoicePrintCount=0`:显示“打印发票”,使用现有首次打印流程。 - `canPrintInvoice=true` 且 `invoicePrintCount>0`:显示“申请补印”,沿用现有补印确认与参数要求,不能再以首次方式申请。 - `invoicePrintCount` 是当前这张发票在本系统成功领取打印文件的申请次数,包含补印;不是打印机出纸次数。无记录为0,重开后按新发票计数。 不要仅根据有发票号就显示可打印,也不要把 `canPrintInvoice` 当作最终打印授权。列表/详情只检查本地状态,不调用 ezPay;点击后仍须调用现有 `printInvoice`,处理状态变化、实时查票失败及补印确认错误。未就绪时后续刷新订单可获取最新状态。 本轮不调整 App 请求参数,不增加 SQL;App 只需读取两个响应字段控制入口。载具转纸本另行实现。本字段针对电子发票证明联,与餐点小票打印无关。