Skip to content

필사 모드: インボイス・請求システムの設計 — お金が漏れないように

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

はじめに

請求システムは、会社の売上が実際に現金へと変わる最後の関門です。どれほど良い製品を売っても、インボイスが誤って発行されたり、決済が二重に請求されたり、税金計算が間違っていれば、その損害は会社と顧客の双方がそのまま負うことになります。

とくに請求システムは、いったん本番に載せると後戻りが非常に難しくなります。すでに発行したインボイスは法的・会計的な根拠となり、顧客がすでに支払った金額はむやみに修正できません。だからこそ請求システムは、最初から「お金が漏れないように」設計することが肝心です。

この記事では、インボイスの構成要素から請求タイプ、税金、ステータス遷移、データモデル、決済連携、入金照合、冪等性、多国・多通貨、監査証跡まで、請求システム全般を設計の観点から整理します。実務でよく出会う落とし穴とその対策もあわせて扱います。

この記事が扱う範囲を先に整理すると、次のとおりです。

- インボイスが何で構成されるか(発行者、受取人、明細行、税金、通貨、支払条件)

- どのような請求タイプがあるか(定額、従量、サブスクリプション)

- 税金と付加価値税(VAT)、電子税務インボイスをどう扱うか

- インボイスがどのようなステータスを経るか(下書き、発行、支払済、延滞、無効、返金)

- データモデルをどう設計するか

- 決済代行(PG/PSP)とどう連携し、入金照合をどう行うか

- 二重請求をどう防ぐか(冪等性)

- 多国・多通貨をどう処理するか

- 監査証跡をどう残すか

- 実務の落とし穴と対策

インボイスの構成要素

インボイス(invoice)は、販売者が購入者に対して「この金額を支払ってください」と請求する公式文書です。表面上は単純に見えますが、法的効力と会計根拠を同時に備える必要があるため、必ず含めるべき必須要素があります。

必須の構成要素

| 構成要素 | 説明 | 例 |

| --- | --- | --- |

| インボイス番号 | 一意で連番の識別子 | INV-2026-000123 |

| 発行日 | インボイスを発行した日付 | 2026-07-01 |

| 発行者情報 | 販売者名、住所、事業者番号 | Acme Corp、事業者番号 123-45-67890 |

| 受取人情報 | 購入者名、住所、税番号 | Beta Inc、住所、税識別番号 |

| 明細行 | 販売した商品またはサービスの一覧 | Pro プラン 1 か月、数量 10 |

| 小計 | 税抜の合計 | JPY 100,000 |

| 税金 | VAT など適用される税額 | VAT 10 パーセント、JPY 10,000 |

| 総額 | 最終的な請求金額 | JPY 110,000 |

| 通貨 | 請求通貨(ISO 4217) | KRW、USD、JPY |

| 支払条件 | 支払期限と方法 | Net 30、銀行振込 |

| 支払期限 | 最終の支払締切日 | 2026-07-31 |

インボイス番号はとくに重要です。税務・会計上、インボイス番号には抜けや重複があってはならず、多くの国では連番かつ連続していることが求められます。番号に欠番が生じると、税務調査で「発行後に削除したインボイスがあるのではないか」と疑われる恐れがあります。

明細行(line item)

インボイスの核心は明細行です。各行は、何を、どれだけ、いくらで売ったかを表します。

明細行はふつう次の値で構成されます。

- 説明(description)

- 数量(quantity)

- 単価(unit price)

- 行小計(数量掛ける単価)

- 税率(tax rate)

- 割引(discount)

ここで重要な原則は、行ごとの小計を先に計算して丸め、その結果を合算することです。合算後に一度だけ丸めると、行ごとの金額の合計と総額が一致しない問題が生じます。この丸め問題は、あとの落とし穴の節で詳しく扱います。

支払条件(payment terms)

支払条件は「いつまでに、どのように支払うか」を定義します。代表的な表現は次のとおりです。

