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

保存されたカード & 繰り返しの決済

Customers APIを使用して、将来の課金のために顧客カードを安全に保存します。サブスクリプション、繰り返し請求、リピーター顧客に最適です。

概要

Omise Customers APIを使用すると、将来の使用のために決済方法を保存できます。顧客が購入ごとにカード情報を再入力する代わりに、カードを安全に保存し、1回のAPI呼び出しで課金できます。

ユースケース:

  • 月額サブスクリプション (Netflix、Spotifyモデル)
  • 繰り返し請求 (公共料金、家賃)
  • リピーター顧客のワンクリックチェックアウト
  • 自動更新
  • 分割払い

仕組み

実装ガイド

ステップ1: カードでカスタマーを作成

顧客が最初の購入を行うときに、Customerオブジェクトを作成します:

curl https://api.omise.co/customers \
-u skey_test_YOUR_SECRET_KEY: \
-d "email=john@example.com" \
-d "description=John Doe - プレミアムメンバー" \
-d "card=tokn_test_5rt6s9vah5lkvi1rh9c"

ステップ2: カスタマーIDを保存

ユーザー情報と一緒にデータベースにカスタマーIDを保存します:

-- データベーススキーマの例
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255),
omise_customer_id VARCHAR(50), -- これを保存!
subscription_status VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- カスタマーIDでユーザーを挿入
INSERT INTO users (email, name, omise_customer_id, subscription_status)
VALUES ('john@example.com', 'John Doe', 'cust_test_...', 'active');

ステップ3: 保存されたカードに課金

将来の決済には、トークンの代わりにカスタマーIDを使用して課金します:

curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=29900" \
-d "currency=thb" \
-d "customer=cust_test_5rt6s9vah5lkvi1rh9c" \
-d "description=月額サブスクリプション - 2024年2月"
トークン不要

カスタマーIDを課金する場合、トークンを作成する必要はありません。Omiseは自動的に顧客のデフォルトカードを使用します。

複数のカードの管理

追加カードの追加

// 最初にOmise.js経由で新しいカードのトークンを作成
const token = 'tokn_test_...';

// 既存のカスタマーにカードを追加
const card = await omise.customers.addCard(customerId, {
card: token
});

カスタマーのカードをリスト

curl https://api.omise.co/customers/cust_test_.../cards \
-u skey_test_YOUR_SECRET_KEY:

デフォルトカードを設定

await omise.customers.update(customerId, {
default_card: 'card_test_...'
});

カードを削除

curl https://api.omise.co/customers/cust_test_.../cards/card_test_... \
-X DELETE \
-u skey_test_YOUR_SECRET_KEY:

繰り返しの決済の実装

例: 月額サブスクリプション

// サブスクリプション請求機能 (cronで毎日実行)
async function processSubscriptionBilling() {
// 今日更新が必要なサブスクリプションを検索
const dueSubscriptions = await db.query(`
SELECT user_id, omise_customer_id, subscription_plan, amount
FROM users
WHERE subscription_status = 'active'
AND next_billing_date = CURRENT_DATE
`);

for (const sub of dueSubscriptions) {
try {
// カスタマーに課金
const charge = await omise.charges.create({
amount: sub.amount,
currency: 'thb',
customer: sub.omise_customer_id,
description: `${sub.subscription_plan} - ${new Date().toISOString().slice(0, 7)}`,
metadata: {
user_id: sub.user_id,
subscription_type: sub.subscription_plan
}
});

if (charge.status === 'successful') {
// 次回請求日を更新
await db.query(`
UPDATE users
SET next_billing_date = DATE_ADD(CURRENT_DATE, INTERVAL 1 MONTH),
last_charge_id = ?
WHERE user_id = ?
`, [charge.id, sub.user_id]);

// 領収書メールを送信
await sendReceiptEmail(sub.user_id, charge);

} else {
// 失敗した決済を処理
await handleFailedPayment(sub.user_id, charge.failure_message);
}

} catch (error) {
console.error(`ユーザー ${sub.user_id} の課金に失敗しました:`, error);
await handleBillingError(sub.user_id, error);
}
}
}

失敗した決済の再試行ロジック

async function handleFailedPayment(userId, failureMessage) {
const user = await db.getUserById(userId);
const retryCount = user.payment_retry_count || 0;

if (retryCount < 3) {
// 再試行をスケジュール
await db.query(`
UPDATE users
SET payment_retry_count = ?,
next_retry_date = DATE_ADD(CURRENT_DATE, INTERVAL ? DAY)
WHERE user_id = ?
`, [retryCount + 1, retryCount + 1, userId]);

// 顧客に通知
await sendPaymentFailedEmail(userId, {
reason: failureMessage,
retryDate: new Date(Date.now() + (retryCount + 1) * 24 * 60 * 60 * 1000)
});

} else {
// 最大再試行回数に達した - サブスクリプションを停止
await db.query(`
UPDATE users
SET subscription_status = 'suspended',
suspension_reason = 'payment_failure'
WHERE user_id = ?
`, [userId]);

await sendSubscriptionSuspendedEmail(userId);
}
}

カード情報の更新

有効期限を更新

顧客はカード番号を再入力せずにカードの有効期限を更新できます:

await omise.customers.updateCard(customerId, cardId, {
expiration_month: 12,
expiration_year: 2028,
name: 'John Doe',
postal_code: '10110'
});

