すべての開発者が決済システムを作れると思っている。カードを保存して、請求して、完了。
深夜2時に金が消えて、どこに行ったか誰も教えられなくなるまでは。
マーケットプレイスアプリ。Stripe連携。買い手が払って、売り手が受け取って、我々が手数料を取る。
初週:顧客が二重請求された。リクエストがタイムアウトし、コードがリトライし、Stripeが両方を処理した。$240が消えた。
冪等性キーを追加。問題解決、だよね?
2週間後:売り手に支払いが届かなかった。分割ロジックの丸め誤差 — $49.99を3つに分けると1セント浮いた。10,000トランザクション後、誰も説明できない本当のお金になった。
ユーザーが$100支払う
↓
プラットフォーム手数料: $10 (10%)
売り手が受け取る: $90
↓
でも$99.99を分割すると:
プラットフォーム: $9.999 → 丸めて$10.00
売り手: $89.991 → 丸めて$89.99
足りない: トランザクションごとに$0.01
× 10,000トランザクション = 説明できない$100
1. 冪等性
ネットワークがタイムアウト。請求は通ったか?冪等性キーがないと、二重請求するかタダでものを渡すかもしれない。
リクエスト1: $50請求 → タイムアウト(でもStripeは処理した)
リクエスト2: $50請求 → 成功(二重請求)
2. トークナイゼーション
クレジットカード番号を絶対に見るな。ログにも、データベースにも。
3. 分割支払い
金はA→Bだけに動くのではない。A→B、C、D — プラットフォーム手数料、税金、ドライバー取り分。
独自の台帳が必要だ。すべてのトランザクション、すべての分割、すべての返金。
君の台帳: 注文123で$100、手数料$10、支払い$90、ステータス完了。
Stripeの記録: pi_abc123で$100、手数料$10、振込$90、ステータス成功。
毎日照合しろ。不一致が積み重なる前に見つけろ。
Uberは毎日3,000万トランザクションを処理し、絶えず照合している。UberがStripeを真実の源として信頼しないなら、君もすべきではない。
決済を他人の金のように扱え。実際そうだから。
— blanho
みんな箱と矢印を描いている。誰もコードをリリースしていない。システム設計は重要だ、でもTwitterが思うほどじゃない。
NetflixはKafka、Cassandra、3つのキャッシュレイヤーを引き剥がした。すべてのキャッシュは嘘だから。
同期呼び出しは動くまでは動く。動かなくなったらメッセージキューが必要だ。その理由がこれ。
# バグ
def charge_customer(amount):
try:
stripe.PaymentIntent.create(amount=amount) # タイムアウト
except Timeout:
stripe.PaymentIntent.create(amount=amount) # リトライ。やばい。# 修正
def charge_customer(amount, order_id):
stripe.PaymentIntent.create(
amount=amount,
idempotency_key=f"order_{order_id}" # 同じキー = 同じ請求
)# 絶対やるな
card_number = request.json['card']
log.info(f"Charging card {card_number}") # PCI違反
# こうしろ
token = stripe.Token.create(card=request.json['card'])
log.info(f"Charging token {token.id}") # 安全# 整数(セント)を使え、floatではなく
total_cents = 4999 # $49.99
platform_fee = total_cents * 10 // 100 # 499セント
seller_amount = total_cents - platform_fee # 4500セント
# 絶対: total * 0.10(float計算 = 丸めバグ)