Magento 2 Payments Capture Partial Explained

In my previous Payment Capture Partial Request article we have opened an important question, how do we call capture and capture_partial commands based on different conditions? This article will help to configure Partial Capture Payment command and trigger this command based on Authorization Transaction.

When we need to render Invoice form with Product items for edit we have an opportunity to specify whether payment method supports partial payment. So Merchant, when Saving Invoice can specify QTY to invoice. It is interesting to notice that Action Controller triggered to prepare and register invoice to capture knows nothing about partial capture. It is logical, taking into account that Invoice::register() method triggers Invoice::capture() method in case Capture is allowed by payment method.

[php]
namespace Magento\Sales\Model\Order;

class Invoice extends AbstractModel implements EntityInterface, InvoiceInterface
{
public function register()
{
//…

$captureCase = $this->getRequestedCaptureCase();
if ($this->canCapture()) {
if ($captureCase) {
if ($captureCase == self::CAPTURE_ONLINE) {
$this->capture();
} elseif ($captureCase == self::CAPTURE_OFFLINE) {
$this->setCanVoidFlag(false);
$this->pay();
}
}
} elseif () {

}

//…
}

public function capture()
{
$this->getOrder()->getPayment()->capture($this);
if ($this->getIsPaid()) {
$this->pay();
}
return $this;
}
}
[/php]

As we can see from the register() and capture() methods there is no Partial Capture support. It seems like there is a room for improvement, but in this article we won’t refactor Invoice class.

Going back to Payment Gateway implementation, our goal is to prepare 2 different commands.

As discussed in the Payment Gateway Configuration all payment commands processed by the Magento\Payment\Model\Method\Adapter class. The capture() method passes payment and amount variables for further processing inside command:
[php]
public function capture(InfoInterface $payment, $amount)
{
$this->executeCommand(
‘capture’,
[‘payment’ => $payment, ‘amount’ => $amount]
);
return $this;
}
[/php]

Configuration

As Magento 2 has been built with configuration in mind, allowing us to configure absolutely everything (with few exceptions), let’s have a look on how Capture and Partial Capture configuration might look like.

Here is an example of the paymentCaptureBuilder virtual builder which is based on the Magento\Payment\Gateway\Request\BuilderComposite class. For builders argument we pass 3 builder virtual types. These builders classes should implement the Magento\Payment\Gateway\Request\BuilderInterface interface.

[php]
<virtualType name="paymentCaptureBuilder" type="Magento\Payment\Gateway\Request\BuilderComposite">
<arguments>
<argument name="builders" xsi:type="array">
<item name="part1" xsi:type="string">capturePart1Builder</item>
<item name="part2" xsi:type="string">capturePart2Builder</item>
<item name="part3" xsi:type="string">capturePart3Builder</item>
</argument>
</arguments>
</virtualType>
[/php]

In order to prepare Partial Capture command we have to declare the paymentPartialCaptureBuilder virtual type. Prety much same way as paymentCaptureBuilder, the only difference might be in part4 builder class.

[php]
<virtualType name="paymentPartialCaptureBuilder" type="Magento\Payment\Gateway\Request\BuilderComposite">
<arguments>
<argument name="builders" xsi:type="array">
<item name="part1" xsi:type="string">partialCapturePart1Builder</item>
<item name="part2" xsi:type="string">partialCapturePart2Builder</item>
<item name="part4" xsi:type="string">partialCapturePart4Builder</item>
</argument>
</arguments>
</virtualType>
[/php]