カードを完全に交換

セキュリティ上の理由から、カード番号は更新できません。カードを交換するには:

  1. Omise.jsで新しいトークンを作成
  2. カスタマーに新しいカードを追加
  3. デフォルトカードとして設定
  4. 古いカードを削除
// 1. Omise.js経由でトークンを作成 (クライアント側)
const newToken = 'tokn_test_...';

// 2. 新しいカードを追加
const newCard = await omise.customers.addCard(customerId, {
card: newToken
});

// 3. デフォルトとして設定
await omise.customers.update(customerId, {
default_card: newCard.id
});

// 4. 古いカードを削除
await omise.customers.destroyCard(customerId, oldCardId);

セキュリティ & コンプライアンス

PCI準拠

OmiseはPCI準拠のボールトでカードストレージを処理します。保存するのは以下のみです:

  • カスタマーID (保存しても安全)
  • カードメタデータ (下4桁、ブランド、有効期限 - 表示しても安全)

決して保存しないでください:

  • 完全なカード番号
  • CVV/セキュリティコード
  • 生のカードデータ

顧客データ保護

// ✅ 良い例: IDとメタデータのみを保存
const user = {
id: 12345,
email: 'john@example.com',
omise_customer_id: 'cust_test_...',
card_last_digits: '4242', // 表示しても安全
card_brand: 'Visa', // 表示しても安全
card_expiry: '12/2027' // 表示しても安全
};

// ❌ 悪い例: これらを保存しないでください
const badExample = {
card_number: '4242424242424242', // これを保存しないでください!
cvv: '123' // これを保存しないでください!
};

顧客の同意

カードを保存する前に常に明示的な同意を得てください:

<form id="payment-form">
<!-- カード入力フィールド -->

<label>
<input type="checkbox" id="save-card" name="save_card" />
将来の購入のためにこのカードを保存する
</label>

<button type="submit">決済を完了</button>
</form>

<script>
document.getElementById('payment-form').addEventListener('submit', function(e) {
e.preventDefault();
const saveCard = document.getElementById('save-card').checked;

// カードを保存する場合、カスタマーを作成
// そうでない場合、トークンで課金を作成するだけ
});
</script>

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

問題: カスタマーが既に存在します

エラー: customer_already_exists

解決策: 既存のカスタマーを取得または更新します:

try {
const customer = await omise.customers.create({
email: email,
card: token
});
} catch (error) {
if (error.code === 'customer_already_exists') {
// 代わりに取得して更新
const existingCustomer = await omise.customers.list({
limit: 1,
// カスタマーIDを見つけるためにデータベースを使用
});
}
}

問題: デフォルトカードが見つかりません

エラー: default_card_not_found

原因: カスタマーにカードがないか、デフォルトカードが削除されました

解決策:

// カスタマーにカードがあるか確認
const customer = await omise.customers.retrieve(customerId);

if (customer.cards.total === 0) {
// ユーザーにカードを追加するよう促す
throw new Error('決済方法を追加してください');
}

if (!customer.default_card) {
// 最初のカードをデフォルトに設定
await omise.customers.update(customerId, {
default_card: customer.cards.data[0].id
});
}

ベストプラクティス

  1. 決済の失敗を適切に処理する

    • 再試行ロジックを実装
    • 顧客に速やかに通知
    • 簡単なカード更新フローを提供
  2. カードの有効期限を監視する

    • 有効期限の30日前にリマインダーを送信
    • ワンクリックでカードを更新
    • カード更新が失敗した場合はサブスクリプションを一時停止
  3. サブスクリプション管理を提供する

    • 顧客が保存されたカードを表示できるようにする
    • 簡単なカードの削除
    • サブスクリプションのキャンセルオプション
    • 請求履歴へのアクセス
  4. メタデータを効果的に使用する

    await omise.customers.create({
    email: email,
    card: token,
    metadata: {
    user_id: '12345',
    signup_source: 'mobile_app',
    subscription_tier: 'premium',
    referral_code: 'FRIEND2024'
    }
    });
  5. Webhookを実装する charge.completecharge.failedイベントをリッスン:

    // Webhookハンドラーで
    if (event.key === 'charge.complete') {
    await updateSubscriptionStatus(charge.customer, 'active');
    } else if (event.key === 'charge.failed') {
    await handleFailedPayment(charge.customer, charge.failure_message);
    }

FAQ

顧客は複数のカードを持つことができますか?

はい! 顧客は複数のカードを添付できます。自動的に課金するカードを指定するにはdefault_cardフィールドを使用します。顧客はいつでもデフォルトカードを切り替えることができます。

デフォルトではなく特定のカードに課金するにはどうすればよいですか?

課金を作成するときにcardパラメータを指定します:

await omise.charges.create({
amount: 10000,
currency: 'thb',
customer: customerId,
card: 'card_test_specific_card_id'
});
保存されたカードで3D Secureを使用できますか?

最初の課金では通常3D Secure認証が必要です。その後の課金では、発行銀行によっては認証が不要な場合があります。これは「摩擦なしフロー」と呼ばれ、リスク評価に基づいて銀行によって自動的に処理されます。

関連リソース

次のステップ

  1. 最初のカスタマーを作成
  2. 繰り返し請求を実装
  3. 失敗処理を設定
  4. Webhookを設定
  5. 徹底的にテスト
  6. 本番環境に移行