Files
interblue.pl/modules/przelewy24/classes/Przelewy24ServiceRefund.php
2024-10-25 14:16:28 +02:00

428 lines
12 KiB
PHP

<?php
/**
* Class Przelewy24ServiceRefund
*
* This service has the features required for cash returns.
*
* @author Przelewy24
* @copyright Przelewy24
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html
*/
if (!defined('_PS_VERSION_')) {
exit;
}
/**
* Class Przelewy24ServiceRefund
*/
class Przelewy24ServiceRefund extends Przelewy24Service
{
/**
* Currency suffix.
*
* @var string
*/
private $suffix = '';
/**
* P24 REST api client.
*
* @var Przelewy24RestRefund
*/
private $restApi;
/**
* Array of allowed refund statuses.
*
* @var array
*/
private $status = [
0 => 'Refund error',
1 => 'Refund done',
3 => 'Awaiting for refund',
4 => 'Refund rejected',
];
/**
* Default refund status.
*
* @var string
*/
private $statusDefault = 'Unknown status of refund';
/**
* Przelewy24ServiceRefund constructor.
*
* @param Przelewy24 $przelewy24
* @param string $suffix
* @param Przelewy24RestRefund $restApi
*/
public function __construct(Przelewy24 $przelewy24, $suffix, $restApi)
{
$this->setSuffix($suffix);
$this->restApi = $restApi;
parent::__construct($przelewy24);
}
/**
* Set suffix.
*
* @param string $suffix
*/
public function setSuffix($suffix)
{
$this->suffix = $suffix;
}
/**
* Get status message.
*
* @param int $status
*
* @return string
*/
public function getStatusMessage($status)
{
$status = (int) $status;
$return = $this->statusDefault;
if (isset($this->status[$status])) {
$return = $this->status[$status];
}
return $return;
}
private function extractDate($input)
{
$rx = '/^(?P<Y>\\d{4})(?P<m>\\d{2})(?P<d>\\d{2})/';
if (preg_match($rx, $input, $m)) {
return $m['Y'] . '-' . $m['m'] . '-' . $m['d'];
} else {
return '';
}
}
/**
* Get refund data from Przelewy24.
*
* @param array $refundDataFromDb
*
* @return array
*/
public function getDataToRefund(array $refundDataFromDb)
{
$return = [];
$order = $this->restApi->transactionBySessionId($refundDataFromDb['sessionId']);
$refunds = $this->restApi->refundByOrderId($refundDataFromDb['p24OrderId']);
if (isset($order['data'])) {
$return = [
'sessionId' => (string) $order['data']['sessionId'],
'p24OrderId' => (int) $order['data']['orderId'],
'originalAmount' => (int) $order['data']['amount'],
'amount' => null, /* Reserve field position. */
'refunded' => 0,
'refunds' => [],
];
if (isset($refunds['data'])) {
foreach ($refunds['data']['refunds'] as $refund) {
$amountRefunded = (int) $refund['amount'];
$return['refunded'] += $amountRefunded;
$return['refunds'][] = [
'amount_refunded' => $amountRefunded,
'created' => $this->extractDate($refund['date']),
'status' => $this->status[$refund['status']],
];
}
}
$return['amount'] = $return['originalAmount'] - $return['refunded'];
}
return $return;
}
/**
* Gets refund data from database.
*
* @param $orderId
*
* @return array
*
* @throws PrestaShopDatabaseException
*/
public function getRefundDataFromDB($orderId)
{
$return = [];
$orderId = (int) $orderId;
$przelewy24Order = new Przelewy24Order();
$result = $przelewy24Order->getByPshopOrderId($orderId);
if ($result) {
$return = [
'sessionId' => $result->p24_session_id,
'p24OrderId' => $result->p24_order_id,
];
}
return $return;
}
/**
* Requests refund by Przelewy24.
*
* @param string $sessionId
* @param int $p24OrderId
* @param int $amountToRefund
*
* @return false|stdClass
*/
public function refundProcess($sessionId, $p24OrderId, $amountToRefund)
{
$sessionId = (string) $sessionId;
$p24OrderId = (int) $p24OrderId;
$amountToRefund = (int) $amountToRefund;
$response = $this->restApi->transactionRefund($p24OrderId, $sessionId, $amountToRefund);
if (isset($response['data'][0])) {
return $response['data'][0]['amount'] === $amountToRefund;
}
return false;
}
/**
* Update products for a refund.
*
* @param Order $order A order
* @param array $products List of products to refund
* @return bool
*/
public function updateProductsForRefund($order, $products)
{
$ok = true;
if (Configuration::get('P24_REFUND_WITH_ALTER_STOCK')) {
$ok &= $this->updateStock($order, $products);
}
$ok &= $this->addOrderSlip($order, $products);
$ok &= $this->updateOrderDetails($order, $products);
return (bool) $ok;
}
/**
* Update stock.
*
* @param Order $order A order
* @param array $products List of products to refund
* @return float|null
*/
private function updateStock($order, $products)
{
$totalPrice = 0.0;
$removed = false;
$orderProducts = $order->getProductsDetail();
foreach ($products as $productId => $quantity) {
if (!$quantity) {
continue;
}
foreach ($orderProducts as $orderProduct) {
if ((int) $orderProduct['id_order_detail'] === (int) $productId) {
$productObject = new Product($orderProduct['product_id']);
$stock = new StockAvailable();
$stock->updateQuantity(
$orderProduct['product_id'],
$orderProduct['product_attribute_id'],
$quantity
);
$totalPrice += round($productObject->price * $quantity, 2);
$totalPrice = round($totalPrice, 2);
$removed = true;
break;
}
}
}
if ($removed) {
$order->update();
return $totalPrice;
} else {
return null;
}
}
/**
* Update a order slip.
*
* @param Order $order A order
* @param array $products List of products to refund
* @return bool
*/
private function addOrderSlip(Order $order, $products)
{
$orderProducts = $order->getProductsDetail();
$productsToPass = [];
foreach ($products as $productId => $quantity) {
if (!$quantity) {
continue;
}
foreach ($orderProducts as $orderProduct) {
if ((int) $orderProduct['id_order_detail'] === (int) $productId) {
$quantity = min((int) $quantity, (int) $orderProduct['product_quantity']);
$orderProduct['quantity'] = $quantity;
$orderProduct['unit_price'] = $orderProduct['unit_price_tax_excl'];
$productsToPass[] = $orderProduct;
break;
}
}
}
return OrderSlip::create($order, $productsToPass);
}
/**
* Update all order dtails.
*
* @param Order $order A order
* @param array $products Array of products to update
* @return bool True on cucess
*/
private function updateOrderDetails(Order $order, $products)
{
$orderProducts = $order->getProductsDetail();
$success = true;
foreach ($products as $productId => $quantity) {
if (!$quantity) {
continue;
}
$productFound = false;
foreach ($orderProducts as $orderProduct) {
if ((int) $orderProduct['id_order_detail'] === (int) $productId) {
$quantity = min((int) $quantity, (int) $orderProduct['product_quantity']);
if ($quantity) {
$this->updateOneLineOfOrderDetails($productId, $quantity);
}
$productFound = true;
break;
}
}
if (!$productFound) {
$success = false;
}
}
$order->update();
return $success;
}
/**
* Internal update one order detail.
*
* @param int $id_order_detail Id of a order detail
* @param int $quantity Quantity of a product to refund
* @return void
*/
private function updateOneLineOfOrderDetails($id_order_detail, $quantity)
{
$orderDetail = new OrderDetail($id_order_detail);
if (!property_exists($orderDetail, 'total_refunded_tax_incl')) {
/* Old version. Nothing to do. */
return;
}
if (!property_exists($orderDetail, 'total_refunded_tax_excl')) {
/* Old version. Nothing to do. */
return;
}
$withTaxUnit = round($orderDetail->unit_price_tax_incl, 2);
$withoutTaxUnit = round($orderDetail->unit_price_tax_excl, 2);
$withTax = round($withTaxUnit * $quantity, 2);
$withoutTax = round($withoutTaxUnit * $quantity, 2);
$withTaxRefunded = round($orderDetail->total_refunded_tax_incl, 2);
$withoutTaxRefunded = round($orderDetail->total_refunded_tax_excl, 2);
$withTaxRefunded = round($withTaxRefunded + $withTax, 2);
$withoutTaxRefunded = round($withoutTaxRefunded + $withoutTax, 2);
$orderDetail->total_refunded_tax_incl = $withTaxRefunded;
$orderDetail->total_refunded_tax_excl = $withoutTaxRefunded;
$orderDetail->update();
}
/**
* Get list of products that may be refunded.
*
* @param Order $order Order that may be refunded
* @return array List of products
*/
public function extractProductsDetails($order)
{
$context = Context::getContext();
if (method_exists($context, 'getCurrentLocale')) {
return $this->extractProductsDetailsModern($order, $context);
} else {
return $this->extractProductsDetailsLegacy($order, $context);
}
}
/**
* Get list of products that may be refunded. Modern version.
*
* @param Order $order Order that may be refunded
* @param Context $context Context of request
* @return array List of products
*/
private function extractProductsDetailsModern($order, $context)
{
$currency = Currency::getCurrencyInstance($order->id_currency);
$locale = $context->getCurrentLocale();
$products = $order->getProductsDetail();
$ret = [];
foreach ($products as $product) {
$line = [
'productId' => $product['id_order_detail'],
'name' => $product['product_name'],
'quantity' => $product['product_quantity'] - $product['product_quantity_refunded'],
'price' => $product['unit_price_tax_incl'],
'priceFormatted' => $locale->formatPrice($product['unit_price_tax_incl'], $currency->iso_code),
];
$ret[] = $line;
}
return $ret;
}
/**
* Get list of products that may be refunded. Legacy version.
*
* It is used for versions older than 1.7.6.
*
* @param Order $order Order that may be refunded
* @param Context $context Context of request
* @return array List of products
*/
private function extractProductsDetailsLegacy($order, $context)
{
$currency = Currency::getCurrencyInstance($order->id_currency);
$products = $order->getProductsDetail();
$ret = [];
foreach ($products as $product) {
$line = [
'productId' => $product['id_order_detail'],
'name' => $product['product_name'],
'quantity' => $product['product_quantity'] - $product['product_quantity_refunded'],
'price' => $product['unit_price_tax_incl'],
'priceFormatted' => Tools::displayPrice($product['unit_price_tax_incl'], $currency, false, $context),
];
$ret[] = $line;
}
return $ret;
}
}