メインコンテンツへスキップ

DANA

インドネシアの主要デジタルウォレットDANAから支払いを受け付けます。1億5000万人以上のユーザーを持ち、群島全体で広く受け入れられています。

概要

DANAはインドネシアで最大かつ最も人気のあるデジタルウォレットの1つで、Ant Group(Alipayの親会社)の支援を受けています。交通機関やフードデリバリーからeコマースや請求書の支払いまで、日常的な取引に広く使用されています。

主な特徴:

  • 大規模なリーチ - インドネシア全土で1億5000万人以上のユーザー
  • 迅速な確認 - ほぼリアルタイムの支払い確認(通常数秒以内)
  • 高い受入率 - 1000万以上の加盟店で受入
  • モバイルファースト - シームレスなスマートフォン体験
  • 低摩擦 - PINまたは生体認証による迅速な認証
  • 幅広いユースケース - eコマース、サービス、交通機関

サポート地域

地域通貨最小金額最大金額日次制限
インドネシアIDRRp 1,000Rp 10,000,000可変*

*日次制限は顧客のDANAアカウント確認レベル(Basic、Premium、またはPriority)によって異なります

確認レベル別のアカウント制限

確認レベル残高制限1取引あたり月次制限
Basic (電話のみ)Rp 2,000,000Rp 2,000,000Rp 10,000,000
Premium (ID確認済み)Rp 10,000,000Rp 5,000,000Rp 30,000,000
Priority (Premium + アップグレード)Rp 100,000,000Rp 10,000,000無制限

仕組み

顧客体験:

  1. 顧客がチェックアウトでDANAを選択
  2. DANA認証ページにリダイレクト
  3. DANAアプリが自動的に開く(ディープリンク)
  4. 顧客が支払い詳細を確認
  5. PINを入力するか生体認証を使用
  6. 加盟店サイトに戻る
  7. 即座に確認を受信

一般的な完了時間: 30〜90秒

支払いフローの例

モバイル支払いフロー:

Mobile Payment Flow

シームレスなモバイルアプリ体験:

  • ❶ DANAを選択 - 顧客がチェックアウトでDANAウォレットを選択
  • ❷ DANAにリダイレクト - ディープリンクが自動的にDANAアプリを開く
  • ❸ 支払いを確認 - 取引詳細が表示される(加盟店、金額、注文ID)
  • ❹ 認証 - DANA PINを入力するか生体認証を使用
  • ❺ 支払いを確認 - 確認ボタンをタップして承認
  • ❻ 支払い完了 - DANAウォレットから資金が引き落とされ、加盟店に戻る

デスクトップ支払いフロー:

Desktop Payment Flow

デスクトップユーザー用のQRコードスキャン:

  • ❶ DANA支払いを開始 - 顧客がデスクトップでDANAを選択
  • ❷ QRコードを生成 - システムが一意のDANA支払いQRを作成
  • ❸ QRを表示 - QRコードが画面に目立つように表示される
  • ❹ DANAアプリを開く - 顧客がモバイルでDANAを起動
  • ❺ QRをスキャン - アプリ内QRスキャナーを使用してコードをキャプチャ
  • ❻ 確認と承認 - 支払い詳細が表示され、PINで認証
  • ❼ 成功 - 支払いが処理され、デスクトップに確認が表示される

実装

ステップ1: DANAソースを作成

curl https://api.omise.co/sources \
-u skey_test_YOUR_SECRET_KEY: \
-d "type=dana" \
-d "amount=100000" \
-d "currency=IDR"

レスポンス:

{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "dana",
"flow": "redirect",
"amount": 100000,
"currency": "IDR"
}

ステップ2: チャージを作成してリダイレクト

app.post('/checkout/dana', async (req, res) => {
try {
const { amount, order_id, customer_email } = req.body;

// 金額を検証
if (amount < 100000) { // Rp 1,000最小
return res.status(400).json({
error: 'Minimum amount is Rp 1,000'
});
}

if (amount > 1000000000) { // Rp 10,000,000最大
return res.status(400).json({
error: 'Maximum amount is Rp 10,000,000'
});
}

// ソースを作成
const source = await omise.sources.create({
type: 'dana',
amount: amount,
currency: 'IDR'
});

// チャージを作成
const charge = await omise.charges.create({
amount: amount,
currency: 'IDR',
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id,
customer_email: customer_email,
payment_method: 'dana'
}
});

// DANAにリダイレクト
res.redirect(charge.authorize_uri);

} catch (error) {
console.error('DANA error:', error);
res.status(500).json({ error: error.message });
}
});

