1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ポイント還元込みの「実質価格」を計算する設計

1
Posted at

「表示価格は A が安いけど、B はポイント還元が多い。結局どっちが得なのか」を計算で出したくなります。素朴に書くと、次の一行です。

const effectivePrice = displayPrice - totalPoints; // これだと数字を盛れる

ですが、全部のポイントを引くと困ります。要エントリーのキャンペーンや自分の SPU 倍率まで含めた「条件を全部満たせば届く価格」になってしまい、誰にとっても確実な最安ではなくなります。確実にもらえる分と「たぶんもらえる」分を同じ土俵に混ぜたのが原因です。

楽天・Yahoo! の点数計算でこれに詰まったので、ポイントを確実性で 3 層に分ける形に整理しました。コードは短く、考え方が要点です。

ポイントを確実性で 3 層に分ける

中身 ランキングに使う
① 確定 API が返す実付与ポイント 使う
② 条件付き 公知キャンペーン(5 と 0 のつく日 など) 使わない
③ 仮定 自分の SPU 倍率・買い回り 使わない

並び順(ランキング)は確定層だけで決め、②③は「最大でここまで下がる」という幅として見せます。こうすると最安順は誰が見ても同じになり、上振れは各自の条件で変わる、という形に切り分けられます。自己申告のポイントを盛った人ほど上位が動く、という挙動を避けられます。

最小実装

ポイントは層ごとに floor(切り捨て)してから合算します。まとめて足してから floor すると、実際の付与額と数円ずれます。

type Tier = "confirmed" | "conditional" | "assumed";

function computeEffectivePrice(
  base: number,
  layers: { rate: number; cap?: number; tier: Tier }[],
) {
  let confirmed = 0,
    conditional = 0,
    assumed = 0;
  for (const l of layers) {
    let pts = Math.floor(base * l.rate); // 層ごとに切り捨て
    if (l.cap != null) pts = Math.min(pts, l.cap); // 上限は floor 後に適用
    if (l.tier === "confirmed") confirmed += pts;
    else if (l.tier === "conditional") conditional += pts;
    else assumed += pts;
  }
  const rankKey = base - confirmed; // 並び順は確定層だけで引く
  const total = confirmed + conditional + assumed;
  const effectivePrice = Math.max(0, base - total); // 表示は全層・負ガード
  return { rankKey, effectivePrice };
}

ポイントは、並び順を決める rankKey には確定層だけを引き、画面に出す effectivePrice は全層を引いた値にします。同じ価格でも、ランキングと表示で引く層を変えるのが要点です。高還元で総ポイントが価格を超えると負になるので、Math.max(0, …) でガードします。

ハマりどころ

  • floor の順序: 上限(cap)も、floor したあとのポイント円に対して Math.min を取ります。先に cap してから floor すると 1 円ずれます。実際の付与もキャンペーンごとに切り捨てて付くことが多いので、層別 floor が実額に近くなります。
  • 倍率は「増分」で持つ: キャンペーンの「+4 倍」は、税込価格への追加付与分(増分)として保持します。倍率をそのまま価格に掛けると、標準でもらえるポイントを二重に数えてしまいます。等倍は増分 0 です。

まとめ

実質価格は「表示価格 − ポイント」の一行に見えて、信用できる数字にするには確実性で層を分ける必要があります。確定層だけでランキングし、条件付き・仮定はレンジで見せる。端数は層ごとに floor してから合算する。これだけで「一番安いはずが条件付きだった」を防げます。

送料を実質価格に入れない判断(API で取れないので推測しない)、要エントリーやキャップの開示、価格履歴と通知への組み込みは Aulvem 本家にまとめました → ポイント還元込みの「実質価格」をどう計算したか — 3層に分けた設計メモ

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?