筆者は本文中で紹介する領収書アプリ「K's 領収書」の開発者です(利益相反の明示)。ただし本記事の大半は、アプリを使わず自分で実装する場合の技術情報と、実装時に踏んだ罠の共有です。
日本のクライアントのECサイトをShopifyで構築すると、ほぼ確実にこの要望が来ます。
「購入者に領収書を発行できるようにしてほしい。宛名と但し書きを入れて、インボイス(適格請求書)対応で」
そしてShopifyの標準機能には、これに相当するものがありません。注文確認メールは出ますが、宛名・但し書き・適格請求書発行事業者の登録番号・税率別内訳といった日本固有の要素は載せられません。
本記事では、この要件を満たす実装方法を3つ、実装コストと限界の観点で比較します。筆者は3つ目の「アプリとして本格実装」を実際にやってApp Store公開まで持っていったので、その過程で踏んだ罠(フォント・App Proxy・審査まわり)も具体的に書きます。
0. まず要件を仕様に落とす
「領収書ください」対応を実装可能な仕様にすると、次の2層に分かれます。
適格請求書(インボイス)の記載要件(国税庁の定める6項目):
- 発行者の氏名または名称 + 登録番号(T+13桁)
- 取引年月日
- 取引内容(軽減税率対象品目にはその旨の注記)
- 税率ごとに区分した対価の額の合計と適用税率
- 税率ごとの消費税額
- 受領者の氏名または名称
日本の商習慣要件(法律ではないが、無いとクレームになる):
- 宛名(「上様」や空欄運用も含む)と但し書き(「お品代として」)
- 再発行時の「再発行」表示と番号の維持(経理で二重計上されないため)
- 収入印紙の扱い(電子発行なら5万円以上でも印紙不要——PDFにその旨を注記すると問い合わせが減る)
Shopify側のデータソースは Order の GraphQL です。税率別内訳は `taxLines`(`rate`, `priceSet`)から組めます。日本のストアは税設定で標準10%と軽減8%が混在し得るので、taxLinesは必ず複数行を前提にしてください。
方法1: Liquidテンプレート(Order Printer系)で済ませる
Shopify公式の Order Printer にLiquidテンプレートを書く方法です。最小実装はこんな形になります。
```liquid
領収書
No. {{ order.name }} / 発行日: {{ "now" | date: "%Y年%m月%d日" }}
________________ 様
但し お品代として
合計 ¥{{ order.total_price | money_without_currency }}(税込)
{% for tax_line in order.tax_lines %}
{{ tax_line.rate | times: 100 | round }}%対象: 消費税 ¥{{ tax_line.price | money_without_currency }}
{% endfor %}発行者: ○○株式会社(登録番号 T1234567890123)
※電子発行のため収入印紙は不要です
\`\`\`コスト: 数時間。追加費用ゼロ。
限界:
- 宛名・但し書きを購入者が指定できない。テンプレートは店側が印刷するものなので、「領収書ください(宛名は○○で)」というメール往復は残る。ここが工数の本丸なので、実は一番痛い
- 連番管理・再発行管理がない(毎回その場で出力するだけ)
- PDF化はブラウザの印刷機能頼みで、レイアウトが環境依存
受注が月数件のクライアントなら、これで十分です。「問い合わせが来たら店側が手動で出す」運用が回るかどうかが分かれ目です。
方法2: アプリとして本格実装する
「購入者が自分で宛名・但し書きを入れてPDFをダウンロードする」までやるなら、アプリ実装になります。筆者が実際に組んだ構成はこうです。
Remix (Shopify公式テンプレート)
└─ Cloudflare Workers にデプロイ
├─ D1 … 発行履歴(連番・再発行回数・監査スナップショット)
├─ KV … セッション + 日本語フォント(サブセット済み)
└─ App Proxy … ストアフロント側の購入者セルフ発行ページ
(注文番号+メールアドレスで本人確認 → PDF生成 → 返却)
肝は「購入者ページ」をApp Proxy(/apps/receipt のようなストアドメイン配下のパス)で提供する点で、これによりテーマを問わず購入者導線を作れます。以下、実際に踏んだ罠です。
罠1: 日本語PDFのフォント埋め込みは一段では終わらない
pdf-libにNotoSansJPをそのまま埋め込むと数MBになるので、サブセット化が必須です。ところが subset-font(HarfBuzz)でサブセット→pdf-libに渡す構成にしても、やり方次第でグリフ欠けやToUnicode化け(コピペすると文字化けするPDF)が起きます。安定したのは「事前サブセット→pdf-lib側で再サブセット」の二段構成でした。
さらに多通貨対応時、₹(ルピー)や₩などの通貨記号はNotoSansJPにグリフが無く豆腐になります。通貨記号を出し分けるより「1,860.36 INR」のようにISOコードを後置する方が安全です。
宛名は自由入力なので、ハングルやデーヴァナーガリー等の収録外文字が「豆腐ですらなく無言の空白」でPDF化される事故もありました。元フォントのcmapで収録判定して、未収録文字は明示的にエラーにしています。
罠2: 「保護対象顧客データ」を申告しないとOrder APIが全拒否
注文データ(メールアドレス含む)を読むには、Partner Dashboardでの保護対象顧客データ(Protected Customer Data)の申告が必要です。これをやらないと、開発ストアですらOrders APIがエラーを返します。「スコープは付けたのに何も返らない」時はまずここを疑ってください。
罠3: App ProxyのHMAC署名検証を自前で書かない
App Proxyへのリクエストにはsignatureパラメータ(HMAC-SHA256)が付き、これの検証を自前実装すると、パラメータのソート順・重複パラメータ・スペースのエンコード(%20と+)あたりで穴を作りがちです。実際、2026年8月には公式SDK側の署名検証にも脆弱性アドバイザリが出て、メジャーバージョン更新が必要になりました。公式SDK(shopify-app-remixのauthenticate.public.appProxy)を使い、SDKの更新に追随できる体制だけ持っておくのが正解です。
罠4: ストアの通貨は開発者がコントロールできない
初期実装で「日本向けだからJPY注文のみ発行可」というガードを入れていました。これはApp Store審査で構造的に詰みます。審査員のテストストアの基本通貨はINRだったりするからです。「対象市場が日本だから」は通りません。金額表記を通貨非依存に設計しておくべきでした(結局、多通貨対応に全面改修)。
罠5: Managed Pricingは「無料プランでもACTIVEな購読」を作る
課金にManaged Pricingを使う場合、activeSubscriptionsに無料プランでもstatus=ACTIVEの購読が入ります。「ACTIVEな購読がある=有料プラン」という判定を書くと、有料→無料へダウングレードしてもUIが有料表示のままになります(審査指摘で発覚)。判定は課金額かトライアル日数で行う必要があります。
コストの現実
実装がおおよそ4週間、その後のApp Store審査で4回差し戻され、公開まで約3週間かかりました(拡張が描画されない→デモ動画は英語で→通貨ガードで発行不可→課金表示のバグ、という順)。1クライアントの受託でこの道を選ぶのは、正直割に合いません。逆に、複数ストアに配る前提(アプリビジネス)なら選択肢になります。
方法3: 既製アプリを入れる
Order Printer Pro等の定番アプリは完成度が高いものの、英語圏の「Invoice」文化前提で、購入者が宛名・但し書きを指定する日本式の運用は標準では想定されていません。日本特化系のアプリを選ぶ場合の技術的なチェックポイントは:
- 購入者セルフ発行か、店側の手動発行だけか(問い合わせ工数が消えるかどうかの分岐点)
- 税率別内訳・登録番号・軽減税率注記(インボイス記載要件)を満たすか
- 再発行時に番号が維持されるか
- 発行履歴を会計ソフトに取り込める形(CSV等)で出せるか
手前味噌ですが、筆者が方法2の構成で実装・公開したのが K's 領収書です(購入者セルフ発行・インボイス対応・無料枠月15枚)。上記チェックポイントは全部この開発で必要に迫られて学んだものです。
まとめ: どれを選ぶか
| 実装コスト | 購入者セルフ発行 | インボイス対応 | 向くケース | |
|---|---|---|---|---|
| 方法1: Liquid | 数時間 | ✗(店側運用) | 自分で組む | 月数件・手動運用が回る |
| 方法2: 自前アプリ | 数週間〜 | ○ | 自分で組む | 複数ストアに配る前提 |
| 方法3: 既製アプリ | 数分 | アプリ次第 | アプリ次第 | それ以外の大半 |
方法2を選ぶ方は、罠1〜5を先に知っておくと数週間分の手戻りが消えるはずです。質問があればコメントでどうぞ。