classes/XLite/Model/Payment/Transaction.php line 68

Open in your IDE?
  1. <?php
  2. /**
  3.  * Copyright (c) 2001-present X-Cart Holdings LLC. All rights reserved.
  4.  * See https://www.x-cart.com/license-agreement.html for license details.
  5.  */
  6. namespace XLite\Model\Payment;
  7. use ApiPlatform\Core\Annotation as ApiPlatform;
  8. use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
  9. use Doctrine\ORM\Mapping as ORM;
  10. use XLite\API\Endpoint\OrderPaymentTransaction\DTO\OrderPaymentTransactionOutput as Output;
  11. use XLite\Core\Model\EntityLock\EntityLockTrait;
  12. /**
  13.  * Payment transaction
  14.  *
  15.  * @ORM\Entity
  16.  * @ORM\Table  (name="payment_transactions",
  17.  *      indexes={
  18.  *          @ORM\Index (name="status", columns={"status"}),
  19.  *          @ORM\Index (name="o", columns={"order_id","status"}),
  20.  *          @ORM\Index (name="pm", columns={"method_id","status"}),
  21.  *          @ORM\Index (name="publicTxnId", columns={"publicTxnId"})
  22.  *      }
  23.  * )
  24.  * @ApiPlatform\ApiResource(
  25.  *     output=Output::class,
  26.  *     itemOperations={},
  27.  *     collectionOperations={
  28.  *          "get"={
  29.  *              "method"="GET",
  30.  *              "path"="/orders/{id}/payment_transactions.{_format}",
  31.  *              "requirements"={"id"="\d+"},
  32.  *              "openapi_context"={
  33.  *                  "summary"="Retrieve a list of order payment transactions",
  34.  *                  "responses"={
  35.  *                      "404"={
  36.  *                          "description"="Resource not found"
  37.  *                      }
  38.  *                  },
  39.  *                  "parameters"={
  40.  *                      {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  41.  *                  }
  42.  *              }
  43.  *          },
  44.  *          "get_cart_payment_transactions"={
  45.  *              "method"="GET",
  46.  *              "path"="/carts/{id}/payment_transactions.{_format}",
  47.  *              "requirements"={"id"="\d+"},
  48.  *              "openapi_context"={
  49.  *                  "summary"="Retrieve a list of cart payment transactions",
  50.  *                  "responses"={
  51.  *                      "404"={
  52.  *                          "description"="Resource not found"
  53.  *                      }
  54.  *                  },
  55.  *                  "parameters"={
  56.  *                      {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  57.  *                  }
  58.  *              }
  59.  *          }
  60.  *     }
  61.  * )
  62.  * @ApiPlatform\ApiFilter(OrderFilter::class, properties={"date"}, arguments={"orderParameterName"="order"})
  63.  */
  64. class Transaction extends \XLite\Model\AEntity
  65. {
  66.     use EntityLockTrait;
  67.     public const LOCK_TYPE_IPN 'ipn';
  68.     /**
  69.      * Transaction status codes
  70.      */
  71.     public const STATUS_INITIALIZED 'I';
  72.     public const STATUS_INPROGRESS  'P';
  73.     public const STATUS_SUCCESS     'S';
  74.     public const STATUS_PENDING     'W';
  75.     public const STATUS_FAILED      'F';
  76.     public const STATUS_CANCELED    'C';
  77.     public const STATUS_VOID        'V';
  78.     /**
  79.      * Transaction initialization result
  80.      */
  81.     public const PROLONGATION 'R';
  82.     public const COMPLETED    'C';
  83.     public const SILENT       'S';
  84.     public const SEPARATE     'E';
  85.     /**
  86.      * Public token length
  87.      */
  88.     public const SUFFIX_TOKEN_LENGTH 4;
  89.     public const ORDERID_TOKEN_LENGTH 6;
  90.     /**
  91.      * Token characters list
  92.      *
  93.      * @var array
  94.      */
  95.     protected static $chars = [
  96.         '0''1''2''3''4''5''6''7''8''9',
  97.         'A''B''C''D''E''F''G''H''I''J',
  98.         'K''L''M''N''O''P''Q''R''S''T',
  99.         'U''V''W''X''Y''Z',
  100.     ];
  101.     /**
  102.      * Primary key
  103.      *
  104.      * @var integer
  105.      *
  106.      * @ORM\Id
  107.      * @ORM\GeneratedValue (strategy="AUTO")
  108.      * @ORM\Column         (type="integer")
  109.      */
  110.     protected $transaction_id;
  111.     /**
  112.      * Transaction creation timestamp
  113.      *
  114.      * @var integer
  115.      *
  116.      * @ORM\Column (type="integer")
  117.      */
  118.     protected $date;
  119.     /**
  120.      * Public transaction id
  121.      *
  122.      * @var string
  123.      *
  124.      * @ORM\Column (type="string", length=16, nullable=true)
  125.      */
  126.     protected $publicTxnId;
  127.     /**
  128.      * Payment method name
  129.      *
  130.      * @var string
  131.      *
  132.      * @ORM\Column (type="string", length=128)
  133.      */
  134.     protected $method_name;
  135.     /**
  136.      * Payment method localized name
  137.      *
  138.      * @var string
  139.      *
  140.      * @ORM\Column (type="string", length=255)
  141.      */
  142.     protected $method_local_name '';
  143.     /**
  144.      * Status
  145.      *
  146.      * @var string
  147.      *
  148.      * @ORM\Column (type="string", options={ "fixed": true }, length=1)
  149.      */
  150.     protected $status self::STATUS_INITIALIZED;
  151.     /**
  152.      * Transaction value
  153.      *
  154.      * @var float
  155.      *
  156.      * @ORM\Column (type="decimal", precision=14, scale=4)
  157.      */
  158.     protected $value 0.0000;
  159.     /**
  160.      * Customer message
  161.      *
  162.      * @var string
  163.      *
  164.      * @ORM\Column (type="string", length=255)
  165.      */
  166.     protected $note '';
  167.     /**
  168.      * Transaction type
  169.      *
  170.      * @var string
  171.      *
  172.      * @ORM\Column (type="string", length=16)
  173.      */
  174.     protected $type \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_SALE;
  175.     /**
  176.      * Public transaction ID
  177.      *
  178.      * @var string
  179.      *
  180.      * @ORM\Column (type="string")
  181.      */
  182.     protected $public_id '';
  183.     /**
  184.      * Order
  185.      *
  186.      * @var \XLite\Model\Order
  187.      *
  188.      * @ORM\ManyToOne  (targetEntity="XLite\Model\Order", inversedBy="payment_transactions")
  189.      * @ORM\JoinColumn (name="order_id", referencedColumnName="order_id", onDelete="CASCADE")
  190.      */
  191.     protected $order;
  192.     /**
  193.      * Payment method
  194.      *
  195.      * @var \XLite\Model\Payment\Method
  196.      *
  197.      * @ORM\ManyToOne  (targetEntity="XLite\Model\Payment\Method")
  198.      * @ORM\JoinColumn (name="method_id", referencedColumnName="method_id", onDelete="SET NULL")
  199.      */
  200.     protected $payment_method;
  201.     /**
  202.      * Transaction data
  203.      *
  204.      * @var \XLite\Model\Payment\TransactionData
  205.      *
  206.      * @ORM\OneToMany (targetEntity="XLite\Model\Payment\TransactionData", mappedBy="transaction", cascade={"all"})
  207.      */
  208.     protected $data;
  209.     /**
  210.      * Related backend transactions
  211.      *
  212.      * @var \XLite\Model\Payment\BackendTransaction
  213.      *
  214.      * @ORM\OneToMany (targetEntity="XLite\Model\Payment\BackendTransaction", mappedBy="payment_transaction", cascade={"all"})
  215.      */
  216.     protected $backend_transactions;
  217.     /**
  218.      * Currency
  219.      *
  220.      * @var \XLite\Model\Currency
  221.      *
  222.      * @ORM\ManyToOne  (targetEntity="XLite\Model\Currency", cascade={"merge","detach"})
  223.      * @ORM\JoinColumn (name="currency_id", referencedColumnName="currency_id")
  224.      */
  225.     protected $currency;
  226.     /**
  227.      * Readable statuses
  228.      *
  229.      * @var array
  230.      */
  231.     protected $readableStatuses = [
  232.         self::STATUS_INITIALIZED => 'Initialized',
  233.         self::STATUS_INPROGRESS  => 'In progress',
  234.         self::STATUS_SUCCESS     => 'Completed',
  235.         self::STATUS_PENDING     => 'Pending',
  236.         self::STATUS_FAILED      => 'Failed',
  237.         self::STATUS_CANCELED    => 'Canceled',
  238.         self::STATUS_VOID        => 'Voided',
  239.     ];
  240.     /**
  241.      * Run-time cache of registered transaction data
  242.      *
  243.      * @var array
  244.      */
  245.     protected $registeredCache;
  246.     /**
  247.      * @return bool
  248.      */
  249.     public static function showInitializedTransactions()
  250.     {
  251.         return (bool) \Includes\Utils\ConfigParser::getOptions(['other''show_initialized_transactions']);
  252.     }
  253.     /**
  254.      * Get statuses
  255.      *
  256.      * @return array
  257.      */
  258.     public static function getStatuses()
  259.     {
  260.         return [
  261.             static::STATUS_INITIALIZED => 'Initialized',
  262.             static::STATUS_INPROGRESS  => 'In progress',
  263.             static::STATUS_SUCCESS     => 'Success',
  264.             static::STATUS_PENDING     => 'Pending',
  265.             static::STATUS_FAILED      => 'Failed',
  266.             static::STATUS_CANCELED    => 'Canceled',
  267.             static::STATUS_VOID        => 'Void',
  268.         ];
  269.     }
  270.     /**
  271.      * Returns default failed reason
  272.      *
  273.      * @return string
  274.      */
  275.     public static function getDefaultFailedReason()
  276.     {
  277.         return static::t('An error occurred, please try again. If the problem persists, contact the administrator.');
  278.     }
  279.     /**
  280.      * Get profile
  281.      *
  282.      * @return \XLite\Model\Profile
  283.      */
  284.     public function getProfile()
  285.     {
  286.         return $this->getOrder() ? $this->getOrder()->getProfile() : null;
  287.     }
  288.     /**
  289.      * Get original profile
  290.      *
  291.      * @return \XLite\Model\Profile
  292.      */
  293.     public function getOrigProfile()
  294.     {
  295.         $profile $this->getOrder() ? $this->getOrder()->getOrigProfile() : null;
  296.         return $profile ?: $this->getProfile();
  297.     }
  298.     /**
  299.      * Generate new transaction id by format: "OrderId-XXXX", where XXXX is random suffix
  300.      *
  301.      * @return string
  302.      */
  303.     public function generateTransactionId()
  304.     {
  305.         $suffix \XLite\Core\Operator::getInstance()->generateToken(static::SUFFIX_TOKEN_LENGTH, static::$chars);
  306.         return $this->getOrder()
  307.             ? str_pad($this->getOrder()->getOrderId(), static::ORDERID_TOKEN_LENGTH'0'STR_PAD_LEFT) . '-' $suffix
  308.             null;
  309.     }
  310.     /**
  311.      * Renew transaction ID
  312.      *
  313.      * @return void
  314.      */
  315.     public function renewTransactionId()
  316.     {
  317.         if (!$this->getPublicTxnId()) {
  318.             $this->setPublicTxnId(
  319.                 $this->generateTransactionId()
  320.             );
  321.         }
  322.         if ($this->getPaymentMethod() && $this->getPaymentMethod()->getProcessor()) {
  323.             $this->setPublicId(
  324.                 $this->getPaymentMethod()->getProcessor()->generateTransactionId($this)
  325.             );
  326.         }
  327.     }
  328.     /**
  329.      * Set transaction value
  330.      *
  331.      * @param float $value Transaction value
  332.      *
  333.      * @return \XLite\Model\Payment\Transaction
  334.      */
  335.     public function setValue($value)
  336.     {
  337.         $this->value $this->getOrder() ? $this->getOrder()->getCurrency()->roundValue($value) : $value;
  338.         return $this;
  339.     }
  340.     /**
  341.      * Set payment method
  342.      *
  343.      * @param \XLite\Model\Payment\Method $method Payment method OPTIONAL
  344.      *
  345.      * @return void
  346.      */
  347.     public function setPaymentMethod(\XLite\Model\Payment\Method $method null)
  348.     {
  349.         $this->payment_method $method;
  350.         if ($method) {
  351.             $this->setMethodName($method->getServiceName());
  352.             $this->setMethodLocalName($method->getName());
  353.             $this->renewTransactionId();
  354.         }
  355.     }
  356.     /**
  357.      * Update value
  358.      *
  359.      * @param \XLite\Model\Order $order Order
  360.      *
  361.      * @return \XLite\Model\Payment\Transaction
  362.      */
  363.     public function updateValue(\XLite\Model\Order $order)
  364.     {
  365.         return $this->setValue($order->getOpenTotal());
  366.     }
  367.     /**
  368.      * Process checkout action
  369.      *
  370.      * @return string
  371.      */
  372.     public function handleCheckoutAction()
  373.     {
  374.         $this->setStatus(static::STATUS_INPROGRESS);
  375.         \XLite\Core\Database::getEM()->flush();
  376.         $data is_array(\XLite\Core\Request::getInstance()->payment)
  377.             ? \XLite\Core\Request::getInstance()->payment
  378.             : [];
  379.         $result $this->getPaymentMethod()->getProcessor()->pay($this$data);
  380.         $return = static::COMPLETED;
  381.         switch ($result) {
  382.             case \XLite\Model\Payment\Base\Processor::PROLONGATION:
  383.                 $return = static::PROLONGATION;
  384.                 break;
  385.             case \XLite\Model\Payment\Base\Processor::SILENT:
  386.                 $return = static::SILENT;
  387.                 break;
  388.             case \XLite\Model\Payment\Base\Processor::SEPARATE:
  389.                 $return = static::SEPARATE;
  390.                 break;
  391.             case \XLite\Model\Payment\Base\Processor::COMPLETED:
  392.                 $this->setStatus(static::STATUS_SUCCESS);
  393.                 break;
  394.             case \XLite\Model\Payment\Base\Processor::PENDING:
  395.                 $this->setStatus(static::STATUS_PENDING);
  396.                 break;
  397.             default:
  398.                 $this->setStatus(static::STATUS_FAILED);
  399.         }
  400.         $this->registerTransactionInOrderHistory();
  401.         return $return;
  402.     }
  403.     /**
  404.      * Get charge value modifier
  405.      *
  406.      * @return float
  407.      */
  408.     public function getChargeValueModifier()
  409.     {
  410.         $value 0;
  411.         $valueCaptured 0;
  412.         $valueRefunded 0;
  413.         if ($this->isCompleted() || $this->isPending()) {
  414.             $value += $this->getValue();
  415.         }
  416.         if ($this->getBackendTransactions()) {
  417.             /** @var \XLite\Model\Payment\BackendTransaction $transaction */
  418.             foreach ($this->getBackendTransactions() as $transaction) {
  419.                 if ($transaction->isCapture() && $transaction->isSucceed()) {
  420.                     ;
  421.                     $valueCaptured += abs($transaction->getValue());
  422.                 }
  423.                 if ($transaction->isRefund() && $transaction->isSucceed()) {
  424.                     $valueRefunded += abs($transaction->getValue());
  425.                 }
  426.             }
  427.         }
  428.         return max(
  429.             0,
  430.             max($valueCaptured$value) - $valueRefunded
  431.         );
  432.     }
  433.     /**
  434.      * Check - transaction is open or not
  435.      *
  436.      * @return boolean
  437.      */
  438.     public function isOpen()
  439.     {
  440.         return $this->getStatus() == static::STATUS_INITIALIZED;
  441.     }
  442.     /**
  443.      * Check - transaction is canceled or not
  444.      *
  445.      * @return boolean
  446.      */
  447.     public function isCanceled()
  448.     {
  449.         return $this->getStatus() == static::STATUS_CANCELED;
  450.     }
  451.     /**
  452.      * Check - transaction is failed or not
  453.      *
  454.      * @return boolean
  455.      */
  456.     public function isFailed()
  457.     {
  458.         return $this->getStatus() == static::STATUS_FAILED;
  459.     }
  460.     /**
  461.      * Check - order is completed or not
  462.      *
  463.      * @return boolean
  464.      */
  465.     public function isCompleted()
  466.     {
  467.         return $this->getStatus() == static::STATUS_SUCCESS;
  468.     }
  469.     /**
  470.      * Check - order is in progress state or not
  471.      *
  472.      * @return boolean
  473.      */
  474.     public function isInProgress()
  475.     {
  476.         return $this->getStatus() == static::STATUS_INPROGRESS;
  477.     }
  478.     /**
  479.      * Return true if transaction is in PENDING status
  480.      *
  481.      * @return boolean
  482.      */
  483.     public function isPending()
  484.     {
  485.         return $this->getStatus() == static::STATUS_PENDING;
  486.     }
  487.     /**
  488.      * Return true if transaction is in VOID status
  489.      *
  490.      * @return boolean
  491.      */
  492.     public function isVoid()
  493.     {
  494.         return $this->getStatus() == static::STATUS_VOID;
  495.     }
  496.     /**
  497.      * Returns true if successful payment has type AUTH
  498.      *
  499.      * @return boolean
  500.      */
  501.     public function isAuthorized()
  502.     {
  503.         $result $this->getType() == \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_AUTH && $this->isCompleted();
  504.         if ($result && $this->getBackendTransactions()) {
  505.             foreach ($this->getBackendTransactions() as $transaction) {
  506.                 if (
  507.                     $transaction->isVoid()
  508.                     && $transaction->isSucceed()
  509.                 ) {
  510.                     $result false;
  511.                 }
  512.             }
  513.         }
  514.         return $result;
  515.     }
  516.     /**
  517.      * Returns true if successful payment has type SALE or has successful CAPTURE transaction
  518.      *
  519.      * @return boolean
  520.      */
  521.     public function isCaptured()
  522.     {
  523.         $result $this->getType() == \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_SALE && $this->isCompleted();
  524.         if ($this->getBackendTransactions()) {
  525.             foreach ($this->getBackendTransactions() as $transaction) {
  526.                 if (
  527.                     $transaction->isCapture()
  528.                     && $transaction->isSucceed()
  529.                 ) {
  530.                     $result true;
  531.                     break;
  532.                 }
  533.             }
  534.         }
  535.         return $result;
  536.     }
  537.     /**
  538.      * Returns true if payment has successful REFUND transaction
  539.      *
  540.      * @return boolean
  541.      */
  542.     public function isRefunded()
  543.     {
  544.         $result false;
  545.         if ($this->getBackendTransactions()) {
  546.             foreach ($this->getBackendTransactions() as $transaction) {
  547.                 if (
  548.                     $transaction->isRefund()
  549.                     && $transaction->isSucceed()
  550.                 ) {
  551.                     $result true;
  552.                     break;
  553.                 }
  554.             }
  555.         }
  556.         return $result;
  557.     }
  558.     /**
  559.      * Returns true if payment has successful REFUND transaction
  560.      *
  561.      * @return boolean
  562.      */
  563.     public function isRefundedNotMulti()
  564.     {
  565.         $result false;
  566.         if ($this->getBackendTransactions()) {
  567.             foreach ($this->getBackendTransactions() as $transaction) {
  568.                 if (
  569.                     $transaction->isRefund()
  570.                     && $transaction->getType() !== \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_REFUND_MULTI
  571.                     && $transaction->isSucceed()
  572.                 ) {
  573.                     $result true;
  574.                     break;
  575.                 }
  576.             }
  577.         }
  578.         return $result;
  579.     }
  580.     /**
  581.      * Returns true if CAPTURE transaction is allowed for this payment
  582.      *
  583.      * @return boolean
  584.      */
  585.     public function isCaptureTransactionAllowed()
  586.     {
  587.         return $this->isAuthorized() && !$this->isCaptured() && !$this->isRefunded();
  588.     }
  589.     /**
  590.      * Returns true if VOID transaction is allowed for this payment
  591.      *
  592.      * @return boolean
  593.      */
  594.     public function isVoidTransactionAllowed()
  595.     {
  596.         return $this->isCaptureTransactionAllowed();
  597.     }
  598.     /**
  599.      * Returns true if REFUND transaction is allowed for this payment
  600.      *
  601.      * @return boolean
  602.      */
  603.     public function isRefundTransactionAllowed()
  604.     {
  605.         return $this->isCaptured() && !$this->isRefunded();
  606.     }
  607.     /**
  608.      * Returns true if REFUND PART transaction is allowed for this payment
  609.      *
  610.      * @return boolean
  611.      */
  612.     public function isRefundPartTransactionAllowed()
  613.     {
  614.         return $this->isCaptured() && !$this->isRefunded();
  615.     }
  616.     /**
  617.      * Returns true if REFUND MULTIPLE transaction is allowed for this payment
  618.      *
  619.      * @return boolean
  620.      */
  621.     public function isRefundMultiTransactionAllowed()
  622.     {
  623.         $currency $this->getCurrency() ?: $this->getOrder()->getCurrency();
  624.         return $this->isCaptured() && $currency->roundValue($this->getChargeValueModifier()) > 0;
  625.     }
  626.     /**
  627.      * Constructor
  628.      *
  629.      * @param array $data Entity properties OPTIONAL
  630.      *
  631.      * @return void
  632.      */
  633.     public function __construct(array $data = [])
  634.     {
  635.         $this->data = new \Doctrine\Common\Collections\ArrayCollection();
  636.         $this->backend_transactions = new \Doctrine\Common\Collections\ArrayCollection();
  637.         parent::__construct($data);
  638.     }
  639.     /**
  640.      * Get human-readable status
  641.      *
  642.      * @param string $status Transaction status
  643.      *
  644.      * @return string
  645.      */
  646.     public function getReadableStatus($status null)
  647.     {
  648.         if (!isset($status)) {
  649.             $status $this->getStatus();
  650.         }
  651.         return $this->readableStatuses[$status] ?? 'Unknown (' $status ')';
  652.     }
  653.     // {{{ Data operations
  654.     /**
  655.      * Get list of transaction data matched to the data list defined in processor
  656.      * Return processor-specific data or (of it is empty and not strict mode) all stored data
  657.      *
  658.      * @param boolean $strict Strict flag
  659.      *
  660.      * @return \Doctrine\Common\Collections\Collection
  661.      */
  662.     public function getTransactionData($strict false)
  663.     {
  664.         $list = new \Doctrine\Common\Collections\ArrayCollection();
  665.         $inputParams $this->getPaymentMethod() && $this->getPaymentMethod()->getProcessor()
  666.             ? $this->getPaymentMethod()->getProcessor()->getInputDataFields()
  667.             : [];
  668.         if ($inputParams) {
  669.             foreach ($this->getData() as $cell) {
  670.                 if (isset($inputParams[$cell->getName()])) {
  671.                     $list->add($cell);
  672.                     unset($inputParams[$cell->getName()]);
  673.                 }
  674.             }
  675.             if ($inputParams && $strict) {
  676.                 foreach ($inputParams as $param => $paramData) {
  677.                     $cell = new \XLite\Model\Payment\TransactionData();
  678.                     $cell->setName($param);
  679.                     $cell->setLabel($paramData['label']);
  680.                     $cell->setAccessLevel($paramData['accessLevel']);
  681.                     $cell->setTransaction($this);
  682.                     $list->add($cell);
  683.                 }
  684.             }
  685.         }
  686.         return $list->isEmpty() && !$strict $this->getData() : $list;
  687.     }
  688.     /**
  689.      * Set data cell
  690.      *
  691.      * @param string $name  Data cell name
  692.      * @param string $value Value
  693.      * @param string $label Public name OPTIONAL
  694.      * @param string $accessLevel access level OPTIONAL
  695.      *
  696.      * @return void
  697.      */
  698.     public function setDataCell($name$value$label null$accessLevel null)
  699.     {
  700.         $data null;
  701.         if (!isset($value)) {
  702.             $value '';
  703.         }
  704.         foreach ($this->getData() as $cell) {
  705.             if ($cell->getName() == $name) {
  706.                 $data $cell;
  707.                 break;
  708.             }
  709.         }
  710.         if (!$data) {
  711.             $data = new \XLite\Model\Payment\TransactionData();
  712.             $data->setName($name);
  713.             $this->addData($data);
  714.             $data->setTransaction($this);
  715.         }
  716.         if (!$data->getLabel() && $label) {
  717.             $data->setLabel($label);
  718.         }
  719.         $data->setValue($value);
  720.         // If access level was specified, and it dosn't match original one
  721.         // Then update it
  722.         if (
  723.             $accessLevel
  724.             && $data->getAccessLevel() != $accessLevel
  725.         ) {
  726.             $data->setAccessLevel($accessLevel);
  727.         }
  728.     }
  729.     /**
  730.      * Get data cell object by name
  731.      *
  732.      * @param string $name Name of data cell
  733.      *
  734.      * @return \XLite\Model\Payment\TransactionData
  735.      */
  736.     public function getDataCell($name)
  737.     {
  738.         $value null;
  739.         foreach ($this->getData() as $cell) {
  740.             if ($cell->getName() == $name) {
  741.                 if (
  742.                     \XLite::isAdminZone()
  743.                     || (!\XLite::isAdminZone() && $cell->getAccessLevel() != 'A')
  744.                 ) {
  745.                     $value $cell;
  746.                     break;
  747.                 }
  748.             }
  749.         }
  750.         // TODO: Consider situations if cells with same names have different access levels
  751.         return $value;
  752.     }
  753.     /**
  754.      * Get data cell object by name
  755.      *
  756.      * @param string $name Name of data cell
  757.      *
  758.      * @return mixed
  759.      */
  760.     public function getDetail($name)
  761.     {
  762.         $value $this->getDataCell($name);
  763.         return $value $value->getValue() : null;
  764.     }
  765.     // }}}
  766.     /**
  767.      * Create backend transaction
  768.      *
  769.      * @param string $transactionType Type of backend transaction
  770.      *
  771.      * @return \XLite\Model\Payment\BackendTransaction
  772.      */
  773.     public function createBackendTransaction($transactionType)
  774.     {
  775.         $bt \XLite\Core\Database::getRepo('XLite\Model\Payment\BackendTransaction')->insert(
  776.             $this->getCreateBackendTransactionData($transactionType),
  777.             false
  778.         );
  779.         $this->addBackendTransactions($bt);
  780.         return $bt;
  781.     }
  782.     /**
  783.      * Get data to create backend transaction
  784.      *
  785.      * @param string $transactionType Type of backend transaction
  786.      *
  787.      * @return array
  788.      */
  789.     protected function getCreateBackendTransactionData($transactionType)
  790.     {
  791.         $data = [
  792.             'date'                => \XLite\Core\Converter::time(),
  793.             'type'                => $transactionType,
  794.             'value'               => $this->getPaymentMethod()->getProcessor()->getTransactionValue($this$transactionType),
  795.             'payment_transaction' => $this,
  796.         ];
  797.         return $data;
  798.     }
  799.     /**
  800.      * Get initial backend transaction (related to the first payment transaction)
  801.      *
  802.      * @return \XLite\Model\Payment\BackendTransaction
  803.      */
  804.     public function getInitialBackendTransaction()
  805.     {
  806.         $bt null;
  807.         foreach ($this->getBackendTransactions() as $transaction) {
  808.             if ($transaction->isInitial()) {
  809.                 $bt $transaction;
  810.                 break;
  811.             }
  812.         }
  813.         return $bt;
  814.     }
  815.     /**
  816.      * Register transaction in order history
  817.      *
  818.      * @param string $suffix Suffix text to add to the end of event description
  819.      *
  820.      * @return void
  821.      */
  822.     public function registerTransactionInOrderHistory($suffix null)
  823.     {
  824.         $descrSuffix = !empty($suffix) ? ' [' . static::t($suffix) . ']' '';
  825.         // Prepare event description
  826.         $description = static::t($this->getHistoryEventDescription(), $this->getHistoryEventDescriptionData()) . $descrSuffix;
  827.         if ($this->getStatus() == static::STATUS_FAILED && !$this->getDataCell('cart_items')) {
  828.             // Failed transaction: Register info about cart items
  829.             $this->setDataCell(
  830.                 'cart_items',
  831.                 serialize($this->getCartItems()),
  832.                 'Cart items'
  833.             );
  834.             \XLite\Core\Database::getEM()->flush($this);
  835.         }
  836.         // Run-time cache key
  837.         $key md5(
  838.             $this->getOrder()->getOrderId() . '.'
  839.             $description '.'
  840.             serialize($this->getEventData())
  841.         );
  842.         if (!isset($this->registeredCache[$key])) {
  843.             // Register transaction in order history
  844.             $this->registeredCache[$key] = true;
  845.             \XLite\Core\OrderHistory::getInstance()->registerTransaction(
  846.                 $this->getOrder()->getOrderId(),
  847.                 $description,
  848.                 $this->getEventData()
  849.             );
  850.             if ($this->getStatus() == static::STATUS_FAILED) {
  851.                 // Send notification 'Failed transaction' to the Orders department
  852.                 \XLite\Core\Mailer::sendFailedTransactionAdmin($this);
  853.             }
  854.         }
  855.     }
  856.     /**
  857.      * Get description of order history event (language label is returned)
  858.      *
  859.      * @return string
  860.      */
  861.     public function getHistoryEventDescription()
  862.     {
  863.         return 'Payment transaction X issued';
  864.     }
  865.     /**
  866.      * Get data for description of order history event (substitution data for language label is returned)
  867.      *
  868.      * @return array
  869.      */
  870.     public function getHistoryEventDescriptionData()
  871.     {
  872.         return [
  873.             'trx_method' => static::t($this->getPaymentMethod()->getName()),
  874.             'trx_type'   => static::t($this->getType()),
  875.             'trx_value'  => $this->getOrder()->getCurrency()->roundValue($this->getValue()),
  876.             'trx_status' => static::t($this->getReadableStatus()),
  877.         ];
  878.     }
  879.     /**
  880.      * return event data
  881.      *
  882.      * @return array
  883.      */
  884.     public function getEventData()
  885.     {
  886.         $result = [];
  887.         $excluded $this->getExcludedTransactionDataFields();
  888.         foreach ($this->getData() as $cell) {
  889.             if (in_array($cell->getName(), $excludedtrue)) {
  890.                 continue;
  891.             }
  892.             $result[] = [
  893.                 'name'  => $cell->getLabel() ?: $cell->getName(),
  894.                 'value' => $cell->getValue()
  895.             ];
  896.         }
  897.         return $result;
  898.     }
  899.     /**
  900.      * @return array
  901.      */
  902.     protected function getExcludedTransactionDataFields()
  903.     {
  904.         return [
  905.             'cart_items'
  906.         ];
  907.     }
  908.     /**
  909.      * Check - transaction's method and specified method is equal or not
  910.      *
  911.      * @param \XLite\Model\Payment\Method $method Anothermethod
  912.      *
  913.      * @return boolean
  914.      */
  915.     public function isSameMethod(\XLite\Model\Payment\Method $method)
  916.     {
  917.         return $this->getPaymentMethod() && $this->getPaymentMethod()->getMethodId() == $method->getMethodId();
  918.     }
  919.     /**
  920.      * Clone payment transaction
  921.      *
  922.      * @return \XLite\Model\Payment\Transaction
  923.      */
  924.     public function cloneEntity()
  925.     {
  926.         $newTransaction parent::cloneEntity();
  927.         $newTransaction->setCurrency($this->getCurrency());
  928.         $newTransaction->setOrder($this->getOrder());
  929.         $newTransaction->setPaymentMethod($this->getPaymentMethod());
  930.         // Clone data cells
  931.         foreach ($this->getData() as $data) {
  932.             $cloned $data->cloneEntity();
  933.             $newTransaction->addData($cloned);
  934.             $cloned->setTransaction($newTransaction);
  935.         }
  936.         // Clone backend transactions
  937.         foreach ($this->getBackendTransactions() as $backend) {
  938.             $cloned $backend->cloneEntity();
  939.             $newTransaction->addBackendTransactions($cloned);
  940.             $cloned->setPaymentTransaction($newTransaction);
  941.         }
  942.         return $newTransaction;
  943.     }
  944.     /**
  945.      * Returns transaction note
  946.      *
  947.      * @return string
  948.      */
  949.     public function getNote()
  950.     {
  951.         return !$this->note && $this->isFailed()
  952.             ? static::getDefaultFailedReason()
  953.             : $this->note;
  954.     }
  955.     /**
  956.      * Get cart items array
  957.      *
  958.      * @return array
  959.      */
  960.     public function getCartItems()
  961.     {
  962.         $result = [];
  963.         if ($this->getOrder()) {
  964.             foreach ($this->getOrder()->getItems() as $item) {
  965.                 $result[] = $this->getCartItemData($item);
  966.             }
  967.         }
  968.         return $result;
  969.     }
  970.     /**
  971.      * Get cart item data as an array
  972.      *
  973.      * @param \XLite\Model\OrderItem $item Order item object
  974.      *
  975.      * @return array
  976.      */
  977.     protected function getCartItemData($item)
  978.     {
  979.         $result = [];
  980.         $result['name'] = $item->getName();
  981.         $result['sku'] = $item->getSku();
  982.         $result['price'] = $item->getPrice();
  983.         $result['amount'] = $item->getAmount();
  984.         if ($item->hasAttributeValues()) {
  985.             foreach ($item->getSortedAttributeValues() as $attr) {
  986.                 $result['attrs'][] = [
  987.                     'name'  => $attr->getActualName(),
  988.                     'value' => $attr->getActualValue(),
  989.                 ];
  990.             }
  991.         } else {
  992.             $result['attrs'] = [];
  993.         }
  994.         return $result;
  995.     }
  996.     /**
  997.      * @param string $type
  998.      *
  999.      * @return integer
  1000.      */
  1001.     protected function getLockTTL($type self::LOCK_TYPE_IPN)
  1002.     {
  1003.         return 3600;
  1004.     }
  1005.     /**
  1006.      * Get transaction_id
  1007.      *
  1008.      * @return integer
  1009.      */
  1010.     public function getTransactionId()
  1011.     {
  1012.         return $this->transaction_id;
  1013.     }
  1014.     /**
  1015.      * Set date
  1016.      *
  1017.      * @param integer $date
  1018.      * @return Transaction
  1019.      */
  1020.     public function setDate($date)
  1021.     {
  1022.         $this->date $date;
  1023.         return $this;
  1024.     }
  1025.     /**
  1026.      * Get date
  1027.      *
  1028.      * @return integer
  1029.      */
  1030.     public function getDate()
  1031.     {
  1032.         return $this->date;
  1033.     }
  1034.     /**
  1035.      * Set publicTxnId
  1036.      *
  1037.      * @param string $publicTxnId
  1038.      * @return Transaction
  1039.      */
  1040.     public function setPublicTxnId($publicTxnId)
  1041.     {
  1042.         $this->publicTxnId $publicTxnId;
  1043.         return $this;
  1044.     }
  1045.     /**
  1046.      * Get publicTxnId
  1047.      *
  1048.      * @return string
  1049.      */
  1050.     public function getPublicTxnId()
  1051.     {
  1052.         return $this->publicTxnId;
  1053.     }
  1054.     /**
  1055.      * Set method_name
  1056.      *
  1057.      * @param string $methodName
  1058.      * @return Transaction
  1059.      */
  1060.     public function setMethodName($methodName)
  1061.     {
  1062.         $this->method_name $methodName;
  1063.         return $this;
  1064.     }
  1065.     /**
  1066.      * Get method_name
  1067.      *
  1068.      * @return string
  1069.      */
  1070.     public function getMethodName()
  1071.     {
  1072.         return $this->method_name;
  1073.     }
  1074.     /**
  1075.      * Set method_local_name
  1076.      *
  1077.      * @param string $methodLocalName
  1078.      * @return Transaction
  1079.      */
  1080.     public function setMethodLocalName($methodLocalName)
  1081.     {
  1082.         $this->method_local_name $methodLocalName;
  1083.         return $this;
  1084.     }
  1085.     /**
  1086.      * Get method_local_name
  1087.      *
  1088.      * @return string
  1089.      */
  1090.     public function getMethodLocalName()
  1091.     {
  1092.         return $this->method_local_name;
  1093.     }
  1094.     /**
  1095.      * Set status
  1096.      *
  1097.      * @param string $status
  1098.      * @return Transaction
  1099.      */
  1100.     public function setStatus($status)
  1101.     {
  1102.         $this->status $status;
  1103.         return $this;
  1104.     }
  1105.     /**
  1106.      * Get status
  1107.      *
  1108.      * @return string
  1109.      */
  1110.     public function getStatus()
  1111.     {
  1112.         return $this->status;
  1113.     }
  1114.     /**
  1115.      * Get value
  1116.      *
  1117.      * @return float
  1118.      */
  1119.     public function getValue()
  1120.     {
  1121.         return $this->value;
  1122.     }
  1123.     /**
  1124.      * Set note
  1125.      *
  1126.      * @param string $note
  1127.      * @return Transaction
  1128.      */
  1129.     public function setNote($note)
  1130.     {
  1131.         $this->note $note;
  1132.         return $this;
  1133.     }
  1134.     /**
  1135.      * Set type
  1136.      *
  1137.      * @param string $type
  1138.      * @return Transaction
  1139.      */
  1140.     public function setType($type)
  1141.     {
  1142.         $this->type $type;
  1143.         return $this;
  1144.     }
  1145.     /**
  1146.      * Get type
  1147.      *
  1148.      * @return string
  1149.      */
  1150.     public function getType()
  1151.     {
  1152.         return $this->type;
  1153.     }
  1154.     /**
  1155.      * Set public_id
  1156.      *
  1157.      * @param string $publicId
  1158.      * @return Transaction
  1159.      */
  1160.     public function setPublicId($publicId)
  1161.     {
  1162.         $this->public_id $publicId;
  1163.         return $this;
  1164.     }
  1165.     /**
  1166.      * Get public_id
  1167.      *
  1168.      * @return string
  1169.      */
  1170.     public function getPublicId()
  1171.     {
  1172.         return $this->public_id;
  1173.     }
  1174.     /**
  1175.      * Set order
  1176.      *
  1177.      * @param \XLite\Model\Order $order
  1178.      * @return Transaction
  1179.      */
  1180.     public function setOrder(\XLite\Model\Order $order null)
  1181.     {
  1182.         $this->order $order;
  1183.         return $this;
  1184.     }
  1185.     /**
  1186.      * Get order
  1187.      *
  1188.      * @return \XLite\Model\Order
  1189.      */
  1190.     public function getOrder()
  1191.     {
  1192.         return $this->order;
  1193.     }
  1194.     /**
  1195.      * Get payment_method
  1196.      *
  1197.      * @return \XLite\Model\Payment\Method
  1198.      */
  1199.     public function getPaymentMethod()
  1200.     {
  1201.         return $this->payment_method;
  1202.     }
  1203.     /**
  1204.      * Add data
  1205.      *
  1206.      * @param \XLite\Model\Payment\TransactionData $data
  1207.      * @return Transaction
  1208.      */
  1209.     public function addData(\XLite\Model\Payment\TransactionData $data)
  1210.     {
  1211.         $this->data[] = $data;
  1212.         return $this;
  1213.     }
  1214.     /**
  1215.      * Get data
  1216.      *
  1217.      * @return \Doctrine\Common\Collections\Collection
  1218.      */
  1219.     public function getData()
  1220.     {
  1221.         return $this->data;
  1222.     }
  1223.     /**
  1224.      * Add backend_transactions
  1225.      *
  1226.      * @param \XLite\Model\Payment\BackendTransaction $backendTransactions
  1227.      * @return Transaction
  1228.      */
  1229.     public function addBackendTransactions(\XLite\Model\Payment\BackendTransaction $backendTransactions)
  1230.     {
  1231.         $this->backend_transactions[] = $backendTransactions;
  1232.         return $this;
  1233.     }
  1234.     /**
  1235.      * Get backend_transactions
  1236.      *
  1237.      * @return \Doctrine\Common\Collections\Collection
  1238.      */
  1239.     public function getBackendTransactions()
  1240.     {
  1241.         return $this->backend_transactions;
  1242.     }
  1243.     /**
  1244.      * Set currency
  1245.      *
  1246.      * @param \XLite\Model\Currency $currency
  1247.      * @return Transaction
  1248.      */
  1249.     public function setCurrency(\XLite\Model\Currency $currency null)
  1250.     {
  1251.         $this->currency $currency;
  1252.         return $this;
  1253.     }
  1254.     /**
  1255.      * Get currency
  1256.      *
  1257.      * @return \XLite\Model\Currency
  1258.      */
  1259.     public function getCurrency()
  1260.     {
  1261.         return $this->currency;
  1262.     }
  1263. }