【问题标题】:Magento Payment Review/Suspected Fraud workflowMagento 付款审查/可疑欺诈工作流程
【发布时间】:2011-07-21 15:12:00
【问题描述】:

我正在一家 Magento 商店工作,客户在该商店中要求对订单进行一些自定义过滤器,以便在将它们发送到履行之前对其进行人工审核。当这些案例出现时,订单会标有内置的Payment Review/Suspected Fraud状态/状态。

我的问题是,在管理界面中,您似乎无法对处于 Payment Review 状态的订单执行太多操作。我添加了一个自定义的“批准”类型的按钮来手动批准订单,但如果它被审查并确认为欺诈,预期要采取什么行动?我会考虑取消或退款订单,但这似乎是不允许的。检查订单上的canCancelcanCreditmemo 将返回false。对于这样的场景,使用 Hold 状态或 Payment Review 状态会更好吗?

【问题讨论】:

标签: php magento status


【解决方案1】:

我没有覆盖 Mage_Sales_Model_Order 对象(不是很理想),而是在 Magento 工具包中发现了一些现有的钩子,这些钩子可以在使用可疑欺诈状态标记订单后启用管理员操作。要启用这些,需要执行以下步骤:

在您的付款方式(继承自Mage_Payment_Model_Method_Abstract)中,添加以下内容:

    protected $_canReviewPayment  = true;

    public function acceptPayment(Mage_Payment_Model_Info $payment) {
        parent::acceptPayment($payment);
        //perform gateway actions to remove Fraud flags. Capture should not occur here
        return true;
        //returning true will trigger a capture on any existing invoices, otherwise the admin can manually Invoice the order
    }

    public function denyPayment(Mage_Payment_Model_Info $payment) {
        parent::denyPayment($payment);
        //if your payment gateway supports it, you should probably void any pre-auth
        return true;  
    }

Magento 的订单视图块将检查$order->canReviewPayment(),它将查看付款方式上的_canReviewPayment 变量,如果为真,则在订单视图上显示两个按钮:“接受付款”和“拒绝付款”。点击后,我们上面刚刚添加的两个新的Payment Method函数会被酌情调用。

如果您已经有与订单关联的发票,则该发票将是 pay'd 或 cancel'd。查看Mage_Sales_Model_Order_Payment::registerPaymentReviewAction 了解更多详情。