| 条件 | 意味 |

| --- | --- |

| Due on receipt | 受領後すぐに支払う |

| Net 15 | 発行日から 15 日以内に支払う |

| Net 30 | 発行日から 30 日以内に支払う |

| Net 60 | 発行日から 60 日以内に支払う |

| EOM | 当月末までに支払う |

企業間取引(B2B)では Net 30 や Net 60 がよく使われ、個人向け(B2C)のサブスクリプションでは即時決済が一般的です。

請求タイプ

請求システムを設計するとき、まず決めるべきは「どのような方式でお金を受け取るか」です。大きく三つのタイプがあります。

定額請求(flat / fixed)

もっとも単純な形です。決まった金額を決まった周期で請求します。

- 例: 月額 JPY 29,000 のプラン、年 1 回のライセンス費用

- 長所: 計算が単純で予測しやすい

- 短所: 使用量と無関係に固定され、柔軟性が低い

従量請求(usage-based / metered)

顧客が実際に使った分だけ請求します。クラウドインフラ、API 呼び出し、メッセージ送信などでよく見られます。

- 例: API 呼び出し 1,000 件あたり JPY 100、ストレージ GB あたり月 JPY 25

- 長所: 使用量に比例して公平に課金

- 短所: 使用量集計(metering)の正確性とリアルタイム性が難しい

従量請求の中核となる難問は「使用量イベントをどう正確に、重複なく集計するか」です。イベントが重複集計されると過大請求が、欠落すると過小請求が発生します。そのため使用量イベントにも冪等キーが必要です。

サブスクリプション請求(subscription / recurring)

決まった周期ごとに自動で繰り返し請求します。SaaS ビジネスの標準モデルです。

- 例: 月次サブスクリプション、年次サブスクリプション

- 長所: 予測可能な繰り返し売上(MRR/ARR)

- 短所: 周期の途中でのプラン変更(proration)、更新失敗、カード期限切れなど、処理すべき例外が多い

実務ではこの三つを組み合わせる場合が多くあります。たとえば「月額基本料 JPY 50,000 の定額に加えて、超過した API 呼び出しは従量」といったハイブリッドなプランが代表的です。

請求タイプの比較

| 項目 | 定額 | 従量 | サブスク |

| --- | --- | --- | --- |

| 予測可能性 | 高い | 低い | 高い |

| 計算の複雑さ | 低い | 高い | 中程度 |

| 集計の必要性 | なし | 必須 | なしまたは一部 |

| 主な難問 | なし | 正確な metering | proration、更新失敗 |

| 代表事例 | ライセンス | クラウド、API | SaaS |

税金・付加価値税(VAT)・電子税務インボイス

税金は請求システムでもっとも誤りが多い領域です。国ごとに規則が異なり、同じ国の中でも商品の種類や顧客のタイプによって税率が変わります。

付加価値税(VAT)の基本

付加価値税(VAT, Value Added Tax)は、商品・サービスの付加価値に課される消費税です。韓国では大半の財貨・用役に 10 パーセントが適用されます。

税金計算で必ず区別すべき二つの概念があります。

- 税抜(tax-exclusive): 表示価格に税金が含まれておらず、税金を別途加算します。B2B でよく使われます。

- 税込(tax-inclusive): 表示価格にすでに税金が含まれています。B2C の消費者向けでよく使われます。

税込価格から税額を逆算するときは、次のように計算します。

税込価格 = 110,000 (VAT 10 パーセントを含む場合)

小計(net) = 110,000 / 1.10 = 100,000

税額(VAT) = 110,000 - 100,000 = 10,000

国境をまたぐ取引とリバースチャージ

EU などでは、国境をまたぐ B2B 取引に「リバースチャージ(reverse charge)」規則が適用されます。販売者が税金を課さず、購入者が自国で税金を申告・納付する方式です。この場合、インボイスには税率 0 パーセントではなく「reverse charge を適用」という文言を明記する必要があります。

