Magento 新增支付方式

最近公司网站的支付页面,需要增加一个新的支付方式,我以前也没加过,所以只得从头开始看 Magento Payment Methods 这里的逻辑,记录一下今天的收获。

抛开 Magento 后台 Payment Methods 配置页面不提,我们先来看 Magento 默认自带的支付方式是如何在 onepage 页面中加载出来的。

Checkout

依次填写好 Billing Information, Shipping Information, Shipping Method 后, 下一部分就是 Payment Information 了, 点击 Shipping Method 中的 Continue 按钮, 会发送一个 saveShippingMethod 的 POST Ajax 请求, 请求成功的话, Payment Information 部分就会加载出来, 其 HTML 就包含在 Ajax 的 response 中。

saveShippingMethod 请求是由 Mage_Checkout_OnepageController::saveShippingMethodAction() 方法处理的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * Shipping method save action
 */
public function saveShippingMethodAction()
{
    if ($this->_expireAjax()) {
        return;
    }
    if ($this->getRequest()->isPost()) {
        $data = $this->getRequest()->getPost('shipping_method', '');
        $result = $this->getOnepage()->saveShippingMethod($data);
        // $result will contain error data if shipping method is empty
        if (!$result) {
            Mage::dispatchEvent(
                'checkout_controller_onepage_save_shipping_method',
                 array(
                      'request' => $this->getRequest(),
                      'quote'   => $this->getOnepage()->getQuote()));
            $this->getOnepage()->getQuote()->collectTotals();
            $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));

            $result['goto_section'] = 'payment';
            $result['update_section'] = array(
                'name' => 'payment-method',
                'html' => $this->_getPaymentMethodsHtml()
            );
        }
        $this->getOnepage()->getQuote()->collectTotals()->save();
        $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));
    }
}

在 Mage_Checkout_OnepageController::saveShippingMethodAction() 中, Payment Information 的 Html 是由 Mage_Checkout_OnepageController::_getPaymentMethodsHtml() 方法生成的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
 * Get payment method step html
 *
 * @return string
 */
protected function _getPaymentMethodsHtml()
{
    $layout = $this->getLayout();
    $update = $layout->getUpdate();
    $update->load('checkout_onepage_paymentmethod');
    $layout->generateXml();
    $layout->generateBlocks();
    $output = $layout->getOutput();
    return $output;
}

在 Mage_Checkout_OnepageController::_getPaymentMethodsHtml() 方法中, 先实例化了一个 layout 对象, 然后给这个 layout 对象增加了一个 “checkout_onepage_paymentmethod” handle, 通过全文搜索,可以知道这个 handle 对应的 layout xml 里面只有一个 block: checkout/onepage_payment_methods。

1
2
3
4
5
6
7
8
<checkout_onepage_paymentmethod>
    <remove name="right"/>
    <remove name="left"/>

    <block type="checkout/onepage_payment_methods" name="root" output="toHtml" template="checkout/onepage/payment/methods.phtml">
        <action method="setMethodFormTemplate"><method>purchaseorder</method><template>payment/form/purchaseorder.phtml</template></action>
    </block>
</checkout_onepage_paymentmethod>

接下来就来看 checkout/onepage_payment_methods 对应的 block: Mage_Checkout_Block_Onepage_Payment_Methods 类。 Mage_Checkout_Block_Onepage_Payment_Methods 继承了 Mage_Payment_Block_Form_Container 类, 在 Mage_Payment_Block_Form_Container::_prepareLayout() 方法中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * Prepare children blocks
 */
protected function _prepareLayout()
{
    /**
     * Create child blocks for payment methods forms
     */
    foreach ($this->getMethods() as $method) {
        $this->setChild(
           'payment.method.'.$method->getCode(),
           $this->helper('payment')->getMethodFormBlock($method)
        );
    }

    return parent::_prepareLayout();
}

/**
 * Retrieve available payment methods
 *
 * @return array
 */
