Skip to main content

Capture Authorization

Overview

Capture an existing authorization to complete the transaction and move funds. Capturing converts a temporary hold into an actual charge. Supports partial captures and split transfers for marketplace scenarios.

Resource Access

  • User Permissions: Merchant users with authorization permissions
  • Endpoint: PUT /authorizations/\{authorization_id}

Path Parameters

ParameterTypeRequiredDescription
authorization_idstringYesID of the authorization to capture

Request Body Parameters

ParameterTypeRequiredDescription
capture_amountintegerNoAmount to capture in cents (defaults to full authorized amount)
feeintegerNoPlatform fee amount in cents
tagsobjectNoKey-value metadata tags
split_transfersarrayNoArray of split transfer configurations

Split Transfer Object

ParameterTypeRequiredDescription
amountintegerYesAmount to split to this merchant (cents)
destinationstringYesDestination merchant ID
tagsobjectNoMetadata for this split

Example Requests

Full Capture

curl -X PUT \
'https://api.ahrvo.network/payments/na/authorizations/AUgspz5X2AwSne5g78qNeYD1' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"capture_amount": 100,
"fee": 10,
"tags": {
"capture_reference": "CAPTURE123"
}
}'

Partial Capture

curl -X PUT \
'https://api.ahrvo.network/payments/na/authorizations/AUgspz5X2AwSne5g78qNeYD1' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"capture_amount": 50,
"fee": 5
}'

Capture with Split Transfers

curl -X PUT \
'https://api.ahrvo.network/payments/na/authorizations/AUgspz5X2AwSne5g78qNeYD1' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"capture_amount": 100,
"split_transfers": [
{
"amount": 50,
"destination": "MU123456789",
"tags": {
"split_reference": "SPLIT001"
}
},
{
"amount": 30,
"destination": "MU987654321",
"tags": {
"split_reference": "SPLIT002"
}
}
]
}'

Example Response

{
"id": "AUgspz5X2AwSne5g78qNeYD1",
"created_at": "2024-12-04T08:25:21.93Z",
"updated_at": "2024-12-04T08:30:15.42Z",
"amount": 100,
"state": "SUCCEEDED",
"transfer": "TRxyz123abc456",
"merchant": "MU7noQ1wdgdAeAfymw2rfBMq",
"source": "PIkxmtueemLD6dN9ZoWGHT44",
"tags": {
"capture_reference": "CAPTURE123"
},
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/authorizations/AUgspz5X2AwSne5g78qNeYD1"
},
"transfer": {
"href": "https://api.ahrvo.network/payments/na/transfers/TRxyz123abc456"
}
}
}

Response Fields

FieldTypeDescription
idstringAuthorization ID
created_atstringOriginal authorization creation time
updated_atstringLast update time (capture time)
amountintegerCaptured amount in cents
statestringAuthorization state (SUCCEEDED)
transferstringID of created transfer
merchantstringMerchant ID
sourcestringPayment instrument ID
tagsobjectMetadata tags

Additional Information

  • Full vs Partial Capture: Amount flexibility

    • Full Capture: Omit capture_amount or use full authorized amount
    • Partial Capture: Specify capture_amount less than authorized
    • Remaining authorization amount is released
    • Cannot capture more than authorized
    • Useful for final invoice amounts
  • Platform Fees: Marketplace revenue

    • Specify fee to collect platform fee
    • Fee deducted from merchant payout
    • Fee goes to platform account
    • Must be less than capture amount
    • Typically percentage of transaction
  • Split Transfers: Marketplace payments

    • Distribute payment to multiple merchants
    • Specify destination and amount for each
    • Each split creates separate transfer
    • Sum of splits cannot exceed capture amount
    • Useful for multi-vendor marketplaces
  • Transfer Creation: Completes transaction

    • Capture creates a Transfer resource
    • Transfer ID returned in transfer field
    • Transfer moves funds from buyer to merchant
    • Use Transfer ID to track settlement
    • Follow transfer link for details
  • Expiration: Time limits

    • Must capture before expires_at
    • Typically 7 days from authorization
    • Expired authorizations cannot be captured
    • Check expiration before capture
  • One-Time Operation: Cannot capture twice

    • Authorization can only be captured once
    • Subsequent capture attempts fail
    • Check transfer field before capture
    • Void if need to cancel

Use Cases

Standard Full Capture

// Capture full authorized amount
async function captureAuthorization(authorizationId, platformFee = 0) {
console.log('Capturing authorization...');

const response = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
fee: platformFee,
tags: {
captured_at: new Date().toISOString()
}
})
}
);

const authorization = await response.json();

console.log('✓ Authorization captured');
console.log(` Amount: $${authorization.amount / 100}`);
console.log(` Transfer ID: ${authorization.transfer}`);

