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

事前認可(認可と実行)

Omiseの2段階決済フローを使用して、顧客の支払いを事前に認可し、後で資金を実行することで、柔軟な支払いタイミングと注文履行を実現します。

概要

事前認可(「認可と実行」とも呼ばれます)は、支払いをまず認可して資金を確認して保留し、その後、決済準備ができたときに実際の請求を実行する2段階のプロセスです。これは、受注生産製品、ホテル予約、レンタルサービス、および購入後に履行が行われるあらゆるシナリオに有用です。

主要な概念:

  • 認可 - カードを確認して資金を保留(顧客はまだ請求されない)
  • 実行 - 保留されている資金を回収(顧客に請求)
  • 無効化 - 実行前に認可をキャンセル(資金をリリース)
  • 自動実行 - デフォルトモード、認可と実行を直ちに実行
  • 手動実行 - 2段階プロセス、明示的な実行が必要

事前認可の使用時期

適切なユースケース

受注生産製品

  • 注文時に認可
  • 品目出荷時に実行
  • 品目が利用不可の場合は無効化

ホテル予約

  • 予約時に認可
  • チェックアウト時に実行
  • 付加サービスの金額を調整

レンタルサービス

  • セキュリティデポジットを認可
  • 損傷または手数料を実行
  • 問題がない場合はリリース

カスタムサービス

  • 推定費用を認可
  • 実際の最終費用を実行
  • スコープの変更に対応

在庫確認

  • 直ちに認可
  • 在庫の可用性を確認
  • 在庫がある場合は実行、ない場合は無効化

非推奨

デジタル商品 - 直ちに配信、自動実行を使用 ❌ サブスクリプション - 通常の請求を使用(定期認可はできません) ❌ 低額商品 - 追加の複雑性が価値がない ❌ 直ちの履行 - 自動実行を使用

仕組み

実装

ステップ1:認可を作成

curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=100000" \
-d "currency=THB" \
-d "card=tokn_test_..." \
-d "capture=false"

レスポンス:

{
"object": "charge",
"id": "chrg_test_5rt6s9vah5lkvi1rh9c",
"amount": 100000,
"currency": "THB",
"status": "pending",
"authorized": true,
"paid": false,
"capture": false,
"capturable": true,
"expires_at": "2025-02-13T00:00:00Z"
}

ステップ2:資金を実行

// 注文が履行された後
const capture = await omise.charges.capture('chrg_test_5rt6s9vah5lkvi1rh9c');

console.log('実行ステータス:', capture.status); // 'successful'
console.log('支払済み:', capture.paid); // true

異なる金額で実行(サポートされている場合):

// 認可額より少ない金額を実行(例:最終請求が低い)
const capture = await omise.charges.capture('chrg_test_...', {
capture_amount: 80000 // ฿1,000の認可額から฿800のみを実行
});

ステップ3:または認可を逆転/無効化

// 注文がキャンセルされた場合は認可をキャンセル
const reversed = await omise.charges.reverse('chrg_test_5rt6s9vah5lkvi1rh9c');

console.log('ステータス:', reversed.status); // 'reversed' or 'expired'
console.log('逆転時刻:', reversed.reversed_at);

完全な例

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

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

// ステップ1:顧客が注文を発注
app.post('/checkout', async (req, res) => {
try {
const { tokenId, amount, orderId } = req.body;

// 支払いを認可(まだ実行しない)
const charge = await omise.charges.create({
amount: amount,
currency: 'THB',
card: tokenId,
capture: false,
description: `Pre-auth for Order #${orderId}`,
metadata: {
order_id: orderId,
authorized_at: new Date().toISOString()
}
});

if (charge.status === 'pending' && charge.authorized) {
// データベースに保存
await db.orders.create({
order_id: orderId,
charge_id: charge.id,
status: 'authorized',
amount: amount,
expires_at: charge.expires_at
});

res.json({
success: true,
message: '支払いが認可されました',
order_id: orderId
});
} else {
res.status(400).json({
error: '認可に失敗しました',
reason: charge.failure_message
});
}

} catch (error) {
res.status(500).json({ error: error.message });
}
});

