SVG + CSS + Node.js + receiptline で電子レシートを発行してみよう

マークダウン言語で紙のレシートや電子レシートを簡単に作れる receiptline。
https://github.com/receiptline/receiptline
https://www.npmjs.com/package/receiptline

今回は receiptline 本来の用途と考えられる、電子レシートの発行です！

# レシートを設計する

## デザインツール

ReceiptLine Designer を使います。使い方はこの連載記事の[初回](https://qiita.com/dopperi46/items/aa866955d24cd9d723b3)から。
いますぐ試したい方は、開発元のホームページで公開されているので[こちら](http://www.ofsc.or.jp/receiptline_/designer/index.html)へ。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/e9da6952-e23b-306a-27c0-51e44bb0ab5d.png)

## ロゴの背景を透明に

[以前の記事](https://qiita.com/dopperi46/items/e871ce96dadea11d243c)で作成したロゴ画像を再利用します。
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/13e018c0-65ad-940d-1614-79ab23bc30c9.png)

GIMP の「色を透明度に」 (Color to Alpha) 機能で、背景色を白から透明にしました。
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/29468bc7-35fa-6706-26a7-c00a7108d75a.png)

## 2023年度インボイス制度対応

[前回の記事](https://qiita.com/dopperi46/items/b1a263bd94a41c0540e1)で学習した、簡易適格請求書等の記載事項も追加しておきます。

- ① 適格請求書発行事業者の氏名又は名称及び登録番号
- ② 課税資産の譲渡等を行った年月日
- ③ 課税資産の譲渡等に係る資産又は役務の内容（課税資産の譲渡等が軽減対象資産の譲渡等である場合には、資産の内容及び軽減対象資産の譲渡等である旨）
- ④ 課税資産の譲渡等の税抜価額又は税込価額を税率ごとに区分して合計した金額
- ⑤ 税率ごとに区分した消費税額等又は適用税率

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/2d374990-3043-3e03-b687-fc3ef1aa22e7.png)

## 作成したレシートデータ

```text:ReceiptLine
{image:iVBORw0KGgoAAAANSUhEUgAAASAAAAAwAgMAAADMTE88AAAACVBMVEVwAAsAAAD///9xeVj9AAAAAXRSTlMAQObYZgAAAdZJREFUSMftlsFu4zAMRMUD73sI/0c59M4Amv//lXJI2XEWaGSgAVrsxuihtqgnmjOk09r7+pcv7esYAEtMM19yrCkWxwnOgEbk1Fc5nwBpRNgCpGdAhKi/4NVYaFmD1qqlYi8ACU6Y6BRo/DaQjk3hiARv4BeB834aVXBDk1bL0lHlCEs0Qz5zP4A89FVW3rgWNwGxWrRuCVIC7Ao3HjBCamY6mt7GHSQ9/rnQVEGZIBmlBPL0Fsvoig8Mg9N6KJAAB5ASFPDYzUcJytgEFXAQzpf2/ajMCMeMjCAwMX8A9ZQjQUJQ7mZs5G4TZMcaXRJd3bKBLOZLz4gC9Q00Y8030FE1Vga5egf5put0rTwDbT4SalV6fQmyp6DZIqCONXe+Aqk/A82mlcF3RO7eQeMRZH0JElePJ3P336BNfjwtdg42CV9PHynu8ldjSO7NIhxAD/Lvo1YDZONPgdIlg26JvwT4BAmud1AZstsOkhz+Fo3pERHhdLxiMCy9z89VgcAmnaCyPfCxg2hqnn4NWr/MtWgD0Fdeqt9qLAwbOyiaNtXRXV3eDeZlfAmKlPrxK6WbD2umR02i0diyyBmTdVEmvhhR3//Mv0H/M8jwIhBWv7ve189enyEmqNlnG50wAAAAAElFTkSuQmCC}
柳都市星降町7丁目8番9号
登録番号 T1234567890123

2020年12月16日(水)12:34  #0903
{border:line; width:22}
^領 収 書
{border:space; width:3,*,3,8; text:nowrap}
166003 |2021葱鏡餅 水引 | 3個| ¥1,620*
691004 |洗面器45RPM N025 | 1個| ¥1,210~
-
{width:auto; text:wrap}
小　計 |4点 | ¥2,830~
(税率10%対象 | ¥1,210)
(内消費税等10% | ¥110)
(税率 8%対象 | ¥1,620)
(内消費税等 8% | ¥120)
-
合　計 | ^¥2,830
お預り | ^¥3,000
お釣り | ^¥170
|*印は軽減税率対象商品です
{code:202012160903; option:code128,2,48}
```

# 電子レシート発行サーバーを作る

## Node.js

Node.js で電子レシートを発行する HTTP サーバーを作ります。
`receipt.js` の内部構成は、以下のようになっています。

- ReceiptLine
  - 作成したレシートデータ (固定) を SVG に変換する
- HTML
  - 作成した SVG を HTML に埋め込む
- HTTP サーバー
  - 任意の GET リクエストを受けて、作成した HTML を返す


```javascript:receipt.js
const http = require('http');
const receiptline = require('receiptline');

// ReceiptLine
const text = `{image:iVBORw0KGgoAAAANSUhEUgAAASAAAAAwAgMAAADMTE88AAAACVBMVEVwAAsAAAD///9xeVj9AAAAAXRSTlMAQObYZgAAAdZJREFUSMftlsFu4zAMRMUD73sI/0c59M4Amv//lXJI2XEWaGSgAVrsxuihtqgnmjOk09r7+pcv7esYAEtMM19yrCkWxwnOgEbk1Fc5nwBpRNgCpGdAhKi/4NVYaFmD1qqlYi8ACU6Y6BRo/DaQjk3hiARv4BeB834aVXBDk1bL0lHlCEs0Qz5zP4A89FVW3rgWNwGxWrRuCVIC7Ao3HjBCamY6mt7GHSQ9/rnQVEGZIBmlBPL0Fsvoig8Mg9N6KJAAB5ASFPDYzUcJytgEFXAQzpf2/ajMCMeMjCAwMX8A9ZQjQUJQ7mZs5G4TZMcaXRJd3bKBLOZLz4gC9Q00Y8030FE1Vga5egf5put0rTwDbT4SalV6fQmyp6DZIqCONXe+Aqk/A82mlcF3RO7eQeMRZH0JElePJ3P336BNfjwtdg42CV9PHynu8ldjSO7NIhxAD/Lvo1YDZONPgdIlg26JvwT4BAmud1AZstsOkhz+Fo3pERHhdLxiMCy9z89VgcAmnaCyPfCxg2hqnn4NWr/MtWgD0Fdeqt9qLAwbOyiaNtXRXV3eDeZlfAmKlPrxK6WbD2umR02i0diyyBmTdVEmvhhR3//Mv0H/M8jwIhBWv7ve189enyEmqNlnG50wAAAAAElFTkSuQmCC}
柳都市星降町7丁目8番9号
登録番号 T1234567890123

2020年12月16日(水)12:34  #0903
{border:line; width:22}
^領 収 書
{border:space; width:3,*,3,8; text:nowrap}
166003 |2021葱鏡餅 水引 | 3個| ¥1,620*
691004 |洗面器45RPM N025 | 1個| ¥1,210~
-
{width:auto; text:wrap}
小　計 |4点 | ¥2,830~
(税率10%対象 | ¥1,210)
(内消費税等10% | ¥110)
(税率 8%対象 | ¥1,620)
(内消費税等 8% | ¥120)
-
合　計 | ^¥2,830
お預り | ^¥3,000
お釣り | ^¥170
|*印は軽減税率対象商品です
{code:202012160903; option:code128,2,48}`;
const svg = receiptline.transform(text, { cpl: 32, encoding: 'cp932', spacing: true });

// HTML
const style = 'float: left; padding: 24px; background: lavender;';
const html = `<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>レシート</title>
    </head>
    <body>
        <div style="${style}">${svg}</div>
    </body>
</html>`;

// HTTP Server
const server = http.createServer((req, res) => {
    switch (req.method) {
        case 'GET':
            res.end(html);
            break;
        default:
            res.end();
            break;
    }
});
server.listen(8080, "127.0.0.1", () => {
    console.log('Server running at http://127.0.0.1:8080/');
});
```

## 実行

電子レシート発行サーバーを起動します。

```bash
$ node receipt.js
```

Web ブラウザーで `localhost:8080` を開きます。
receiptline ライブラリが動作しない IE11 でも表示 OK。
![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/c91e8dc8-2d7c-6ec7-bd58-996719b45289.png)

## 作成された SVG データ

レシートデータから生成された SVG データです。
中身はパス、画像、テキスト、Web フォント、フィルター、いろいろ入っています。

```xml:SVG
<svg width="384px" height="684px" viewBox="0 0 384 684" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"><style type="text/css"><![CDATA[@import url("https://fonts.googleapis.com/css2?family=Kosugi+Maru&display=swap");]]></style><defs><filter id="receiptlineinvert" x="0" y="0" width="100%" height="100%"><feFlood flood-color="#000"/><feComposite in="SourceGraphic" operator="xor"/></filter></defs><g font-family="'Kosugi Maru', 'MS Gothic', 'San Francisco', 'Osaka-Mono', 'Courier New', 'Courier', monospace" fill="#000" font-size="24" dominant-baseline="text-after-edge"><g transform="translate(48,0)"><image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASAAAAAwAgMAAADMTE88AAAACVBMVEVwAAsAAAD///9xeVj9AAAAAXRSTlMAQObYZgAAAdZJREFUSMftlsFu4zAMRMUD73sI/0c59M4Amv//lXJI2XEWaGSgAVrsxuihtqgnmjOk09r7+pcv7esYAEtMM19yrCkWxwnOgEbk1Fc5nwBpRNgCpGdAhKi/4NVYaFmD1qqlYi8ACU6Y6BRo/DaQjk3hiARv4BeB834aVXBDk1bL0lHlCEs0Qz5zP4A89FVW3rgWNwGxWrRuCVIC7Ao3HjBCamY6mt7GHSQ9/rnQVEGZIBmlBPL0Fsvoig8Mg9N6KJAAB5ASFPDYzUcJytgEFXAQzpf2/ajMCMeMjCAwMX8A9ZQjQUJQ7mZs5G4TZMcaXRJd3bKBLOZLz4gC9Q00Y8030FE1Vga5egf5put0rTwDbT4SalV6fQmyp6DZIqCONXe+Aqk/A82mlcF3RO7eQeMRZH0JElePJ3P336BNfjwtdg42CV9PHynu8ldjSO7NIhxAD/Lvo1YDZONPgdIlg26JvwT4BAmud1AZstsOkhz+Fo3pERHhdLxiMCy9z89VgcAmnaCyPfCxg2hqnn4NWr/MtWgD0Fdeqt9qLAwbOyiaNtXRXV3eDeZlfAmKlPrxK6WbD2umR02i0diyyBmTdVEmvhhR3//Mv0H/M8jwIhBWv7ve189enyEmqNlnG50wAAAAAElFTkSuQmCC" x="0" y="0" width="288" height="48"/></g><g transform="translate(0,72)"><text x="54,78,102,126,150,174,198,210,234,258,270,294,306">柳都市星降町7丁目8番9号</text></g><g transform="translate(0,102)"><text x="54,78,102,126,150,162,174,186,198,210,222,234,246,258,270,282,294,306,318">登録番号&#xa0;T1234567890123</text></g><g transform="translate(0,132)"><text x="0">&#xa0;</text></g><g transform="translate(0,162)"><text x="12,24,36,48,60,84,96,108,132,144,156,180,192,216,228,240,252,264,276,288,300,312,324,336,348,360">2020年12月16日(水)12:34&#xa0;&#xa0;#0903</text></g><g transform="translate(48,192)"><text x="0,12,24,36,48,60,72,84,96,108,120,132,144,156,168,180,192,204,216,228,240,252,264,276">╔══════════════════════╗</text></g><g transform="translate(48,216)"><text transform="scale(1,1)" x="0,12,24,36,48,60,72,84,96,108,120,132,144,156,168,180,192,204,216,228,240,252,264,276">║&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;&#xa0;║</text><text transform="scale(2,1)" x="24,48,60,84,96">領&#xa0;収&#xa0;書</text></g><g transform="translate(48,240)"><text x="0,12,24,36,48,60,72,84,96,108,120,132,144,156,168,180,192,204,216,228,240,252,264,276">╚══════════════════════╝</text></g><g transform="translate(0,270)"><text x="0,12,24">166</text><text x="48,60,72,84,96,120,144,168,180,204">2021葱鏡餅&#xa0;水引</text><text x="240,252">3個</text><text x="300,312,324,336,348,360,372">¥1,620*</text></g><g transform="translate(0,300)"><text x="0,12,24">691</text><text x="48,72,96,120,132,144,156,168,180,192,204,216">洗面器45RPM&#xa0;N02</text><text x="240,252">1個</text><text x="300,312,324,336,348,360,372">¥1,210&#xa0;</text></g><g transform="translate(0,330)"><text x="0,12,24,36,48,60,72,84,96,108,120,132,144,156,168,180,192,204,216,228,240,252,264,276,288,300,312,324,336,348,360,372">════════════════════════════════</text></g><g transform="translate(0,360)"><text x="0,24,48">小　計</text><text x="132,144">4点</text><text x="300,312,324,336,348,360,372">¥2,830&#xa0;</text></g><g transform="translate(0,390)"><text x="0,12,36,60,72,84,96,120">(税率10%対象</text><text x="300,312,324,336,348,360,372">¥1,210)</text></g><g transform="translate(0,420)"><text x="0,12,36,60,84,108,132,144,156">(内消費税等10%</text><text x="324,336,348,360,372">¥110)</text></g><g transform="translate(0,450)"><text x="0,12,36,60,72,84,96,120">(税率&#xa0;8%対象</text><text x="300,312,324,336,348,360,372">¥1,620)</text></g><g transform="translate(0,480)"><text x="0,12,36,60,84,108,132,144,156">(内消費税等&#xa0;8%</text><text x="324,336,348,360,372">¥120)</text></g><g transform="translate(0,510)"><text x="0,12,24,36,48,60,72,84,96,108,120,132,144,156,168,180,192,204,216,228,240,252,264,276,288,300,312,324,336,348,360,372">════════════════════════════════</text></g><g transform="translate(0,540)"><text x="0,24,48">合　計</text><text transform="scale(2,1)" x="120,132,144,156,168,180">¥2,830</text></g><g transform="translate(0,570)"><text x="0,24,48">お預り</text><text transform="scale(2,1)" x="120,132,144,156,168,180">¥3,000</text></g><g transform="translate(0,600)"><text x="0,24,48">お釣り</text><text transform="scale(2,1)" x="144,156,168,180">¥170</text></g><g transform="translate(0,630)"><text x="0,12,36,60,84,108,132,156,180,204,228,252,276">*印は軽減税率対象商品です</text></g><g transform="translate(91,636)"><path d="M0,0h4v48h-4zM6,0h2v48h-2zM12,0h6v48h-6zM22,0h4v48h-4zM30,0h2v48h-2zM36,0h6v48h-6zM44,0h4v48h-4zM52,0h2v48h-2zM58,0h6v48h-6zM66,0h2v48h-2zM70,0h4v48h-4zM78,0h6v48h-6zM88,0h2v48h-2zM94,0h6v48h-6zM102,0h4v48h-4zM110,0h4v48h-4zM118,0h2v48h-2zM124,0h2v48h-2zM132,0h2v48h-2zM138,0h2v48h-2zM144,0h4v48h-4zM154,0h4v48h-4zM162,0h2v48h-2zM166,0h6v48h-6zM176,0h4v48h-4zM186,0h6v48h-6zM194,0h2v48h-2zM198,0h4v48h-4z" fill="#000"/></g></g></svg>
```

# 電子レシートの背景をカスタマイズする

ソースコードの CSS を変更して、電子レシートの背景をカスタマイズします。

## 単色

お店のキャンペーンでよく使われるピンクレシートと黄色いレシート。

```javascript:receipt.js
const style = 'float: left; padding: 24px; background: pink;';
```

![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/8bea051d-2f6f-a7c8-e7bb-41448a535c8d.png)

```javascript:receipt.js
const style = 'float: left; padding: 24px; background: #ff9;';
```

![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/e13f2cff-648a-1dda-b514-c4d523cf9020.png)

## 多色

サーマルロール紙にはできないグラデーション。

```javascript:receipt.js
const style = 'float: left; padding: 24px; background: linear-gradient(skyblue, lightyellow);';
```

![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/0a136e8e-5ffe-cace-04ff-1dbeb4bb05f5.png)

```javascript:receipt.js
const style = 'float: left; padding: 24px; background: linear-gradient(lightgreen, snow, beige);';
```

![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/e54eaa3a-124c-be60-3066-93d26271ab3e.png)

## 透かし

「複写」「COPY」の画像をタイル状に並べます。画像は CSS に埋め込みます。
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/e240e89f-f19c-682f-4b8d-eb9c50dd92ac.png)

画像の Data URI 形式への変換は ReceiptLine Designer を使うと簡単です。
画像をロードして `image:` を `data:image/png;base64,` に置き換えます。
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/31d2050d-20cc-093a-3579-859358b70fdf.png)


