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