// ステップ2:注文を履行して実行
app.post('/orders/:orderId/ship', async (req, res) => {
try {
const { orderId } = req.params;
const { trackingNumber } = req.body;

// 注文を取得
const order = await db.orders.findOne({ order_id: orderId });

if (order.status !== 'authorized') {
return res.status(400).json({
error: '注文が認可された状態ではありません'
});
}

// 支払いを実行
const capture = await omise.charges.capture(order.charge_id);

if (capture.status === 'successful') {
// 注文を更新
await db.orders.update({
order_id: orderId,
status: 'captured',
shipped: true,
tracking_number: trackingNumber,
captured_at: new Date()
});

res.json({
success: true,
message: '支払いが実行され、注文が発送されました'
});
} else {
res.status(400).json({
error: '実行に失敗しました'
});
}

} catch (error) {
res.status(500).json({ error: error.message });
}
});

// ステップ3:注文をキャンセルして資金をリリース
app.post('/orders/:orderId/cancel', async (req, res) => {
try {
const { orderId } = req.params;
const { reason } = req.body;

const order = await db.orders.findOne({ order_id: orderId });

if (order.status !== 'authorized') {
return res.status(400).json({
error: '注文をキャンセルできません'
});
}

// 認可を逆転
const reversed = await omise.charges.reverse(order.charge_id);

// 注文を更新
await db.orders.update({
order_id: orderId,
status: 'canceled',
cancellation_reason: reason,
canceled_at: new Date()
});

res.json({
success: true,
message: '認可が逆転され、資金がリリースされました'
});

} catch (error) {
res.status(500).json({ error: error.message });
}
});

// バックグラウンドジョブ:有効期限が切れた認可を自動逆転
cron.schedule('0 * * * *', async () => {
const expiredOrders = await db.orders.find({
status: 'authorized',
expires_at: { $lt: new Date() }
});

for (const order of expiredOrders) {
try {
await omise.charges.reverse(order.charge_id);
await db.orders.update({
order_id: order.order_id,
status: 'expired'
});
console.log(`逆転された有効期限切れ注文: ${order.order_id}`);
} catch (error) {
console.error(`${order.order_id}の逆転に失敗:`, error);
}
}
});

app.listen(3000);

認可の有効期限

認可は実行されない場合は有効期限切れになります:

カードネットワーク有効期限
Visa7日
Mastercard7日
Amex7日
JCB7日
自動有効期限切れ

実行されていない認可は、カードネットワークの時間制限後に自動的に有効期限切れになります。有効期限日を監視し、有効期限切れ前に実行してください。

// 認可がまもなく有効期限切れになるかを確認
function isExpiringSoon(charge) {
const expiryDate = new Date(charge.expires_at);
const now = new Date();
const daysUntilExpiry = (expiryDate - now) / (1000 * 60 * 60 * 24);

return daysUntilExpiry < 2; // 2日未満
}

// まもなく有効期限切れになる場合はアラート
if (isExpiringExpiringSoon(charge)) {
await sendExpiryAlert(order.id, charge.expires_at);
}

実行金額の調整

一部のシナリオでは異なる金額の実行を許可します:

実行を減らす(下方調整)

// ฿1,000を認可したが、最終費用は฿800
const capture = await omise.charges.capture('chrg_test_...', {
capture_amount: 80000 // ฿1,000ではなく฿800を実行
});

ユースケース:

  • 最終請求が見積より低い
  • 顧客が出荷前にいくつかの商品を返品
  • プロモーション割引が適用
  • 損傷または欠陥が発見

より多く実行(非サポート)

より多く実行できません

認可額より多い金額を実行することはできません。最終費用がより高い場合は、以下のいずれかを実行する必要があります:

  1. 差額に対して新しいチャージを作成、または
  2. 元の認可を無効化して、正しい金額で新しい認可を作成

ベストプラクティス

1. 認可の有効期限を監視

// 毎日のcronジョブ
cron.schedule('0 8 * * *', async () => {
// 2日以内に有効期限切れになる認可を検索
const expiringCharges = await db.orders.find({
status: 'authorized',
expires_at: {
$gte: new Date(),
$lt: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000)
}
});

for (const order of expiringCharges) {
// 履行チームに通知
await sendSlackNotification({
message: `⚠️ 注文 ${order.order_id} の認可が2日以内に有効期限切れになります!`,
urgency: 'high'
});

// 遅延について顧客にメール
await sendEmailToCustomer(order.customer_email, {
template: 'order_delay',
order_id: order.order_id
});
}
});