また EU では、購入者の VAT 番号(VAT ID)が有効かどうかを VIES システムで検証してはじめてリバースチャージを適用できます。検証に失敗すれば、販売者が税金を課さなければなりません。

電子税務インボイス

韓国では、一定規模以上の事業者に電子税務インボイスの発行が義務づけられています。国税庁ホームタックスや連携サービスを通じて発行し、発行と同時に国税庁へ送信されます。

請求システムの観点では、電子税務インボイスは次を意味します。

- インボイスの発行と税務インボイスの発行は、別々のイベントになりうる。

- 税務インボイスはいったん発行すると変更が難しく、修正税務インボイスという別の手続きが必要になる。

- 国税庁への送信ステータス(成功、失敗、再送)をシステムで追跡する必要がある。

税金処理の設計原則

- 税率はハードコードせず、時間・地域・商品タイプごとに参照できる税率テーブルで管理します。

- インボイスには発行時点で適用した税率をスナップショットとして保存します。あとで税率が変わっても、過去のインボイスはそのまま保たれなければなりません。

- 税額計算ロジックは必ずユニットテストで境界値を検証します。

インボイスのステータス遷移

インボイスは、作成から終了まで複数のステータスを経ます。この状態遷移を明確な状態機械(state machine)として設計することが重要です。許可されない遷移を防ぐことで、データの整合性が守られます。

主なステータス

| ステータス | 説明 | 次に取りうるステータス |

| --- | --- | --- |

| draft(下書き) | まだ確定していない、編集可能 | issued、void |

| issued(発行) | 顧客に請求済み、金額確定 | paid、overdue、void |

| paid(支払済) | 全額支払済 | refunded |

| overdue(延滞) | 期限超過、未払い | paid、void |

| void(無効) | 取消、会計上は無効処理 | なし(終了) |

| refunded(返金) | 決済後に返金 | なし(終了) |

ここには一つ重要な規則があります。すでに発行(issued)したインボイスは、決して修正してはいけません。金額が間違っていれば無効(void)処理して新しいインボイスを発行するか、貸方インボイス(credit note)を発行します。発行済みのインボイスをこっそり修正することは、会計不正とみなされます。

ステータス遷移図

+--------+

| draft |

+--------+

| |

issue | | discard

v v

+--------+ +------+

| issued |-->| void |

+--------+ +------+

| |

pay | | overdue (期限超過)

v v

+------+ +---------+

| paid | | overdue |

+------+ +---------+

| |

refund| | pay

v v

+----------+ +------+

| refunded | | paid |

+----------+ +------+

分割支払いの処理

実務では、一つのインボイスが複数回に分けて支払われる場合があります。このときインボイスのステータスを単純に paid/unpaid とするより、決済(payment)を別エンティティとして持ち、そのインボイスに対する支払合計を計算する方式が安全です。

インボイス総額 = 110,000

決済1 (2026-07-05) = 50,000

決済2 (2026-07-10) = 60,000

------------------------------------

累計支払額 = 110,000 -> ステータス paid

このように決済を別エンティティとして持てば、部分返金、過払い、再決済といった複雑なケースも一貫して扱えます。

データモデル

では、実際のデータモデルを設計してみます。中核となるエンティティは、顧客(customer)、インボイス(invoice)、明細行(line_item)、決済(payment)です。

ASCII ERD

+----------------+ +------------------+

| customers | | invoices |

+----------------+ +------------------+

| id (PK) |1 *| id (PK) |

| name |--------| customer_id (FK) |

| tax_id | | number (unique) |

| country | | status |

| currency | | currency |

| created_at | | subtotal |

+----------------+ | tax_total |

| total |

| issued_at |

| due_at |

| created_at |

+------------------+

|1 |1

| |

|* |*

+----------------+ +------------------+

| line_items | | payments |

+----------------+ +------------------+

| id (PK) | | id (PK) |

