3D Secure
銀行発行の二要素認証を追加して不正を70%以上削減し、チャージバックの責任をカード発行者に移転します。
概要
3D Secure(3DS)は、オンラインクレジットカードおよびデビットカード取引の追加セキュリティレイヤーで、カード所有者がカード発行銀行で追加の確認ステップを完了する必要があります。この認証により不正が大幅に削減され、チャージバックの責任が加盟店から銀行に移転されます。
主なメリット:
- ✅ 70%以上の不正削減 - 追加の認証により不正使用を防止
- ✅ 責任移転 - 銀行が認証済み取引のチャージバック責任を負担
- ✅ チャージバック率の低下 - 異議申し立てと関連 手数料を削減
- ✅ フリクションレスオプション - 3DS2により低リスク取引の即座承認が可能
- ✅ コンプライアンス要件 - 特定の事業タイプと地域で必須
3D Secure 1 vs 3D Secure 2
| 機能 | 3DS1(非推奨) | 3DS2(現在の標準) |
|---|---|---|
| ステータス | 2022年10月に非推奨 | アクティブで必須 |
| 認証方法 | OTP/SMSのみ | OTP、SMS、生体認証、顔認識 |
| フリクションレスフロー | 利用不可 | 低リスク取引で利用可能 |
| モバイルサポート | 制限あり | アプリ内フルサポート |
| データ共有 | 制限あり | リッチな取引コンテキスト(デバイス、場所、履歴) |
| ユーザーエクスペリエンス | 常にインタラクションが必要 | スマートなリスクベース判断 |
| 承認率 | 低い | 高い(フリクションレスがカート放棄を削減) |
3DS1の終了
3D Secure 1は2022年10月に非推奨となりました。すべての実装では3D Secure 2(3DS2)を使用する必要があります。
3DS2の仕組み
フリクションレスフロー
- 銀行がリッチデータ(デバイスID、ジオロケーション、購入履歴)を使用して取引リスクを分析
- リスクが十分低い場合、顧客のインタラクションなしで即座に取引承認
- チャレンジフローと同じ責任保護
- 大幅に優れたUX - リダイレクトなし、より速いチェックアウト
チャレンジフロー
- 追加の確認が必要な高リスク取引に使用
- 顧客は銀行の認証ページにリダイレクト
- 複数の認証オプション:
- OTP(SMSによるワンタイムパスワード)
- 生体認証(指紋、Face ID)
- 銀行アプリのプッシュ通知
- 通常1〜3分かかります
- 初回登録の場合は最大10分かかる場合があります
3D Secureを有効にするタイミング
必須の場合
- ✅ 旅行 & 宿泊 - ホテル、航空会社、予約プラットフォーム
- ✅ デジタル商品 - 音楽、映画、ソフトウェア、ゲーム
- ✅ バーチャルアイテム - ゲーム通貨、アプリ内購入
- ✅ プリペイドカード - ギフトカード、チャージサービス
- ✅ 高リスク加盟店 - 不正アナリストによって決定
推奨される場合
- 高額取引(10,000 THB以上または同等額)
- チャージバック率が0.3%を超える企業
- 国際カード取引
- 初回顧客
- 疑わしい取引パターン
推奨されない場合
- ❌ 継続課金 - 自動課金を妨げます
- ❌ ワンクリックリピート購入 - 毎回インタラクションが必要
- ❌ 少額取引 - 小額購入でのコンバージョンを低下させる可能性
サブスクリプションの回避策
サブスクリプションの場合: 初回カード設定時に3DSを使用し、その後の支払いには3DSなしで保存済み顧客IDを課金(銀行ルールに従う)。
実装ガイド
ステップ1: アカウントで3DSを有効化
Omiseサポートに連絡して3D Secureを有効にします:
Email: support@omise.co
件名: [アカウント名]のための3D Secureを有効化
ステップ2: トークンを作成(標準プロセス)
// クライアントサイド: 通常通りトークンを作成
Omise.setPublicKey("pkey_test_YOUR_KEY");
Omise.createToken("card", {
name: "John Doe",
number: "4242424242424242",
expiration_month: 12,
expiration_year: 2027,
security_code: "123"
}, function(statusCode, response) {
if (statusCode === 200) {
// サーバーにトークンを送信
submitToServer(response.id);
}
});
ステップ3: return_uriを含むチャージを作成
return_uriパラメータは3DSに必須です:
- cURL
- Node.js
- PHP
- Python
curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=100000" \
-d "currency=THB" \
-d "card=tokn_test_..." \
-d "return_uri=https://yourdomain.com/payment/callback"
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: tokenId,
return_uri: 'https://yourdomain.com/payment/callback'
});
// 3DS認証が必要かチェック
if (charge.authorize_uri) {
// 認証のためにauthorize_uriに顧客をリダイレクト
res.redirect(charge.authorize_uri);
} else {
// フリクションレスでチャージ完了
handleSuccess(charge);
}
<?php
$charge = OmiseCharge::create(array(
'amount' => 100000,
'currency' => 'THB',
'card' => $tokenId,
'return_uri' => 'https://yourdomain.com/payment/callback'
));
if (isset($charge['authorize_uri'])) {
// 3DS認証にリダイレクト
header('Location: ' . $charge['authorize_uri']);
exit;
} else {
// チャージ成功(フリクションレスフロー)
handleSuccess($charge);
}
?>
import omise
omise.api_secret = 'skey_test_YOUR_SECRET_KEY'
charge = omise.Charge.create(
amount=100000,
currency='THB',
card=token_id,
return_uri='https://yourdomain.com/payment/callback'
)
if charge.authorize_uri:
# 3DSページにリダイレクト
return redirect(charge.authorize_uri)
else:
# フリクションレス成功
return handle_success(charge)
ステップ4: 顧客のリダイレクトを処理
レスポンスにauthorize_uriが存在する場合、顧客をリダイレクト:
// サーバーサイド
app.post('/create-charge', async (req, res) => {
const charge = await omise.charges.create({
amount: req.body.amount,
currency: 'THB',
card: req.body.token,
return_uri: 'https://yourdomain.com/payment/callback',
metadata: {
order_id: req.body.order_id
}
});
if (charge.authorize_uri) {
// 3DS認証が必要
res.json({
requires_3ds: true,
authorize_uri: charge.authorize_uri,
charge_id: charge.id
});
} else {
// フリクションレス - チャージ完了
res.json({
requires_3ds: false,
status: charge.status,
charge_id: charge.id
});
}
});
// クライアントサイド
fetch('/create-charge', {
method: 'POST',
body: JSON.stringify({ token, amount, order_id })
})
.then(res => res.json())
.then(data => {
if (data.requires_3ds) {
// 3DS認証にリダイレクト
window.location.href = data.authorize_uri;
} else {
// 支払い完了
window.location.href = '/payment-success';
}
});
ステップ5: コールバックを処理
認証後、顧客はreturn_uriにリダイレクトされます:
app.get('/payment/callback', async (req, res) => {
// チャージステータスを取得
const chargeId = req.query.charge_id || req.session.charge_id;
const charge = await omise.charges.retrieve(chargeId);
if (charge.status === 'successful') {
// 3DS認証成功
await processOrder(charge.metadata.order_id);
res.redirect('/payment-success?order=' + charge.metadata.order_id);
} else if (charge.status === 'failed') {
// 3DS認証失敗
res.redirect('/payment-failed?reason=' + encodeURIComponent(charge.failure_message));
} else {
// まだ保留中 - 再度チェックまたはwebhookを使用
res.redirect('/payment-pending');
}
});
ステップ6: Webhookハンドラーを実装
信頼性のあるステータス更新のためにwebhookを使用:
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
if (event.key === 'charge.complete') {
const charge = event.data;
if (charge.status === 'successful') {
// 支払い成功を処理
processOrder(charge.metadata.order_id);
} else if (charge.status === 'failed') {
// 支払い失敗を処理
handleFailedPayment(charge.metadata.order_id, charge.failure_message);
}
}
res.sendStatus(200);
});
完全な実装例
<!DOCTYPE html>
<html>
<head>
<title>3DS Checkout</title>
<script src="https://cdn.omise.co/omise.js"></script>
</head>
<body>
<form id="payment-form">
<input type="text" id="card-name" placeholder="Card Holder" required />
<input type="text" id="card-number" placeholder="Card Number" required />
<input type="text" id="expiry-month" placeholder="MM" required />
<input type="text" id="expiry-year" placeholder="YYYY" required />
<input type="text" id="cvv" placeholder="CVV" required />
<button type="submit">Pay THB 1,000</button>
</form>
<script>
Omise.setPublicKey("pkey_test_YOUR_KEY");
document.getElementById('payment-form').addEventListener('submit', async (e) => {
e.preventDefault();
// ステップ1: トークンを作成
Omise.createToken("card", {
name: document.getElementById('card-name').value,
number: document.getElementById('card-number').value,
expiration_month: parseInt(document.getElementById('expiry-month').value),
expiration_year: parseInt(document.getElementById('expiry-year').value),
security_code: document.getElementById('cvv').value
}, async (statusCode, response) => {
if (statusCode !== 200) {
alert('Error: ' + response.message);
return;
}
// ステップ2: サーバーでチャージを作成
const result = await fetch('/create-charge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: response.id,
amount: 100000,
order_id: 'ORD-12345'
})
}).then(r => r.json());
// ステップ3: レスポンスを処理
if (result.requires_3ds) {
// 3DS認証にリダイレクト
window.location.href = result.authorize_uri;
} else {
// フリクションレス - 支払い完了
window.location.href = '/payment-success';
}
});
});
</script>
</body>
</html>
トレードオフと考慮事項
利点
- 不正削減: 不正取引が70%以上減少
- 責任移転: 銀行がチャージバック責任を負担
- 手数料削減: 異議申し立てが少ない = 異議申し立て手数料が低い
- コンプライアンス: 特定の事業タイプで必須
- 顧客信頼: プロフェッショナルなセキュリティ = 信頼の向上
欠点
- コンバージョンへの影響: 一部の顧客がリダイレクトで離脱(フリクションレスが役立つ)
- 継続課金不可: 保存済みカードを自動課金できない
- ユーザーフリクション: チャレンジフローがチェックアウトに1〜3分追加
- 技術的複雑さ: 追加のリダイレクト処理が必要
- 国際的なバリエーション: 銀行の実装品質が異なる
緩和戦略
カート放棄の削減:
// リダイレクト前に明確なメッセージを表示
function initiate3DS(authorizeUri) {
const modal = showModal({
title: "安全な支払い確認",
message: "確認のため銀行にリダイレクトされます。これにより支払いが安全に保たれます。",
button: "確認に進む"
});
modal.onConfirm = () => {
window.location.href = authorizeUri;
};
}
継続課金の処理:
// 初回チャージ: 3DSを使用
const firstCharge = await omise.charges.create({
amount: 29900,
currency: 'THB',
card: tokenId,
return_uri: 'https://example.com/callback',
metadata: { type: 'subscription_signup' }
});
// 今後のチャージのために顧客を保存
const customer = await omise.customers.create({
email: 'customer@example.com',
card: tokenId
});
// 後続のチャージ: 3DSなし(顧客IDを使用)
const recurringCharge = await omise.charges.create({
amount: 29900,
currency: 'THB',
customer: customer.id,
// return_uriなし = 3DS不要
});
3D Secureのテスト
テストモードで3DSを有効化
サポートに連絡してテストモードで3DSを有効にします:
Email: support@omise.co
件名: テストモードで3DSを有効化
テストカード
さまざまな3DSシナリオをテストするためにこれらのカードを使用:
注記
3DSテストには3DS対応のテストアカウントが必要です。support@omise.coに連絡して有効化してください。
標準成功カード(3DS問題なし):
| カード番号 | ブランド | 予想される結果 |
|---|---|---|
| 4242 4242 4242 4242 | Visa | フリクションレス承認 |
| 5555 5555 5555 4444 | Mastercard | フリクションレス承認 |
3DS登録失敗カード:
| カード番号 | ブランド | 予想される結果 |
|---|---|---|
| 4111 1111 1115 0002 | Visa | 3DS登録失敗 |
| 5555 5511 1112 0002 | Mastercard | 3DS登録失敗 |
| 3530 1111 1110 0002 | JCB | 3DS登録失敗 |
3DS認証失敗カード:
| カード番号 | ブランド | 予想される結果 |
|---|---|---|
| 4111 1111 1114 0003 | Visa | 3DS認証失敗 |
| 5555 5511 1111 0003 | Mastercard | 3DS認証失敗 |
| 3771 3816 1111 003 | Amex | 3DS認証失敗 |
テスト認証フロー
- テストカードとreturn_uriでチャージを作成
authorize_uriが存在する場合、URLにアクセス- テストページでモック認証を完了
- return_uriへのリダイレクトを確認
- 最終的なチャージステータスをチェック
# 3DSでテストチャージ
curl https://api.omise.co/charges \
-u skey_test_YOUR_KEY: \
-d "amount=100000" \
-d "currency=THB" \
-d "card=tokn_test_..." \
-d "return_uri=https://example.com/callback"
よくある問題とトラブルシューティング
問題: "3d secure is requested, but return URI is not set"
原因: アカウントで3DSが有効だがreturn_uriが提供されていない
解決策:
// 3DSが有効な場合は常にreturn_uriを含める
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: tokenId,
return_uri: 'https://yourdomain.com/callback' // 必須!
});