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

feat: double redirection protection in webpayplus #250

Merged
merged 21 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions plugin/shared/Exceptions/Webpay/AlreadyProcessedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Transbank\Plugin\Exceptions\Webpay;

use Transbank\Plugin\Exceptions\BaseException;

class AlreadyProcessedException extends BaseException
{
private $transaction;
private $flow;

public function __construct(
$message,
$transaction,
$flow,
\Exception $previous = null
) {
$this->transaction = $transaction;
$this->flow = $flow;
parent::__construct($message, $previous);
}

public function getTransaction()
{
return $this->transaction;
}

public function getFlow()
{
return $this->flow;
}
}
10 changes: 0 additions & 10 deletions plugin/shared/Exceptions/Webpay/WithoutTokenWebpayException.php

This file was deleted.

14 changes: 7 additions & 7 deletions plugin/src/Blocks/js/notice_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ const params = new URLSearchParams(url.search);
const hasTbkData = params.has('transbank_status');

const noticeTypes = {
SUCESS: 'success',
SUCCESS: 'success',
ERROR: 'error'
}

const oneClickNoticeData = {
0: { message: 'La tarjeta ha sido inscrita satisfactoriamente. Aún no se realiza ningún cobro. Ahora puedes realizar el pago.',
type: noticeTypes.SUCESS },
type: noticeTypes.SUCCESS },
1: { message: 'La inscripción fue cancelada automáticamente por estar inactiva mucho tiempo.',
type: noticeTypes.ERROR },
2: { message: 'No se recibió el token de la inscripción.',
Expand All @@ -27,12 +27,12 @@ const oneClickNoticeData = {

const webPayNoticeNoticeData = {
7: { message: 'Transacción aprobada',
type: noticeTypes.SUCESS },
8: { message: 'El usuario intentó pagar esta orden nuevamente, cuando esta ya estaba pagada.',
type: noticeTypes.SUCCESS },
8: { message: 'Ocurrió un error al confirmar la transacción, ya se encontraba procesada.',
type: noticeTypes.ERROR },
9: { message: 'El usuario intentó pagar una orden con estado inválido.',
type: noticeTypes.ERROR },
10: { message: 'La transacción fue cancelada automáticamente por estar inactiva mucho tiempo en el formulario de pago de Webpay. Puede reintentar el pago',
10: { message: 'La transacción fue cancelada por exceder el tiempo en el formulario de Webpay.',
type: noticeTypes.ERROR },
11: { message: 'El usuario canceló la transacción en el formulario de pago, pero esta orden ya estaba pagada o en un estado diferente a INICIALIZADO',
type: noticeTypes.ERROR },
Expand All @@ -44,7 +44,7 @@ const webPayNoticeNoticeData = {
type: noticeTypes.ERROR },
15: { message: 'El commit de la transacción ha sido rechazada en Transbank',
type: noticeTypes.ERROR },
16: { message: 'Ocurrió un error al ejecutar el commit de la transacción.',
16: { message: 'Ocurrió un error al confirmar la transacción, favor intente nuevamente.',
type: noticeTypes.ERROR },
17: { message: 'Ocurrió un error inesperado.',
type: noticeTypes.ERROR }
Expand All @@ -60,7 +60,7 @@ export const noticeHandler = ( productId ) => {
const noticeMessage = productNoticeData[statusCode]['message'];
const notificationType = productNoticeData[statusCode]['type'];
switch (notificationType){
case noticeTypes.SUCESS:
case noticeTypes.SUCCESS:
wp.data.dispatch('core/notices').createSuccessNotice( noticeMessage, { context: 'wc/checkout' } );
break;
case noticeTypes.ERROR:
Expand Down
92 changes: 42 additions & 50 deletions plugin/src/Controllers/ResponseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Transbank\WooCommerce\WebpayRest\Models\Transaction;
use Transbank\WooCommerce\WebpayRest\Helpers\HposHelper;
use Transbank\WooCommerce\WebpayRest\Helpers\BlocksHelper;
use Transbank\Plugin\Exceptions\Webpay\AlreadyProcessedException;
use Transbank\Plugin\Exceptions\Webpay\TimeoutWebpayException;
use Transbank\Plugin\Exceptions\Webpay\UserCancelWebpayException;
use Transbank\Plugin\Exceptions\Webpay\DoubleTokenWebpayException;
Expand All @@ -24,11 +25,10 @@ class ResponseController
* @var array
*/
protected $pluginConfig;

protected $logger;

/**
* @var Transbank\WooCommerce\WebpayRest\WebpayplusTransbankSdk
* @var \Transbank\WooCommerce\WebpayRest\WebpayplusTransbankSdk
*/
protected $webpayplusTransbankSdk;

Expand All @@ -48,64 +48,47 @@ public function __construct(array $pluginConfig)
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \Transbank\Plugin\Exceptions\TokenNotFoundOnDatabaseException
*/
public function response($postData)

public function response($requestMethod, $params)
{
$requestData = [
'method' => $requestMethod,
'params' => $params
];

try {
$transaction = $this->webpayplusTransbankSdk->processRequestFromTbkReturn($_SERVER, $_GET, $_POST);
$transaction = $this->webpayplusTransbankSdk->handleRequestFromTbkReturn($requestData);
$wooCommerceOrder = $this->getWooCommerceOrderById($transaction->order_id);
if ($wooCommerceOrder->is_paid()) {
// TODO: Revisar porqué no se muestra el mensaje de abajo. H4x
//SessionMessageHelper::set('Orden <strong>ya ha sido pagada</strong>.', 'notice');
$errorMessage = 'El usuario intentó pagar esta orden nuevamente, cuando esta ya estaba pagada.';
$this->webpayplusTransbankSdk->logError($errorMessage);
$this->webpayplusTransbankSdk->saveTransactionWithErrorByTransaction($transaction, 'transbank_webpay_plus_already_paid_transaction', $errorMessage);
$wooCommerceOrder->add_order_note($errorMessage);
do_action('transbank_webpay_plus_already_paid_transaction', $wooCommerceOrder);
return wp_safe_redirect($wooCommerceOrder->get_checkout_order_received_url());
}
if (!$wooCommerceOrder->needs_payment()) {
// TODO: Revisar porqué no se muestra el mensaje de abajo.
//SessionMessageHelper::set('El estado de la orden no permite que sea pagada. Comuníquese con la tienda.', 'error');
$errorMessage = 'El usuario intentó pagar la orden cuando estaba en estado: '.$wooCommerceOrder->get_status().".\n".'No se ejecutó captura del pago de esta solicitud.';
$this->webpayplusTransbankSdk->logError($errorMessage);
$this->webpayplusTransbankSdk->saveTransactionWithErrorByTransaction($transaction, 'transbank_webpay_plus_paying_transaction_that_does_not_needs_payment', $errorMessage);
$wooCommerceOrder->add_order_note($errorMessage);
do_action('transbank_webpay_plus_paying_transaction_that_does_not_needs_payment', $wooCommerceOrder);
return wp_safe_redirect($wooCommerceOrder->get_checkout_order_received_url());
}
$commitResponse = $this->webpayplusTransbankSdk->commitTransaction($transaction->order_id, $transaction->token);
$this->completeWooCommerceOrder($wooCommerceOrder, $commitResponse, $transaction);

do_action('wc_transbank_webpay_plus_transaction_approved', [
'order' => $wooCommerceOrder->get_data(),
'transbankTransaction' => $transaction
]);
return wp_redirect($wooCommerceOrder->get_checkout_order_received_url());

} catch (TimeoutWebpayException $e) {
$this->throwError($e->getMessage());

do_action('transbank_webpay_plus_timeout_on_form');
$urlWithErrorCode = $this->addErrorQueryParams(wc_get_checkout_url(), BlocksHelper::WEBPAY_TIMEOUT);
wp_redirect($urlWithErrorCode);
return wp_redirect($urlWithErrorCode);
} catch (UserCancelWebpayException $e) {
$params = ['transbank_webpayplus_cancelled_order' => 1];
$redirectUrl = add_query_arg($params, wc_get_checkout_url());
$transaction = $e->getTransaction();
$wooCommerceOrder = $this->getWooCommerceOrderById($transaction->order_id);
if ($transaction->status !== Transaction::STATUS_INITIALIZED || $wooCommerceOrder->is_paid()) {
$wooCommerceOrder->add_order_note('El usuario canceló la transacción en el formulario de pago, pero esta orden ya estaba pagada o en un estado diferente a INICIALIZADO');
wp_safe_redirect($redirectUrl);
return;
}
$this->setOrderAsCancelledByUser($wooCommerceOrder, $transaction);

do_action('transbank_webpay_plus_transaction_cancelled_by_user', $wooCommerceOrder, $transaction);
$urlWithErrorCode = $this->addErrorQueryParams($redirectUrl, BlocksHelper::WEBPAY_USER_CANCELED);
wp_safe_redirect($urlWithErrorCode);
return;
return wp_safe_redirect($urlWithErrorCode);
} catch (DoubleTokenWebpayException $e) {
$this->throwError($e->getMessage());

do_action('transbank_webpay_plus_unexpected_error');
$urlWithErrorCode = $this->addErrorQueryParams(wc_get_checkout_url(), BlocksHelper::WEBPAY_DOUBLE_TOKEN);
wp_redirect($urlWithErrorCode);
return wp_redirect($urlWithErrorCode);
} catch (InvalidStatusWebpayException $e) {
$errorMessage = 'No se puede confirmar la transacción, estado de transacción invalido.';
$wooCommerceOrder = $this->getWooCommerceOrderById($transaction->order_id);
Expand All @@ -115,7 +98,6 @@ public function response($postData)
'order' => $wooCommerceOrder->get_data(),
'transbankTransaction' => $e->getTransaction()
]);

$urlWithErrorCode = $this->addErrorQueryParams(wc_get_checkout_url(), BlocksHelper::WEBPAY_INVALID_STATUS);
return wp_redirect($urlWithErrorCode);
} catch (RejectedCommitWebpayException $e) {
Expand All @@ -129,7 +111,6 @@ public function response($postData)
'transbankTransaction' => $transaction,
'transbankResponse' => $commitResponse
]);

return wp_redirect($wooCommerceOrder->get_checkout_order_received_url());
} catch (CommitWebpayException $e) {
$errorMessage = 'Error al confirmar la transacción de Transbank';
Expand All @@ -140,16 +121,27 @@ public function response($postData)
'order' => $wooCommerceOrder->get_data(),
'transbankTransaction' => $e->getTransaction()
]);

$urlWithErrorCode = $this->addErrorQueryParams(wc_get_checkout_url(), BlocksHelper::WEBPAY_COMMIT_ERROR);
return wp_redirect($urlWithErrorCode);
} catch (AlreadyProcessedException $e) {
$errorMessage = 'Error al confirmar la transacción, ya fue procesada anteriormente';
$transaction = $e->getTransaction();
$order_id = $transaction['order_id'];
gasspper marked this conversation as resolved.
Show resolved Hide resolved
$wooCommerceOrder = $this->getWooCommerceOrderById($order_id);
$wooCommerceOrder->add_order_note($errorMessage);

if ($e->getFlow() == 'Normal') {
gasspper marked this conversation as resolved.
Show resolved Hide resolved
return wp_redirect($wooCommerceOrder->get_checkout_order_received_url());
}

$urlWithErrorCode = $this->addErrorQueryParams(wc_get_checkout_url(), BlocksHelper::WEBPAY_ALREADY_PROCESSED);
return wp_redirect($urlWithErrorCode);
} catch (\Exception $e) {
$this->throwError($e->getMessage());
do_action('transbank_webpay_plus_unexpected_error');
$urlWithErrorCode = $this->addErrorQueryParams(wc_get_checkout_url(), BlocksHelper::WEBPAY_EXCEPTION);
wp_redirect($urlWithErrorCode);
}
return "";
}

/**
Expand Down Expand Up @@ -189,8 +181,7 @@ protected function completeWooCommerceOrder(
WC_Order $wooCommerceOrder,
TransactionCommitResponse $commitResponse,
$webpayTransaction
)
{
) {
$status = TbkResponseUtil::getStatus($commitResponse->getStatus());
$paymentType = TbkResponseUtil::getPaymentType($commitResponse->getPaymentTypeCode());
$date_accepted = new DateTime($commitResponse->getTransactionDate(), new DateTimeZone('UTC'));
Expand Down Expand Up @@ -221,7 +212,8 @@ protected function completeWooCommerceOrder(

$maskedBuyOrder = $this->webpayplusTransbankSdk->dataMasker->maskBuyOrder($commitResponse->getBuyOrder());
$this->logger->logInfo(
'C.5. Transacción con commit exitoso en Transbank y guardado => OC: '.$maskedBuyOrder);
'C.5. Transacción con commit exitoso en Transbank y guardado => OC: ' . $maskedBuyOrder
);

$this->setAfterPaymentOrderStatus($wooCommerceOrder);
}
Expand All @@ -235,8 +227,7 @@ protected function setWooCommerceOrderAsFailed(
WC_Order $wooCommerceOrder,
$webpayTransaction,
TransactionCommitResponse $commitResponse
)
{
) {
$_SESSION['woocommerce_order_failed'] = true;
$wooCommerceOrder->update_status('failed');
if ($commitResponse !== null) {
Expand All @@ -249,14 +240,14 @@ protected function setWooCommerceOrderAsFailed(
$webpayTransaction->token
);

$this->logger->logError('C.5. Respuesta de tbk commit fallido => token: '.$webpayTransaction->token);
$this->logger->logError('C.5. Respuesta de tbk commit fallido => token: ' . $webpayTransaction->token);
$this->logger->logError(json_encode($commitResponse));
}

Transaction::update(
$webpayTransaction->id,
[
'status' => Transaction::STATUS_FAILED,
'status' => Transaction::STATUS_FAILED,
'transbank_response' => json_encode($commitResponse),
]
);
Expand Down Expand Up @@ -329,18 +320,19 @@ protected function throwError(string $msg)
/**
* @param WC_Order $order
*/
private function setAfterPaymentOrderStatus(WC_Order $order){
private function setAfterPaymentOrderStatus(WC_Order $order)
{
$status = $this->pluginConfig['STATUS_AFTER_PAYMENT'];
if ($status == ''){
if ($status == '') {
$order->payment_complete();
}
else{
} else {
$order->payment_complete();
$order->update_status($status);
}
}

protected function addErrorQueryParams($url, $errorCode) {
protected function addErrorQueryParams($url, $errorCode)
{
$params = ['transbank_status' => $errorCode];
return add_query_arg($params, $url);
}
Expand Down
8 changes: 5 additions & 3 deletions plugin/src/Helpers/BlocksHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class BlocksHelper
const ONECLICK_FINISH_ERROR = 5;
const ONECLICK_REJECTED_INSCRIPTION = 6;
const WEBPAY_APPROVED = 7;
const WEBPAY_ALREADY_PAID = 8;
const WEBPAY_ALREADY_PROCESSED = 8;
const WEBPAY_INVALID_ORDER_STATUS = 9;
const WEBPAY_TIMEOUT = 10;
const WEBPAY_USER_CANCELED_ALREADY_PAID = 11;
Expand All @@ -24,14 +24,16 @@ class BlocksHelper
const WEBPAY_COMMIT_ERROR = 16;
const WEBPAY_EXCEPTION = 17;

public static function checkBlocksEnabled() {
public static function checkBlocksEnabled()
{

$checkout_page = wc_get_page_id('checkout');
return has_block('woocommerce/checkout', $checkout_page);

}

public static function addLegacyNotices($message, $type) {
public static function addLegacyNotices($message, $type)
{
if (self::checkBlocksEnabled()) {
return;
}
Expand Down
5 changes: 3 additions & 2 deletions plugin/src/Models/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ class Transaction extends BaseModel

const STATUS_PREPARED = 'prepared';
const STATUS_INITIALIZED = 'initialized';
const STATUS_FAILED = 'failed';
const STATUS_ABORTED_BY_USER = 'aborted_by_user';
const STATUS_APPROVED = 'approved';
const STATUS_TIMEOUT = 'timeout';
const STATUS_ABORTED_BY_USER = 'aborted_by_user';
const STATUS_FAILED = 'failed';

const PRODUCT_WEBPAY_PLUS = 'webpay_plus';
const PRODUCT_WEBPAY_ONECLICK = 'webpay_oneclick';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,10 @@ public function check_ipn_response()
ob_clean();
if (isset($_POST)) {
header('HTTP/1.1 200 OK');
$data = ($_SERVER['REQUEST_METHOD'] === 'GET') ? $_GET : $_POST;
$requestMethod = $_SERVER['REQUEST_METHOD'];
$params = ($requestMethod === 'GET') ? $_GET : $_POST;
mastudillot marked this conversation as resolved.
Show resolved Hide resolved

return (new ResponseController($this->config))->response($data);
return (new ResponseController($this->config))->response($requestMethod, $params);
} else {
echo 'Ocurrió un error al procesar su compra';
}
Expand Down
Loading
Loading