| invoice_id (FK)| | invoice_id (FK) |

| description | | amount |

| quantity | | currency |

| unit_price | | status |

| tax_rate | | psp_ref |

| line_subtotal | | idempotency_key |

| line_tax | | created_at |

+----------------+ +------------------+

SQL スキーマ

CREATE TABLE customers (

id BIGSERIAL PRIMARY KEY,

name TEXT NOT NULL,

tax_id TEXT,

country CHAR(2) NOT NULL,

currency CHAR(3) NOT NULL,

created_at TIMESTAMPTZ NOT NULL DEFAULT now()

);

CREATE TABLE invoices (

id BIGSERIAL PRIMARY KEY,

customer_id BIGINT NOT NULL REFERENCES customers(id),

number TEXT NOT NULL UNIQUE,

status TEXT NOT NULL DEFAULT 'draft',

currency CHAR(3) NOT NULL,

subtotal BIGINT NOT NULL DEFAULT 0,

tax_total BIGINT NOT NULL DEFAULT 0,

total BIGINT NOT NULL DEFAULT 0,

issued_at TIMESTAMPTZ,

due_at TIMESTAMPTZ,

created_at TIMESTAMPTZ NOT NULL DEFAULT now(),

CONSTRAINT chk_status CHECK (

status IN ('draft','issued','paid','overdue','void','refunded')

)

);

CREATE TABLE line_items (

id BIGSERIAL PRIMARY KEY,

invoice_id BIGINT NOT NULL REFERENCES invoices(id),

description TEXT NOT NULL,

quantity NUMERIC(18,4) NOT NULL,

unit_price BIGINT NOT NULL,

tax_rate NUMERIC(5,4) NOT NULL DEFAULT 0,

line_subtotal BIGINT NOT NULL,

line_tax BIGINT NOT NULL

);

CREATE TABLE payments (

id BIGSERIAL PRIMARY KEY,

invoice_id BIGINT NOT NULL REFERENCES invoices(id),

amount BIGINT NOT NULL,

currency CHAR(3) NOT NULL,

status TEXT NOT NULL DEFAULT 'pending',

psp_ref TEXT,

idempotency_key TEXT NOT NULL UNIQUE,

created_at TIMESTAMPTZ NOT NULL DEFAULT now()

);

ここで注目すべき点がいくつかあります。

- 金額はすべて整数(BIGINT)で保存します。最小通貨単位(ウォン、セント)で保存し、浮動小数点の誤差を根本から断ちます。

- invoices.number に UNIQUE 制約をかけ、インボイス番号の重複を防ぎます。

- payments.idempotency_key に UNIQUE 制約をかけ、二重決済をデータベースレベルで防ぎます。

- status に CHECK 制約をかけ、許可されないステータス値が入らないようにします。

金額を整数で保存する理由

浮動小数点(float, double)でお金を扱うと、必ず誤差が生じます。たとえば 0.1 足す 0.2 が正確に 0.3 にならない問題です。そのためお金は、必ず整数(最小単位)または固定小数点(decimal)型で扱わなければなりません。

誤り: 金額 = 10.10 (float) -> 累積演算で誤差が発生

正しい: 金額 = 1010 (整数、セント単位) -> 誤差なし

表示するとき: 1010 / 100 = 10.10 に変換

決済連携・入金照合・冪等性

インボイスを発行したら、次は実際にお金を受け取らなければなりません。ここで決済代行(PG, Payment Gateway あるいは PSP, Payment Service Provider)との連携が登場します。

PG/PSP 連携の基本フロー

[自社システム] [PSP] [カード会社/銀行]

| | |

| 決済リクエスト | |

|--------------->| |

| | 承認リクエスト |

| |------------------>|

| | 承認レスポンス |

| |<------------------|

| 決済結果 | |

|<---------------| |

| | |

| Webhook(通知) | |

|<---------------| |

ここで重要なのは、決済結果を二つの経路で受け取るという点です。