【讨论】:

    【解决方案2】:

    我们的一些客户遇到了付款门问题,涉及发票付款的这种可疑欺诈或“欺诈”标志,付款被手动批准后付款门没有通知 Magento。这似乎是 authorize.net 和某些 paypal 授权和响应配置的问题。

    这是我开发的三部分解决方案,用于操纵订单至少足以允许创建贷项通知单并清除欺诈状态。此流程还会创建订单发货并将订单推送到“处理中”,然后“完成”。

    我在核心代码文件/public_html/app/code/local/Mage/Adminhtml/Block/Sales/Order/View.php的本地副本中做了这三个修改

    (1) 注释代码行并替换为允许在订单处于处理或完成状态时显示贷项通知单按钮的代码行,即使 $order->canCreditmemo() 未正确设置为 true。

    // if ($this->_isAllowedAction('creditmemo') && $order->canCreditmemo()) {
    if ($this->_isAllowedAction('creditmemo') && ($order->getState() == 'complete' || $order->getState() == 'processing')) {
    

    (2) 创建了一个用于清除欺诈状态的按钮,该按钮调用#3 中引用的函数

    // 06/10/2014 Rand created button on Admin panel to clear fraud AFTER payment is authorized manually.
    if ($order->getStatus() === 'fraud') {
    $message = Mage::helper('sales')->__('*** CAUTION *** Payment must FIRST be authorized manually. Are you sure you want to clear this fraud status?');
    $this->addButton('clear_fraud', array(
        'label'     => Mage::helper('sales')->__('Clear Fraud'),
        'onclick'   => 'setLocation(\'' . $this->clearFraud($order) . '\')',
    ));
    }
    

    (3) 创建公共函数 clearFraud($order) 来完成发票到已付款的清算、发货的创建(如果需要)以及 $order 状态从“欺诈”到完成的清算。

    public function clearFraud($order)
    {
    // THIS FUNCTION IS CREATED BY RAND TO HANDLE CLEARING INCOMPLETED RECORDS
    // CREATED BY AUTHORIZE.NET FRAUD PROTECTION PROCESS
    
    // setState of order invoice(s) to one that will accept order completion, and save it.
    if ($order->hasInvoices()) {
        foreach ($order->getInvoiceCollection() as $invoice) {
            $invoice->setState('2');
            $invoice->save();
        }
    }
    
    // Handle Shipment: Create it (if needed) and save the transaction.
    if (!$order->hasShipments()) {
        $shipment = $order->prepareShipment();
        $shipment->register();
        $order->setIsInProcess(true);
        $transactionSave = Mage::getModel('core/resource_transaction')
            ->addObject($shipment)
            ->addObject($shipment->getOrder())
            ->save();
        $order->save();
    }
    
    // Set order to complete, and save the order
    $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true);
    $order->save();
    $order->setState(Mage_Sales_Model_Order::STATE_COMPLETE, true);
    $order->save();
    
    return $this->getUrl('*/sales_order');
    }
    

    这应该有助于解决如何使 Magento 以你想要的方式运行。

    【讨论】:

    • 感谢这对我有很大帮助。我注意到错误的一件事是,当您转到该页面时,$this->clearFraud 会立即调用...
    【解决方案3】:

    我认为在语义上区分 Hold 状态和 Payment Review 很重要,因此我建议您保留单独的状态/状态并使其在其中工作客户工作流程。

    您可以覆盖canCancel()canCreditmemo() 函数,以便它们在订单处于付款审核状态时允许执行这些操作。在扩展 Mage_Sales_Model_Order 的类中,重新定义这些函数以检查您的自定义状态/状态。

    HTH,
    京东

    【讨论】:

    • 谢谢乔纳森!我想我最担心在 Magento/支付流程中破坏东西——在支付审核状态下取消或退款订单没有任何问题吗?
    • 嗨,克里斯,我不希望如此,但一套完整的测试应该有助于证明......
    • canCreditmemo 在哪里/如何定义?它是销售订单操作的一部分,但是否有可以设置为允许信用备忘录的管理员配置?
    【解决方案4】:

    在为这个问题寻找解决方案后,B.Sharp 的选项是我找到的唯一解决方案。我将它与https://www.hummingbirduk.com/suspected-fraud-transactions-magento/ 混合并添加了 $invoice->sendEmail();将电子邮件发送给客户。

    在我们的情况下,“涉嫌欺诈”的状态是随机发生的,具有相同购物篮和相同贝宝帐户的同一客户可能会得到或没有。与四舍五入、税收或货币无关。

    class Mage_Adminhtml_Block_Sales_Order_View extends Mage_Adminhtml_Block_Widget_Form_Container
    

    {

    public function __construct()
    {
        $this->_objectId    = 'order_id';
        $this->_controller  = 'sales_order';
        $this->_mode        = 'view';
    
        parent::__construct();
    
        $this->_removeButton('delete');
        $this->_removeButton('reset');
        $this->_removeButton('save');
        $this->setId('sales_order_view');
        $order = $this->getOrder();
        $coreHelper = Mage::helper('core');
    
        if ($this->_isAllowedAction('edit') && $order->canEdit()) {
            $confirmationMessage = $coreHelper->jsQuoteEscape(
                Mage::helper('sales')->__('Are you sure? This order will be canceled and a new one will be created instead')
            );
            $onclickJs = 'deleteConfirm(\'' . $confirmationMessage . '\', \'' . $this->getEditUrl() . '\');';
            $this->_addButton('order_edit', array(
                'label'    => Mage::helper('sales')->__('Edit'),
                'onclick'  => $onclickJs,
            ));
            // see if order has non-editable products as items
            $nonEditableTypes = array_keys($this->getOrder()->getResource()->aggregateProductsByTypes(
                $order->getId(),
                array_keys(Mage::getConfig()
                    ->getNode('adminhtml/sales/order/create/available_product_types')
                    ->asArray()
                ),
                false
            ));
            if ($nonEditableTypes) {
                $confirmationMessage = $coreHelper->jsQuoteEscape(
                    Mage::helper('sales')
                        ->__('This order contains (%s) items and therefore cannot be edited through the admin interface at this time, if you wish to continue editing the (%s) items will be removed, the order will be canceled and a new order will be placed.',
                        implode(', ', $nonEditableTypes), implode(', ', $nonEditableTypes))
                );
                $this->_updateButton('order_edit', 'onclick',
                    'if (!confirm(\'' . $confirmationMessage . '\')) return false;' . $onclickJs
                );
            }
        }
    
        if ($this->_isAllowedAction('cancel') && $order->canCancel()) {
            $confirmationMessage = $coreHelper->jsQuoteEscape(
                Mage::helper('sales')->__('Are you sure you want to cancel this order?')
            );
            $this->_addButton('order_cancel', array(
                'label'     => Mage::helper('sales')->__('Cancel'),
                'onclick'   => 'deleteConfirm(\'' . $confirmationMessage . '\', \'' . $this->getCancelUrl() . '\')',
            ));
        }
    
        if ($this->_isAllowedAction('emails') && !$order->isCanceled()) {
            $confirmationMessage = $coreHelper->jsQuoteEscape(
                Mage::helper('sales')->__('Are you sure you want to send order email to customer?')
            );
            $this->addButton('send_notification', array(
                'label'     => Mage::helper('sales')->__('Send Email'),
                'onclick'   => "confirmSetLocation('{$confirmationMessage}', '{$this->getEmailUrl()}')",
            ));
        }
    
        //if ($this->_isAllowedAction('creditmemo') && $order->canCreditmemo()) {
        if ($this->_isAllowedAction('creditmemo') && ($order->getState() == 'complete' || $order->getState() == 'processing')) {
            $confirmationMessage = $coreHelper->jsQuoteEscape(
                Mage::helper('sales')->__('This will create an offline refund. To create an online refund, open an invoice and create credit memo for it. Do you wish to proceed?')
            );
            $onClick = "setLocation('{$this->getCreditmemoUrl()}')";
            if ($order->getPayment()->getMethodInstance()->isGateway()) {
                $onClick = "confirmSetLocation('{$confirmationMessage}', '{$this->getCreditmemoUrl()}')";
            }
            $this->_addButton('order_creditmemo', array(
                'label'     => Mage::helper('sales')->__('Credit Memo'),
                'onclick'   => $onClick,
                'class'     => 'go'
            ));
        }
    
        // invoice action intentionally
        if ($this->_isAllowedAction('invoice') && $order->canVoidPayment()) {
            $confirmationMessage = $coreHelper->jsQuoteEscape(
                Mage::helper('sales')->__('Are you sure you want to void the payment?')
            );
            $this->addButton('void_payment', array(
                'label'     => Mage::helper('sales')->__('Void'),
                'onclick'   => "confirmSetLocation('{$confirmationMessage}', '{$this->getVoidPaymentUrl()}')",
            ));
        }
    
        if ($this->_isAllowedAction('hold') && $order->canHold()) {
            $this->_addButton('order_hold', array(
                'label'     => Mage::helper('sales')->__('Hold'),
                'onclick'   => 'setLocation(\'' . $this->getHoldUrl() . '\')',
            ));
        }
    
        if ($this->_isAllowedAction('unhold') && $order->canUnhold()) {
            $this->_addButton('order_unhold', array(
                'label'     => Mage::helper('sales')->__('Unhold'),
                'onclick'   => 'setLocation(\'' . $this->getUnholdUrl() . '\')',
            ));
        }
    
        if ($this->_isAllowedAction('review_payment')) {
            if ($order->canReviewPayment()) {
                $confirmationMessage = $coreHelper->jsQuoteEscape(
                    Mage::helper('sales')->__('Are you sure you want to accept this payment?')
                );
                $onClick = "confirmSetLocation('{$confirmationMessage}', '{$this->getReviewPaymentUrl('accept')}')";
                $this->_addButton('accept_payment', array(
                    'label'     => Mage::helper('sales')->__('Accept Payment'),
                    'onclick'   => $onClick,
                ));
                $confirmationMessage = $coreHelper->jsQuoteEscape(
                    Mage::helper('sales')->__('Are you sure you want to deny this payment?')
                );
                $onClick = "confirmSetLocation('{$confirmationMessage}', '{$this->getReviewPaymentUrl('deny')}')";
                $this->_addButton('deny_payment', array(
                    'label'     => Mage::helper('sales')->__('Deny Payment'),
                    'onclick'   => $onClick,
                ));
            }
            if ($order->canFetchPaymentReviewUpdate()) {
                $this->_addButton('get_review_payment_update', array(
                    'label'     => Mage::helper('sales')->__('Get Payment Update'),
                    'onclick'   => 'setLocation(\'' . $this->getReviewPaymentUrl('update') . '\')',
                ));
            }
        }
    
        if ($this->_isAllowedAction('invoice') && $order->canInvoice()) {
            $_label = $order->getForcedDoShipmentWithInvoice() ?
                Mage::helper('sales')->__('Invoice and Ship') :
                Mage::helper('sales')->__('Invoice');
            $this->_addButton('order_invoice', array(
                'label'     => $_label,
                'onclick'   => 'setLocation(\'' . $this->getInvoiceUrl() . '\')',
                'class'     => 'go'
            ));
        }
    
        if ($this->_isAllowedAction('ship') && $order->canShip()
            && !$order->getForcedDoShipmentWithInvoice()) {
            $this->_addButton('order_ship', array(
                'label'     => Mage::helper('sales')->__('Ship'),
                'onclick'   => 'setLocation(\'' . $this->getShipUrl() . '\')',
                'class'     => 'go'
            ));
        }
    
        if ($this->_isAllowedAction('reorder')
            && $this->helper('sales/reorder')->isAllowed($order->getStore())
            && $order->canReorderIgnoreSalable()
        ) {
            $this->_addButton('order_reorder', array(
                'label'     => Mage::helper('sales')->__('Reorder'),
                'onclick'   => 'setLocation(\'' . $this->getReorderUrl() . '\')',
                'class'     => 'go'
            ));
        }
    
    
        // 06/10/2014 Rand created button on Admin panel to clear fraud AFTER payment is authorized manually.
        if ($order->getStatus() === 'fraud') {
                $message = Mage::helper('sales')->__('*** CAUTION *** Payment must FIRST be authorized manually. Are you sure you want to clear this fraud status?');
                    $this->addButton('clear_fraud', array(
                        'label'     => Mage::helper('sales')->__('Clear Fraud'),
                        'onclick'   => 'setLocation(\'' . $this->clearFraud($order) . '\')',
                        'class'     => 'go'
                    ));
            }
    
    
    
    }
    
    /**
     * Retrieve order model object
     *
     * @return Mage_Sales_Model_Order
     */
    public function getOrder()
    {
        return Mage::registry('sales_order');
    }
    
    /**
     * Retrieve Order Identifier
     *
     * @return int
     */
    public function getOrderId()
    {
        return $this->getOrder()->getId();
    }
    
    public function getHeaderText()
    {
        if ($_extOrderId = $this->getOrder()->getExtOrderId()) {
            $_extOrderId = '[' . $_extOrderId . '] ';
        } else {
            $_extOrderId = '';
        }
        return Mage::helper('sales')->__('Order # %s %s | %s', $this->getOrder()->getRealOrderId(), $_extOrderId, $this->formatDate($this->getOrder()->getCreatedAtDate(), 'medium', true));
    }
    
    public function getUrl($params='', $params2=array())
    {
        $params2['order_id'] = $this->getOrderId();
        return parent::getUrl($params, $params2);
    }
    
    public function getEditUrl()
    {
        return $this->getUrl('*/sales_order_edit/start');
    }
    
    public function getEmailUrl()
    {
        return $this->getUrl('*/*/email');
    }
    
    public function getCancelUrl()
    {
        return $this->getUrl('*/*/cancel');
    }
    
    public function getInvoiceUrl()
    {
        return $this->getUrl('*/sales_order_invoice/start');
    }
    
    public function getCreditmemoUrl()
    {
        return $this->getUrl('*/sales_order_creditmemo/start');
    }
    
    public function getHoldUrl()
    {
        return $this->getUrl('*/*/hold');
    }
    
    public function getUnholdUrl()
    {
        return $this->getUrl('*/*/unhold');
    }
    
    public function getShipUrl()
    {
        return $this->getUrl('*/sales_order_shipment/start');
    }
    
    public function getCommentUrl()
    {
        return $this->getUrl('*/*/comment');
    }
    
    public function getReorderUrl()
    {
        return $this->getUrl('*/sales_order_create/reorder');
    }
    
    /**
     * Payment void URL getter
     */
    public function getVoidPaymentUrl()
    {
        return $this->getUrl('*/*/voidPayment');
    }
    
    protected function _isAllowedAction($action)
    {
        return Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/' . $action);
    }
    
    /**
     * Return back url for view grid
     *
     * @return string
     */
    public function getBackUrl()
    {
        if ($this->getOrder()->getBackUrl()) {
            return $this->getOrder()->getBackUrl();
        }
    
        return $this->getUrl('*/*/');
    }
    
    public function getReviewPaymentUrl($action)
    {
        return $this->getUrl('*/*/reviewPayment', array('action' => $action));
    }
    
    
    public function clearFraud($order)
    {
    
    
        $order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true);
        $order->setStatus('processing', false);
        $order->save();
        try {
            if(!$order->canInvoice()) {
                Mage::throwException(Mage::helper('core')->__('Cannot create an invoice.'));
            }
            $invoice = Mage::getModel('sales/service_order', $order)->prepareInvoice();
            if (!$invoice->getTotalQty()) {
                Mage::throwException(Mage::helper('core')->__('Cannot create an invoice without products.'));
            }
            $invoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_OFFLINE);
            $invoice->register();
            $invoice->sendEmail();
            $transactionSave = Mage::getModel('core/resource_transaction')->addObject($invoice)->addObject($invoice->getOrder());
            $transactionSave->save();
            } catch (Mage_Core_Exception $e) {
        }
    
    
    
    
    
        return $this->getUrl('*/sales_order');
    }
    

    }

    【讨论】:

      猜你喜欢
      • 2012-04-05
      • 2016-08-18
      • 1970-01-01
      • 2018-04-18
      • 2010-11-24
      • 2011-11-29
      • 1970-01-01
      • 2013-02-13
      • 2015-08-05
      相关资源
      最近更新 更多