---
title: "お金に触れる機能を、誰の料金も勝手に変えずに出す方法"
description: "料金のバグは、カードに請求されたあとでは取り消せません。割引、スタッフ別料金、セールを予約の流れに足しながら、こちらが意図するまで1円も動かさないために使っている決まりごとをご紹介します。"
url: "https://bookatu.com/ja-jp/blog/shipping-money-features-safely"
language: "ja"
section: "ブログ"
category: "エンジニアリング"
author: "Bookatu エンジニアリングチーム"
published: "2026-07-01"
updated: "2026-07-01"
tags: ["フィーチャーフラグ", "お金まわりの安全なコード", "料金計算の設計", "サーバー側の検証", "信頼境界"]
---

# お金に触れる機能を、誰の料金も勝手に変えずに出す方法

料金のバグは、カードに請求されたあとでは取り消せません。割引、スタッフ別料金、セールを予約の流れに足しながら、こちらが意図するまで1円も動かさないために使っている決まりごとをご紹介します。

> **要点**
>
> - 料金を動かしうるものは、既定でオフのフラグの後ろに出します。コードは本番にありますが、こちらが有効にするまで何もしません。
> - 最終的な料金は、会計時にサーバー側でもう一度計算します。ブラウザから届いた数字は決して信用しません。
> - 割引の計算は、専用のテストを持つ小さな純粋関数1つに置いてあります。単独で筋道を追えるようにするためです。
> - 料金の機能を公開する前に、過大請求、過少請求、マイナスへの突き抜けを起こそうとする一連の意地悪な検査を走らせます。

たいていのバグは、謝罪で済みます。料金のバグは、返金とチャージバック、そして画面の数字を信じられなくなったお客様という代償を伴います。損得が釣り合っていないので、お金に触れるものの手順は、ほかの作業よりも意図的に遅くしてあります。その形を、最近の機能を例にご説明します。サロンがフェイシャルを一括でセールにできる、カテゴリー単位のセールです。

*Diagram: The price pipeline: base price, then staff price, then category sale, then the best coupon, then the amount charged*

Diagram labels: REBUILT ON THE SERVER, EVERY BOOKING · Base price · Staff price · Category sale · Best coupon · Charged

*どの予約の料金も同じ決まった順序で組み立てられ、合計はサーバー側で計算し直されます。*

## 既定はオフ。本番にあり、何もしない

この機能の最初の一行はフラグです。環境変数を読み、既定はオフです。つまり、何かをするずっと前にコードを取り込んで配備でき、料金は前日と1バイトも変わりません。緊張する一斉公開はありません。公開は、こちらが自信を持った時点で行う、別の退屈な手順だからです。

```ts
export const FEATURE_LIVE =
  process.env.FEATURE_LIVE === "true";

// Read at the one place it matters. When the flag is off, the branch
// is skipped entirely: no extra query, no price change, nothing.
if (FEATURE_LIVE) {
  const pct = await activeCategorySalePercent(orgId, "service", service.category);
  if (pct > 0) fullPriceCents = applyCategorySaleCents(fullPriceCents, pct);
}
```

*Diagram: The flag lifecycle: written behind a flag that is off by default, merged and deployed while still off, then after the audit passes the flag is flipped and the feature goes live*

Diagram labels: 1 · Behind a flag · off by default · 2 · Merge & deploy · still off · 3 · Audit passes · 0 money bugs · 4 · Flip the flag · the go-live · Live

*公開は別の退屈な手順です。コードは、何かをするずっと前に出ています。*

## 1つの関数に、1つの仕事

割引の計算は、1つの純粋関数の中にあります。セント単位の金額とパーセントを受け取り、セント単位の金額を返します。データベースも、リクエストも、時計も使いません。だから端の条件を単独で試すのが簡単です。割引ゼロ、通常の割引、上限での頭打ち、起きてはならないマイナス。計算が頭に入る小さな関数1つになっていれば、それについて推測する必要がなくなります。

```ts
export function applyCategorySaleCents(
  baseCents: number,
  salePercent: number | null | undefined,
): number {
  const base = cents(baseCents);
  const pct = Math.max(0, Math.min(100, Math.round(salePercent ?? 0)));
  if (pct === 0) return base;
  return cents((base * (100 - pct)) / 100);
}
```

## サーバーで、必ず計算し直す

ブラウザは、お客様が支払う額を知るために料金を表示します。ただしその数字は、あくまで表示にすぎません。予約が届くと、サーバーは本物のメニューを読み込み、スタッフ別の料金を決め、有効なカテゴリーセールを適用し、そのうえで割引を計算します。すべて自分で取ってきたデータからです。途中で誰かがリクエストを書き換えても、何も変わりません。カードに請求される金額は、そもそもブラウザが送ってきた金額ではないからです。お金を扱うコードで最も大事な習慣がこれです。信頼境界はサーバーであり、計算するのはサーバーです。

*Diagram: The browser only displays a price; the server independently loads the service, resolves the staff and sale price, applies the best coupon, and charges that amount*

Diagram labels: BROWSER · Shows a price so the client · knows what they will pay. · A display only. Never trusted. · request · SERVER · SOURCE OF TRUTH · 1 · Load the real service · 2 · Resolve staff + sale price · 3 · Apply the best coupon · 4 · Charge exactly that

*ブラウザが見せる数字は表示にすぎません。サーバーは自分でデータを取り、もう一度計算します。*

## お客様より先に、自分で壊しにいく

テストが通ることで分かるのは、思いついた場合が動くということだけです。見落とした場合については何も教えてくれません。だから料金のフラグを入れる前に、お金を間違わせる方法を探すことだけを仕事にした意地悪な検査を走らせます。それぞれ別の角度から見る検査を分けてあります。お客様に過大請求する経路を探すもの、お店が損をする経路を探すもの、フラグがオフのときに本当に何もしないかを確かめるもの、そして端数と境界のずれを狙うもの。前払いにクーポン、その上にセールが重なったところは、まさに1セント未満が隠れる場所なので、そこを一番厳しく見ます。

*Diagram: One new pricing feature is checked by four adversarial skeptics — does it overcharge, lose money, leak when the flag is off, or drift on rounding — and only a clean sweep is a go*

Diagram labels: New pricing feature · Overcharges? · Loses money? · Leaks when off? · Rounding drift? · Clean sweep → GO

*4人の懐疑家が、それぞれ違う間違い方を担当します。全員が問題なしのときだけ、進めます。*

どれも気の利いた工夫ではありません。フラグが1つ、小さな関数が1つ、自分で計算するサーバー、そして出す前に自分の作業を壊しにいく習慣です。合わせると、お金に触れる機能を足すことと、それで眠れなくなることの差になります。
