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

FPX(ファイナンシャルプロセスエクスチェンジ)

FPXはマレーシアの国家的銀行間決済ゲートウェイであり、19以上のマレーシア銀行からのインスタント送金を受け付けます。全国に広く採用されている決済ゲートウェイです。

概要

FPX(ファイナンシャルプロセスエクスチェンジ)はPayNetが運営するマレーシアの国家的オンライン銀行決済ゲートウェイです。銀行口座から直接のオンライン銀行経由での支払いが可能であり、マレーシアの電子商取引において最も人気のある支払い方法の一つです。

主な機能:

  • 19行以上の銀行 - マレーシア主要銀行全て対応
  • 国家システム - マレーシアの公式銀行間ゲートウェイ
  • 即座の確認 - リアルタイム決済処理
  • 高額制限 - 1取引につきRM30,000まで
  • 登録不要 - 顧客は既存のオンライン銀行を使用
  • 信頼性 - PayNet(マレーシア中央銀行)が運営

対応地域

地域通貨最小金額最大金額1日の制限
マレーシアMYRRM1.00RM30,000銀行により異なる

対応銀行

FPXは19以上のマレーシア主要銀行に対応:

主要銀行:

  • Affin Bank
  • Alliance Bank
  • AmBank
  • Bank Islam
  • Bank Muamalat
  • Bank Rakyat
  • BSN(Bank Simpanan Nasional)
  • CIMB Bank
  • Hong Leong Bank
  • HSBC Bank
  • Kuwait Finance House
  • Maybank
  • OCBC Bank
  • Public Bank
  • RHB Bank
  • Standard Chartered
  • UOB(United Overseas Bank)

仕組み

顧客体験:

  1. 顧客がチェックアウトで「FPX」を選択
  2. FPXゲートウェイへリダイレクト
  3. リストから銀行を選択
  4. 銀行のオンライン銀行へリダイレクト
  5. オンライン銀行の認証情報でログイン
  6. 支払いを確認&認可
  7. 加盟店サイトに戻す

標準的な完了時間: 2~5分

支払いフローの例

銀行選択及びログイン:

銀行選択及びログイン

FPXゲートウェイの認証段階:

  • ❶ FPXを選択 - 顧客がチェックアウトでFPX銀行送金を選択
  • ❷ FPXへリダイレクト - マレーシアのFXP決済ゲートウェイに移動
  • ❸ 銀行を選択 - 参加している19以上の銀行から選択
  • ❹ 銀行へリダイレクト - 選択した銀行のオンライン銀行ページに転送
  • ❺ ログイン - オンライン銀行のユーザーネーム及びパスワードを入力
  • ❻ 2FAの確認 - 2要素認証を完了(TAC/OTP)
  • ❼ 認証完了 - 支払い認可に進む

支払い確認:

支払い確認

取引の認可及び完了:

  • ❽ 詳細を確認 - 加盟店名、支払い金額、参照番号を表示
  • ❾ 口座を確認 - 支払い元口座を確認
  • ❿ 支払いを認可 - 承認ボタンをクリックして送金を承認
  • ⓫ 処理中 - 銀行がFPX経由で銀行間送金を処理
  • ⓬ 成功確認 - 支払い完了メッセージと取引IDを表示
  • ⓭ 加盟店に戻す - 加盟店の確認ページへ戻す

実装

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

curl https://api.omise.co/sources \
-u skey_test_YOUR_SECRET_KEY: \
-d "type=fpx" \
-d "amount=10000" \
-d "currency=MYR" \
-d "email=customer@example.com"

レスポンス:

{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "fpx",
"flow": "redirect",
"amount": 10000,
"currency": "MYR",
"email": "customer@example.com"
}

ステップ2:請求を作成

curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=10000" \
-d "currency=MYR" \
-d "source=src_test_5rt6s9vah5lkvi1rh9c" \
-d "return_uri=https://yourdomain.com/payment/callback"

ステップ3:顧客をリダイレクト

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

// 金額を検証
if (amount < 100 || amount > 3000000) {
return res.status(400).json({
error: 'RM1~RM30,000の間である必要があります'
});
}

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

// 請求を作成
const charge = await omise.charges.create({
amount: amount,
currency: 'MYR',
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id
}
});