- 同期レスポンス: 決済リクエストに対する即時のレスポンス

- Webhook: PSP があとから非同期で送るステータス変更通知

二つの経路が異なる時点で同じ決済ステータスを伝えることがあるため、どちらが先に届いても結果は同じでなければなりません。これがまさに冪等性が必要な理由です。

冪等性(idempotency)

冪等性とは「同じリクエストを何度送っても、一度送ったのと同じ結果になる」という性質です。決済において冪等性は生命線です。ネットワークエラーで決済リクエストを再試行したとき、実際の請求が二度起きてはならないからです。

冪等性を実装する標準的な方法が、冪等キー(idempotency key)です。

- クライアントが決済リクエストごとに一意なキーを生成し、一緒に送ります。

- サーバはそのキーですでに処理したリクエストがあるか確認します。

- すでにあれば新たに処理せず、保存された結果をそのまま返します。

def charge(idempotency_key, invoice_id, amount):

すでに処理したリクエストか確認する

existing = payments.find_by_key(idempotency_key)

if existing is not None:

重複リクエスト: 保存された結果をそのまま返す

return existing

UNIQUE 制約により同時リクエストも一つだけ成功する

try:

payment = payments.insert(

idempotency_key=idempotency_key,

invoice_id=invoice_id,

amount=amount,

status="pending",

)

except UniqueViolation:

競合状態: ほかのリクエストが先に挿入した

return payments.find_by_key(idempotency_key)

result = psp.charge(amount)

payment.status = "succeeded" if result.ok else "failed"

payment.psp_ref = result.reference

payment.save()

return payment

肝心なのは、データベースの UNIQUE 制約を安全網として使うことです。アプリケーションレベルの「先に照会してなければ挿入する」ロジックだけでは、同時リクエストの競合状態を防げません。UNIQUE 制約があってはじめて、同時に来た二つのリクエストのうち一つだけが成功します。

入金照合(reconciliation)

入金照合とは「自社システムが受け取ったと記録したお金」と「PSP や銀行が実際に入金したお金」を突き合わせる作業です。この二つが常に一致するとは限りません。

照合でよく見つかる不一致は次のとおりです。

| 不一致のタイプ | 原因 | 対応 |

| --- | --- | --- |

| 自社は成功、PSP は失敗 | Webhook の欠落、ステータス未更新 | PSP のステータスに訂正 |

| PSP は成功、自社は記録なし | Webhook の消失、レスポンス欠落 | 決済レコードを作成 |

| 金額の不一致 | 手数料控除、部分返金 | 手数料・返金を反映 |

| 通貨の不一致 | 為替変動、多通貨精算 | 精算レートで再計算 |

照合はふつう毎日バッチで実行します。PSP が提供する精算レポート(settlement report)を取得し、自社の決済レコードと一つずつ照合して、不一致は別途フラグを立てて人が確認します。

照合でもっとも重要な原則は「自社システムの記録を真実の源(source of truth)と勘違いしないこと」です。お金が実際にやり取りされたのは、銀行と PSP の記録です。自社システムの記録は、それに合わせられるべき対象です。

多国・多通貨

グローバルなサービスであれば、複数の国の顧客に複数の通貨で請求しなければなりません。この領域は誤りが非常に多いところです。

通貨コードと最小単位

通貨は ISO 4217 標準コードで管理します。KRW、USD、JPY、EUR などの三文字コードです。

注意すべきは、通貨ごとに小数点の桁数(最小単位)が異なることです。

| 通貨 | 小数桁数 | 最小単位 | 100 の意味 |

| --- | --- | --- | --- |

| USD | 2 | セント | 1.00 ドル |

| EUR | 2 | セント | 1.00 ユーロ |

| KRW | 0 | ウォン | 100 ウォン |

| JPY | 0 | 円 | 100 円 |

| BHD | 3 | フィルス | 0.100 ディナール |