Going forward, both paymentCaptureBuilder and paymentPartialCaptureBuilder virtual types are passed into the Pronko\Payment\Gateway\Request\CaptureStrategyBuilder class (implementation of the class below in this article). This is our silver bullet – the CaptureStrategyBuilder class incapsulates decision making logic for whether paymentCaptureBuilder virtual builder or the paymentPartialCaptureBuilder builder should be executed.
[php]
<type name="Pronko\Payment\Gateway\Request\CaptureStrategyBuilder">
<arguments>
<argument name="captureBuilderComposite" xsi:type="object">paymentCaptureBuilder</argument>
<argument name="partialCaptureBuilderComposite" xsi:type="object">paymentPartialCaptureBuilder</argument>
</arguments>
</type>
[/php]
Finally the Pronko\Payment\Gateway\Request\CaptureStrategyBuilder class is passed via requestBuilder argument for the paymentCaptureGatewayCommand command.

[php]
<virtualType name="paymentCaptureGatewayCommand" type="Magento\Payment\Gateway\Command\GatewayCommand">
<arguments>
<argument name="requestBuilder" xsi:type="object">Pronko\Payment\Gateway\Request\CaptureStrategyBuilder</argument>
<argument name="transferFactory" xsi:type="object">Pronko\Payment\Gateway\Http\TransferFactory</argument>
<argument name="client" xsi:type="object">Pronko\Payment\Gateway\Http\Client\Zend</argument>
<argument name="validator" xsi:type="object">Pronko\Payment\Gateway\Validator\ResponseValidator</argument>
</arguments>
</virtualType>
[/php]

The paymentCaptureGatewayCommand virtual command class is passed into paymentCommandPool virtual type.

[php]
<virtualType name="paymentCommandPool" type="Magento\Payment\Gateway\Command\CommandPool">
<arguments>
<argument name="commands" xsi:type="array">
<item name="capture" xsi:type="string">paymentCaptureGatewayCommand</item>
</argument>
</arguments>
</virtualType>
[/php]

The Capture Strategy Builder Class

The main role of our implementation plays the Pronko\Payment\Gateway\Request\CaptureStrategyBuilder class. The class implements BuilderInterface interface.

[php]
namespace Pronko\Payment\Gateway\Request;

use Magento\Payment\Gateway\Request\BuilderInterface;

class CaptureStrategyBuilder implements BuilderInterface
{
/**
* @var BuilderInterface
*/
private $captureBuilderComposite;

/**
* @var BuilderInterface
*/
private $partialCaptureBuilderComposite;

/**
* @param BuilderInterface $captureBuilderComposite
* @param BuilderInterface $partialCaptureBuilderComposite
*/
public function __construct(
BuilderInterface $captureBuilderComposite,
BuilderInterface $partialCaptureBuilderComposite
) {
$this->captureBuilderComposite = $captureBuilderComposite;
$this->partialCaptureBuilderComposite = $partialCaptureBuilderComposite;
}

/**
* @param array $buildSubject
* @return array
*/
public function build(array $buildSubject)
{
$paymentDO = SubjectReader::readPayment($buildSubject);
/** @var \Magento\Sales\Model\Order\Payment $payment */
$payment = $paymentDO->getPayment();
ContextHelper::assertOrderPayment($payment);

$authTransaction = $payment->getAuthorizationTransaction();
if ($authTransaction) {
return $this->partialCaptureBuilderComposite->build($buildSubject);
}

return $this->captureBuilderComposite->build($buildSubject);
}
[/php]

The build() method when called during capture command execution gets $buildSubject argument with Payment Data Object (The Magento\Payment\Gateway\Data\PaymentDataObjectInterface interface). This class provides Payment and Order instances ready to be used. In order to identify which builder should be triggered the Authorization Transaction should exist (in case Payment Method is configured to “Authorize Only” payment during placing an order).

Summary

There are might be other conditions which can trigger partial capture command. Once you have an idea on how to configure partial capture command you can extend or modify example provided in this article.

It might be a good exercise to revisit and refactor the Magento\Sales\Model\Order\Invoice class to include Partial Capture logic. Also, the Magento\Payment\Model\Method\Adapter class can include the capturePartial() method.

Recommended articles to read:

Get updates quicker by following me @max_pronko on Twitter and Facebook Page.

Have a great week.


Posted

in

,

by

Comments

Leave a Reply