Withdraw Funds from Card
Overview
Use this endpoint to withdraw funds from a Dedicated Funds Card back to your budget account. This operation moves funds from the card's balance back to your budget, reducing the card's available balance. Withdrawals are the reverse operation of top-ups.
Common reasons to withdraw funds:
- Reclaiming unused card balance
- Rebalancing funds across cards
- Deactivating a card and recovering funds
- End of campaign or project
- Budget reallocation
NOTE: This API is offered on a client-by-client approval basis. Speak to your account manager to check eligibility.
IMPORTANT: This endpoint only works for Dedicated Funds Cards. Shared Funds Cards do not hold independent balances and therefore cannot have withdrawals.
Resource Access
Production (api.ahrvo.network)
POST https://api.ahrvo.network/card/issuance/api/issuing/card/v2/funding/withdraw
Staging (gateway.ahrvo.network)
POST https://gateway.ahrvo.network/card/issuance/api/issuing/card/v2/funding/withdraw
Request Headers
| Header | Value | Required | Description |
|---|---|---|---|
Accept | application/json | Yes | Content type for the response |
Content-Type | application/json | Yes | Content type of the request body |
Authorization | Bearer {access_token} | Yes | Bearer token for authentication |
x-api-key | API Key | Yes | API key for authentication |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
unique_order_id | string | Yes | Withdrawal order ID provided by you for idempotency. Character limit: 36 |
card_id | string | Yes | A unique ID of the card to withdraw from |
amount | string | Yes | Monetary amount expressed in USD (e.g., "100.00") |
Request Example
{
"card_id": "card2020092710410645531",
"amount": "100.00",
"unique_order_id": "1cc24f5b-5dda-4c2f-8811-df2047469f9f"
}
Response
Success Response (200 OK)
{
"code": "SUCCESS",
"data": {
"record_id": "coi202511211358115253"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
code | string | Status string indicating the result. "SUCCESS" refers to a successful withdrawal initiation |
data | object | Response data object |
data.record_id | string | A unique ID of the withdrawal record generated by PingPong |
Error Responses
- 400 Bad Request: Invalid parameters, insufficient card balance, or duplicate
unique_order_id - 401 Unauthorized: Invalid or missing authentication token
- 403 Forbidden: Card issuance feature not enabled for this account
- 404 Not Found: Card does not exist
Code Examples
cURL
curl -X POST \
'https://gateway.ahrvo.network/card/issuance/api/issuing/card/v2/funding/withdraw' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'x-api-key: YOUR_API_KEY' \
-d '{
"card_id": "card2020092710410645531",
"amount": "100.00",
"unique_order_id": "1cc24f5b-5dda-4c2f-8811-df2047469f9f"
}'
Python
import requests
import uuid
url = "https://gateway.ahrvo.network/card/issuance/api/issuing/card/v2/funding/withdraw"
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"x-api-key": "YOUR_API_KEY"
}
payload = {
"card_id": "card2020092710410645531",
"amount": "100.00",
"unique_order_id": str(uuid.uuid4())
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
if result['code'] == 'SUCCESS':
data = result['data']
print("Funds Withdrawn Successfully!")
print(f" Record ID: {data['record_id']}")
print(f" Amount: ${payload['amount']}")
print(f" Card ID: {payload['card_id']}")
else:
print(f"Failed to withdraw funds: {result}")
JavaScript (Node.js)
const axios = require('axios');
const { v4: uuidv4 } = require('uuid');
const url = 'https://gateway.ahrvo.network/card/issuance/api/issuing/card/v2/funding/withdraw';
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'x-api-key': 'YOUR_API_KEY'
};
const payload = {
card_id: 'card2020092710410645531',
amount: '100.00',
unique_order_id: uuidv4()
};
axios.post(url, payload, { headers })
.then(response => {
const result = response.data;
if (result.code === 'SUCCESS') {
const data = result.data;
console.log('Funds Withdrawn Successfully!');
console.log(` Record ID: ${data.record_id}`);
console.log(` Amount: $${payload.amount}`);
console.log(` Card ID: ${payload.card_id}`);
}
})
.catch(error => {
console.error('Failed to withdraw funds:', error.response.data);
});
Usage Notes
- Idempotency: Use unique
unique_order_idvalues to prevent duplicate withdrawals - Immediate Processing: Funds are typically transferred within seconds
- Card Balance: Ensure card has sufficient balance before withdrawal
- USD Only: All amounts must be in USD with exactly 2 decimal places
- Record Tracking: Save the returned
record_idto track the withdrawal in funding orders - Dedicated Funds Only: This endpoint only works for Dedicated Funds Cards
Common Use Cases
Withdraw Unused Card Balance
Reclaim unused funds from a card:
def withdraw_unused_funds(card_id):
"""
Withdraw all or most unused funds from a card
Args:
card_id: Card ID to withdraw from
"""
import uuid
print(f"\n=== Withdraw Unused Funds ===")
print(f"Card ID: {card_id}\n")
# Step 1: Check current balance
print("Step 1: Checking card balance...")
balance_response = query_card_balance(card_id)
if balance_response['code'] != 'SUCCESS':
print(f"✗ Failed to check balance: {balance_response}")
return {'success': False, 'error': balance_response}
card_number = balance_response['data']['card_number']
available_balance = float(balance_response['data']['available_balance'])
print(f" Card: {card_number}")
print(f" Available Balance: ${available_balance:.2f}")
if available_balance <= 0:
print(f"\n⚠️ No funds to withdraw")
return {'success': False, 'reason': 'No balance'}
# Leave a small buffer (e.g., $1) to keep card operational if needed
buffer_amount = 1.00
withdraw_amount = max(0, available_balance - buffer_amount)
if withdraw_amount <= 0:
print(f"\n⚠️ Balance too low to withdraw (keeping ${buffer_amount:.2f} buffer)")
return {'success': False, 'reason': 'Balance too low'}
print(f"\nStep 2: Withdrawing ${withdraw_amount:.2f}...")
print(f" (Keeping ${buffer_amount:.2f} buffer on card)")
# Execute withdrawal
payload = {
"card_id": card_id,
"amount": f"{withdraw_amount:.2f}",
"unique_order_id": str(uuid.uuid4())
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
if result['code'] == 'SUCCESS':
data = result['data']
expected_new_balance = available_balance - withdraw_amount
print(f"\n✓ Withdrawal Successful!")
print(f" Record ID: {data['record_id']}")
print(f" Amount Withdrawn: ${withdraw_amount:.2f}")
print(f" Previous Balance: ${available_balance:.2f}")
print(f" Expected New Balance: ${expected_new_balance:.2f}")
# Verify new balance
time.sleep(2)
verify_response = query_card_balance(card_id)
if verify_response['code'] == 'SUCCESS':
new_balance = float(verify_response['data']['available_balance'])
print(f" Actual New Balance: ${new_balance:.2f}")
return {
'success': True,
'record_id': data['record_id'],
'amount_withdrawn': withdraw_amount,
'previous_balance': available_balance,
'new_balance': expected_new_balance
}
else:
print(f"\n✗ Withdrawal Failed: {result}")
return {'success': False, 'error': result}
# Example usage
result = withdraw_unused_funds('card2020092710410645531')
if result['success']:
print(f"\n✅ Successfully withdrew ${result['amount_withdrawn']:.2f}")
else:
print(f"\n❌ Failed to withdraw funds")
Withdraw All Funds Before Card Closure
Recover all funds before closing a card:
async function withdrawAllFundsBeforeClosure(cardId) {
console.log('\n=== Withdraw All Funds Before Closure ===\n');
try {
// Step 1: Get current balance
console.log('Step 1: Checking card balance...');
const balanceResponse = await queryCardBalance(cardId);
if (balanceResponse.code !== 'SUCCESS') {
console.log('✗ Failed to check balance');
return { success: false, error: balanceResponse };
}
const cardNumber = balanceResponse.data.card_number;
const availableBalance = parseFloat(balanceResponse.data.available_balance);
console.log(` Card: ${cardNumber}`);
console.log(` Available Balance: $${availableBalance.toFixed(2)}`);
if (availableBalance <= 0) {
console.log('\n✓ No balance to withdraw - card ready for closure');
return {
success: true,
no_balance: true,
ready_for_closure: true
};
}
// Step 2: Withdraw entire balance
console.log(`\nStep 2: Withdrawing entire balance of $${availableBalance.toFixed(2)}...`);
const { v4: uuidv4 } = require('uuid');
const payload = {
card_id: cardId,
amount: availableBalance.toFixed(2),
unique_order_id: uuidv4()
};
const withdrawResponse = await withdrawFunds(payload);
if (withdrawResponse.code !== 'SUCCESS') {
console.log('✗ Withdrawal failed');
return { success: false, error: withdrawResponse };
}
console.log('✓ Withdrawal successful');
console.log(` Record ID: ${withdrawResponse.data.record_id}`);
// Step 3: Verify balance is zero
console.log('\nStep 3: Verifying balance is zero...');
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
const verifyResponse = await queryCardBalance(cardId);
if (verifyResponse.code === 'SUCCESS') {
const newBalance = parseFloat(verifyResponse.data.available_balance);
console.log(` New Balance: $${newBalance.toFixed(2)}`);
if (newBalance === 0 || newBalance < 0.01) {
console.log('\n✅ Card balance cleared - ready for closure!');
return {
success: true,
card_number: cardNumber,
amount_withdrawn: availableBalance,
record_id: withdrawResponse.data.record_id,
ready_for_closure: true
};
} else {
console.log(`\n⚠️ Balance not fully cleared: $${newBalance.toFixed(2)} remaining`);
return {
success: true,
card_number: cardNumber,
amount_withdrawn: availableBalance,
record_id: withdrawResponse.data.record_id,
ready_for_closure: false,
remaining_balance: newBalance
};
}
} else {
console.log('⚠️ Could not verify new balance');
return {
success: true,
amount_withdrawn: availableBalance,
record_id: withdrawResponse.data.record_id,
verification_failed: true
};
}
} catch (error) {
console.error('Error withdrawing funds:', error.message);
return { success: false, error: error.message };
}
}
// Example usage with card closure workflow
async function closeCardWorkflow(cardId) {
console.log('\n🔒 Starting Card Closure Workflow\n');
// Step 1: Withdraw all funds
const withdrawResult = await withdrawAllFundsBeforeClosure(cardId);
if (!withdrawResult.success) {
console.log('\n❌ Failed to withdraw funds - cannot close card safely');
return;
}
if (!withdrawResult.ready_for_closure) {
console.log('\n⚠️ Card not ready for closure - balance remains');
return;
}
// Step 2: Close the card
console.log('\nStep 4: Closing card...');
const closeResult = await closeCard(cardId);
if (closeResult.code === 'SUCCESS') {
console.log('✓ Card closed successfully');
console.log('\n✅ Card Closure Complete!');
console.log(` Funds Withdrawn: $${withdrawResult.amount_withdrawn?.toFixed(2) || '0.00'}`);
console.log(` Card Status: CLOSED`);
} else {
console.log('✗ Failed to close card');
}
}
// Run the workflow
await closeCardWorkflow('card2020092710410645531');
Batch Withdraw from Multiple Cards
Withdraw funds from multiple cards at once:
def batch_withdraw_funds(withdrawals):
"""
Withdraw funds from multiple cards
Args:
withdrawals: List of dicts with 'card_id' and 'amount'
"""
import uuid
from datetime import datetime
print("\n" + "=" * 70)
print("BATCH FUNDS WITHDRAWAL".center(70))
print("=" * 70)
print(f"Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Total Cards: {len(withdrawals)}\n")
results = {
'successful': [],
'failed': [],
'total_amount': 0
}
for idx, item in enumerate(withdrawals, 1):
card_id = item['card_id']
amount = float(item['amount'])
print(f"[{idx}/{len(withdrawals)}] Processing {card_id}...")
print(f" Amount: ${amount:.2f}")
try:
# Check balance first
balance_response = query_card_balance(card_id)
if balance_response['code'] == 'SUCCESS':
card_number = balance_response['data']['card_number']
available_balance = float(balance_response['data']['available_balance'])
print(f" Card: {card_number}")
print(f" Available Balance: ${available_balance:.2f}")
if amount > available_balance:
print(f" ✗ Insufficient balance (need ${amount:.2f}, have ${available_balance:.2f})\n")
results['failed'].append({
'card_id': card_id,
'card_number': card_number,
'amount': amount,
'error': 'Insufficient balance'
})
continue
# Execute withdrawal
payload = {
"card_id": card_id,
"amount": f"{amount:.2f}",
"unique_order_id": str(uuid.uuid4())
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
if result['code'] == 'SUCCESS':
data = result['data']
print(f" ✓ Success - Record ID: {data['record_id']}")
results['successful'].append({
'card_id': card_id,
'card_number': card_number,
'amount': amount,
'record_id': data['record_id']
})
results['total_amount'] += amount
else:
print(f" ✗ Failed: {result.get('message', 'Unknown error')}")
results['failed'].append({
'card_id': card_id,
'card_number': card_number,
'amount': amount,
'error': result
})
else:
print(f" ✗ Failed to check balance")
results['failed'].append({
'card_id': card_id,
'amount': amount,
'error': 'Balance check failed'
})
except Exception as e:
print(f" ✗ Exception: {e}")
results['failed'].append({
'card_id': card_id,
'amount': amount,
'error': str(e)
})
print()
# Summary
print("=" * 70)
print("BATCH WITHDRAWAL SUMMARY".center(70))
print("=" * 70)
print(f"Total Cards: {len(withdrawals)}")
print(f"Successful: {len(results['successful'])} ✓")
print(f"Failed: {len(results['failed'])} ✗")
print(f"Total Amount Withdrawn: ${results['total_amount']:,.2f}")
if results['failed']:
print(f"\n⚠️ Failed Withdrawals:")
for item in results['failed']:
card_info = item.get('card_number', item['card_id'])
print(f" • {card_info}: ${item['amount']:.2f} - {item.get('error', 'Unknown')}")
print("\n" + "=" * 70 + "\n")
return results
# Example usage
withdrawals = [
{'card_id': 'card2020092710410645531', 'amount': '500.00'},
{'card_id': 'card2020092710410645532', 'amount': '250.00'},
{'card_id': 'card2020092710410645533', 'amount': '1000.00'},
{'card_id': 'card2020092710410645534', 'amount': '750.00'}
]
results = batch_withdraw_funds(withdrawals)
print(f"Batch complete: {len(results['successful'])}/{len(withdrawals)} successful")
print(f"Total funds recovered: ${results['total_amount']:,.2f}")
Rebalance Funds Across Cards
Move funds from over-funded to under-funded cards:
async function rebalanceFundsAcrossCards(cardIds, targetBalance) {
console.log('\n=== Rebalance Funds Across Cards ===');
console.log(`Target Balance per Card: $${targetBalance.toFixed(2)}\n`);
const operations = {
withdrawals: [],
topups: [],
balanced: []
};
// Step 1: Check all card balances
console.log('Step 1: Checking all card balances...\n');
for (const cardId of cardIds) {
const balanceResponse = await queryCardBalance(cardId);
if (balanceResponse.code === 'SUCCESS') {
const cardNumber = balanceResponse.data.card_number;
const currentBalance = parseFloat(balanceResponse.data.available_balance);
const difference = currentBalance - targetBalance;
console.log(`Card: ${cardNumber}`);
console.log(` Current: $${currentBalance.toFixed(2)}`);
console.log(` Target: $${targetBalance.toFixed(2)}`);
console.log(` Difference: ${difference >= 0 ? '+' : ''}$${difference.toFixed(2)}`);
if (Math.abs(difference) < 0.01) {
console.log(` ✓ Already balanced\n`);
operations.balanced.push({
card_id: cardId,
card_number: cardNumber,
balance: currentBalance
});
} else if (difference > 0) {
console.log(` ➖ Withdraw $${difference.toFixed(2)}\n`);
operations.withdrawals.push({
card_id: cardId,
card_number: cardNumber,
current_balance: currentBalance,
amount: difference
});
} else {
console.log(` ➕ Top-up $${Math.abs(difference).toFixed(2)}\n`);
operations.topups.push({
card_id: cardId,
card_number: cardNumber,
current_balance: currentBalance,
amount: Math.abs(difference)
});
}
}
}
// Step 2: Execute withdrawals
if (operations.withdrawals.length > 0) {
console.log(`\nStep 2: Executing ${operations.withdrawals.length} withdrawal(s)...\n`);
for (const withdrawal of operations.withdrawals) {
console.log(`Withdrawing $${withdrawal.amount.toFixed(2)} from ${withdrawal.card_number}...`);
const { v4: uuidv4 } = require('uuid');
const payload = {
card_id: withdrawal.card_id,
amount: withdrawal.amount.toFixed(2),
unique_order_id: uuidv4()
};
const result = await withdrawFunds(payload);
if (result.code === 'SUCCESS') {
console.log(` ✓ Withdrawn successfully - Record: ${result.data.record_id}\n`);
withdrawal.success = true;
withdrawal.record_id = result.data.record_id;
} else {
console.log(` ✗ Failed: ${result.message || 'Unknown error'}\n`);
withdrawal.success = false;
withdrawal.error = result;
}
}
}
// Step 3: Execute top-ups
if (operations.topups.length > 0) {
console.log(`\nStep 3: Executing ${operations.topups.length} top-up(s)...\n`);
for (const topup of operations.topups) {
console.log(`Topping up $${topup.amount.toFixed(2)} to ${topup.card_number}...`);
const { v4: uuidv4 } = require('uuid');
const payload = {
card_id: topup.card_id,
amount: topup.amount.toFixed(2),
unique_order_id: uuidv4()
};
const result = await topUpCard(payload);
if (result.code === 'SUCCESS') {
console.log(` ✓ Topped up successfully - Record: ${result.data.record_id}\n`);
topup.success = true;
topup.record_id = result.data.record_id;
} else {
console.log(` ✗ Failed: ${result.message || 'Unknown error'}\n`);
topup.success = false;
topup.error = result;
}
}
}
// Summary
console.log('\n=== Rebalancing Summary ===');
console.log(`Total Cards: ${cardIds.length}`);
console.log(`Already Balanced: ${operations.balanced.length}`);
console.log(`Withdrawals: ${operations.withdrawals.length}`);
console.log(`Top-ups: ${operations.topups.length}`);
const successfulWithdrawals = operations.withdrawals.filter(w => w.success).length;
const successfulTopups = operations.topups.filter(t => t.success).length;
console.log(`\nSuccessful Operations:`);
console.log(` Withdrawals: ${successfulWithdrawals}/${operations.withdrawals.length}`);
console.log(` Top-ups: ${successfulTopups}/${operations.topups.length}`);
return operations;
}
// Example usage
const cardList = [
'card2020092710410645531',
'card2020092710410645532',
'card2020092710410645533',
'card2020092710410645534'
];
const result = await rebalanceFundsAcrossCards(cardList, 500.00);
Scheduled Fund Recovery
Automatically withdraw excess funds on a schedule:
def schedule_fund_recovery(card_id, threshold_amount, schedule_time):
"""
Schedule automatic withdrawal when balance exceeds threshold
Args:
card_id: Card ID to monitor
threshold_amount: Balance threshold to trigger withdrawal
schedule_time: Time to check and withdraw (cron format)
"""
import schedule
import time
print(f"\n📅 Scheduled Fund Recovery")
print(f" Card: {card_id}")
print(f" Threshold: ${threshold_amount:.2f}")
print(f" Schedule: {schedule_time}\n")
def check_and_withdraw():
print(f"\n⏰ Running scheduled fund recovery check...")
print(f" Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
# Check balance
balance_response = query_card_balance(card_id)
if balance_response['code'] == 'SUCCESS':
card_number = balance_response['data']['card_number']
available_balance = float(balance_response['data']['available_balance'])
print(f"Card: {card_number}")
print(f" Current Balance: ${available_balance:.2f}")
print(f" Threshold: ${threshold_amount:.2f}")
if available_balance > threshold_amount:
excess_amount = available_balance - threshold_amount
print(f" ⚠️ Balance exceeds threshold by ${excess_amount:.2f}")
print(f" Withdrawing excess funds...")
payload = {
"card_id": card_id,
"amount": f"{excess_amount:.2f}",
"unique_order_id": str(uuid.uuid4())
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
if result['code'] == 'SUCCESS':
print(f" ✓ Withdrawal successful!")
print(f" Record ID: {result['data']['record_id']}")
print(f" Amount: ${excess_amount:.2f}")
print(f" New Balance: ${threshold_amount:.2f}")
# Send notification
send_notification(
f"Scheduled fund recovery: ${excess_amount:.2f} withdrawn from card {card_number}"
)
else:
print(f" ✗ Withdrawal failed: {result}")
send_alert(f"Failed to withdraw from card {card_number}", result)
else:
print(f" ✓ Balance below threshold - no withdrawal needed")
else:
print(f"✗ Failed to check balance: {balance_response}")
# Schedule the job (example: daily at 2 AM)
schedule.every().day.at("02:00").do(check_and_withdraw)
print("✓ Scheduled fund recovery configured")
print(" Will check daily at 2:00 AM")
# Keep the scheduler running
while True:
schedule.run_pending()
time.sleep(60) # Check every minute
# Example usage
schedule_fund_recovery(
'card2020092710410645531',
threshold_amount=1000.00,
schedule_time='02:00'
)
Campaign End Cleanup
Withdraw funds at campaign end:
async function campaignEndCleanup(campaignCards) {
console.log('\n=== Campaign End Cleanup ===\n');
console.log(`Processing ${campaignCards.length} campaign card(s)\n`);
const results = {
total_recovered: 0,
cards_processed: []
};
for (const campaign of campaignCards) {
const { card_id, campaign_name, end_date } = campaign;
console.log(`\nCampaign: ${campaign_name}`);
console.log(`End Date: ${end_date}`);
console.log(`Card: ${card_id}\n`);
try {
// Get card balance
const balanceResponse = await queryCardBalance(card_id);
if (balanceResponse.code === 'SUCCESS') {
const cardNumber = balanceResponse.data.card_number;
const balance = parseFloat(balanceResponse.data.available_balance);
console.log(` Balance: $${balance.toFixed(2)}`);
if (balance > 0) {
console.log(' Withdrawing remaining funds...');
const { v4: uuidv4 } = require('uuid');
const withdrawPayload = {
card_id: card_id,
amount: balance.toFixed(2),
unique_order_id: uuidv4()
};
const withdrawResponse = await withdrawFunds(withdrawPayload);
if (withdrawResponse.code === 'SUCCESS') {
console.log(` ✓ Funds withdrawn successfully`);
console.log(` Record ID: ${withdrawResponse.data.record_id}`);
results.total_recovered += balance;
results.cards_processed.push({
campaign_name: campaign_name,
card_id: card_id,
card_number: cardNumber,
amount_recovered: balance,
record_id: withdrawResponse.data.record_id,
success: true
});
// Update card remark
await updateCardRemark({
card_id: card_id,
remark: `Campaign Ended: ${campaign_name} - Funds recovered ${new Date().toLocaleDateString()}`
});
console.log(` ✓ Card remark updated`);
} else {
console.log(` ✗ Withdrawal failed`);
results.cards_processed.push({
campaign_name: campaign_name,
card_id: card_id,
success: false,
error: withdrawResponse
});
}
} else {
console.log(' ✓ No balance to withdraw');
results.cards_processed.push({
campaign_name: campaign_name,
card_id: card_id,
card_number: cardNumber,
amount_recovered: 0,
success: true,
no_balance: true
});
}
} else {
console.log(' ✗ Failed to check balance');
results.cards_processed.push({
campaign_name: campaign_name,
card_id: card_id,
success: false,
error: 'Balance check failed'
});
}
} catch (error) {
console.error(` ✗ Error: ${error.message}`);
results.cards_processed.push({
campaign_name: campaign_name,
card_id: card_id,
success: false,
error: error.message
});
}
}
// Summary
console.log('\n' + '='.repeat(60));
console.log('CAMPAIGN CLEANUP SUMMARY');
console.log('='.repeat(60));
console.log(`Total Campaigns: ${campaignCards.length}`);
console.log(`Successfully Processed: ${results.cards_processed.filter(c => c.success).length}`);
console.log(`Failed: ${results.cards_processed.filter(c => !c.success).length}`);
console.log(`Total Funds Recovered: $${results.total_recovered.toFixed(2)}`);
console.log('='.repeat(60) + '\n');
return results;
}
// Example usage
const campaignCards = [
{
card_id: 'card2020092710410645531',
campaign_name: 'Spring Sale 2026',
end_date: '2026-03-31'
},
{
card_id: 'card2020092710410645532',
campaign_name: 'Product Launch - Widget Pro',
end_date: '2026-04-15'
}
];
const result = await campaignEndCleanup(campaignCards);
console.log(`✅ Campaign cleanup complete`);
console.log(` Total funds recovered: $${result.total_recovered.toFixed(2)}`);
Best Practices
- Idempotency: Always use unique
unique_order_idvalues (UUID recommended) - Balance Check: Verify card balance before attempting withdrawal
- Amount Validation: Ensure withdrawal amount doesn't exceed card balance
- Verification: Query balance after withdrawal to confirm transaction
- Record Tracking: Store
record_idvalues for reconciliation - Budget Monitoring: Track budget account balance after receiving funds
- Error Handling: Implement retry logic for network errors
- Notifications: Alert administrators of large withdrawals
Understanding Card Funding Operations
Withdrawal Flow
- Initiate: Call this endpoint with card ID and amount
- Debit Card: Amount is deducted from card's balance
- Credit Budget: Amount is added to your budget account
- Immediate: Funds typically available within seconds
- Track: Use returned
record_idto track the funding order
Funding Operations Comparison
| Operation | Description | Amount Direction | Use Case |
|---|---|---|---|
| Top-Up (card_in) | Add funds to card | Budget → Card | Prepare card for use |
| Withdrawal (card_out) | Remove funds from card | Card → Budget | Recover unused funds |
Related Endpoints
- Top-Up Dedicated Funds Card - Add funds to a card
- Query Dedicated Funds Card Balance - Check current card balance
- Query Card Funding Orders - View withdrawal and top-up history
- Query Budget Balance - Check budget account balance after withdrawal
- Close a Card - Permanently close a card (withdraw funds first)
Troubleshooting
Insufficient Card Balance (400)
- Cause: Card doesn't have enough funds for withdrawal
- Solution:
- Query card balance first
- Reduce withdrawal amount
- Withdrawal amount cannot exceed available balance
Duplicate Order ID (400)
- Cause: The
unique_order_idwas used in a previous request - Solution:
- Generate a new UUID for each withdrawal attempt
- Don't retry with the same
unique_order_id
Card Not Found (404)
- Cause: Invalid
card_idor card doesn't exist - Solution:
- Verify the card ID is correct
- Use Query All Cards to list available cards
- Check if card was deleted or closed
Wrong Card Type (400)
- Cause: Attempting to withdraw from a Shared Funds Card
- Solution:
- This endpoint only works for Dedicated Funds Cards
- Shared Funds Cards don't hold independent balances
Withdrawal Not Reflected
- Cause: Balance query timing or processing delay
- Solution:
- Wait 2-5 seconds and query balance again
- Use
record_idto verify the funding order status - Check Query Card Funding Orders for the transaction
Security Considerations
- Access Control: Restrict withdrawal operations to authorized users only
- Amount Limits: Implement maximum withdrawal limits in your application
- Approval Workflow: Require approval for large withdrawals
- Audit Logging: Log all withdrawal operations for security auditing
- Fraud Detection: Monitor for unusual withdrawal patterns
- Rate Limiting: Implement reasonable limits on withdrawal frequency
- Balance Validation: Always verify sufficient balance before withdrawal