public function getMethods()
{
    $methods = $this->getData('methods');
    if ($methods === null) {
        $quote = $this->getQuote();
        $store = $quote ? $quote->getStoreId() : null;
        $methods = array();
        foreach ($this->helper('payment')->getStoreMethods($store, $quote) as $method) {
            if ($this->_canUseMethod($method) && $method->isApplicableToQuote(
                $quote,
                Mage_Payment_Model_Method_Abstract::CHECK_ZERO_TOTAL
            )) {
                $this->_assignMethod($method);
                $methods[] = $method;
            }
        }
        $this->setData('methods', $methods);
    }
    return $methods;
}

可以看出, 是在 Mage_Payment_Block_Form_Container::_prepareLayout() 方法中动态加入了各个 Payment method block, 通过 Mage_Payment_Block_Form_Container::getMethods() 方法获取 系统支持的 Payment methods。而在 Mage_Payment_Block_Form_Container::getMethods() 方法中, 又是通过 Mage_Payment_Helper_Data::getStoreMethods() 方法(18层地狱啊!!!)来取得各个 store 支持(允许) 的 payment methods。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const XML_PATH_PAYMENT_METHODS = 'payment';
/**
 * Get and sort available payment methods for specified or current store
 *
 * array structure:
 *  $index => Varien_Simplexml_Element
 *
 * @param mixed $store
 * @param Mage_Sales_Model_Quote $quote
 * @return array
 */
public function getStoreMethods($store = null, $quote = null)
{
    $res = array();
    foreach ($this->getPaymentMethods($store) as $code => $methodConfig) {
        $prefix = self::XML_PATH_PAYMENT_METHODS . '/' . $code . '/';
        if (!$model = Mage::getStoreConfig($prefix . 'model', $store)) {
            continue;
        }
        $methodInstance = Mage::getModel($model);
        if (!$methodInstance) {
            continue;
        }
        $methodInstance->setStore($store);
        if (!$methodInstance->isAvailable($quote)) {
            /* if the payment method cannot be used at this time */
            continue;
        }
        $sortOrder = (int)$methodInstance->getConfigData('sort_order', $store);
        $methodInstance->setSortOrder($sortOrder);
        $res[] = $methodInstance;
    }

    usort($res, array($this, '_sortMethods'));
    return $res;
}

/**
 * Retrieve all payment methods
 *
 * @param mixed $store
 * @return array
 */
public function getPaymentMethods($store = null)
{
    return Mage::getStoreConfig(self::XML_PATH_PAYMENT_METHODS, $store);
}

最终,Mage_Payment_Helper_Data::getStoreMethods() 方法才是正真获取系统里配置的 payment methods 的地方。"" 具体逻辑是:

读取配置中以 “payment” 为父节点的配置内容, 子节点的名称做为 code, 内容作为 xml 对象内容 继续读取配置中 “payment” + code + “model” 的配置内容, 记为 $model 如果 $model 有值, 则实例化此 model, 检测其可用后, 记为 $method, 将其加入预先初始化的数组 $res 中 返回 $res 给 Mage_Payment_Block_Form_Container::getMethods() Mage_Payment_Block_Form_Container::getMethods() 中又再次检测每一个 $method 的可用性, 无误后, 为 $method 添加 Info Instance (quote 的 payment 对象), 然后加入预先初始化的数组 $methods 中 返回 $methods 给 Mage_Payment_Block_Form_Container::_prepareLayout() 在 Mage_Payment_Block_Form_Container::_prepareLayout() 中, 实例化每个 $method->_formBlockType 指向的 block, 并将其作为 checkout/onepage_payment_methods block 的子block 这样,所有支持的、符合条件的 payment methods 就会显示在 onepage 页面。

现在我们弄明白了 onepage 页面上的逻辑,顺着刚才的思路,逆向一下,就可以新增一个 payment method 出来。

Built with Hugo
主题 StackJimmy 设计