ステップ3: リターンコールバックを処理

app.get('/payment/callback', async (req, res) => {
try {
const chargeId = req.query.charge_id;
const charge = await omise.charges.retrieve(chargeId);

if (charge.status === 'successful') {
await processOrder(charge.metadata.order_id);
res.redirect(`/order-success?order=${charge.metadata.order_id}`);
} else if (charge.status === 'failed') {
res.redirect(`/payment-failed?reason=${charge.failure_message}`);
} else {
res.redirect('/payment-pending');
}
} catch (error) {
res.redirect('/payment-error');
}
});

ステップ4: Webhookを処理

app.post('/webhooks/omise', async (req, res) => {
const event = req.body;

if (event.key === 'charge.complete') {
const charge = event.data;

if (charge.source.type === 'dana') {
if (charge.status === 'successful') {
await fulfillOrder(charge.metadata.order_id);
await sendConfirmationEmail(charge.metadata.customer_email);
} else if (charge.status === 'failed') {
await handleFailedPayment(charge.metadata.order_id);
}
}
}

res.sendStatus(200);
});

完全な実装例

const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});

const app = express();
app.use(express.json());

// DANA設定
const DANA_CONFIG = {
currency: 'IDR',
minAmount: 100000, // Rp 1,000
maxAmount: 1000000000, // Rp 10,000,000
displayName: 'DANA',
timeout: 15 * 60 * 1000 // 15分
};

// DANA支払いを作成
app.post('/checkout/dana', async (req, res) => {
try {
const { amount, order_id, customer_email, customer_name } = req.body;

// 金額を検証
if (amount < DANA_CONFIG.minAmount) {
return res.status(400).json({
error: `Minimum amount is Rp ${DANA_CONFIG.minAmount / 100}`
});
}

if (amount > DANA_CONFIG.maxAmount) {
return res.status(400).json({
error: `Maximum amount is Rp ${DANA_CONFIG.maxAmount / 100}`
});
}

// ソースを作成
const source = await omise.sources.create({
type: 'dana',
amount: amount,
currency: DANA_CONFIG.currency
});

// チャージを作成
const charge = await omise.charges.create({
amount: amount,
currency: DANA_CONFIG.currency,
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id,
customer_email: customer_email,
customer_name: customer_name,
payment_method: 'dana'
}
});

// トラッキングのためにログ記録
console.log(`DANA payment initiated: ${charge.id} for order ${order_id}`);

// 認証URLを返す
res.json({
authorize_uri: charge.authorize_uri,
charge_id: charge.id
});

} catch (error) {
console.error('DANA error:', error);
res.status(500).json({ error: error.message });
}
});

// 支払いコールバック
app.get('/payment/callback', async (req, res) => {
try {
const charge = await omise.charges.retrieve(req.query.charge_id);

if (charge.status === 'successful') {
res.redirect(`/order-confirmation?order=${charge.metadata.order_id}`);
} else {
res.redirect(`/payment-failed?charge=${req.query.charge_id}`);
}
} catch (error) {
res.redirect('/payment-error');
}
});

// Webhookハンドラ
app.post('/webhooks/omise', async (req, res) => {
const event = req.body;

if (event.key === 'charge.complete' && event.data.source.type === 'dana') {
const charge = event.data;

if (charge.status === 'successful') {
await fulfillOrder(charge.metadata.order_id);
await sendReceipt(charge.metadata.customer_email, charge);
console.log(`DANA payment successful: ${charge.id}`);
} else {
await cancelOrder(charge.metadata.order_id);
console.log(`DANA payment failed: ${charge.id}`);
}
}

res.sendStatus(200);
});

app.listen(3000);

返金サポート

DANAは30日以内全額および一部返金をサポートします:

// 全額返金
const fullRefund = await omise.charges.refund('chrg_test_...', {
amount: 100000 // 全額
});

