Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow gateway transactions to voided #1579

Open
wants to merge 5 commits into
base: 4.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public static function t($message, $params = [], $language = null)
* @inheritDoc
*/

public $schemaVersion = '3.1.13';
public $schemaVersion = '3.1.22';

/**
* @inheritdoc
Expand Down Expand Up @@ -362,6 +362,9 @@ private function _registerPermissions()
'commerce-capturePayment' => [
'label' => self::t('Capture payment')
],
'commerce-voidPayment' => [
'label' => self::t('Void payment')
],
'commerce-refundPayment' => [
'label' => self::t('Refund payment')
],
Expand Down
196 changes: 187 additions & 9 deletions src/base/Gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@
use Craft;
use craft\base\SavableComponent;
use craft\commerce\elements\Order;
use craft\commerce\errors\NotImplementedException;
use craft\commerce\models\payments\BasePaymentForm;
use craft\commerce\models\PaymentSource;
use craft\commerce\models\Transaction;
use craft\commerce\Plugin;
use craft\helpers\StringHelper;
use craft\helpers\UrlHelper;
use craft\web\Response as WebResponse;
use Throwable;

/**
* Class Gateway
Expand Down Expand Up @@ -99,6 +104,14 @@ public function rules()
];
}

/**
* returns the void window in seconds
*/
public function getVoidWindow()
{

}

/**
* Returns the html to use when paying with a stored payment source.
*
Expand All @@ -119,18 +132,183 @@ public function availableForUseWithOrder(Order $order): bool
}

/**
* Returns payment Form HTML
*
* @param array $params
* @return string|null
* @inheritDoc
*/
abstract public function getPaymentFormHtml(array $params);
public function getPaymentFormHtml(array $params)
{
return '';
}

/**
* Returns the transaction hash based on a webhook request
*
* @return string|null
* @since 3.1.9
* @inheritDoc
*/
public function authorize(Transaction $transaction, BasePaymentForm $form): RequestResponseInterface
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function capture(Transaction $transaction, string $reference): RequestResponseInterface
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function completeAuthorize(Transaction $transaction): RequestResponseInterface
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function completePurchase(Transaction $transaction): RequestResponseInterface
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function createPaymentSource(BasePaymentForm $sourceData, int $userId): PaymentSource
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function deletePaymentSource($token): bool
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function getPaymentFormModel(): BasePaymentForm
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function purchase(Transaction $transaction, BasePaymentForm $form): RequestResponseInterface
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function refund(Transaction $transaction): RequestResponseInterface
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function void(Transaction $transaction): RequestResponseInterface
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function processWebHook(): WebResponse
{
throw new NotImplementedException('Not implemented by the payment gateway');
}

/**
* @inheritDoc
*/
public function supportsAuthorize(): bool
{
return false;
}

/**
* @inheritDoc
*/
public function supportsCapture(): bool
{
return false;
}

/**
* @inheritDoc
*/
public function supportsCompleteAuthorize(): bool
{
return false;
}

/**
* @inheritDoc
*/
public function supportsCompletePurchase(): bool
{
return false;
}

/**
* @inheritDoc
*/
public function supportsPaymentSources(): bool
{
return false;
}

/**
* @inheritDoc
*/
public function supportsPurchase(): bool
{
return false;
}

/**
* @inheritDoc
*/
public function supportsRefund(): bool
{
return false;
}

/**
* @inheritDoc
*/
public function supportsPartialRefund(): bool
{
return false;
}

/**
* @inheritDoc
*/
public function supportsVoid(): bool
{
return false;
}

/**
* @inheritDoc
*/
public function supportsWebhooks(): bool
{
return false;
}

