1. 前台js执行逻辑
点击页面的 Place Order 按钮执行下面default.js中的 placeOrder 方法
vendor/magento/module-checkout/view/frontend/web/js/view/payment/default.js
placeOrder: function (data, event) {
var self = this;
if (event) {
event.preventDefault();
}
if (this.validate() &&
additionalValidators.validate() &&
this.isPlaceOrderActionAllowed() === true
) {
this.isPlaceOrderActionAllowed(false);
this.getPlaceOrderDeferredObject()
.done(
function () {
self.afterPlaceOrder();
if (self.redirectAfterPlaceOrder) {
redirectOnSuccessAction.execute();
}
}
).always(
function () {
self.isPlaceOrderActionAllowed(true);
}
);
return true;
}
return false;
},
其中重要方法是 getPlaceOrderDeferredObject
getPlaceOrderDeferredObject: function () {
return $.when(
placeOrderAction(this.getData(), this.messageContainer)
);
},
其中 placeOrderAction 调用 vendor/magento/module-checkout/view/frontend/web/js/action/place-order.js
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/**
* @api
*/
define([
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/url-builder',
'Magento_Customer/js/model/customer',
'Magento_Checkout/js/model/place-order'
], function (quote, urlBuilder, customer, placeOrderService) {
'use strict';
return function (paymentData, messageContainer) {
var serviceUrl, payload;
payload = {
cartId: quote.getQuoteId(),
billingAddress: quote.billingAddress(),
paymentMethod: paymentData
};
if (customer.isLoggedIn()) {
serviceUrl = urlBuilder.createUrl('/carts/mine/payment-information', {});
} else {
serviceUrl = urlBuilder.createUrl('/guest-carts/:quoteId/payment-information', {
quoteId: quote.getQuoteId()
});
payload.email = quote.guestEmail;
}
return placeOrderService(serviceUrl, payload, messageContainer);
};
});
其中 placeOrderService 方法是’Magento_Checkout/js/model/place-order 产生的 ,该方法来自 vendor\magento\module-checkout\view\frontend\web\js\model\place-order.js
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/**
* @api
*/
define(
[
'mage/storage',
'Magento_Checkout/js/model/error-processor',
'Magento_Checkout/js/model/full-screen-loader',
'Magento_Customer/js/customer-data',
'Magento_Checkout/js/model/payment/place-order-hooks',
'underscore'
],
function (storage, errorProcessor, fullScreenLoader, customerData, hooks, _) {
'use strict';
return function (serviceUrl, payload, messageContainer) {
var headers = {}, redirectURL = '';
fullScreenLoader.startLoader();
_.each(hooks.requestModifiers, function (modifier) {
modifier(headers, payload);
});
return storage.post(
serviceUrl, JSON.stringify(payload), true, 'application/json', headers
).fail(
function (response) {
errorProcessor.process(response, messageContainer);
redirectURL = response.getResponseHeader('errorRedirectAction');
if (redirectURL) {
setTimeout(function () {
errorProcessor.redirectTo(redirectURL);
}, 3000);
}
}
).done(
function (response) {
var clearData = {
'selectedShippingAddress': null,
'shippingAddressFromData': null,
'newCustomerShippingAddress': null,
'selectedShippingRate': null,
'selectedPaymentMethod': null,
'selectedBillingAddress': null,
'billingAddressFromData': null,
'newCustomerBillingAddress': null
};
if (response.responseType !== 'error') {
customerData.set('checkout-data', clearData);
}
}
).always(
function () {
fullScreenLoader.stopLoader();
_.each(hooks.afterRequestListeners, function (listener) {
listener();
});
}
);
};
}
);
其中 storage.post 提交数据,response是返回的数据。可以通过google F12 为js设置断点进行调试。


2. 后台php执行逻辑
登录用户执行:/carts/mine/payment-information
未登录用户执行:/guest-carts/:quoteId/payment-information
上面url定义在vendor/magento/module-checkout/etc/webapi.xml
2.1 登录用户
<route url="/V1/carts/mine/payment-information" method="POST">
<service class="Magento\Checkout\Api\PaymentInformationManagementInterface" method="savePaymentInformationAndPlaceOrder"/>
<resources>
<resource ref="self" />
</resources>
<data>
<parameter name="cartId" force="true">%cart_id%</parameter>
</data>
</route>
(1) 核心属性
属性 | 说明 |
---|---|
url="/V1/carts/mine/payment-information" | API 端点路径,mine 表示当前用户的购物车 |
method="POST" | 仅接受 POST 请求 |
(2) 关键节点
节点 | 作用 |
---|---|
<service class="...PaymentInformationManagementInterface" method="savePaymentInformationAndPlaceOrder"/> | 绑定到 PaymentInformationManagementInterface 的 savePaymentInformationAndPlaceOrder 方法 |
<resources><resource ref="self" /></resources> | 权限控制:仅允许操作当前用户的购物车(需登录认证) |
<data><parameter name="cartId" force="true">%cart_id%</parameter></data> |
- 后端处理
- 框架自动将
%cart_id%
替换为当前用户的购物车 ID。 - 调用
PaymentInformationManagementInterface::savePaymentInformationAndPlaceOrder($cartId, $paymentMethod, $billingAddress)
。
- 框架自动将
上面接口是由“Magento\Checkout\Model\PaymentInformationManagement”来实现的。配置在
vendor/magento/module-checkout/etc/di.xml