// 一部返金
const partialRefund = await omise.charges.refund('chrg_test_...', {
amount: 50000 // 一部金額
});

console.log('Refund status:', fullRefund.status);
返金処理

返金は3〜7営業日以内に顧客のDANAウォレットに処理されます。

一般的な問題とトラブルシューティング

問題: 顧客がDANAアプリを持っていない

原因: 顧客がDANAを選択したがアプリがインストールされていない

解決策:

// モバイルを検出してアプリ要件を表示
function checkDANA() {
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

if (!isMobile) {
alert('DANA is only available on mobile devices. Please use a smartphone.');
return false;
}

// 必要に応じてインストールプロンプトを表示
showMessage('DANA app required. Download from:');
showLinks({
android: 'https://play.google.com/store/apps/details?id=id.dana',
ios: 'https://apps.apple.com/id/app/dana-indonesia/id1388056324'
});

return true;
}

問題: 残高不足

エラー: 支払い拒否

解決策:

if (charge.failure_code === 'insufficient_balance') {
showMessage('Saldo DANA tidak mencukupi. Silakan top up atau gunakan metode pembayaran lain.');
showAlternativePaymentMethods();
}

問題: アカウント制限超過

エラー: 取引制限超過

解決策:

if (charge.failure_code === 'transaction_limit_exceeded') {
showMessage(
'Transaksi melebihi batas akun DANA Anda. ' +
'Tingkatkan verifikasi akun atau gunakan metode pembayaran lain.'
);
}

問題: 支払いタイムアウト

原因: 顧客が15分以内に支払いを完了しなかった

解決策:

const TIMEOUT = 15 * 60 * 1000;

setTimeout(() => {
if (!paymentCompleted) {
showMessage('Waktu pembayaran habis. Silakan coba lagi.');
enableRetry();
}
}, TIMEOUT);

ベストプラクティス

1. インドネシア語で表示

<div class="dana-payment">
<h3>Bayar dengan DANA</h3>
<div class="instructions">
<ol>
<li>Pastikan aplikasi DANA sudah terinstal</li>
<li>Pastikan saldo DANA mencukupi</li>
<li>Anda akan diarahkan ke aplikasi DANA</li>
<li>Masukkan PIN DANA untuk konfirmasi</li>
</ol>
</div>
<p class="help-text">
Belum punya DANA?
<a href="https://dana.id" target="_blank">Download di sini</a>
</p>
</div>

2. モバイル専用検出

function validateDANAPayment() {
const userAgent = navigator.userAgent;
const isMobile = /Android|iPhone|iPad|iPod/i.test(userAgent);

if (!isMobile) {
return {
valid: false,
message: 'DANA hanya tersedia di perangkat mobile'
};
}

// AndroidまたはiOSを確認
const isAndroid = /Android/i.test(userAgent);
const isIOS = /iPhone|iPad|iPod/i.test(userAgent);

return {
valid: true,
platform: isAndroid ? 'android' : (isIOS ? 'ios' : 'unknown')
};
}

3. ディープリンクの処理

function openDANAApp(authorizeUri) {
// DANAにリダイレクト
window.location = authorizeUri;

// アプリが開いたかを確認(フォールバック)
setTimeout(() => {
if (!document.hidden) {
// アプリが開かなかった
showInstallPrompt();
}
}, 2500);
}

function showInstallPrompt() {
const isAndroid = /Android/i.test(navigator.userAgent);
const downloadUrl = isAndroid
? 'https://play.google.com/store/apps/details?id=id.dana'
: 'https://apps.apple.com/id/app/dana-indonesia/id1388056324';

const message = 'Aplikasi DANA belum terinstal. Download sekarang?';

if (confirm(message)) {
window.location = downloadUrl;
}
}

4. 金額のフォーマット

function formatIDR(amount) {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(amount / 100);
}

// 使用法
const displayAmount = formatIDR(100000); // "Rp 1.000"

5. Webhookを使用

// Webhookが主要な真実のソース
app.post('/webhooks/omise', handleWebhook);

// コールバックはユーザーエクスペリエンスのバックアップ
app.get('/payment/callback', handleCallback);

テスト

テスト認証情報