if (platformFee > 0) {
console.log(` Platform Fee: $${platformFee / 100}`);
console.log(` Merchant Receives: $${(authorization.amount - platformFee) / 100}`);
}

return authorization;
}

Partial Capture for Final Invoice

// Capture partial amount (final invoice less than estimate)
async function capturePartialAmount(authorizationId, finalAmount) {
// Fetch current authorization
const authResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

const auth = await authResponse.json();

console.log(`Original authorization: $${auth.amount / 100}`);
console.log(`Final invoice amount: $${finalAmount / 100}`);

if (finalAmount > auth.amount) {
throw new Error('Cannot capture more than authorized amount');
}

// Capture final amount
const response = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
capture_amount: finalAmount,
tags: {
final_invoice: 'true',
amount_released: (auth.amount - finalAmount).toString()
}
})
}
);

const captured = await response.json();

const released = auth.amount - finalAmount;
console.log('✓ Partial capture complete');
console.log(` Captured: $${finalAmount / 100}`);
console.log(` Released: $${released / 100}`);

return captured;
}

Marketplace Split Payment

// Split payment between multiple merchants
async function captureWithSplits(authorizationId, splits) {
console.log('Capturing with split transfers...');

// Validate split amounts
const totalSplit = splits.reduce((sum, split) => sum + split.amount, 0);

const response = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
split_transfers: splits
})
}
);

const authorization = await response.json();

console.log('✓ Authorization captured with splits');
console.log(` Total Amount: $${authorization.amount / 100}`);
console.log(` Split to ${splits.length} merchants:`);

splits.forEach(split => {
console.log(` - ${split.destination}: $${split.amount / 100}`);
});

return authorization;
}

// Example usage
await captureWithSplits('AUgspz5X2AwSne5g78qNeYD1', [
{
amount: 7000, // $70.00 to vendor A
destination: 'MU123456789',
tags: { vendor: 'A', product: 'Widget' }
},
{
amount: 3000, // $30.00 to vendor B
destination: 'MU987654321',
tags: { vendor: 'B', product: 'Shipping' }
}
]);

Capture with Fee Calculation

// Calculate and capture with platform fee
async function captureWithFee(authorizationId, feePercentage = 2.9) {
// Fetch authorization
const authResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

const auth = await authResponse.json();

// Calculate fee
const feeAmount = Math.round(auth.amount * (feePercentage / 100));
const merchantAmount = auth.amount - feeAmount;

console.log('Capture Breakdown:');
console.log(` Total: $${auth.amount / 100}`);
console.log(` Platform Fee (${feePercentage}%): $${feeAmount / 100}`);
console.log(` Merchant Receives: $${merchantAmount / 100}`);

// Capture with fee
const response = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
fee: feeAmount,
tags: {
fee_percentage: feePercentage.toString(),
merchant_amount: merchantAmount.toString()
}
})
}
);

return response.json();
}

Delayed Capture Workflow

// Wait for fulfillment before capturing
async function delayedCaptureWorkflow(authorizationId) {
console.log('=== Delayed Capture Workflow ===\n');

// Step 1: Check authorization still valid
console.log('Step 1: Verifying authorization...');
const authResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

const auth = await authResponse.json();

if (auth.transfer) {
console.log('✗ Already captured');
return null;
}

if (auth.is_void) {
console.log('✗ Authorization was voided');
return null;
}

const expiresAt = new Date(auth.expires_at);
if (expiresAt < new Date()) {
console.log('✗ Authorization expired');
return null;
}

console.log('✓ Authorization valid');

// Step 2: Wait for fulfillment
console.log('\nStep 2: Waiting for fulfillment...');
// In real scenario, this would check order status
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('✓ Order fulfilled');

// Step 3: Capture authorization
console.log('\nStep 3: Capturing authorization...');
const captureResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
tags: {
fulfillment_status: 'shipped',
captured_after_fulfillment: 'true'
}
})
}
);

const captured = await captureResponse.json();

console.log('✓ Authorization captured');
console.log(` Transfer ID: ${captured.transfer}`);

return captured;
}

Capture with Error Handling

// Robust capture with comprehensive error handling
async function safeCapture(authorizationId, captureAmount = null) {
try {
// Pre-flight check
const authResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);

if (!authResponse.ok) {
throw new Error('Authorization not found');
}

const auth = await authResponse.json();

// Validation checks
if (auth.state !== 'SUCCEEDED') {
throw new Error(`Authorization state is ${auth.state}, cannot capture`);
}

if (auth.transfer) {
throw new Error('Authorization already captured');
}

if (auth.is_void) {
throw new Error('Authorization was voided');
}

if (new Date(auth.expires_at) < new Date()) {
throw new Error('Authorization has expired');
}

if (captureAmount && captureAmount > auth.amount) {
throw new Error('Capture amount exceeds authorized amount');
}

// Perform capture
const captureResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
...(captureAmount && { capture_amount: captureAmount })
})
}
);