// FPXへリダイレクト
res.redirect(charge.authorize_uri);

} catch (error) {
console.error('FPXエラー:', error);
res.status(500).json({ error: error.message });
}
});

ステップ4:リターンを処理

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('/payment-success');
} else if (charge.status === 'failed') {
res.redirect('/payment-failed?reason=' + charge.failure_message);
} else {
res.redirect('/payment-pending');
}
} catch (error) {
res.redirect('/payment-error');
}
});

ステップ5:Webhookを処理

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

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

if (charge.status === 'successful') {
processOrder(charge.metadata.order_id);
} else if (charge.status === 'failed') {
handleFailedPayment(charge.metadata.order_id);
}
}

res.sendStatus(200);
});

完全な実装例

// Express.jsサーバー
const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});

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

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

// 金額を検証(RM1~RM30,000)
if (amount < 100 || amount > 3000000) {
return res.status(400).json({
error: 'RM1~RM30,000の間である必要があります'
});
}

// メールアドレスを検証
if (!customer_email || !customer_email.includes('@')) {
return res.status(400).json({
error: 'FPX支払いに有効なメールアドレスが必要です'
});
}

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

// 請求を作成
const charge = await omise.charges.create({
amount: amount,
currency: 'MYR',
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id,
payment_method: 'fpx',
customer_email: customer_email
}
});

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

} catch (error) {
console.error('FPXエラー:', error);
res.status(500).json({ error: error.message });
}
});

// コールバックハンドラー
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') {
res.redirect(`/order-success?order=${charge.metadata.order_id}`);
} else {
res.redirect(`/payment-failed?charge=${chargeId}`);
}
} catch (error) {
res.redirect('/payment-error');
}
});

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

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

if (charge.source.type === 'fpx') {
if (charge.status === 'successful') {
updateOrderStatus(charge.metadata.order_id, 'paid');
sendConfirmation(charge.metadata.customer_email);
} else {
updateOrderStatus(charge.metadata.order_id, 'failed');
}
}
}

res.sendStatus(200);
});

app.listen(3000);

返金対応

FPXは 6カ月以内全額及び部分的な返金 に対応:

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

// 部分返金
const partialRefund = await omise.charges.refund('chrg_test_...', {
amount: 5000 // 半額返金
});
返金の有効期限

返金は元の取引後6カ月以内に対応します。全額及び部分的な返金の両方に対応しています。

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

問題:銀行が利用できない

原因: 顧客の銀行が一時的に利用できないか、メンテナンス中

解決方法:

  • リアルタイム銀行の利用可能状況を表示(FPXが提供している場合)
  • 代替支払い方法を表示
  • 推定ダウンタイム情報を提供

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

原因: 顧客が銀行のオンライン銀行ページで支払いを放棄

解決方法:

// 合理的なタイムアウトを設定
const TIMEOUT = 30 * 60 * 1000; // 30分

setTimeout(() => {
if (!paymentConfirmed) {
showTimeoutMessage();
allowRetry();
}
}, TIMEOUT);

問題:取引額の制限を超える

エラー: 金額が銀行の制限を超過

解決方法:

  • 明確な制限情報を表示(RM30,000上限)
  • 複数取引への分割を提案
  • 代替の高額支払い方法を提供

問題:顧客がブラウザーを閉じた

原因: Return URIが呼ばれていない

解決方法:

  • Webhookハンドラーを実装(より確実)
  • 注文ステータス確認ページを提供
  • メール確認を送信

ベストプラクティス

1. 銀行選択を表示

<div class="fpx-instructions">
<h3>FPXオンライン銀行で支払い</h3>
<p>これらの銀行から選択できます:</p>
<ul class="bank-list">
<li>Maybank</li>
<li>CIMB Bank</li>
<li>Public Bank</li>
<li>RHB Bank</li>
<li>Hong Leong Bank</li>
<li>AmBank</li>
<li>その他13行...</li>
</ul>
<p><strong>要件:</strong></p>
<ul>
<li>口座でオンライン銀行が有効化</li>
<li>十分な残高</li>
<li>取引金額:RM1~RM30,000</li>
</ul>
</div>

2. メールアドレスを収集

// メールはオプションですが領収書に推奨
function validateCheckout(data) {
if (!data.email || !data.email.includes('@')) {
throw new Error('有効なメールアドレスを入力してください');
}

return true;
}

3. 金額を検証

