/** * Capture a previously authorized payment * * If the transaction-reference of the payment to capture is known, pass it via $data as * `transactionReference` parameter. Otherwise the service will try to look up the reference * from previous payment messages. * * If there's no transaction-reference to be found, this method will raise an exception. * * You can issue partial captures (if the gateway supports it) by passing an `amount` parameter in the $data * array. The amount can also exceed the authorized amount, if the configuration allows it (`max_capture` setting). * An amount that exceeds the authorized amount will always be considered as a full capture! * If the amount given is not a number, or if it exceeds the total possible capture amount, an exception * will be raised. * * @inheritdoc * @throws MissingParameterException if no transaction reference can be found from messages or parameters * @throws InvalidParameterException if the amount parameter was invalid */ public function initiate($data = array()) { if (!$this->payment->canCapture()) { throw new InvalidConfigurationException('Capture of this payment not allowed.'); } if (!$this->payment->isInDB()) { $this->payment->write(); } $reference = null; // If the gateway isn't manual, we need a transaction reference to refund a payment if (!GatewayInfo::isManual($this->payment->Gateway)) { if (!empty($data['transactionReference'])) { $reference = $data['transactionReference']; } elseif (!empty($data['receipt'])) { // legacy code? $reference = $data['receipt']; } else { $reference = $this->payment->TransactionReference; } if (empty($reference)) { throw new MissingParameterException('transactionReference not found and is not set as parameter'); } } $gateway = $this->oGateway(); if (!$gateway->supportsCapture()) { throw new InvalidConfigurationException(sprintf('The gateway "%s" doesn\'t support capture', $this->payment->Gateway)); } $authorized = $amount = $this->payment->MoneyAmount; $diff = 0; if (!empty($data['amount'])) { $amount = $data['amount']; if (!is_numeric($amount)) { throw new InvalidParameterException('The "amount" parameter has to be numeric.'); } if (!($amount > 0)) { throw new InvalidParameterException('The "amount" parameter has to be positive.'); } // check if the amount exceeds the max. amount that can be captured if (PaymentMath::compare($this->payment->getMaxCaptureAmount(), $amount) === -1) { throw new InvalidParameterException('The "amount" given exceeds the amount that can be captured.'); } $diff = PaymentMath::subtract($amount, $authorized); } if ($diff < 0 && !$this->payment->canCapture(null, true)) { throw new InvalidParameterException('This payment cannot be partially captured (unsupported by gateway).'); } $gatewayData = array_merge($data, array('amount' => (double) $amount, 'currency' => $this->payment->MoneyCurrency, 'transactionReference' => $reference, 'notifyUrl' => $this->getEndpointUrl('notify'))); $this->extend('onBeforeCapture', $gatewayData); $request = $this->oGateway()->capture($gatewayData); $this->extend('onAfterCapture', $request); $message = $this->createMessage($this->requestMessageType, $request); $message->write(); try { $response = $this->response = $request->send(); } catch (OmnipayException $e) { $this->createMessage($this->errorMessageType, $e); return $this->generateServiceResponse(ServiceResponse::SERVICE_ERROR); } Helper::safeExtend($this, 'onAfterSendCapture', $request, $response); $serviceResponse = $this->wrapOmnipayResponse($response); if ($serviceResponse->isAwaitingNotification()) { if ($diff < 0) { $this->createPartialPayment(PaymentMath::multiply($amount, '-1'), $this->pendingState); } elseif ($diff > 0) { $this->createPartialPayment($diff, $this->pendingState); } $this->payment->Status = $this->pendingState; $this->payment->write(); } else { if ($serviceResponse->isError()) { $this->createMessage($this->errorMessageType, $response); } else { if ($diff < 0) { $this->createPartialPayment(PaymentMath::multiply($amount, '-1'), $this->pendingState); } elseif ($diff > 0) { $this->createPartialPayment($diff, $this->pendingState); } $this->markCompleted($this->endState, $serviceResponse, $response); } } return $serviceResponse; }
/** * Calculate the max amount that can be captured for this payment. * If the Status of the payment isn't 'Authorized', this will return 0 * @return int|string the max amount that can be captured for this payment. */ public function getMaxCaptureAmount() { if ($this->Status !== 'Authorized') { return 0; } $percent = GatewayInfo::maxExcessCapturePercent($this->Gateway); $fixedAmount = GatewayInfo::maxExcessCaptureAmount($this->Gateway, $this->getCurrency()); // -1 will only be returned if there's a fixed amount, but no percentage. // We can safely return the fixed amount here if ($percent === -1) { return PaymentMath::add($this->MoneyAmount, $fixedAmount); } // calculate what amount the percentage will result in $percentAmount = PaymentMath::multiply(PaymentMath::multiply($percent, '0.01'), $this->MoneyAmount); // if there's no fixed amount and only the percentage is set, we can return the percentage amount right away. if ($fixedAmount === -1) { return PaymentMath::add($this->MoneyAmount, $percentAmount); } // If the amount from the percentage is smaller, use the percentage if (PaymentMath::compare($fixedAmount, $percentAmount) > 0) { return PaymentMath::add($this->MoneyAmount, $percentAmount); } // otherwise use the fixed amount return PaymentMath::add($this->MoneyAmount, $fixedAmount); }