/**
* @inheritDoc
*/
public function getTransactionHashFromWebhook()
{
Expand Down
15 changes: 15 additions & 0 deletions src/base/GatewayInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ public function purchase(Transaction $transaction, BasePaymentForm $form): Reque
*/
public function refund(Transaction $transaction): RequestResponseInterface;

/**
* Makes an void request.
*
* @param Transaction $transaction The voidable transaction
* @return RequestResponseInterface
*/
public function void(Transaction $transaction): RequestResponseInterface;

/**
* Processes a webhook and return a response
*
Expand Down Expand Up @@ -165,6 +173,13 @@ public function supportsRefund(): bool;
*/
public function supportsPartialRefund(): bool;

/**
* Returns true if gateway supports void requests.
*
* @return bool
*/
public function supportsVoid(): bool;

/**
* Returns true if gateway supports webhooks.
*
Expand Down
71 changes: 58 additions & 13 deletions src/controllers/OrdersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,44 @@ public function actionTransactionCapture(): Response
return $this->redirectToPostedUrl();
}

/**
* Voids Transaction
*
* @return Response
* @throws TransactionException
* @throws MissingComponentException
* @throws BadRequestHttpException
*/
public function actionTransactionVoid(): Response
{
$this->requirePermission('commerce-VoidPayment');
$this->requirePostRequest();
$id = Craft::$app->getRequest()->getRequiredBodyParam('id');
$transaction = Plugin::getInstance()->getTransactions()->getTransactionById($id);

if ($transaction->canVoid()) {
// void transaction and display result
$child = Plugin::getInstance()->getPayments()->voidTransaction($transaction);

$message = $child->message ? ' (' . $child->message . ')' : '';

if ($child->status == TransactionRecord::STATUS_SUCCESS) {
$child->order->updateOrderPaidInformation();
Craft::$app->getSession()->setNotice(Plugin::t('Transaction voided successfully: {message}', [
'message' => $message
]));
} else {
Craft::$app->getSession()->setError(Plugin::t('Couldn’t void transaction: {message}', [
'message' => $message
]));
}
} else {
Craft::$app->getSession()->setError(Plugin::t('Couldn’t void transaction.', ['id' => $id]));
}

return $this->redirectToPostedUrl();
}

/**
* Refunds transaction.
*
Expand Down Expand Up @@ -1293,19 +1331,26 @@ private function _getTransactionsWIthLevelsTableArray($transactions, $level = 0)
$user = Craft::$app->getUser()->getIdentity();
foreach ($transactions as $transaction) {
if (!ArrayHelper::firstWhere($return, 'id', $transaction->id)) {
$refundCapture = '';
if ($user->can('commerce-capturePayment') && $transaction->canCapture()) {
$refundCapture = Craft::$app->getView()->renderTemplate(
'commerce/orders/includes/_capture',
[
'currentUser' => $user,
'transaction' => $transaction,
]
);
} else if ($user->can('commerce-refundPayment') && $transaction->canRefund()) {
$refundCapture = Craft::$app->getView()->renderTemplate(
'commerce/orders/includes/_refund',
$refundCaptureVoid = '';
$actions = [];

if ($user->can('commerce-capturePayment') && $canCapture = $transaction->canCapture()) {
$actions[] = 'capture';
}

if ($user->can('commerce-voidPayment') && $canVoid = $transaction->canVoid()) {
$actions[] = 'void';
}

if (!$canCapture && !$canVoid && $user->can('commerce-refundPayment') && $transaction->canRefund()) {
$actions[] = 'refund';
}

if (!empty($actions)) {
$refundCaptureVoid = Craft::$app->getView()->renderTemplate(
'commerce/orders/includes/_refundCaptureVoid',
[
'actions' => $actions,
'currentUser' => $user,
'transaction' => $transaction,
]
Expand Down Expand Up @@ -1345,7 +1390,7 @@ private function _getTransactionsWIthLevelsTableArray($transactions, $level = 0)
['label' => Html::encode(Plugin::t('Converted Price')), 'type' => 'text', 'value' => Plugin::getInstance()->getPaymentCurrencies()->convert($transaction->paymentAmount, $transaction->paymentCurrency) . ' <small class="light">(' . $transaction->currency . ')</small>' . ' <small class="light">(1 ' . $transaction->currency . ' = ' . number_format($transaction->paymentRate) . ' ' . $transaction->paymentCurrency . ')</small>'],
['label' => Html::encode(Plugin::t('Gateway Response')), 'type' => 'response', 'value' => $transactionResponse],
],
'actions' => $refundCapture,
'actions' => $refundCaptureVoid,
];

if (!empty($transaction->childTransactions)) {
Expand Down
7 changes: 6 additions & 1 deletion src/elements/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -2244,10 +2244,15 @@ public function getTotalPaid(): float
return $transaction->status == TransactionRecord::STATUS_SUCCESS && $transaction->type == TransactionRecord::TYPE_REFUND;
});

$voidedTransactions = ArrayHelper::where($this->_transactions, static function(Transaction $transaction) {
return $transaction->status == TransactionRecord::STATUS_SUCCESS && $transaction->type == TransactionRecord::TYPE_VOID;
});

$paid = array_sum(ArrayHelper::getColumn($paidTransactions, 'amount', false));
$refunded = array_sum(ArrayHelper::getColumn($refundedTransactions, 'amount', false));
$voided = array_sum(ArrayHelper::getColumn($voidedTransactions, 'amount', false));

return $paid - $refunded;
return $paid - ($refunded + $voided);
}

/**
Expand Down