Authorization Flow
Authorization Lifecycle
┌─────────────â”
│ Place │
│ Order │
└──────┬──────┘
│
┌──────▼──────â”
│ Authorize │
│ (Hold $) │
└──────┬──────┘
│
┌──────▼──────â”
│ Capture │
│ (Collect) │
└──────┬──────┘
│
┌──────▼──────â”
│ Complete │
└─────────────┘
Authorization Service
namespace Vendor\Payment\Service\Auth;
class AuthorizationService
{
private GatewayFactoryInterface $gatewayFactory;
private TransactionRepositoryInterface $transactionRepo;
private LoggerInterface $logger;
public function authorize(
OrderInterface $order,
PaymentInterface $payment
): AuthResult {
$gateway = $this->gatewayFactory->create($order->getPayment()->getMethod());
try {
$result = $gateway->authorize(new AuthorizeRequest([
'amount' => $order->getGrandTotal(),
'currency' => $order->getCurrencyCode(),
'order_id' => $order->getIncrementId(),
'payment_method' => $payment,
]));
if ($result->isSuccess()) {
// Save transaction
$transaction = new TransactionData([
'order_id' => $order->getId(),
'transaction_id' => $result->getTransactionId(),
'type' => TransactionTypeInterface::TYPE_AUTH,
'amount' => $order->getGrandTotal(),
'status' => TransactionStatusInterface::STATUS_APPROVED,
]);
$this->transactionRepo->save($transaction);
// Update order state
$order->setState(Order::STATE_PROCESSING);
$order->getPayment()->setAuthorizationTransaction(
$result->getTransactionId()
);
}
return $result;
} catch (\Exception $e) {
$this->logger->error('Authorization failed', [
'order_id' => $order->getIncrementId(),
'error' => $e->getMessage(),
]);
throw new PaymentException('Authorization failed', 0, $e);
}
}
}
Capture Flow
Capture Service
namespace Vendor\Payment\Service\Capture;
class CaptureService
{
public function capture(
OrderInterface $order,
float $amount = null
): CaptureResult {
$payment = $order->getPayment();
$authTransactionId = $payment->getAuthorizationTransaction();
$amount = $amount ?? $order->getGrandTotal();
// Validate capture amount
$this->validateCaptureAmount($order, $amount);
$gateway = $this->gatewayFactory->create($payment->getMethod());
$result = $gateway->capture(new CaptureRequest([
'transaction_id' => $authTransactionId,
'amount' => $amount,
'order_id' => $order->getIncrementId(),
]));
if ($result->isSuccess()) {
// Create capture transaction
$transaction = new TransactionData([
'order_id' => $order->getId(),
'transaction_id' => $result->getTransactionId(),
'parent_transaction_id' => $authTransactionId,
'type' => TransactionTypeInterface::TYPE_CAPTURE,
'amount' => $amount,
'status' => TransactionStatusInterface::STATUS_APPROVED,
]);
$this->transactionRepo->save($transaction);
// Update order if fully captured
$totalCaptured = $this->getTotalCaptured($order) + $amount;
if ($totalCaptured >= $order->getGrandTotal()) {
$payment->setIsTransactionClosed(true);
}
}
return $result;
}
private function validateCaptureAmount(
OrderInterface $order,
float $amount
): void {
$remaining = $this->getRemainingCaptureAmount($order);
if ($amount > $remaining) {
throw new PaymentException(
sprintf(
'Capture amount %s exceeds remaining %s',
$amount,
$remaining
)
);
}
}
}
Partial Capture
Partial Capture Implementation
namespace Vendor\Payment\Service\Capture;
class PartialCaptureService
{
public function captureItems(
OrderInterface $order,
array $items
): CaptureResult {
$captureAmount = 0;
foreach ($items as $item) {
$captureAmount += $item->getPrice() * $item->getQty();
}
// Add shipping if needed
if ($this->shouldCaptureShipping($order, $items)) {
$captureAmount += $order->getShippingAmount();
}
// Add tax if needed
if ($this->shouldCaptureTax($order, $items)) {
$captureAmount += $this->calculateTaxForItems($order, $items);
}
return $this->captureService->capture($order, $captureAmount);
}
}
Multi-Capture Tracking
namespace Vendor\Payment\Model\Transaction;
class CaptureTracker
{
public function getCapturedAmount(OrderInterface $order): float
{
$transactions = $this->transactionRepo->getByOrder(
$order->getId(),
TransactionTypeInterface::TYPE_CAPTURE
);
$total = 0;
foreach ($transactions as $transaction) {
if ($transaction->getStatus() === TransactionStatusInterface::STATUS_APPROVED) {
$total += $transaction->getAmount();
}
}
return $total;
}
public function getRemainingAmount(OrderInterface $order): float
{
return $order->getGrandTotal() - $this->getCapturedAmount($order);
}
}
Authorization Management
Authorization Expiration
namespace Vendor\Payment\Service\Auth;
class AuthorizationExpiration
{
private int $defaultExpirationDays = 7;
public function checkExpired(): array
{
$expiredAuths = $this->transactionRepo->getExpiredAuthorizations(
$this->defaultExpirationDays
);
foreach ($expiredAuths as $auth) {
$this->voidAuthorization($auth);
}
return $expiredAuths;
}
private function voidAuthorization(TransactionInterface $auth): void
{
$order = $this->orderRepo->get($auth->getOrderId());
$gateway = $this->gatewayFactory->create(
$order->getPayment()->getMethod()
);
$gateway->void(new VoidRequest([
'transaction_id' => $auth->getTransactionId(),
]));
$auth->setStatus(TransactionStatusInterface::STATUS_VOIDED);
$this->transactionRepo->save($auth);
}
}
Void Flow
namespace Vendor\Payment\Service\Void;
class VoidService
{
public function void(OrderInterface $order): VoidResult
{
$payment = $order->getPayment();
$authTransactionId = $payment->getAuthorizationTransaction();
// Check if any captures exist
$capturedAmount = $this->captureTracker->getCapturedAmount($order);
if ($capturedAmount > 0) {
throw new PaymentException(
'Cannot void order with existing captures. Use refund instead.'
);
}
$gateway = $this->gatewayFactory->create($payment->getMethod());
$result = $gateway->void(new VoidRequest([
'transaction_id' => $authTransactionId,
]));
if ($result->isSuccess()) {
$payment->setIsTransactionClosed(true);
$order->setState(Order::STATE_CANCELED);
}
return $result;
}
}
Quiz
1. What is the difference between authorize and capture?
2. What is partial capture?
3. Why void an authorization?
Flashcards
Question
What does authorize() do?
Click to reveal answer
Answer
Verifies and holds funds without collecting
Question
What does capture() do?
Click to reveal answer
Answer
Collects previously authorized funds
Question
What is partial capture?
Click to reveal answer
Answer
Capturing only a portion of the authorized amount
Question
When to void vs refund?
Click to reveal answer
Answer
Void cancels authorization; refund returns captured funds
Revision Notes
Key Takeaways
- 1. Authorize holds funds, capture collects them later
- 2. Partial capture allows collecting portions of authorized amount
- 3. Track all capture transactions against authorization
- 4. Void releases held funds before any capture
- 5. Authorizations expire after gateway-defined period
Interview Tips
- • Explain the full authorize → capture lifecycle
- • Discuss when partial capture is needed
- • Describe handling authorization expiration
- • Talk about void vs refund differences
Cheat Sheet
Payment Auth:
authorize() → hold funds (7-30 days)
capture() → collect held funds
partial → capture portion
void → release hold
Transaction Types:
AUTH → authorization
CAPTURE → capture
VOID → void
REFUND → refund
Checks:
Remaining = auth_amount - captured
Expired = auth older than 7 days