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
| Parameter | Type | Required | Description |
|---|---|---|---|
| authorization_id | string | Yes | ID of the authorization to capture |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| capture_amount | integer | No | Amount to capture in cents (defaults to full authorized amount) |
| fee | integer | No | Platform fee amount in cents |
| tags | object | No | Key-value metadata tags |
| split_transfers | array | No | Array of split transfer configurations |
Split Transfer Object
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | integer | Yes | Amount to split to this merchant (cents) |
| destination | string | Yes | Destination merchant ID |
| tags | object | No | Metadata 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
| Field | Type | Description |
|---|---|---|
| id | string | Authorization ID |
| created_at | string | Original authorization creation time |
| updated_at | string | Last update time (capture time) |
| amount | integer | Captured amount in cents |
| state | string | Authorization state (SUCCEEDED) |
| transfer | string | ID of created transfer |
| merchant | string | Merchant ID |
| source | string | Payment instrument ID |
| tags | object | Metadata tags |
Additional Information
-
Full vs Partial Capture: Amount flexibility
- Full Capture: Omit
capture_amountor use full authorized amount - Partial Capture: Specify
capture_amountless than authorized - Remaining authorization amount is released
- Cannot capture more than authorized
- Useful for final invoice amounts
- Full Capture: Omit
-
Platform Fees: Marketplace revenue
- Specify
feeto collect platform fee - Fee deducted from merchant payout
- Fee goes to platform account
- Must be less than capture amount
- Typically percentage of transaction
- Specify
-
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
transferfield - 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
- Must capture before
-
One-Time Operation: Cannot capture twice
- Authorization can only be captured once
- Subsequent capture attempts fail
- Check
transferfield 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
transferfield 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_attimestamp - Must capture within 7 days typically
- Create new authorization if expired
- Monitor expiration proactively
Capture Fails - Voided
- Check
is_voidfield - Voided authorizations cannot be captured
- Create new authorization instead
Partial Capture Not Working
- Verify
capture_amountless than authorized - Ensure processor supports partial capture
- Check amount is greater than zero
Related Endpoints
- 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