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:
- Magento 2 Payment Adapter Configuration
- Magento 2 Payment Partial Capture Request
- Magento 2 Payment Gateway API
Get updates quicker by following me @max_pronko on Twitter and Facebook Page.
Have a great week.
Leave a Reply
You must be logged in to post a comment.