即:vendor/magento/module-checkout/Model/PaymentInformationManagement.php的 savePaymentInformationAndPlaceOrder 方法
public function savePaymentInformationAndPlaceOrder(
$cartId,
PaymentInterface $paymentMethod,
?QuoteAddressInterface $billingAddress = null
) {
$this->paymentRateLimiter->limit();
try {
//Have to do this hack because of plugins for savePaymentInformation()
$this->saveRateLimiterDisabled = true;
$this->savePaymentInformation($cartId, $paymentMethod, $billingAddress);
} finally {
$this->saveRateLimiterDisabled = false;
}
try {
$orderId = $this->cartManagement->placeOrder($cartId);
} catch (LocalizedException $e) {
$this->logger->critical(
'Placing an Order failed (reason: '. $e->getMessage() .')',
[
'quote_id' => $cartId,
'exception' => (string)$e,
'is_guest_checkout' => false
]
);
throw new CouldNotSaveException(
__($e->getMessage()),
$e
);
} catch (\Exception $e) {
$this->logger->critical($e);
throw new CouldNotSaveException(
__('A server error stopped your order from being placed. Please try to place your order again.'),
$e
);
}
return $orderId;
}
其中 速率限制检查
if (!$this->saveRateLimiterDisabled) {
try {
$this->saveRateLimiter->limit();
} catch (PaymentProcessingRateLimitExceededException $ex) {
//Limit reached
return false;
}
}
含义:防止恶意用户频繁调用此API。如果启用速率限制且超过阈值,返回 false
阻止操作。
2.2 未登录用户执行
2.2.1 登录用户执行url地址
<route url="/V1/guest-carts/:cartId/payment-information" method="POST">
<service class="Magento\Checkout\Api\GuestPaymentInformationManagementInterface" method="savePaymentInformationAndPlaceOrder"/>
<resources>
<resource ref="anonymous" />
</resources>
</route>
2.2.2 GuestPaymentInformationManagementInterface 该接口由下面类来实现:vendor\magento\module-checkout\etc\di.xml
<preference for="Magento\Checkout\Api\GuestPaymentInformationManagementInterface" type="Magento\Checkout\Model\GuestPaymentInformationManagement" />
该类的 savePaymentInformationAndPlaceOrder 方法如下:
public function savePaymentInformationAndPlaceOrder(
$cartId,
PaymentInterface $paymentMethod,
?QuoteAddressInterface $billingAddress = null
) {
$this->paymentRateLimiter->limit();
try {
//Have to do this hack because of plugins for savePaymentInformation()
$this->saveRateLimiterDisabled = true;
$this->savePaymentInformation($cartId, $paymentMethod, $billingAddress);
} finally {
$this->saveRateLimiterDisabled = false;
}
try {
$orderId = $this->cartManagement->placeOrder($cartId);
} catch (LocalizedException $e) {
$this->logger->critical(
'Placing an Order failed (reason: '. $e->getMessage() .')',
[
'quote_id' => $cartId,
'exception' => (string)$e,
'is_guest_checkout' => false
]
);
throw new CouldNotSaveException(
__($e->getMessage()),
$e
);
} catch (\Exception $e) {
$this->logger->critical($e);
throw new CouldNotSaveException(
__('A server error stopped your order from being placed. Please try to place your order again.'),
$e
);
}
return $orderId;
}
上面有savePaymentInformation 方法如下:
public function savePaymentInformation(
$cartId,
$email,
\Magento\Quote\Api\Data\PaymentInterface $paymentMethod,
?\Magento\Quote\Api\Data\AddressInterface $billingAddress = null
) {
if (!$this->saveRateLimitDisabled) {
try {
$this->savingRateLimiter->limit();
} catch (PaymentProcessingRateLimitExceededException $ex) {
//Limit reached
return false;
}
}
$quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id');
/** @var Quote $quote */
$quote = $this->cartRepository->getActive($quoteIdMask->getQuoteId());
$shippingAddress = $quote->getShippingAddress();
if ($this->addressComparator->isEqual($shippingAddress, $billingAddress)) {
$shippingAddress->setSameAsBilling(1);
}
if ($billingAddress) {
$billingAddress->setEmail($email);
$quote->removeAddress($quote->getBillingAddress()->getId());
$quote->setBillingAddress($billingAddress);
$quote->setDataChanges(true);
} else {
$quote->getBillingAddress()->setEmail($email);
}
$this->limitShippingCarrier($quote);
if (!(float)$quote->getItemsQty()) {
throw new CouldNotSaveException(__('Some of the products are disabled.'));
}
$this->paymentMethodManagement->set($cartId, $paymentMethod);
return true;
}
其中生成订单方法
$orderId = $this->cartManagement->placeOrder($cartId);
$this->cartManagement 这个对象来源接口 Magento\Quote\Api\GuestCartManagementInterface
这个接口是由 \vendor\magento\module-quote\Model\GuestCart\GuestCartManagement.php 类来实现其对象的.
该类下有placeOrder 方法如下:
public function placeOrder($cartId, ?PaymentInterface $paymentMethod = null)
{
/** @var $quoteIdMask QuoteIdMask */
$quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id');
$this->cartRepository->get($quoteIdMask->getQuoteId())
->setCheckoutMethod(CartManagementInterface::METHOD_GUEST);
return $this->quoteManagement->placeOrder($quoteIdMask->getQuoteId(), $paymentMethod);
}
其中 $this->quoteManagement->placeOrder($quoteIdMask->getQuoteId(), $paymentMethod);
$quoteManagement 对象是接口Magento\Quote\Api\CartManagementInterface 通过Magento\Quote\Model\QuoteManagement 类来实现的对象
vendor\magento\module-quote\etc\di.xml
<preference for="Magento\Quote\Api\CartManagementInterface" type="Magento\Quote\Model\QuoteManagement" />