FPX(ファイナンシャルプロセスエクスチェンジ)
FPXはマレーシアの国家的銀行間決済ゲートウェイであり、19以上のマレーシア銀行からのインスタント送金を受け付けます。全国に広く採用されている決済ゲートウェイです。
概要
FPX(ファイナンシャルプロセスエクスチェンジ)はPayNetが運営するマレーシアの国家的オンライン銀行決済ゲートウェイです。銀行口座から直接のオンライン銀行経由での支払いが可能であり、マレーシアの電子商取引において最も人気のある支払い方法の一つです。
主な機能:
- ✅ 19行以上の銀行 - マレーシア主要銀行全て対応
- ✅ 国家システム - マレーシアの公式銀行間ゲートウェイ
- ✅ 即座の確認 - リアルタイム決済処理
- ✅ 高額制限 - 1取引につきRM30,000まで
- ✅ 登録不要 - 顧客は既存のオンライン銀行を使用
- ✅ 信頼性 - PayNet(マレーシア中央銀行)が運営
対応地域
| 地域 | 通貨 | 最小金額 | 最大金額 | APIバージョン |
|---|---|---|---|---|
| Malaysia | MYR | RM1.00 | RM30,000.00 | 2017-11-02 |
対応銀行
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)
仕組み
顧客体験:
- 顧客がチェックアウトで「FPX」を選択
- FPXゲートウェイへリダイレクト
- リストから銀行を選択
- 銀行のオンライン銀行へリダイレクト
- オンライン銀行の認証情報でログイン
- 支払いを確認&認可
- 加盟店サイトに戻す
標準的な完了時間: 2~5分
支払いフローの例
銀行選択及びログイン:

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

取引の認可及び完了:
- ❽ 詳細を確認 - 加盟店名、支払い金額、参照番号を表示
- ❾ 口座を確認 - 支払い元口座を確認
- ❿ 支払いを認可 - 承認ボタンをクリックして送金を承認
- ⓫ 処理中 - 銀行がFPX経由で銀行間送金を処理
- ⓬ 成功確認 - 支払い完了メッセージと取引IDを表示
- ⓭ 加盟店に戻す - 加盟店の確認ページへ戻す
実装
ステップ1:FPXソースを作成
- cURL
- Node.js
- PHP
- Python
- Ruby
- Go
- Java
- C#
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"
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
const source = await omise.sources.create({
type: 'fpx',
amount: 10000, // MYR 100.00
currency: 'MYR',
email: 'customer@example.com' // オプションだが推奨
});
<?php
$source = OmiseSource::create(array(
'type' => 'fpx',
'amount' => 10000,
'currency' => 'MYR',
'email' => 'customer@example.com'
));
?>
import omise
omise.api_secret = 'skey_test_YOUR_SECRET_KEY'
source = omise.Source.create(
type='fpx',
amount=10000,
currency='MYR',
email='customer@example.com'
)
require 'omise'
Omise.api_key = 'skey_test_YOUR_SECRET_KEY'
source = Omise::Source.create({
type: 'fpx',
amount: 10000,
currency: 'MYR',
email: 'customer@example.com'
})
source, err := client.Sources().Create(&operations.CreateSource{
Type: "fpx",
Amount: 10000,
Currency: "MYR",
Email: "customer@example.com",
})
Source source = client.sources().create(new Source.CreateParams()
.type("fpx")
.amount(10000L)
.currency("MYR")
.email("customer@example.com"));
var source = await client.Sources.Create(new CreateSourceRequest
{
Type = "fpx",
Amount = 10000,
Currency = "MYR",
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ヶ月以内の全額及び部分的な返金に対応しています。
顧客の銀行がメンテナンス中の場合はどうなりますか?
銀行は時々定期メンテナンスがあります(通常は日曜日の朝早く)。顧客は以下のオプションがあります:
- 銀行が利用可能になるまで待つ
- 別の銀行を使用
- 代替支払い方法を選択
FPXは24/7利用可能ですか?
FPXゲートウェイは24/7利用可能ですが、個別の銀行はメンテナンス窓を有する場合があります(通常は日曜日の朝早く)。ほとんどの銀行は24/7利用可能です。
テスト
テストモード
FPXはテストAPIキーを使用してテストできます。テストモードでは:
テスト認証情報:
- テストAPIキー(skey_test_xxx)を使用
- 通貨:MYR(マレーシアリンギット)
- テスト用にメールフィールドを推奨
- 実際の銀行口座は不要
テストフロー:
- テストAPIキーでソースと請求を作成
- 顧客がテスト authorize_uri へリダイレクト
- テストページがFPX銀行選択及び認可をシミュレート
- Omise ダッシュボードアクションを使用して請求を成功/失敗にマーク
- 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処理を確認
- 成功及び失敗シナリオの両方をテスト
- メールフィールド収集を検証
詳細なテストガイドは テストドキュメント をご参照ください。