```javascript:receipt.js
const style = 'float: left; padding: 24px; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANgAAADYAQMAAACz7r+uAAAABlBMVEXc3tv5+/j5MJA8AAADWklEQVRYw+2YPW7bMBSAqTAwMxTmBVwqR2g3G1AiH8W9gYMsCuLGSg3UW3yBwDlElwLNYMGDlza+QFsoMFqvDAy0NCpENQqRj3yCshfRmyh9En8eH98PCamllucih0+wPjT3MFPQZAh5GbQ5YvQ7tH3E2ATaAWK8Ae0MMXEAXUjEgpZpDjGLzCA0TxGTkelSIeal0tP/XSJGk9Uo1t/N0PJGi0UV478nS9MHYiL3Hw2L0fIuWpnF9t3lRQrGTps2GwQS2Fg5O3H88h765NmxzXx//tXowRcDZw18PIVJc2WrpcG5ME8tZ5cYYQKYoLZGOWkEwJpsZg2429hoYA1+mQPrxATWR9gNy0FvpzMvBcOmj3xu2It1aivfy3zYJ38pma18dUzMTP2pcuy8HwDjt1nDOW+Rxc6u8YEzjG3ayCI9YD/P4wrWYNnaZbAk7m8XLuuxpHjB8uHNO3cufFJ0SrMwv3KZr7d3p6M/U2SSLNPsy8Y9q35GivNIPydr5R4BRQoTpVcJOo+7JWjGt3mMfZE2bb5dILJnGNu+x+4mNiy5wgz+W01LTBhvc11i2qewqF1i2lxZEJXmopVBRR+7zEtZ5U2JB4piJbXksyq3sXOG0EVcB8Faaqmllv9TIGW5KMWHGUSKEksh/S3FAAg5CUpHyX7U1SzGaXMz0NFLEMyEiWwBwTGuxWUl6yw0i0p9vp7LcupUyInFephNZLnG0WFzIrsVjOajdjFMm3C3nuCrUccw381twsWHT4UaRT+8ddjbpfILxuXAydLJ2bdMMyaVW2e92T7qisZLpMu8zU2mZ0dTxNZh2jN5Fard7k7BKGLEWmsJuQbKRI7u+sCEW2CG1+Y/SkTDzSM7CsYLnE4l31gs82Pbctn6o3lIVAgm2qAzurT2Mo3ALDiLPSuZG85k36pXnJoztGuEQPRI5GSjkNq1A0mcaia1WCQPbcPj0mIPst+1D0cEIxytlJMfBqGCMmRx65TGEZdQhkxajur7LAUmhEBlP7AgDKpcQ6iGUZVLOZe/ZBV7uP+BS2PD0vkc+RQa6xddNka+iI6hXirdJkD66y7vX0UDllu6TYB5H+BU3DK0V/jGwKqFT7C7GdiWVX1ZEsZVnrZ8IWK7reYTAWC/joG1PBv5C4viMg7LEHCAAAAAAElFTkSuQmCC);';
```

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/788f6aec-06df-b9f1-ad72-4ce03a53aab2.png)

## 地紋

領収書やチケットでよく見かける地紋です。小さな素片をタイル状に並べます。
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/bed8f7d7-d4a5-1487-05c0-ecc12bf7027c.png)

```javascript
const style = 'float: left; padding: 24px; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADAQMAAACplL1tAAAABlBMVEX/pQD////52iT3AAAAFElEQVQI12M4l/+AYf/mCQyFdwoAJNIF3T0xRTsAAAAASUVORK5CYII=);';
```

![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669512/b4597586-6292-4d4d-4d8f-62a5273eb22f.png)

また何か作ったら投稿します。ではまた！