if (!captureResponse.ok) {
const error = await captureResponse.json();
throw new Error(`Capture failed: ${error.message || 'Unknown error'}`);
}

const captured = await captureResponse.json();

console.log('✓ Capture successful');

return { success: true, authorization: captured };

} catch (error) {
console.error('✗ Capture failed:', error.message);
return { success: false, error: error.message };
}
}

Best Practices

  • Verify Before Capture: Always check authorization

    // Good - verify first
    const auth = await fetchAuthorization(authId);
    if (auth.state === 'SUCCEEDED' && !auth.transfer && !auth.is_void) {
    await captureAuthorization(authId);
    }

    // Bad - capture blindly
    await captureAuthorization(authId); // May fail
  • Capture After Fulfillment: Not immediately

    • Wait until product shipped or service delivered
    • Reduces chargebacks
    • Better customer experience
    • Monitor expiration dates
  • Use Partial Capture: When appropriate

    • Final invoice less than estimate
    • Tips/gratuity adjusted
    • Partial fulfillment
    • Releases unused authorization
  • Store Transfer ID: Reference for settlement

    const captured = await captureAuthorization(authId);
    await saveToDatabase({
    authorizationId: authId,
    transferId: captured.transfer,
    capturedAt: new Date()
    });
  • Handle Errors Gracefully: Capture may fail

    • Authorization expired
    • Already captured
    • Network errors
    • Retry with idempotency
  • Monitor Expirations: Capture before expires

    • Set alerts for expiring authorizations
    • Capture within 5-6 days
    • Don't wait until last day

Common Workflows

Batch Capture Processing

// Capture multiple authorizations in batch
async function batchCapture(authorizationIds) {
console.log(`Processing batch of ${authorizationIds.length} captures...\n`);

const results = {
successful: [],
failed: []
};

for (const authId of authorizationIds) {
try {
const response = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
tags: {
batch_capture: 'true',
batch_timestamp: new Date().toISOString()
}
})
}
);

const captured = await response.json();

results.successful.push({
authorizationId: authId,
transferId: captured.transfer
});

console.log(`${authId} captured`);

} catch (error) {
results.failed.push({
authorizationId: authId,
error: error.message
});

console.log(`${authId} failed: ${error.message}`);
}

// Rate limiting
await new Promise(resolve => setTimeout(resolve, 100));
}

console.log(`\n=== Batch Complete ===`);
console.log(`Successful: ${results.successful.length}`);
console.log(`Failed: ${results.failed.length}`);

return results;
}

Security Considerations

  • Verify Ownership: Ensure authorization belongs to merchant

    • Check merchant ID matches
    • Validate permissions
    • Don't capture other merchants' authorizations
  • Validate Amounts: Check capture amount

    • Must be ≤ authorized amount
    • Validate split amounts sum correctly
    • Verify fees are reasonable
  • Audit Trail: Log all captures

    • Record who captured
    • Record when captured
    • Record amount captured
    • Use tags for tracking
  • Idempotency: Prevent duplicate captures

    • Check transfer field before capture
    • Use proper error handling
    • Don't retry blindly

Error Responses

Authorization Expired

{
"status": 422,
"message": "Authorization has expired and cannot be captured"
}

Already Captured

{
"status": 422,
"message": "Authorization has already been captured"
}

Authorization Voided

{
"status": 422,
"message": "Authorization has been voided and cannot be captured"
}

Invalid Capture Amount

{
"status": 400,
"message": "Capture amount exceeds authorized amount"
}

Processor Error

{
"status": 402,
"message": "Upstream processor error"
}

Troubleshooting

Capture Fails - Already Captured

  • Check transfer field is null before capture
  • Fetch fresh authorization before capture
  • Authorization can only be captured once
  • Use transfer ID if already captured

Capture Fails - Expired

  • Check expires_at timestamp
  • Must capture within 7 days typically
  • Create new authorization if expired
  • Monitor expiration proactively

Capture Fails - Voided

  • Check is_void field
  • Voided authorizations cannot be captured
  • Create new authorization instead

Partial Capture Not Working

  • Verify capture_amount less than authorized
  • Ensure processor supports partial capture
  • Check amount is greater than zero
  • POST /authorizations: Create authorization to capture later
  • GET /authorizations/{id}: Check authorization before capture
  • GET /transfers/{id}: View transfer created by capture
  • PUT /authorizations/{id} (void): Void instead of capture