したがって「金額を 100 で割って表示する」ロジックをすべての通貨に同じく適用すると、KRW や JPY では 100 倍間違った金額になります。通貨ごとの小数桁数を必ずテーブルで管理する必要があります。

為替と通貨混在の禁止

一つのインボイスの中では、一つの通貨だけを使わなければなりません。異なる通貨の明細を一つのインボイスに混ぜると、総額を計算できません。

為替が必要な場合(たとえばウォンで請求したがドルで精算)には、次の原則を守ります。

- インボイスには請求通貨の金額を保存します。

- 換算が必要なら、換算時点の為替レートと換算結果をあわせてスナップショットとして保存します。

- 為替レートは時間とともに変わるため、あとで再計算せず、保存された値を使います。

請求: KRW 1,100,000

レート: 1 USD = 1,375 KRW (2026-07-01 時点、スナップショット)

精算: 1,100,000 / 1,375 = USD 800.00

監査証跡(audit trail)

お金を扱うシステムは「誰が、いつ、何を、なぜ変えたか」を必ず残さなければなりません。これが監査証跡です。監査証跡は、会計監査、紛争解決、不正検知の根拠となります。

監査証跡の設計原則

- 不変(append-only): 監査ログは決して修正・削除せず、追加のみ行います。

- 完全性: ステータスを変えるすべてのイベントを記録します。

- 追跡可能性: 各イベントに行為者(actor)、時刻、変更前の値、変更後の値を残します。

+---------------------------------------------------------------+

| audit_log (append-only) |

+---------------------------------------------------------------+

| id | entity_type | entity_id | action | actor | at | detail |

|----|-------------|-----------|---------|-------|----|---------|

| 1 | invoice | 123 | created | u:42 | .. | {...} |

| 2 | invoice | 123 | issued | u:42 | .. | {...} |

| 3 | payment | 987 | charged | sys | .. | {...} |

| 4 | invoice | 123 | paid | sys | .. | {...} |

+---------------------------------------------------------------+

イベントソーシングとの関係

もう一歩進めると、イベントソーシング(event sourcing)パターンを使えます。ステータスそのものではなく、ステータスを変えたイベントの連なりを真実の源とする方式です。現在のステータスは、イベントを再生(replay)して計算します。

イベントソーシングは請求システムと相性が良い方式です。すべての変更履歴が自然に残り、特定時点のステータスを再現でき、照合と監査に有利だからです。ただし実装の複雑さが上がるため、システムの規模と要件を見て導入を決めるべきです。

実務の落とし穴

最後に、請求システムで実際にお金を漏らす代表的な落とし穴を整理します。

丸め(rounding)の落とし穴

先に述べたとおり、行ごとに丸めてから合算するか、合算してから丸めるかで、総額が最小単位で変わることがあります。

行A: 33.333... -> 丸めて 33

行B: 33.333... -> 丸めて 33

行C: 33.333... -> 丸めて 33

行ごとの合算: 33 + 33 + 33 = 99

合算後に丸め: 33.333 掛ける 3 = 99.999 -> 丸めて 100

二つの方式で結果が 1 違う!

一貫した丸めポリシー(たとえば常に行ごとに丸めてから合算、銀行丸めを使用)を決め、それを文書化してテストする必要があります。

税金の境界値の落とし穴

税率が変わる時点(税法改正など)、非課税対象の判定、リバースチャージの適用可否などで誤りが多発します。税率は必ず発行時点を基準にスナップショットし、税金ロジックには境界値テストを緻密に書きます。

比例計算(proration)の落とし穴

サブスクリプションのプランを月の途中で変更すると、残りの期間について比例計算を行わなければなりません。

月額料金 30,000 (30 日基準)

15 日目に上位プラン(月額 60,000)へ変更

残り 15 日の既存プラン返金: 30,000 掛ける 15 / 30 = 15,000

残り 15 日の上位プラン請求: 60,000 掛ける 15 / 30 = 30,000

