Quote.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. <?php
  2. /*
  3. * FecShop file.
  4. *
  5. * @link http://www.fecshop.com/
  6. * @copyright Copyright (c) 2016 FecShop Software LLC
  7. * @license http://www.fecshop.com/license/
  8. */
  9. namespace fecshop\services\cart;
  10. use fecshop\services\Service;
  11. use Yii;
  12. /**
  13. * Cart service.
  14. *
  15. * @method createCart() create cart
  16. * @see \fecshop\services\cart\Quote::actionCreateCart()
  17. * @method setCartId($cart_id) set cart id
  18. * @see \fecshop\services\cart\Quote::actionSetCartId()
  19. *
  20. * @author Terry Zhao <2358269014@qq.com>
  21. * @since 1.0
  22. */
  23. class Quote extends Service
  24. {
  25. /**
  26. * 购物车的个数计算,是否仅仅计算active状态的产品个数总和,如果设置false,则将购物车中所有的产品个数累加。
  27. */
  28. const SESSION_CART_ID = 'current_session_cart_id';
  29. protected $_cart_id;
  30. protected $_cart;
  31. protected $_shipping_cost;
  32. protected $_cartModelName = '\fecshop\models\mysqldb\Cart';
  33. protected $_cartModel;
  34. /**
  35. * 存储购物车的信息。
  36. */
  37. protected $cartInfo;
  38. public function init()
  39. {
  40. parent::init();
  41. list($this->_cartModelName, $this->_cartModel) = Yii::mapGet($this->_cartModelName);
  42. }
  43. /**
  44. * @return int|null get cart id, or null if user does not do any cart operation
  45. * @see \fecshop\services\cart\Quote::createCart()
  46. * @see \fecshop\services\cart\Quote::mergeCartAfterUserLogin()
  47. */
  48. public function getCartId()
  49. {
  50. if (!$this->_cart_id) {
  51. $cart_id = Yii::$service->session->get(self::SESSION_CART_ID);
  52. if (! $cart_id) {
  53. if (! Yii::$app->user->isGuest) {
  54. $customerId = Yii::$app->user->getId();
  55. $cart = $this->getCartByCustomerId($customerId);
  56. if ($cart && $cart['cart_id']) {
  57. $this->setCartId($cart['cart_id']);
  58. }
  59. }
  60. } else {
  61. $this->_cart_id = $cart_id;
  62. }
  63. }
  64. return $this->_cart_id;
  65. }
  66. /**
  67. * @param $address|array 地址信息数组,详细参看下面函数显示的字段。
  68. * @param $shipping_method | String 货运方式
  69. * @param $payment_method | String 支付方式
  70. * @param bool
  71. * 更新游客购物车信息,用户下次下单 或者 重新下单,可以不需要重新填写货运地址信息。
  72. */
  73. public function updateGuestCart($address, $shipping_method, $payment_method)
  74. {
  75. $cart = $this->getCurrentCart();
  76. if ($cart) {
  77. $cart->customer_firstname = $address['first_name'];
  78. $cart->customer_lastname = $address['last_name'];
  79. $cart->customer_email = $address['email'];
  80. $cart->customer_telephone = $address['telephone'];
  81. $cart->customer_address_street1 = $address['street1'];
  82. $cart->customer_address_street2 = $address['street2'];
  83. $cart->customer_address_country = $address['country'];
  84. $cart->customer_address_city = $address['city'];
  85. $cart->customer_address_state = $address['state'];
  86. $cart->customer_address_zip = $address['zip'];
  87. $cart->shipping_method = $shipping_method;
  88. $cart->payment_method = $payment_method;
  89. return $cart->save();
  90. }
  91. }
  92. /**
  93. * @param $address_id | int 用户customer address id
  94. * @param $shipping_method 货运方式
  95. * @param $payment_method 支付方式
  96. * @param bool
  97. * 登录用户的cart信息,进行更新,更新cart的$address_id,$shipping_method,$payment_method。
  98. * 用途:对于登录用户,create new address(在下单页面),新创建的address会被保存,
  99. * 然后需要把address_id更新到cart中。
  100. * 对于 shipping_method 和 payment_method,保存到cart中,下次进入下单页面,会被记录
  101. * 下次登录用户进行下单,进入下单页面,会自动填写。
  102. */
  103. public function updateLoginCart($address_id, $shipping_method, $payment_method)
  104. {
  105. $cart = $this->getCurrentCart();
  106. if ($cart && $address_id) {
  107. $cart->customer_address_id = $address_id;
  108. $cart->shipping_method = $shipping_method;
  109. $cart->payment_method = $payment_method;
  110. return $cart->save();
  111. }
  112. }
  113. /**
  114. * @return object
  115. * 得到当前的cart,如果当前的cart不存在,则返回为空
  116. * 注意:这是getCurrentCart() 和 getCart()两个函数的区别,getCart()函数在没有cart_id的时候会创建cart。
  117. */
  118. public function getCurrentCart()
  119. {
  120. if (!$this->_cart) {
  121. $cart_id = $this->getCartId();
  122. if ($cart_id) {
  123. $one = $this->_cartModel->findOne(['cart_id' => $cart_id]);
  124. if ($one['cart_id']) {
  125. $this->_cart = $one;
  126. }
  127. }
  128. }
  129. return $this->_cart;
  130. }
  131. /**
  132. * 如果当前的Cart不存在,则创建Cart
  133. * 如果当前的cart存在,则查询,如果查询得到cart,则返回,如果查询不到,则重新创建
  134. * 设置$this->_cart 为 上面新建或者查询得到的cart对象。
  135. */
  136. public function getCart()
  137. {
  138. if (!$this->_cart) {
  139. $cart_id = $this->getCartId();
  140. if (!$cart_id) {
  141. $this->createCart();
  142. } else {
  143. $one = $this->_cartModel->findOne(['cart_id' => $cart_id]);
  144. if ($one['cart_id']) {
  145. $this->_cart = $one;
  146. } else {
  147. // 如果上面查询为空,则创建cart
  148. $this->createCart();
  149. }
  150. }
  151. }
  152. return $this->_cart;
  153. }
  154. /**
  155. * @param $cart | $this->_cartModel Object
  156. * 设置$this->_cart 为 当前传递的$cart对象。
  157. */
  158. public function setCart($cart)
  159. {
  160. $this->_cart = $cart;
  161. }
  162. /**
  163. * @return $items_count | Int , 得到购物车中产品的个数。头部的ajax请求一般访问这个.
  164. * 目前是通过表查询获取的。
  165. */
  166. public function getCartItemCount()
  167. {
  168. $items_count = 0;
  169. if ($cart_id = $this->getCartId()) {
  170. if ($cart_id) {
  171. $cart = $this->getCart();
  172. //$one = $this->_cartModel->findOne(['cart_id' => $cart_id]);
  173. if (isset($cart['items_count']) && $cart['items_count']) {
  174. $items_count = $cart['items_count'];
  175. }
  176. }
  177. }
  178. return $items_count;
  179. }
  180. /**
  181. * @param $item_qty | Int
  182. * 当$active_item_qty为null时,从cart items表中查询产品总数。
  183. * 当$item_qty 不等于null时,代表已经知道购物车中active产品的个数,不需要去cart_item表中查询,譬如清空购物车操作,直接就知道产品个数肯定为零。
  184. * 当购物车的产品变动后,会调用该函数,更新cart表的产品总数
  185. */
  186. public function computeCartInfo($active_item_qty = null)
  187. {
  188. $items_all_count = 0;
  189. if ($active_item_qty === null) {
  190. $active_item_qty = Yii::$service->cart->quoteItem->getActiveItemQty();
  191. }
  192. $items_all_count = Yii::$service->cart->quoteItem->getItemAllQty();
  193. $cart = $this->getCart();
  194. $cart->items_all_count = $items_all_count;
  195. $cart->items_count = $active_item_qty;
  196. $cart->save();
  197. return true;
  198. }
  199. /**
  200. * @param int $cart_id cart id
  201. * 设置cart_id类变量以及session中记录当前cartId的值
  202. * Cart的session的超时时间由session组件决定。
  203. */
  204. protected function actionSetCartId($cart_id)
  205. {
  206. $this->_cart_id = $cart_id;
  207. Yii::$service->session->set(self::SESSION_CART_ID, $cart_id);
  208. }
  209. /**
  210. * 删除掉active状态的购物车产品
  211. * 对于active的产品,在支付成功后,这些产品从购物车清楚
  212. * 而对于noActive产品,这些产品并没有支付,因而在购物车中保留。
  213. */
  214. protected function actionClearCart()
  215. {
  216. //Yii::$service->session->remove(self::SESSION_CART_ID);
  217. Yii::$service->cart->quoteItem->removeNoActiveItemsByCartId();
  218. }
  219. /**
  220. * 初始化创建cart信息,
  221. * 在用户的第一个产品加入购物车时,会在数据库中创建购物车.
  222. */
  223. protected function actionCreateCart()
  224. {
  225. $myCart = new $this->_cartModelName;
  226. $myCart->store = Yii::$service->store->currentStore;
  227. $myCart->created_at = time();
  228. $myCart->updated_at = time();
  229. if (!Yii::$app->user->isGuest) {
  230. $identity = Yii::$app->user->identity;
  231. $id = $identity['id'];
  232. $firstname = $identity['firstname'];
  233. $lastname = $identity['lastname'];
  234. $email = $identity['email'];
  235. $myCart->customer_id = $id;
  236. $myCart->customer_email = $email;
  237. $myCart->customer_firstname = $firstname;
  238. $myCart->customer_lastname = $lastname;
  239. $myCart->customer_is_guest = 2;
  240. } else {
  241. $myCart->customer_is_guest = 1;
  242. }
  243. $myCart->remote_ip = \fec\helpers\CFunc::get_real_ip();
  244. $myCart->app_name = Yii::$service->helper->getAppName();
  245. //if ($defaultShippingMethod = Yii::$service->shipping->getDefaultShippingMethod()) {
  246. // $myCart->shipping_method = $defaultShippingMethod;
  247. //}
  248. $myCart->save();
  249. $cart_id = $myCart['cart_id'];
  250. $this->setCartId($cart_id);
  251. $this->setCart($this->_cartModel->findOne($cart_id));
  252. }
  253. /** 该函数已经废弃
  254. * 购物车数据中是否含有address_id,address_id,是登录用户才会有的。
  255. */
  256. /*
  257. public function hasAddressId()
  258. {
  259. $cart = $this->getCart();
  260. $address_id = $cart['customer_address_id'];
  261. if ($address_id) {
  262. return true;
  263. }
  264. }
  265. */
  266. /**
  267. * 得到购物车中的用户地址信息.
  268. * @deprecated 该函数已经废弃
  269. */
  270. /*
  271. public function getCartAddress()
  272. {
  273. $email = '';
  274. $first_name = '';
  275. $last_name = '';
  276. if (!Yii::$app->user->isGuest) {
  277. $identity = Yii::$app->user->identity;
  278. $email = isset($identity['email']) ? $identity['email'] : '';
  279. $first_name = isset($identity['first_name']) ? $identity['first_name'] : '';
  280. $last_name = isset($identity['last_name']) ? $identity['last_name'] : '';
  281. }
  282. $cart = $this->getCurrentCart();
  283. $customer_email = isset($cart['customer_email']) ? $cart['customer_email'] : '';
  284. $customer_firstname = isset($cart['customer_firstname']) ? $cart['customer_firstname'] : '';
  285. $customer_lastname = isset($cart['customer_lastname']) ? $cart['customer_lastname'] : '';
  286. $customer_telephone = isset($cart['customer_telephone']) ? $cart['customer_telephone'] : '';
  287. $customer_address_country = isset($cart['customer_address_country']) ? $cart['customer_address_country'] : '';
  288. $customer_address_state = isset($cart['customer_address_state']) ? $cart['customer_address_state'] : '';
  289. $customer_address_city = isset($cart['customer_address_city']) ? $cart['customer_address_city'] : '';
  290. $customer_address_zip = isset($cart['customer_address_zip']) ? $cart['customer_address_zip'] : '';
  291. $customer_address_street1 = isset($cart['customer_address_street1']) ? $cart['customer_address_street1'] : '';
  292. $customer_address_street2 = isset($cart['customer_address_street2']) ? $cart['customer_address_street2'] : '';
  293. $customer_email = $customer_email ? $customer_email : $email;
  294. $customer_firstname = $customer_firstname ? $customer_firstname : $first_name;
  295. $customer_lastname = $customer_lastname ? $customer_lastname : $last_name;
  296. return [
  297. 'first_name' => $customer_firstname,
  298. 'last_name' => $customer_lastname,
  299. 'email' => $customer_email,
  300. 'telephone' => $customer_telephone,
  301. 'country' => $customer_address_country,
  302. 'state' => $customer_address_state,
  303. 'city' => $customer_address_city,
  304. 'zip' => $customer_address_zip,
  305. 'street1' => $customer_address_street1,
  306. 'street2' => $customer_address_street2,
  307. ];
  308. }
  309. */
  310. /**
  311. * @param $activeProduct | boolean , 是否只要active的产品
  312. * @param $shipping_method | String 传递的货运方式
  313. * @param $country | String 货运国家
  314. * @param $region | String 省市
  315. * @return bool OR array ,如果存在问题返回false,对于返回的数组的格式参看下面$this->cartInfo[$cartInfoKey] 部分的数组。
  316. * 返回当前购物车的信息。包括购物车对应的产品信息。
  317. * 对于可选参数,如果不填写,就是返回当前的购物车的数据。
  318. * 对于填写了参数,返回的是填写参数后的数据,这个一般是用户选择了了货运方式,国家等,然后
  319. * 实时的计算出来数据反馈给用户,但是,用户选择的数据并没有进入cart表
  320. */
  321. public function getCartInfo($activeProduct = true, $shipping_method = '', $country = '', $region = '*')
  322. {
  323. // 根据传递的参数的不同,购物车数据计算一次后,第二次调用,不会重新计算数据。
  324. $cartInfoKey = $shipping_method.'-shipping-'.$country.'-country-'.$region.'-region';
  325. if (!isset($this->cartInfo[$cartInfoKey])) {
  326. $cart_id = $this->getCartId();
  327. if (!$cart_id) {
  328. return false;
  329. }
  330. $cart = $this->getCart();
  331. // 购物车中所有的产品个数
  332. $items_all_count = $cart['items_all_count'];
  333. // 购物车中active状态的产品个数
  334. $items_count = $cart['items_count'];
  335. if ($items_count <=0 && $items_all_count <= 0) {
  336. return false;
  337. }
  338. $coupon_code = $cart['coupon_code'];
  339. $cart_product_info = Yii::$service->cart->quoteItem->getCartProductInfo($activeProduct);
  340. //var_dump($cart_product_info);
  341. if (is_array($cart_product_info)) {
  342. $product_weight = $cart_product_info['product_weight'];
  343. $product_volume_weight = $cart_product_info['product_volume_weight'];
  344. $product_volume = $cart_product_info['product_volume'];
  345. $product_final_weight = max($product_weight, $product_volume_weight);
  346. $products = $cart_product_info['products'];
  347. $product_total = $cart_product_info['product_total'];
  348. $base_product_total = $cart_product_info['base_product_total'];
  349. $product_qty_total = $cart_product_info['product_qty_total'];
  350. //if (!$shipping_method) {
  351. // $shipping_method = Yii::$service->shipping->getDefaultShippingMethod($country,$region,$product_weight);
  352. //}
  353. if (is_array($products) && !empty($products)) {
  354. $currShippingCost = 0;
  355. $baseShippingCost = 0;
  356. if ($shipping_method && $product_final_weight) {
  357. $shippingCost = $this->getShippingCost($shipping_method, $product_final_weight, $country, $region);
  358. $currShippingCost = $shippingCost['currCost'];
  359. $baseShippingCost = $shippingCost['baseCost'];
  360. }
  361. $couponCost = $this->getCouponCost($base_product_total, $coupon_code);
  362. $baseDiscountCost = $couponCost['baseCost'];
  363. $currDiscountCost = $couponCost['currCost'];
  364. $curr_grand_total = $product_total + $currShippingCost - $currDiscountCost;
  365. $base_grand_total = $base_product_total + $baseShippingCost - $baseDiscountCost;
  366. $this->cartInfo[$cartInfoKey] = [
  367. 'cart_id' => $cart_id,
  368. 'store' => $cart['store'], // store nme
  369. 'items_count' => $product_qty_total, // 因为购物车使用了active,因此生成订单的产品个数 = 购物车中active的产品的总个数(也就是在购物车页面用户勾选的产品的总数),而不是字段 $cart['items_count']
  370. 'coupon_code' => $coupon_code, // coupon卷码
  371. 'shipping_method' => $shipping_method,
  372. 'payment_method' => $cart['payment_method'],
  373. 'grand_total' => Yii::$service->helper->format->number_format($curr_grand_total), // 当前货币总金额
  374. 'shipping_cost' => Yii::$service->helper->format->number_format($currShippingCost), // 当前货币,运费
  375. 'coupon_cost' => Yii::$service->helper->format->number_format($currDiscountCost), // 当前货币,优惠券优惠金额
  376. 'product_total' => Yii::$service->helper->format->number_format($product_total), // 当前货币,购物车中产品的总金额
  377. 'base_grand_total' => Yii::$service->helper->format->number_format($base_grand_total), // 基础货币总金额
  378. 'base_shipping_cost'=> Yii::$service->helper->format->number_format($baseShippingCost), // 基础货币,运费
  379. 'base_coupon_cost' => Yii::$service->helper->format->number_format($baseDiscountCost), // 基础货币,优惠券优惠金额
  380. 'base_product_total'=> Yii::$service->helper->format->number_format($base_product_total), // 基础货币,购物车中产品的总金额
  381. 'products' => $products, //产品信息。
  382. 'product_weight' => Yii::$service->helper->format->number_format($product_weight), //产品的总重量。
  383. 'product_volume_weight' => Yii::$service->helper->format->number_format($product_volume_weight),
  384. 'product_volume' => Yii::$service->helper->format->number_format($product_volume),
  385. ];
  386. }
  387. }
  388. }
  389. return $this->cartInfo[$cartInfoKey];
  390. }
  391. /**
  392. * @param $shippingCost | Array ,example:
  393. * [
  394. * 'currCost' => 33.22, #当前货币的运费金额
  395. * 'baseCost' => 26.44, #基础货币的运费金额
  396. * ];
  397. * 设置快递运费金额。根据国家地址和产品重量等信息计算出来的运费
  398. */
  399. public function setShippingCost($shippingCost)
  400. {
  401. $this->_shipping_cost = $shippingCost;
  402. }
  403. /**
  404. * @param $shipping_method | String 货运方式
  405. * @param $weight | Float 产品重量
  406. * @param $country | String 国家
  407. * @param $region | String 省/市
  408. * @return $this->_shipping_cost | Array ,format:
  409. * [
  410. * 'currCost' => 33.22, #当前货币的运费金额
  411. * 'baseCost' => 26.44, #基础货币的运费金额
  412. * ];
  413. * 得到快递运费金额。
  414. */
  415. public function getShippingCost($shipping_method = '', $weight = '', $country = '', $region = '')
  416. {
  417. if (!$this->_shipping_cost) {
  418. $available_method = Yii::$service->shipping->getAvailableShippingMethods($country, $region, $weight);
  419. $shippingInfo = $available_method[$shipping_method];
  420. $shippingCost = Yii::$service->shipping->getShippingCost($shipping_method, $shippingInfo, $weight, $country, $region);
  421. $this->_shipping_cost = $shippingCost;
  422. }
  423. return $this->_shipping_cost;
  424. }
  425. /**
  426. * 得到优惠券的折扣金额.
  427. * @return array , example:
  428. * [
  429. * 'baseCost' => $base_discount_cost, # 基础货币的优惠金额
  430. * 'currCost' => $curr_discount_cost # 当前货币的优惠金额
  431. * ]
  432. */
  433. public function getCouponCost($base_product_total, $coupon_code)
  434. {
  435. $dc_discount = Yii::$service->cart->coupon->getDiscount($coupon_code, $base_product_total);
  436. return $dc_discount;
  437. }
  438. /**
  439. * @param $coupon_code | String
  440. * 设置购物车的优惠券
  441. */
  442. public function setCartCoupon($coupon_code)
  443. {
  444. $cart = $this->getCart();
  445. $cart->coupon_code = $coupon_code;
  446. $cart->save();
  447. return true;
  448. }
  449. /**
  450. * @param $coupon_code | String
  451. * 取消购物车的优惠券
  452. */
  453. public function cancelCartCoupon($coupon_code)
  454. {
  455. $cart = $this->getCart();
  456. $cart->coupon_code = null;
  457. $cart->save();
  458. return true;
  459. }
  460. /**
  461. * 当用户登录账号后,将用户未登录时的购物车和用户账号中保存
  462. * 的购物车信息进行合并。
  463. */
  464. public function mergeCartAfterUserLogin()
  465. {
  466. if (!Yii::$app->user->isGuest) {
  467. $identity = Yii::$app->user->identity;
  468. $customer_id = $identity['id'];
  469. $email = $identity->email;
  470. $customer_firstname = $identity->firstname;
  471. $customer_lastname = $identity->lastname;
  472. $customer_cart = $this->getCartByCustomerId($customer_id);
  473. $cart_id = $this->getCartId();
  474. if (!$customer_cart) {
  475. if ($cart_id) {
  476. $cart = $this->getCart();
  477. if ($cart) {
  478. $cart['customer_email'] = $email;
  479. $cart['customer_id'] = $customer_id;
  480. $cart['customer_firstname'] = $customer_firstname;
  481. $cart['customer_lastname'] = $customer_lastname;
  482. $cart['customer_is_guest'] = 2;
  483. $cart->save();
  484. }
  485. }
  486. } else {
  487. $cart = $this->getCart();
  488. if (!$cart || !$cart_id) {
  489. $cart_id = $customer_cart['cart_id'];
  490. $this->setCartId($cart_id);
  491. } else {
  492. // 将无用户产品(当前)和 购物车中的产品(登录用户对应的购物车)进行合并。
  493. $new_cart_id = $customer_cart['cart_id'];
  494. if ($cart['coupon_code']) {
  495. // 如果有优惠券则取消,以登录用户的购物车的优惠券为准。
  496. Yii::$service->cart->coupon->cancelCoupon($cart['coupon_code']);
  497. }
  498. // 将当前购物车产品表的cart_id 改成 登录用户对应的cart_id
  499. if ($new_cart_id && $cart_id && ($new_cart_id != $cart_id)) {
  500. Yii::$service->cart->quoteItem->updateCartId($new_cart_id, $cart_id);
  501. // 当前的购物车删除掉
  502. $cart->delete();
  503. // 设置当前的cart_id
  504. $this->setCartId($new_cart_id);
  505. // 设置当前的cart
  506. $this->setCart($customer_cart);
  507. // 重新计算购物车中产品的个数
  508. $this->computeCartInfo();
  509. }
  510. }
  511. }
  512. }
  513. }
  514. /**
  515. * @param $customer_id | int
  516. * @return $this->_cartModel Object。
  517. * 通过用户的customer_id,在cart表中找到对应的购物车
  518. */
  519. public function getCartByCustomerId($customer_id)
  520. {
  521. if ($customer_id) {
  522. $one = $this->_cartModel->findOne(['customer_id' => $customer_id]);
  523. if ($one['cart_id']) {
  524. return $one;
  525. }
  526. }
  527. }
  528. }