Omiseテストモード認証情報を使用:

  • Secret Key: skey_test_YOUR_SECRET_KEY
  • Public Key: pkey_test_YOUR_PUBLIC_KEY

テスト金額

金額 (IDR)期待される結果
100000 - 999999Success
1000000Insufficient balance
1000001Transaction declined

テストフロー

  1. テスト認証情報でチャージを作成
  2. 上記のテスト金額を使用
  3. DANAテスト環境で支払いを完了
  4. Webhookを受信したことを確認
  5. チャージステータスを確認

FAQ

DANAとは何ですか?

DANAはインドネシアの主要デジタルウォレットで、1億5000万人以上のユーザーを持っています。Ant Group(Alipayの親会社)の支援を受けており、eコマース、交通機関、日常的な取引に広く受け入れられています。

顧客はDANAアカウントが必要ですか?

はい、顧客はDANAモバイルアプリがインストールされ、アクティブなDANAアカウントが必要です。アプリは無料でAndroidとiOSで利用可能です。

取引制限は何ですか?
  • 最小: Rp 1,000
  • 最大: 取引あたりRp 10,000,000

制限は顧客のアカウント確認レベル(Basic、Premium、Priority)によっても異なります。

決済にはどのくらいかかりますか?

DANAの決済は通常1〜3営業日以内に行われます。具体的な決済スケジュールについてはOmiseダッシュボードをご確認ください。

DANA支払いを返金できますか?

はい、DANAは元の取引の30日以内に全額および一部返金をサポートします。

顧客の残高が不足している場合はどうなりますか?

支払いは拒否されます。顧客は次の方法でDANAウォレットにトップアップできます:

  • 銀行振込
  • コンビニエンスストア(Indomaret、Alfamart)
  • ATM
  • その他のリンクされた決済手段

明確なエラーメッセージを提供し、代替決済手段を提示してください。

DANAはデスクトップで動作しますか?

いいえ、DANAにはモバイルアプリが必要で、モバイル専用です。デスクトップユーザーにはクレジットカードや銀行振込などの代替決済手段を表示する必要があります。

インドネシア語で指示を表示する必要がありますか?

はい!DANAはインドネシア専用なので、より良いユーザーエクスペリエンスのために、支払い指示をバハサインドネシア語で表示することを強くお勧めします。

テスト

テストモード

DANAはテストAPIキーを使用してテストできます。テストモードでは:

テスト認証情報:

  • テストAPIキーを使用 (skey_test_xxx)
  • 通貨: IDR(インドネシアルピア)
  • テストに実際のDANAアカウントは不要

テストフロー:

  1. テストAPIキーでソースとチャージを作成
  2. 顧客がテストauthorize_uriにリダイレクト
  3. テストページがDANA認証をシミュレート
  4. Omiseダッシュボードのアクションを使用してチャージを成功/失敗としてマーク
  5. WebhookとReturn_uriの処理を確認

テスト実装:

// DANA支払いをテスト
const source = await omise.sources.create({
type: 'dana',
amount: 10000, // Rp10,000
currency: 'IDR'
});

const charge = await omise.charges.create({
amount: 10000,
currency: 'IDR',
source: source.id,
return_uri: 'https://example.com/callback'
});

console.log('Test authorize URL:', charge.authorize_uri);

テストシナリオ:

  • 支払い成功: 注文完了ワークフローを確認
  • 支払い失敗: エラー処理とメッセージングをテスト
  • 金額制限: 最小および最大金額をテスト
  • モバイルフロー: DANAアプリへのディープリンクをテスト
  • 残高不足: 低ウォレット残高をシミュレート
  • タイムアウト: 放棄された支払いシナリオをテスト
  • Webhook配信: すべてのWebhook通知を確認

重要な注意事項:

  • テストモードは実際のDANAサーバーに接続しません
  • ダッシュボードを使用して支払い結果をシミュレート
  • モバイルアプリフローを徹底的にテスト
  • すべてのチャージステータスのWebhook処理を確認
  • IDRの金額検証をテスト

包括的なテストガイドラインについては、テストドキュメントを参照してください。

関連リソース

次のステップ

  1. DANAソースを作成
  2. リダイレクトフローを実装
  3. リターンコールバックを処理
  4. Webhookを設定
  5. 支払いフローをテスト
  6. 本番稼働