差額を請求: 30,000 - 15,000 = 15,000

比例計算で「何日基準で割るか(実日数か 30 日固定か)」「変更した日をどちら側に含めるか」を明確に定義しないと、顧客ごとに金額が微妙に変わってしまいます。

通貨の落とし穴

- 通貨ごとの小数桁数を無視して一括処理する

- 一つのインボイスに複数の通貨を混在させる

- 為替を再計算して過去の金額が変わってしまう

先に扱った原則(通貨ごとの最小単位テーブル、単一通貨のインボイス、為替スナップショット)を守れば、その大半は防げます。

二重請求(double-charge)の落とし穴

もっとも致命的な落とし穴です。ユーザーが決済ボタンを二度押したり、ネットワークタイムアウトで再試行したりしたときに、実際の請求が二度起きる場合です。

防御線は何層にも設けます。

- クライアント: ボタンの二重クリック防止、冪等キーの生成

- サーバ: 冪等キーの確認

- データベース: 冪等キーの UNIQUE 制約(最後の防御線)

- 照合: 事後に二重決済を見つけるバッチ

落とし穴まとめ表

| 落とし穴 | 症状 | 対応 |

| --- | --- | --- |

| 丸め | 行の合計と総額の不一致 | 一貫した丸めポリシー、テスト |

| 税金の境界 | 税率の誤適用 | 税率スナップショット、境界値テスト |

| proration | 顧客ごとの金額のずれ | 明確な日数ポリシー |

| 通貨 | 100 倍の誤り、総額の誤り | 通貨ごとの最小単位、単一通貨 |

| 二重請求 | 二重決済 | 冪等キー、UNIQUE 制約、照合 |

おわりに

請求システムは華やかではありませんが、会社の売上と信頼がかかった中核インフラです。この記事で扱った原則を整理すると、次のとおりです。

- 金額は整数(最小通貨単位)で保存し、浮動小数点の誤差をなくす。

- 発行済みのインボイスは修正せず、無効・返金・貸方インボイスで訂正する。

- ステータス遷移を明確な状態機械として設計する。

- 決済は冪等キーと UNIQUE 制約で重複を根本から断つ。

- 自社の記録を真実の源と勘違いせず、PSP・銀行の記録と照合する。

- 通貨ごとの最小単位を尊重し、一つのインボイスには一つの通貨だけを使う。

- 税率と為替レートは発行時点のスナップショットとして保存する。

- すべての変更を監査証跡に append-only で残す。

お金が漏れないシステムは、一度で完成するものではありません。落とし穴を一つずつ知り、防御線を何層にも設け、照合で事後検証する習慣が積み重なってはじめて、信頼できる請求システムになります。

参考資料

- Stripe Invoicing ドキュメント: https://stripe.com/docs/invoicing

- Stripe Billing ドキュメント: https://stripe.com/docs/billing

- Stripe Idempotent Requests: https://stripe.com/docs/api/idempotent_requests

- PayPal Developer ドキュメント: https://developer.paypal.com/

- ISO 4217 通貨コード: https://www.iso.org/iso-4217-currency-codes.html

- Idempotence (Wikipedia): https://en.wikipedia.org/wiki/Idempotence

- Martin Fowler, Event Sourcing: https://martinfowler.com/eaaDev/EventSourcing.html

- PostgreSQL 公式ドキュメント: https://www.postgresql.org/docs/

- EU 税務・関税(VAT): https://taxation-customs.ec.europa.eu/

- 韓国国税庁ホームタックス電子税務インボイス: https://www.hometax.go.kr/

현재 단락 (1/351)

請求システムは、会社の売上が実際に現金へと変わる最後の関門です。どれほど良い製品を売っても、インボイスが誤って発行されたり、決済が二重に請求されたり、税金計算が間違っていれば、その損害は会社と顧客の双...

작성 글자: 0원문 글자: 13,048작성 단락: 0/351