2. 明確なコミュニケーション

// 認可時
await sendEmail({
to: customer.email,
subject: '注文確定 - 支払いが認可されました',
html: `
<h2>注文 #${orderId} が確定しました</h2>
<p>あなたの支払い ฿${amount / 100} を認可しました。</p>
<p><strong>重要:</strong> あなたのカードは注文が発送されたときにのみ請求されます。</p>
<ul>
<li>推定発送日: ${estimatedShipDate}</li>
<li>認可の有効期限: ${expiresAt}</li>
</ul>
<p>あなたのカードが請求されたときに確認メールが届きます。</p>
`
});

3. 発送時の自動実行

// 配送システムと統合
app.post('/webhooks/shipstation', async (req, res) => {
const shipment = req.body;

if (shipment.event === 'shipment_created') {
const order = await db.orders.findOne({
tracking_number: shipment.tracking_number
});

if (order && order.status === 'authorized') {
// 発送時に自動実行
await omise.charges.capture(order.charge_id);

await db.orders.update({
order_id: order.order_id,
status: 'captured',
captured_at: new Date()
});

// 顧客に通知
await sendShippedEmail(order.customer_email, {
tracking: shipment.tracking_number,
amount: order.amount
});
}
}

res.sendStatus(200);
});

4. 部分履行を処理

async function handlePartialShipment(orderId, shippedItems) {
const order = await db.orders.findOne({ order_id: orderId });

// 出荷された商品の金額を計算
const shippedAmount = shippedItems.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);

// 出荷された商品のみを実行
await omise.charges.capture(order.charge_id, {
capture_amount: shippedAmount
});

// 残りの商品に対して新しい認可を作成
const remainingAmount = order.amount - shippedAmount;
if (remainingAmount > 0) {
const newCharge = await omise.charges.create({
amount: remainingAmount,
currency: 'THB',
customer: order.customer_id,
capture: false
});

await db.orders.create({
parent_order_id: orderId,
charge_id: newCharge.id,
status: 'authorized',
amount: remainingAmount
});
}
}

5. 在庫確認の猶予期間

async function processOrder(orderId) {
const order = await db.orders.findOne({ order_id: orderId });

// 在庫を確認
const inStock = await checkInventory(order.items);

if (inStock) {
// 在庫がある場合は直ちに実行
await omise.charges.capture(order.charge_id);
await fulfillOrder(orderId);
} else {
// 再入荷に24時間の猶予を与える
setTimeout(async () => {
const stillInStock = await checkInventory(order.items);

if (stillInStock) {
await omise.charges.capture(order.charge_id);
await fulfillOrder(orderId);
} else {
// キャンセルして払い戻し
await omise.charges.reverse(order.charge_id);
await notifyCustomer(order.customer_email, 'out_of_stock');
}
}, 24 * 60 * 60 * 1000); // 24時間
}
}

テスト

テストモード事前認可

テストカードを使用して事前認可実装を検証します:

const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});

// 認可をテスト
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: 'tokn_test_5rt6s9vah5lkvi1rh9c', // テストカードトークン
capture: false,
metadata: {
test_scenario: 'pre_authorization'
}
});

console.log('ステータス:', charge.status); // 'pending'
console.log('認可:', charge.authorized); // true
console.log('実行可能:', charge.capturable); // true

テストシナリオ

1. 成功した認可と実行

// ステップ1:認可
const auth = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: 'tokn_test_4242', // 成功カード
capture: false
});

console.log('認可が成功:', auth.status === 'pending');

// ステップ2:実行
const capture = await omise.charges.capture(auth.id);
console.log('実行が成功:', capture.status === 'successful');
console.log('顧客に請求:', capture.paid); // true

2. 認可と無効化

// 認可
const auth = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: 'tokn_test_4242',
capture: false
});

// 認可をキャンセル
const reversed = await omise.charges.reverse(auth.id);
console.log('無効化:', reversed.status === 'reversed');

3. 部分実行

