integrate-payouts
Payramのペイアウト機能を統合し、受取人への暗号資産支払いを送付するための完全ガイドです。このスキルを使用することで、APIを通じてペイアウト機能を実装し、暗号資産による支払い処理を自動化できます。EC事業者や開発者は、顧客への返金処理やパートナーへの報酬支払いなど、様々なペイアウトシーンに対応可能になります。詳細な統合手順、API仕様、実装例を含めており、スムーズに導入できます。
description の原文を見る
Complete guide to integrating Payram payout functionality for sending cryptocurrency payments to recipients
SKILL.md 本文
Payramペイアウトの統合
概要
このスキルは、Payramペイアウト機能をアプリケーションに統合するための包括的な手順を提供します。ペイアウトを使用すると、暗号資産の支払いをプログラム的に受取人に送信できます。公式SDKを使用してペイアウトを作成し、ステータスを確認し、ペイアウトのライフサイクル全体を管理する方法について学習します。
このスキルを使用する場合
このスキルは、以下のことが必要な場合に使用します:
- 顧客、ベンダー、またはパートナーに暗号資産の支払いを送信する
- 自動化された分配システムを実装する
- 出金/キャッシュアウト機能を作成する
- 暗号資産でのリファンドまたはリワードを処理する
- ペイアウトのステータスとトランザクション詳細を追跡する
前提条件
開始する前に、以下のことを確認してください:
setup-payramスキルが完了している(APIクレデンシャルで環境が設定されている)docs/payram-docs-live/api-integration/payouts-apis/create-payouts.mdを確認済み- Payram SDKがインストール済み:
npm install payram - マーチャントアカウントにペイアウト用の十分な残高がある
手順
Part 1: ペイアウトフローの理解
1.1 ペイアウトのライフサイクル
ペイアウトは以下の状態を経過します:
- pending-otp-verification - OTP確認待機中(必要な場合)
- pending-approval - 手動承認待機中(閾値が必要な場合)
- pending - 処理キュー待機中
- initiated - 処理が開始された
- sent - トランザクションがブロックチェーンにブロードキャストされた
- processed - ブロックチェーンで確認された
- failed - トランザクション失敗(残高不足、無効なアドレス)
- rejected - 管理者により手動で却下された
- cancelled - 処理前にキャンセルされた
アクション: これらの状態を通じた進行を追跡するためにペイアウトのステータスを監視します。
1.2 必要な情報
ペイアウトを作成するには、以下が必要です:
- email: ペイアウトに関連するマーチャントメールアドレス
- blockchainCode: ブロックチェーンネットワーク(例: 'ETH', 'BTC', 'MATIC')
- currencyCode: 送信する通貨(例: 'USDC', 'USDT', 'BTC')
- amount: 金額を文字列で指定(例: '125.50')
- toAddress: 受取人のウォレットアドレス
- customerID: 内部参照ID
- mobileNumber: 受取人の電話番号(形式: +15555555555)
- residentialAddress: 受取人の住所(KYC/コンプライアンス)
Part 2: SDK統合(Node.js/TypeScript)
2.1 SDKのインストール
npm install payram
# または
yarn add payram
# または
pnpm add payram
2.2 SDKでペイアウトを作成
ファイル: src/payram/payouts/createPayout.ts
import { Payram, CreatePayoutRequest, MerchantPayout, isPayramSDKError } from 'payram';
const payram = new Payram({
apiKey: process.env.PAYRAM_API_KEY!,
baseUrl: process.env.PAYRAM_BASE_URL!,
config: {
timeoutMs: 10_000, // オプション: リクエストタイムアウト
maxRetries: 2, // オプション: 失敗したリクエストの再試行
retryPolicy: 'safe', // オプション: 安全なメソッドのみ再試行
allowInsecureHttp: false, // オプション: HTTPSを必須とする
},
});
export async function createPayout(payload: CreatePayoutRequest): Promise<MerchantPayout> {
try {
const payout = await payram.payouts.createPayout(payload);
console.log('ペイアウト作成:', {
id: payout.id,
status: payout.status,
blockchain: payout.blockchainCode,
currency: payout.currencyCode,
amount: payout.amount,
});
return payout;
} catch (error) {
if (isPayramSDKError(error)) {
console.error('Payramエラー:', {
status: error.status,
requestId: error.requestId,
isRetryable: error.isRetryable,
message: error.message,
});
}
throw error;
}
}
// 実行例
await createPayout({
email: 'merchant@example.com',
blockchainCode: 'ETH',
currencyCode: 'USDC',
amount: '125.50',
toAddress: '0xfeedfacecafebeefdeadbeefdeadbeefdeadbeef',
customerID: 'cust_123',
mobileNumber: '+15555555555',
residentialAddress: '1 Market St, San Francisco, CA 94105',
});
フィールド詳細:
- email: Payramアカウント内の検証済みマーチャントメールと一致する必要があります
- blockchainCode:
docs/payram-docs-live/support/supported-networks-and-coins.mdでサポートされているネットワークを確認 - currencyCode: トークン/コインコード(選択したブロックチェーンでサポートされている必要があります)
- amount: 金額の文字列表現(数値ではなく) - 例: '125.50'、125.50ではなく
- toAddress: 受取人のウォレットアドレス(ブロックチェーン互換性が検証されます)
- customerID: 内部参照(追跡/照合用)
- mobileNumber: E.164形式が必須(+国番号 + 電話番号)
- residentialAddress: コンプライアンス目的の完全な住所
2.3 ペイアウトのステータスを確認
ファイル: src/payram/payouts/payoutStatus.ts
import { Payram, MerchantPayout, isPayramSDKError } from 'payram';
const payram = new Payram({
apiKey: process.env.PAYRAM_API_KEY!,
baseUrl: process.env.PAYRAM_BASE_URL!,
});
export async function getPayoutStatus(payoutId: number): Promise<MerchantPayout> {
if (!payoutId) {
throw new Error('createPayoutレスポンスから取得した数値のpayoutIdが必要です。');
}
try {
const payout = await payram.payouts.getPayoutById(payoutId);
console.log('ペイアウトステータス:', {
id: payout.id,
status: payout.status,
transactionHash: payout.transactionHash,
blockchain: payout.blockchainCode,
currency: payout.currencyCode,
amount: payout.amount,
createdAt: payout.createdAt,
updatedAt: payout.updatedAt,
});
return payout;
} catch (error) {
if (isPayramSDKError(error)) {
console.error('Payramエラー:', {
status: error.status,
requestId: error.requestId,
isRetryable: error.isRetryable,
});
}
throw error;
}
}
// 使用例
const payout = await getPayoutStatus(120);
レスポンスフィールド:
- id: ペイアウトの一意の識別子
- status: 現在のペイアウト状態(上記のライフサイクルを参照)
- transactionHash: ブロックチェーンのトランザクションハッシュ(ステータスが'sent'または'processed'の場合に利用可能)
- blockchainCode: ペイアウトに使用されたネットワーク
- currencyCode: 送信された通貨
- amount: 送信された金額(文字列)
- toAddress: 受取人アドレス
- customerID: 参照ID
- createdAt: ペイアウト作成のタイムスタンプ
- updatedAt: 最後のステータス更新のタイムスタンプ
Part 3: ステータス監視パターン
3.1 ポーリングパターン
ウェブフックが利用できない場合にポーリングを使用します:
async function pollPayoutStatus(payoutId: number, maxAttempts = 20): Promise<MerchantPayout> {
const finalStates = ['processed', 'failed', 'rejected', 'cancelled'];
for (let i = 0; i < maxAttempts; i++) {
const payout = await getPayoutStatus(payoutId);
// ペイアウトが最終状態に達したかを確認
if (finalStates.includes(payout.status)) {
return payout;
}
// 次のチェック前に待機(指数バックオフ)
const delay = Math.min(5000 * Math.pow(1.5, i), 60000); // 60秒でキャップ
console.log(`ペイアウト${payoutId}のステータス: ${payout.status}、${delay}ms後に再度チェック`);
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error(`ペイアウト${payoutId}は${maxAttempts}回の試行後に最終状態に達しませんでした`);
}
// 使用例
try {
const payout = await createPayout({ ... });
const finalPayout = await pollPayoutStatus(payout.id);
if (finalPayout.status === 'processed') {
console.log('ペイアウト成功!トランザクション:', finalPayout.transactionHash);
} else {
console.error('ペイアウト失敗。ステータス:', finalPayout.status);
}
} catch (error) {
console.error('ペイアウト処理エラー:', error);
}
3.2 ウェブフックパターン(推奨)
本番環境ではリアルタイムアップデートにウェブフックを使用します:
// 完全なウェブフック設定については'handle-webhooks'スキルを参照
// Expressウェブフックハンドラの例
router.post('/webhooks/payram/payout-status', async (req, res) => {
const event = req.body;
if (event.eventType === 'payout.status.updated') {
const payoutId = event.data.id;
const status = event.data.status;
// データベースを更新
await db.payouts.update({
where: { payramPayoutId: payoutId },
data: {
status,
transactionHash: event.data.transactionHash,
updatedAt: new Date(),
},
});
// ペイアウト完了時に顧客に通知
if (status === 'processed') {
await sendNotification(event.data.customerID, {
message: 'あなたのペイアウトが処理されました!',
transactionHash: event.data.transactionHash,
});
}
}
res.status(200).json({ received: true });
});
Part 4: フレームワーク統合例
4.1 Express.jsルート
ファイル: src/routes/payram/payouts.ts
import { Router } from 'express';
import { Payram, CreatePayoutRequest, isPayramSDKError } from 'payram';
const router = Router();
const payram = new Payram({
apiKey: process.env.PAYRAM_API_KEY!,
baseUrl: process.env.PAYRAM_BASE_URL!,
});
router.post('/api/payouts/payram', async (req, res) => {
const payload = req.body as Partial<CreatePayoutRequest>;
// 必須フィールドを検証
const requiredFields = [
'email',
'blockchainCode',
'currencyCode',
'amount',
'toAddress',
'customerID',
'mobileNumber',
'residentialAddress',
];
const missing = requiredFields.filter((field) => !payload[field as keyof CreatePayoutRequest]);
if (missing.length > 0) {
return res.status(400).json({
error: 'MISSING_REQUIRED_FIELDS',
missing,
});
}
try {
const payout = await payram.payouts.createPayout(payload as CreatePayoutRequest);
return res.status(201).json({
id: payout.id,
status: payout.status,
blockchain: payout.blockchainCode,
currency: payout.currencyCode,
amount: payout.amount,
});
} catch (error) {
if (isPayramSDKError(error)) {
console.error('Payramエラー:', {
status: error.status,
requestId: error.requestId,
retryable: error.isRetryable,
});
}
return res.status(502).json({ error: 'PAYRAM_CREATE_PAYOUT_FAILED' });
}
});
router.get('/api/payouts/payram/:payoutId', async (req, res) => {
const payoutId = parseInt(req.params.payoutId, 10);
if (isNaN(payoutId)) {
return res.status(400).json({ error: 'INVALID_PAYOUT_ID' });
}
try {
const payout = await payram.payouts.getPayoutById(payoutId);
return res.json(payout);
} catch (error) {
if (isPayramSDKError(error)) {
console.error('Payramエラー:', error);
}
return res.status(502).json({ error: 'PAYRAM_STATUS_CHECK_FAILED' });
}
});
export default router;
4.2 Next.js App Router
ファイル: app/api/payram/create-payout/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Payram, CreatePayoutRequest, isPayramSDKError } from 'payram';
const payram = new Payram({
apiKey: process.env.PAYRAM_API_KEY!,
baseUrl: process.env.PAYRAM_BASE_URL!,
});
export async function POST(request: NextRequest) {
const payload = (await request.json()) as Partial<CreatePayoutRequest>;
// 必須フィールドを検証
const requiredFields = [
'email',
'blockchainCode',
'currencyCode',
'amount',
'toAddress',
'customerID',
'mobileNumber',
'residentialAddress',
];
const missing = requiredFields.filter((field) => !payload[field as keyof CreatePayoutRequest]);
if (missing.length > 0) {
return NextResponse.json(
{
error: 'MISSING_REQUIRED_FIELDS',
missing,
},
{ status: 400 },
);
}
try {
const payout = await payram.payouts.createPayout(payload as CreatePayoutRequest);
return NextResponse.json({
id: payout.id,
status: payout.status,
blockchain: payout.blockchainCode,
currency: payout.currencyCode,
amount: payout.amount,
});
} catch (error) {
if (isPayramSDKError(error)) {
console.error('Payramエラー:', {
status: error.status,
requestId: error.requestId,
});
}
return NextResponse.json({ error: 'PAYRAM_CREATE_PAYOUT_FAILED' }, { status: 502 });
}
}
ファイル: app/api/payram/payout-status/[payoutId]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Payram, isPayramSDKError } from 'payram';
const payram = new Payram({
apiKey: process.env.PAYRAM_API_KEY!,
baseUrl: process.env.PAYRAM_BASE_URL!,
});
export async function GET(request: NextRequest, { params }: { params: { payoutId: string } }) {
const payoutId = parseInt(params.payoutId, 10);
if (isNaN(payoutId)) {
return NextResponse.json({ error: 'INVALID_PAYOUT_ID' }, { status: 400 });
}
try {
const payout = await payram.payouts.getPayoutById(payoutId);
return NextResponse.json(payout);
} catch (error) {
if (isPayramSDKError(error)) {
console.error('Payramエラー:', error);
}
return NextResponse.json({ error: 'PAYRAM_STATUS_CHECK_FAILED' }, { status: 502 });
}
}
ベストプラクティス
1. 金額のフォーマット
重要: 金額は数値ではなく文字列である必要があります。
// ✅ 正しい
amount: '125.50';
// ❌ 間違い - 検証エラーが発生します
amount: 125.5;
amount: 125;
理由: 財務計算の精度を確保します。JavaScriptの数値は小数で精度を失います。
2. アドレス検証
ペイアウトを作成する前に、常に受取人アドレスを検証します:
function validateAddress(address: string, blockchainCode: string): boolean {
const patterns: Record<string, RegExp> = {
ETH: /^0x[a-fA-F0-9]{40}$/,
BTC: /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/,
MATIC: /^0x[a-fA-F0-9]{40}$/,
// 必要に応じてさらにパターンを追加
};
const pattern = patterns[blockchainCode];
if (!pattern) {
throw new Error(`不明なブロックチェーン: ${blockchainCode}`);
}
return pattern.test(address);
}
// 使用例
if (!validateAddress(toAddress, blockchainCode)) {
throw new Error('ブロックチェーンに対して無効な受取人アドレス');
}
3. 残高確認
大規模なペイアウトを作成する前にマーチャント残高を確認します:
async function checkBalanceBeforePayout(
blockchainCode: string,
currencyCode: string,
amount: string,
): Promise<boolean> {
// Payram APIまたは内部記録を通じて残高確認を実装
const balance = await getMerchantBalance(blockchainCode, currencyCode);
const requestedAmount = parseFloat(amount);
if (balance < requestedAmount) {
throw new Error(`残高不足。必要額: ${amount}、利用可能額: ${balance}`);
}
return true;
}
4. データベースストレージ
照合のためにペイアウトレコードを保存します:
CREATE TABLE payouts (
id SERIAL PRIMARY KEY,
payram_payout_id INTEGER UNIQUE NOT NULL,
customer_id VARCHAR(255) NOT NULL,
blockchain_code VARCHAR(50) NOT NULL,
currency_code VARCHAR(50) NOT NULL,
amount DECIMAL(20, 8) NOT NULL,
to_address VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL,
transaction_hash VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_payram_payout_id ON payouts(payram_payout_id);
CREATE INDEX idx_customer_id ON payouts(customer_id);
CREATE INDEX idx_status ON payouts(status);
5. エラーハンドリング
包括的なエラーハンドリングを実装します:
async function createPayoutWithRetry(
payload: CreatePayoutRequest,
maxRetries = 3,
): Promise<MerchantPayout> {
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await createPayout(payload);
} catch (error) {
lastError = error as Error;
if (isPayramSDKError(error)) {
// 検証エラーは再試行しない
if (error.status === 400) {
throw error;
}
// 再試行不可の場合は再試行しない
if (!error.isRetryable) {
throw error;
}
}
if (attempt < maxRetries) {
const delay = 1000 * Math.pow(2, attempt);
console.log(`ペイアウト試行${attempt}が失敗、${delay}ms後に再試行`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
throw lastError || new Error('再試行後もペイアウト作成が失敗');
}
トラブルシューティング
エラー: "Insufficient balance"(400)
原因: マーチャントアカウントに十分な資金がありません。
解決策:
- Payramダッシュボードでマーチャント残高を確認
- マーチャントアカウントに資金をデポジット
- ブロックチェーン/通貨の組み合わせが正しいか確認
- 残高計算にガス代を考慮
エラー: "Invalid address format"(400)
原因: 受取人アドレスがブロックチェーン形式に合致しません。
解決策:
- イーサリアム/Polygon: '0x'で始まり42文字である必要があります
- ビットコイン: '1'または'3'で始まり26〜35文字である必要があります
- ブロックチェーン固有の正規表現パターンでアドレスを検証
- ペイアウトを送信する前にアドレス形式をテスト
エラー: "Unsupported blockchain/currency"(400)
原因: リクエストされたブロックチェーンまたは通貨がサポートされていません。
解決策:
docs/payram-docs-live/support/supported-networks-and-coins.mdを確認- 通貨が選択したブロックチェーンで利用可能か確認
- 正しい通貨コードを使用(例: 'USDC'ではなく'usdc'など)
- マーチャントアカウントがリクエストされたネットワークをサポートしているか確認
ペイアウトが"pending-approval"で止まっている
原因: ペイアウトが自動承認閾値を超えています。
解決策:
- Payramサポートに連絡して承認閾値を調整
- 管理者による手動承認を待機
- マーチャントダッシュボードで承認ルールを設定
- 大規模なペイアウトをより小さな金額に分割
エラー: "Invalid mobile number format"(400)
原因: 電話番号がE.164形式ではありません。
解決策:
// ✅ 正しい
mobileNumber: '+15555555555';
// ❌ 間違い
mobileNumber: '555-555-5555';
mobileNumber: '5555555555';
E.164形式を使用します: +[国番号]番号
ペイアウトが"sent"ステータス後に失敗
原因: ブロックチェーンのトランザクションが取り消されたか失敗しました。
解決策:
- ブロックチェーンエクスプローラーでトランザクションハッシュを確認
- 受取人アドレスが通貨を受け入れるか確認
- スマートコントラットの問題がないか確認(ERC-20トークン)
- ガス価格設定を確認(低すぎる場合がある)
- トランザクションハッシュを含めてPayramサポートに連絡
関連スキル
- setup-payram: 環境を設定して接続をテスト
- integrate-payments: 顧客から暗号資産の支払いを受け入れる
- handle-webhooks: リアルタイムペイアウトステータスアップデートを受信
まとめ
ペイアウト統合が完全に完成しました:
- ペイアウト作成: 必須フィールドで
payram.payouts.createPayout()を使用 - ステータス監視:
getPayoutById()でポーリングするか、ウェブフックを使用 - フレームワーク統合: すぐに使用できるExpressおよびNext.jsハンドラー
- ベストプラクティス: 金額のフォーマット、アドレス検証、残高確認、エラーハンドリング
重要な注意:
- 金額は文字列である必要があります(例: '125.50')
- ペイアウトを作成する前にアドレスを検証
- ステータス追跡用にペイアウトIDを保存
- 本番環境ではウェブフックを使用
- アプリケーション内のすべてのペイアウト状態を処理
次のステップ:
- ペイアウト作成エンドポイントを実装
- ステータスポーリングまたはウェブフックハンドラーを追加
- ペイアウトレコード用のデータベーステーブルをセットアップ
- まず小さな金額でテスト
docs/payram-docs-live/api-integration/payouts-apis.mdで詳細オプションを確認
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- majiayu000
- ライセンス
- MIT
- 最終更新
- 2026/5/4
Source: https://github.com/majiayu000/claude-skill-registry / ライセンス: MIT