function validateFPXAmount(amount) {
const MIN = 100; // RM1.00
const MAX = 3000000; // RM30,000.00

if (amount < MIN) {
return '最小金額はRM1.00です';
}

if (amount > MAX) {
return '最大金額はRM30,000.00です';
}

return null; // 有効
}

4. Webhookを使用

// Webhookは主要な通知方法
app.post('/webhooks/omise', handleWebhook);

// コールバックはバックアップ
app.get('/payment/callback', handleCallback);

5. 銀行メンテナンスを処理

// 既知のメンテナンス窓を確認
const MAINTENANCE_SCHEDULE = {
'maybank': { day: 'sunday', time: '00:00-06:00' },
'cimb': { day: 'sunday', time: '01:00-05:00' }
};

function checkMaintenanceWindow(bank) {
const now = new Date();
// 現在の時刻がメンテナンス窓に該当するかを確認
// 顧客に警告を表示
}

FAQ

FPXとは何ですか?

FPX(ファイナンシャルプロセスエクスチェンジ)はPayNetが運営するマレーシアの国家的オンライン銀行決済ゲートウェイです。19以上のマレーシア銀行をつなぐインスタント送金です。

顧客はFPXに登録する必要がありますか?

いいえ。顧客は既存のオンライン銀行口座を使用します。別途FPX登録は不要です。

取引額の制限は何ですか?
  • 最小額:RM1.00
  • 最大額:1取引RM30,000
  • 1日の制限:銀行により異なる(通常RM30,000~RM100,000)
決済にはどのくらい時間がかかりますか?

FPX支払いはインスタント処理されますが、マーチャント口座への決済は通常1~3営業日かかります。決済スケジュールについては Omise ダッシュボードをご確認ください。

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

はい。FPXは元の取引から6ヶ月以内の全額及び部分的な返金に対応しています。

顧客の銀行がメンテナンス中の場合はどうなりますか?

銀行は時々定期メンテナンスがあります(通常は日曜日の朝早く)。顧客は以下のオプションがあります:

  1. 銀行が利用可能になるまで待つ
  2. 別の銀行を使用
  3. 代替支払い方法を選択
FPXは24/7利用可能ですか?

FPXゲートウェイは24/7利用可能ですが、個別の銀行はメンテナンス窓を有する場合があります(通常は日曜日の朝早く)。ほとんどの銀行は24/7利用可能です。

テスト

テストモード

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

テスト認証情報:

  • テストAPIキー(skey_test_xxx)を使用
  • 通貨:MYR(マレーシアリンギット)
  • テスト用にメールフィールドを推奨
  • 実際の銀行口座は不要

テストフロー:

  1. テストAPIキーでソースと請求を作成
  2. 顧客がテスト authorize_uri へリダイレクト
  3. テストページがFPX銀行選択及び認可をシミュレート
  4. Omise ダッシュボードアクションを使用して請求を成功/失敗にマーク
  5. Webhook及びreturn_uri処理を確認

テスト実装:

// FPXをテスト
const source = await omise.sources.create({
type: 'fpx',
amount: 10000, // RM100.00
currency: 'MYR',
email: 'customer@example.com'
});

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

console.log('テスト認可URL:', charge.authorize_uri);

テストシナリオ:

  • 支払い成功:リダイレクトフロー完了及び注文処理
  • 支払い失敗:エラー処理及びユーザーメッセージングをテスト
  • 金額制限:RM1最小及びRM30,000最大をテスト
  • 銀行選択:異なる銀行フローをテスト
  • タイムアウト:放棄された支払いシナリオをテスト(顧客が完了しない)
  • Return URI:支払い後の適切なリターンを確認
  • Webhook配信:すべてのWebhookイベントが受信されることを確認

重要な注意事項:

  • テストモードは実際のFPXまたは銀行サーバーに接続しません
  • ダッシュボードを使用して支払いステータス変更をシミュレート
  • 完全なリダイレクトフローをテスト
  • すべての請求ステータスのWebhook処理を確認
  • 成功及び失敗シナリオの両方をテスト
  • メールフィールド収集を検証

詳細なテストガイドは テストドキュメント をご参照ください。

関連リソース

次のステップ

  1. FPXソースを作成
  2. リダイレクトフローを実装
  3. Webhookハンドラーをセットアップ
  4. 支払いフローをテスト
  5. 本番運用を開始