ジャーナル
エンジニアリング1分で読めます

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

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

BエBookatu エンジニアリングチーム

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

REBUILT ON THE SERVER, EVERY BOOKINGBase priceStaff priceCategory saleBest couponCharged
どの予約の料金も同じ決まった順序で組み立てられ、合計はサーバー側で計算し直されます。

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

この機能の最初の一行はフラグです。環境変数を読み、既定はオフです。つまり、何かをするずっと前にコードを取り込んで配備でき、料金は前日と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);}
1Behind a flagoff by default2Merge & deploystill off3Audit passes0 money bugs4Flip the flagthe go-liveLive
公開は別の退屈な手順です。コードは、何かをするずっと前に出ています。

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);}

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

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

BROWSERShows a price so the clientknows what they will pay.A display only. Never trusted.requestSERVER · SOURCE OF TRUTH1 · Load the real service2 · Resolve staff + sale price3 · Apply the best coupon4 · Charge exactly that
ブラウザが見せる数字は表示にすぎません。サーバーは自分でデータを取り、もう一度計算します。

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

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

New pricing featureOvercharges?Loses money?Leaks when off?Rounding drift?Clean sweep → GO
4人の懐疑家が、それぞれ違う間違い方を担当します。全員が問題なしのときだけ、進めます。

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

フィーチャーフラグお金まわりの安全なコード料金計算の設計サーバー側の検証信頼境界
さっそく試してみませんか。

Bookatuなら、あなたのブランドの予約ページ、デポジット、会員制度、ギフトカード、リマインダーが揃って、予約手数料は0%です。

無料で始める