subscribepro-magento2-ext
subscribepro-magento2-ext copied to clipboard
Voids on already released Stripe Charges
Currently, if you try to cancel a pending order 7 days after an authorization has been made, you'll receive a Transaction Declined error.
This happens because stripe will release authorizations automatically after 7 days. When performing a cancel in the admin, the SP gateway will try to void the payment via the SP api which then tries a refund request on the given charge. Stripe will throw 40x error because you can't refund a charge that's already been refunded. SP Api then returns this error back to the client which in turn triggers the Transaction Declined error message.
Possible Solutions
- Update in the SP Api to where they will check stripe (and maybe even other gateway providers) to see if the charge has been already refunded and then return a successful response back to the Client
- Update in the SP api to return a specific error code when trying to refund a charge that's already been refunded, and then in the
Swarming\SubscribePro\Gateway\Validator::validate
check for that error code and allow the transaction to be seen as valid
Workaround
Currently, to get around this, I've built the following class like this:
<?php
namespace JoinEby\Sales\Gateway\Validator;
use Magento\Payment\Gateway\Validator\ValidatorInterface;
use Magento\Payment\Gateway\Validator\AbstractValidator;
use SubscribePro\Service\Transaction\TransactionInterface;
/**
* Stripe will release authorizations after 7 days. If you try to void a charge that's already been released, stripe will throw an error.
* This validator decorator will check for that specific error message and allow the transaction to be valid if that's the case.
*/
class StripeCancelledResponseValidator extends AbstractValidator implements ValidatorInterface
{
private $validator;
/**
* @var \Swarming\SubscribePro\Gateway\Helper\SubjectReader
*/
protected $subjectReader;
/**
* @param \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory
* @param \Swarming\SubscribePro\Gateway\Helper\SubjectReader $subjectReader
*/
public function __construct(
ValidatorInterface $validator,
\Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory,
\Swarming\SubscribePro\Gateway\Helper\SubjectReader $subjectReader
) {
parent::__construct($resultFactory);
$this->validator = $validator;
$this->subjectReader = $subjectReader;
}
/**
* @param array $validationSubject
* @return \Magento\Payment\Gateway\Validator\ResultInterface
* @throws \InvalidArgumentException
*/
public function validate(array $validationSubject)
{
$transaction = $this->subjectReader->readTransaction($validationSubject);
if ($transaction->getState() != TransactionInterface::STATE_GATEWAY_PROCESSING_FAILED) {
return $this->validator->validate($validationSubject);
}
$message = $transaction->getResponseMessage();
return $this->createResult(preg_match('/Charge .+ has already been refunded./i', $message) === 1);
}
}
and have the following entries in my di.xml
<!-- Decorate the SP Response Validator -->
<type name="JoinEby\Sales\Gateway\Validator\StripeCancelledResponseValidator">
<arguments>
<argument name="validator" xsi:type="object">Swarming\SubscribePro\Gateway\Validator\ResponseValidator</argument>
</arguments>
</type>
<!-- VoidCommand - override the validator -->
<type name="Swarming\SubscribePro\Gateway\Command\VoidCommand">
<arguments>
<argument name="validator" xsi:type="object">JoinEby\Sales\Gateway\Validator\StripeCancelledResponseValidator</argument>
</arguments>
</type>
This 100% is a brittle hack and will break if any of the messaging changes, but it works for now...