// ฿1,000を認可
const auth = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: 'tokn_test_4242',
capture: false
});

// ฿800のみを実行
const partial = await omise.charges.capture(auth.id, {
capture_amount: 80000
});
console.log('実行金額:', partial.amount); // 80000

4. 有効期限が切れた認可

// 認可を作成
const auth = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: 'tokn_test_4242',
capture: false
});

// 有効期限日を確認
console.log('有効期限:', auth.expires_at);

// テストモードでは、有効期限切れマークは7日後に行われます

事前認可用テストカード

カード番号認可実行結果
4242 4242 4242 4242成功成功フルフロー動作
4000 0000 0000 0002低下N/A認可が低下
4242 4242 4242 4242成功(実行なし)7日後に自動有効期限切れ

ダッシュボード経由でのテスト

  1. テストモードで認可を作成
  2. ダッシュボード → チャージに移動
  3. 保留中のチャージを検索(ステータス: pending
  4. アクション ドロップダウンをクリック:
    • 「実行」 - 成功した実行をシミュレート
    • 「逆転」 - 無効化/キャンセルをシミュレート

Webhookをテスト

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

switch (event.key) {
case 'charge.create':
// 認可が作成
console.log('認可作成:', event.data.capture); // false
break;

case 'charge.capture':
// 認可が実行
console.log('実行:', event.data.paid); // true
break;

case 'charge.reverse':
// 認可が無効化
console.log('逆転:', event.data.status); // 'reversed'
break;

case 'charge.expire':
// 認可が有効期限切れ(実行されない)
console.log('有効期限切れ:', event.data.status); // 'expired'
break;
}

res.sendStatus(200);
});

エラーケースをテスト

// テスト:認可額より多く実行できない
try {
const auth = await omise.charges.create({
amount: 100000,
capture: false
});

// これは失敗する必要があります
await omise.charges.capture(auth.id, {
capture_amount: 150000 // 認可より多い
});
} catch (error) {
console.log('予期されたエラー:', error.message);
}

// テスト:既に実行されたチャージを実行できない
try {
const auth = await omise.charges.create({
amount: 100000,
capture: false
});

await omise.charges.capture(auth.id);
await omise.charges.capture(auth.id); // 2番目の実行は失敗する必要があります
} catch (error) {
console.log('予期されたエラー:', error.message);
}

// テスト:逆転された認可を実行できない
try {
const auth = await omise.charges.create({
amount: 100000,
capture: false
});

await omise.charges.reverse(auth.id);
await omise.charges.capture(auth.id); // 失敗する必要があります
} catch (error) {
console.log('予期されたエラー:', error.message);
}

FAQ

事前認可と通常のチャージの違いは何ですか?
  • 通常のチャージ(自動実行): 1つのステップで直ちに認可と実行します。顧客は直ちに請求されます。
  • 事前認可(手動実行): まず認可(資金を保留)し、後で実行(顧客に請求)します。2つの別々のステップです。
認可を保留できる期間はどのくらいですか?

認可保留は7日間続きます。その後、それらは自動的に有効期限切れになり、資金は顧客にリリースされます。

認可額より多く実行できますか?

いいえ。認可額までのみ実行できます。より多く請求する場合は、以下のいずれかを実行する必要があります:

  1. 差額に対して別の料金を作成、または
  2. 元の認可を無効化して、正しい金額で新しい認可を作成
有効期限切れ前に実行しない場合はどうなりますか?

認可は自動的に有効期限切れになり、逆転されます。資金は顧客にリリースされます。有効期限後は実行できません。

認可を部分実行できますか?

はい。最終費用が低い場合は、認可額より少ない金額を実行できます。残りの金額は自動的にリリースされます。

顧客は保留中のチャージを見ますか?

はい。ほとんどの銀行は、オンラインバンキングで「保留中」または「認可済み」のトランザクションを表示します。顧客は実行されるまで請求されません。

事前認可で3D Secureを使用できますか?

はい。3D Secureは事前認可で動作します。顧客は認可中に認証してから、後で実行します。

関連リソース

次のステップ

  1. 認可を作成
  2. 実行ロジックを実装
  3. 有効期限監視をセットアップ
  4. フローをテスト
  5. 履行と統合
  6. 本番へ移行