
はじめに
Payment Gateway の概要を理解した後、この記事では承認から決済までの支払い処理プロセスについて詳しく説明します。これは最も複雑な部分であり、お金に直接関係するため絶対的な精度が必要です。
1. 認可フロー
1.1 カード認証
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Customer │ │ Merchant │ │ Acquirer │ │ Card │ │ Issuer │
│ │ │ │ │ Bank │ │ Network │ │ Bank │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │ │
│ Card Info │ │ │ │
├─────────────►│ │ │ │
│ │ Auth Req │ │ │
│ ├─────────────►│ │ │
│ │ │ Auth Req │ │
│ │ ├─────────────►│ │
│ │ │ │ Auth Req │
│ │ │ ├─────────────►│
│ │ │ │ │
│ │ │ │ Check: │
│ │ │ │ - Balance │
│ │ │ │ - Fraud │
│ │ │ │ - Limits │
│ │ │ │ │
│ │ │ │ Auth Resp │
│ │ │ │◄─────────────┤
│ │ │ Auth Resp │ │
│ │ │◄─────────────┤ │
│ │ Auth Resp │ │ │
│ │◄─────────────┤ │ │
│ Result │ │ │ │
│◄─────────────┤ │ │ │
1.2 認可応答コード
| コード | 意味 | アクション |
|---|---|---|
00 | 承認済み | キャプチャを続行 |
05 | 尊重しないでください | ターミナル - 再試行しないでください |
14 | 無効なカード番号 | ターミナル |
51 | 資金不足 | ターミナル |
54 | 期限切れのカード | ターミナル |
61 | 制限を超えています | 金額を下げて再試行できます |
91 | 発行者が利用できません | 後で再試行 |
1.3 認証ホールドの管理
Authorization creates a "hold" on customer funds:
Timeline:
Day 0: Auth $100 ──► Hold $100 on card
Day 1-7: Hold active, merchant can capture
Day 7+: Hold expires (auto-release by issuer)
Rules:
├── Auth hold duration varies by card network
│ ├── Visa: 7-30 days
│ ├── Mastercard: 7-30 days
│ └── NAPAS: Varies by issuer
├── Partial capture: Can capture <= auth amount
├── Multi-capture: Some networks support partial captures
└── Void: Cancel auth before capture (release hold)
2. キャプチャプロセス
2.1 キャプチャ戦略
1. Auto-capture (immediate):
Auth ──► Immediate Capture
Use case: Digital goods, instant delivery
2. Manual capture (delayed):
Auth ──► Wait ──► Capture when ready
Use case: Physical goods (capture on ship)
3. Partial capture:
Auth $100 ──► Capture $80 (partial shipment)
Remaining $20 ──► Void or capture later
4. Multi-capture:
Auth $100 ──► Capture $50 ──► Capture $30 ──► Capture $20
Use case: Marketplace with multiple sellers
2.2 キャプチャの実装
public class CaptureService {
@Transactional
public CaptureResult capture(String paymentId, Money amount) {
var payment = paymentRepository.findById(paymentId)
.orElseThrow(() -> new PaymentNotFoundException(paymentId));
// Validate
if (payment.getStatus() != PaymentStatus.AUTHORIZED) {
throw new InvalidStateException("Payment must be AUTHORIZED");
}
if (amount.isGreaterThan(payment.remainingCapturable())) {
throw new AmountExceedsAuthException();
}
// Capture with PSP
var pspResult = pspAdapter.capture(payment, amount);
// Update payment
payment.capture(amount, pspResult);
paymentRepository.save(payment);
// Publish event
eventBus.publish(new PaymentCapturedEvent(payment, amount));
return CaptureResult.success(payment);
}
}
3. 決済プロセス
3.1 決済の流れ
Settlement = Transfer of actual funds from issuer to acquirer to merchant
Daily Settlement Cycle:
┌─────────────────────────────────────────────────────────┐
│ │
│ 23:00 Cut-off time │
│ │ │
│ ▼ │
│ Batch all captured transactions for the day │
│ │ │
│ ▼ │
│ Submit settlement file to acquirer/network │
│ │ │
│ ▼ │
│ T+1: Clearing (acquirer receives funds from network) │
│ │ │
│ ▼ │
│ T+1~T+3: Payout to merchant account │
│ │ │
│ ▼ │
│ Settlement complete │
│ │
└─────────────────────────────────────────────────────────┘
3.2 居住地のアーキテクチャ
┌─────────────────────────────────────────────────────────┐
│ SETTLEMENT ENGINE │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Batch │ │ Fee │ │ Payout │ │
│ │ Builder │───►│ Engine │───►│ Calculator │ │
│ └──────────┘ └──────────┘ └────────┬─────────┘ │
│ │ │
│ ┌──────────┐ ┌──────────┐ ┌────────▼─────────┐ │
│ │ Report │◄───│Reconcile │◄───│ Transfer │ │
│ │ Generator│ │ Engine │ │ Executor │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────┘
3.3 料金計算
Transaction Amount: 1,000,000 VND
Fee Breakdown:
├── Interchange fee (issuer): 1.5% = 15,000 VND
├── Network fee (Visa/MC): 0.2% = 2,000 VND
├── Acquirer fee: 0.3% = 3,000 VND
├── Gateway fee (our platform): 0.5% = 5,000 VND
└── Total fees: 2.5% = 25,000 VND
Merchant receives: 975,000 VND
public record SettlementCalculation(
Money grossAmount,
Money interchangeFee,
Money networkFee,
Money acquirerFee,
Money platformFee,
Money netAmount
) {
public static SettlementCalculation calculate(
Payment payment, MerchantFeeConfig config) {
Money gross = payment.getCapturedAmount();
Money interchange = gross.multiply(config.interchangeRate());
Money network = gross.multiply(config.networkRate());
Money acquirer = gross.multiply(config.acquirerRate());
Money platform = gross.multiply(config.platformRate());
Money net = gross.subtract(interchange)
.subtract(network)
.subtract(acquirer)
.subtract(platform);
return new SettlementCalculation(
gross, interchange, network, acquirer, platform, net);
}
}
4. バッチ処理
4.1 一日の終わりのバッチ
@Scheduled(cron = "0 0 23 * * *") // 23:00 daily
public void runDailySettlement() {
var cutoffTime = LocalDateTime.now();
var transactions = paymentRepository
.findCapturedNotSettled(cutoffTime);
// Group by merchant
var byMerchant = transactions.stream()
.collect(Collectors.groupingBy(Payment::getMerchantId));
for (var entry : byMerchant.entrySet()) {
var merchantId = entry.getKey();
var payments = entry.getValue();
// Calculate settlement
var settlement = settlementService
.createSettlement(merchantId, payments);
// Generate settlement file
settlementFileGenerator.generate(settlement);
// Queue payout
payoutService.queuePayout(settlement);
}
}
4.2 リアルタイム決済
即時決済の新しいトレンド (VietQR、UPI):
Traditional: Capture → T+1~T+3 Settlement
Real-time: Capture → Instant Settlement (< 30 seconds)
Real-time settlement requires:
├── Real-time clearing network (NAPAS for Vietnam)
├── Pre-funded settlement accounts
├── Real-time balance management
└── Instant notification to merchant
5. 返金処理
5.1 返金の種類
Full Refund: Payment $100 → Refund $100
Partial Refund: Payment $100 → Refund $30 (keep $70)
Multiple Refund: Payment $100 → Refund $30 → Refund $20 → ... (total ≤ $100)
5.2 返金の流れ
@Transactional
public RefundResult processRefund(String paymentId, Money amount, String reason) {
var payment = paymentRepository.findById(paymentId)
.orElseThrow(() -> new PaymentNotFoundException(paymentId));
// Validate
if (!payment.isRefundable()) {
throw new PaymentNotRefundableException();
}
if (amount.isGreaterThan(payment.refundableAmount())) {
throw new RefundAmountExceedsException();
}
// Process refund with PSP
var pspResult = pspAdapter.refund(payment, amount);
// Create refund record
var refund = Refund.create(payment, amount, reason, pspResult);
refundRepository.save(refund);
// Update payment
payment.addRefund(refund);
paymentRepository.save(payment);
// Reverse ledger entries
eventBus.publish(new RefundProcessedEvent(refund));
return RefundResult.success(refund);
}
概要
支払い処理は多くの手順を伴う複雑なプロセスです。
- 承認: 資金の準備、カードの検証
- 獲得: 承認された資金を収集します (即時または遅延)
- 決済: 決済ネットワークを介した実際の資金移動
- 返金: 支払いフローを逆にします
重要な原則: すべてのステップでの べき等性、ステート マシンの明確さ、各変更の 監査証跡。
次の記事: マルチ PSP 統合 — 複数の PSP (VNPay、MoMo、Stripe) をスマート ルーティングと統合するための抽象化レイヤーを設計します。