src/Eccube/Service/OrderHelper.php line 514

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of EC-CUBE
  4. *
  5. * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
  6. *
  7. * http://www.ec-cube.co.jp/
  8. *
  9. * For the full copyright and license information, please view the LICENSE
  10. * file that was distributed with this source code.
  11. */
  12. namespace Eccube\Service;
  13. use Detection\MobileDetect;
  14. use Doctrine\Common\Collections\ArrayCollection;
  15. use Doctrine\Common\Collections\Collection;
  16. use Doctrine\ORM\EntityManagerInterface;
  17. use Eccube\Entity\Cart;
  18. use Eccube\Entity\CartItem;
  19. use Eccube\Entity\Customer;
  20. use Eccube\Entity\Master\DeviceType;
  21. use Eccube\Entity\Master\OrderItemType;
  22. use Eccube\Entity\Master\OrderStatus;
  23. use Eccube\Entity\Order;
  24. use Eccube\Entity\OrderItem;
  25. use Eccube\Entity\Shipping;
  26. use Eccube\Entity\Master\TaxDisplayType;
  27. use Eccube\EventListener\SecurityListener;
  28. use Eccube\Repository\DeliveryRepository;
  29. use Eccube\Repository\Master\DeviceTypeRepository;
  30. use Eccube\Repository\Master\OrderItemTypeRepository;
  31. use Eccube\Repository\Master\OrderStatusRepository;
  32. use Eccube\Repository\Master\PrefRepository;
  33. use Eccube\Repository\OrderRepository;
  34. use Eccube\Repository\PaymentRepository;
  35. use Eccube\Util\StringUtil;
  36. use Symfony\Component\DependencyInjection\ContainerInterface;
  37. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  38. use Symfony\Component\Security\Core\User\UserInterface;
  39. class OrderHelper
  40. {
  41. /**
  42. * @var ContainerInterface
  43. */
  44. protected $container;
  45. /**
  46. * @var string 非会員情報を保持するセッションのキー
  47. */
  48. public const SESSION_NON_MEMBER = 'eccube.front.shopping.nonmember';
  49. /**
  50. * @var string 非会員の住所情報を保持するセッションのキー
  51. */
  52. public const SESSION_NON_MEMBER_ADDRESSES = 'eccube.front.shopping.nonmember.customeraddress';
  53. /**
  54. * @var string 受注IDを保持するセッションのキー
  55. */
  56. public const SESSION_ORDER_ID = 'eccube.front.shopping.order.id';
  57. /**
  58. * @var string カートが分割されているかどうかのフラグ. 購入フローからのログイン時にカートが分割された場合にtrueがセットされる.
  59. *
  60. * @see SecurityListener
  61. */
  62. public const SESSION_CART_DIVIDE_FLAG = 'eccube.front.cart.divide';
  63. /**
  64. * @var SessionInterface
  65. */
  66. protected $session;
  67. /**
  68. * @var PrefRepository
  69. */
  70. protected $prefRepository;
  71. /**
  72. * @var OrderRepository
  73. */
  74. protected $orderRepository;
  75. /**
  76. * @var OrderItemTypeRepository
  77. */
  78. protected $orderItemTypeRepository;
  79. /**
  80. * @var OrderStatusRepository
  81. */
  82. protected $orderStatusRepository;
  83. /**
  84. * @var DeliveryRepository
  85. */
  86. protected $deliveryRepository;
  87. /**
  88. * @var PaymentRepository
  89. */
  90. protected $paymentRepository;
  91. /**
  92. * @var DeviceTypeRepository
  93. */
  94. protected $deviceTypeRepository;
  95. /**
  96. * @var MobileDetector
  97. */
  98. protected $mobileDetector;
  99. /**
  100. * @var EntityManagerInterface
  101. */
  102. protected $entityManager;
  103. public function __construct(
  104. ContainerInterface $container,
  105. EntityManagerInterface $entityManager,
  106. OrderRepository $orderRepository,
  107. OrderItemTypeRepository $orderItemTypeRepository,
  108. OrderStatusRepository $orderStatusRepository,
  109. DeliveryRepository $deliveryRepository,
  110. PaymentRepository $paymentRepository,
  111. DeviceTypeRepository $deviceTypeRepository,
  112. PrefRepository $prefRepository,
  113. MobileDetect $mobileDetector,
  114. SessionInterface $session
  115. ) {
  116. $this->container = $container;
  117. $this->orderRepository = $orderRepository;
  118. $this->orderStatusRepository = $orderStatusRepository;
  119. $this->orderItemTypeRepository = $orderItemTypeRepository;
  120. $this->deliveryRepository = $deliveryRepository;
  121. $this->paymentRepository = $paymentRepository;
  122. $this->deviceTypeRepository = $deviceTypeRepository;
  123. $this->entityManager = $entityManager;
  124. $this->prefRepository = $prefRepository;
  125. $this->mobileDetector = $mobileDetector;
  126. $this->session = $session;
  127. }
  128. /**
  129. * 購入処理中の受注を生成する.
  130. *
  131. * @param Customer $Customer
  132. * @param $CartItems
  133. *
  134. * @return Order
  135. */
  136. public function createPurchaseProcessingOrder(Cart $Cart, Customer $Customer)
  137. {
  138. $OrderStatus = $this->orderStatusRepository->find(OrderStatus::PROCESSING);
  139. $Order = new Order($OrderStatus);
  140. $preOrderId = $this->createPreOrderId();
  141. $Order->setPreOrderId($preOrderId);
  142. // 顧客情報の設定
  143. $this->setCustomer($Order, $Customer);
  144. $DeviceType = $this->deviceTypeRepository->find($this->mobileDetector->isMobile() ? DeviceType::DEVICE_TYPE_MB : DeviceType::DEVICE_TYPE_PC);
  145. $Order->setDeviceType($DeviceType);
  146. // 明細情報の設定
  147. $OrderItems = $this->createOrderItemsFromCartItems($Cart->getCartItems());
  148. $OrderItemsGroupBySaleType = array_reduce($OrderItems, function ($result, $item) {
  149. /* @var OrderItem $item */
  150. $saleTypeId = $item->getProductClass()->getSaleType()->getId();
  151. $result[$saleTypeId][] = $item;
  152. return $result;
  153. }, []);
  154. foreach ($OrderItemsGroupBySaleType as $OrderItems) {
  155. $Shipping = $this->createShippingFromCustomer($Customer);
  156. $Shipping->setOrder($Order);
  157. $this->addOrderItems($Order, $Shipping, $OrderItems);
  158. $this->setDefaultDelivery($Shipping);
  159. $this->entityManager->persist($Shipping);
  160. $Order->addShipping($Shipping);
  161. }
  162. $this->setDefaultPayment($Order);
  163. $this->entityManager->persist($Order);
  164. return $Order;
  165. }
  166. /**
  167. * @param Cart $Cart
  168. *
  169. * @return bool
  170. */
  171. public function verifyCart(Cart $Cart)
  172. {
  173. if (count($Cart->getCartItems()) > 0) {
  174. $divide = $this->session->get(self::SESSION_CART_DIVIDE_FLAG);
  175. if ($divide) {
  176. log_info('ログイン時に販売種別が異なる商品がカートと結合されました。');
  177. return false;
  178. }
  179. return true;
  180. }
  181. log_info('カートに商品が入っていません。');
  182. return false;
  183. }
  184. /**
  185. * 注文手続き画面でログインが必要かどうかの判定
  186. *
  187. * @return bool
  188. */
  189. public function isLoginRequired()
  190. {
  191. // フォームログイン済はログイン不要
  192. if ($this->isGranted('IS_AUTHENTICATED_FULLY')) {
  193. return false;
  194. }
  195. // Remember Meログイン済の場合はフォームからのログインが必要
  196. if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
  197. return true;
  198. }
  199. // 未ログインだがお客様情報を入力している場合はログイン不要
  200. if (!$this->getUser() && $this->getNonMember()) {
  201. return false;
  202. }
  203. return true;
  204. }
  205. /**
  206. * 購入処理中の受注を取得する.
  207. *
  208. * @param string|null $preOrderId
  209. *
  210. * @return Order|null
  211. */
  212. public function getPurchaseProcessingOrder($preOrderId = null)
  213. {
  214. if (null === $preOrderId) {
  215. return null;
  216. }
  217. return $this->orderRepository->findOneBy([
  218. 'pre_order_id' => $preOrderId,
  219. 'OrderStatus' => OrderStatus::PROCESSING,
  220. ]);
  221. }
  222. /**
  223. * セッションに保持されている非会員情報を取得する.
  224. * 非会員購入時に入力されたお客様情報を返す.
  225. *
  226. * @param string $session_key
  227. *
  228. * @return Customer|null
  229. */
  230. public function getNonMember($session_key = self::SESSION_NON_MEMBER)
  231. {
  232. $data = $this->session->get($session_key);
  233. if (empty($data)) {
  234. return null;
  235. }
  236. $Customer = new Customer();
  237. $Customer
  238. ->setName01($data['name01'])
  239. ->setName02($data['name02'])
  240. ->setKana01($data['kana01'])
  241. ->setKana02($data['kana02'])
  242. ->setCompanyName($data['company_name'])
  243. ->setEmail($data['email'])
  244. ->setPhonenumber($data['phone_number'])
  245. ->setPostalcode($data['postal_code'])
  246. ->setAddr01($data['addr01'])
  247. ->setAddr02($data['addr02']);
  248. if (!empty($data['pref'])) {
  249. $Pref = $this->prefRepository->find($data['pref']);
  250. $Customer->setPref($Pref);
  251. }
  252. return $Customer;
  253. }
  254. /**
  255. * @param Cart $Cart
  256. * @param Customer $Customer
  257. *
  258. * @return Order|null
  259. */
  260. public function initializeOrder(Cart $Cart, Customer $Customer)
  261. {
  262. // 購入処理中の受注情報を取得
  263. if ($Order = $this->getPurchaseProcessingOrder($Cart->getPreOrderId())) {
  264. return $Order;
  265. }
  266. // 受注情報を作成
  267. $Order = $this->createPurchaseProcessingOrder($Cart, $Customer);
  268. $Cart->setPreOrderId($Order->getPreOrderId());
  269. return $Order;
  270. }
  271. public function removeSession()
  272. {
  273. $this->session->remove(self::SESSION_ORDER_ID);
  274. $this->session->remove(self::SESSION_NON_MEMBER);
  275. $this->session->remove(self::SESSION_NON_MEMBER_ADDRESSES);
  276. }
  277. /**
  278. * 会員情報の更新日時が受注の作成日時よりも新しければ, 受注の注文者情報を更新する.
  279. *
  280. * @param Order $Order
  281. * @param Customer $Customer
  282. */
  283. public function updateCustomerInfo(Order $Order, Customer $Customer)
  284. {
  285. if ($Order->getCreateDate() < $Customer->getUpdateDate()) {
  286. $this->setCustomer($Order, $Customer);
  287. }
  288. }
  289. public function createPreOrderId()
  290. {
  291. // ランダムなpre_order_idを作成
  292. do {
  293. $preOrderId = sha1(StringUtil::random(32));
  294. $Order = $this->orderRepository->findOneBy(
  295. [
  296. 'pre_order_id' => $preOrderId,
  297. ]
  298. );
  299. } while ($Order);
  300. return $preOrderId;
  301. }
  302. protected function setCustomer(Order $Order, Customer $Customer)
  303. {
  304. if ($Customer->getId()) {
  305. $Order->setCustomer($Customer);
  306. }
  307. $Order->copyProperties(
  308. $Customer,
  309. [
  310. 'id',
  311. 'create_date',
  312. 'update_date',
  313. 'del_flg',
  314. ]
  315. );
  316. }
  317. /**
  318. * @param Collection|ArrayCollection|CartItem[] $CartItems
  319. *
  320. * @return OrderItem[]
  321. */
  322. protected function createOrderItemsFromCartItems($CartItems)
  323. {
  324. $ProductItemType = $this->orderItemTypeRepository->find(OrderItemType::PRODUCT);
  325. return array_map(function ($item) use ($ProductItemType) {
  326. /* @var $item CartItem */
  327. /* @var $ProductClass \Eccube\Entity\ProductClass */
  328. $ProductClass = $item->getProductClass();
  329. /* @var $Product \Eccube\Entity\Product */
  330. $Product = $ProductClass->getProduct();
  331. $OrderItem = new OrderItem();
  332. $OrderItem
  333. ->setProduct($Product)
  334. ->setProductClass($ProductClass)
  335. ->setProductName($Product->getName())
  336. ->setProductCode($ProductClass->getCode())
  337. ->setPrice($ProductClass->getPrice02())
  338. ->setQuantity($item->getQuantity())
  339. ->setOrderItemType($ProductItemType);
  340. $ClassCategory1 = $ProductClass->getClassCategory1();
  341. if (!is_null($ClassCategory1)) {
  342. $OrderItem->setClasscategoryName1($ClassCategory1->getName());
  343. $OrderItem->setClassName1($ClassCategory1->getClassName()->getName());
  344. }
  345. $ClassCategory2 = $ProductClass->getClassCategory2();
  346. if (!is_null($ClassCategory2)) {
  347. $OrderItem->setClasscategoryName2($ClassCategory2->getName());
  348. $OrderItem->setClassName2($ClassCategory2->getClassName()->getName());
  349. }
  350. return $OrderItem;
  351. }, $CartItems instanceof Collection ? $CartItems->toArray() : $CartItems);
  352. }
  353. /**
  354. * @param Customer $Customer
  355. *
  356. * @return Shipping
  357. */
  358. protected function createShippingFromCustomer(Customer $Customer)
  359. {
  360. $Shipping = new Shipping();
  361. $Shipping
  362. ->setName01($Customer->getName01())
  363. ->setName02($Customer->getName02())
  364. ->setKana01($Customer->getKana01())
  365. ->setKana02($Customer->getKana02())
  366. ->setCompanyName($Customer->getCompanyName())
  367. ->setPhoneNumber($Customer->getPhoneNumber())
  368. ->setPostalCode($Customer->getPostalCode())
  369. ->setPref($Customer->getPref())
  370. ->setAddr01($Customer->getAddr01())
  371. ->setAddr02($Customer->getAddr02());
  372. return $Shipping;
  373. }
  374. /**
  375. * @param Shipping $Shipping
  376. */
  377. protected function setDefaultDelivery(Shipping $Shipping)
  378. {
  379. // 配送商品に含まれる販売種別を抽出.
  380. $OrderItems = $Shipping->getOrderItems();
  381. $SaleTypes = [];
  382. /** @var OrderItem $OrderItem */
  383. foreach ($OrderItems as $OrderItem) {
  384. $ProductClass = $OrderItem->getProductClass();
  385. $SaleType = $ProductClass->getSaleType();
  386. $SaleTypes[$SaleType->getId()] = $SaleType;
  387. }
  388. // 販売種別に紐づく配送業者を取得.
  389. $Deliveries = $this->deliveryRepository->getDeliveries($SaleTypes);
  390. // 初期の配送業者を設定
  391. $Delivery = current($Deliveries);
  392. $Shipping->setDelivery($Delivery);
  393. $Shipping->setShippingDeliveryName($Delivery->getName());
  394. }
  395. /**
  396. * @param Order $Order
  397. */
  398. protected function setDefaultPayment(Order $Order)
  399. {
  400. $OrderItems = $Order->getOrderItems();
  401. // 受注明細に含まれる販売種別を抽出.
  402. $SaleTypes = [];
  403. /** @var OrderItem $OrderItem */
  404. foreach ($OrderItems as $OrderItem) {
  405. $ProductClass = $OrderItem->getProductClass();
  406. if (is_null($ProductClass)) {
  407. // 商品明細のみ対象とする. 送料明細等はスキップする.
  408. continue;
  409. }
  410. $SaleType = $ProductClass->getSaleType();
  411. $SaleTypes[$SaleType->getId()] = $SaleType;
  412. }
  413. // 販売種別に紐づく配送業者を抽出
  414. $Deliveries = $this->deliveryRepository->getDeliveries($SaleTypes);
  415. // 利用可能な支払い方法を抽出.
  416. // ここでは支払総額が決まっていないため、利用条件に合致しないものも選択対象になる場合がある
  417. $Payments = $this->paymentRepository->findAllowedPayments($Deliveries, true);
  418. // 初期の支払い方法を設定.
  419. $Payment = current($Payments);
  420. if ($Payment) {
  421. $Order->setPayment($Payment);
  422. $Order->setPaymentMethod($Payment->getMethod());
  423. }
  424. }
  425. /**
  426. * @param Order $Order
  427. * @param Shipping $Shipping
  428. * @param array $OrderItems
  429. */
  430. protected function addOrderItems(Order $Order, Shipping $Shipping, array $OrderItems)
  431. {
  432. foreach ($OrderItems as $OrderItem) {
  433. $Shipping->addOrderItem($OrderItem);
  434. $Order->addOrderItem($OrderItem);
  435. $OrderItem->setOrder($Order);
  436. $OrderItem->setShipping($Shipping);
  437. }
  438. }
  439. /**
  440. * @see Symfony\Bundle\FrameworkBundle\Controller\AbstractController
  441. */
  442. private function isGranted($attribute, $subject = null): bool
  443. {
  444. return $this->container->get('security.authorization_checker')->isGranted($attribute, $subject);
  445. }
  446. /**
  447. * @see Symfony\Bundle\FrameworkBundle\Controller\AbstractController
  448. */
  449. private function getUser(): ?UserInterface
  450. {
  451. if (null === $token = $this->container->get('security.token_storage')->getToken()) {
  452. return null;
  453. }
  454. if (!\is_object($user = $token->getUser())) {
  455. return null;
  456. }
  457. return $user;
  458. }
  459. /**
  460. * 税表示区分を取得する.
  461. *
  462. * - 商品: 税抜
  463. * - 送料: 税込
  464. * - 値引き: 税抜
  465. * - 手数料: 税込
  466. * - ポイント値引き: 税込
  467. *
  468. * @param $OrderItemType
  469. *
  470. * @return TaxDisplayType
  471. */
  472. public function getTaxDisplayType($OrderItemType)
  473. {
  474. if ($OrderItemType instanceof OrderItemType) {
  475. $OrderItemType = $OrderItemType->getId();
  476. }
  477. switch ($OrderItemType) {
  478. case OrderItemType::PRODUCT:
  479. return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::EXCLUDED);
  480. case OrderItemType::DELIVERY_FEE:
  481. return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::INCLUDED);
  482. case OrderItemType::DISCOUNT:
  483. return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::EXCLUDED);
  484. case OrderItemType::CHARGE:
  485. return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::INCLUDED);
  486. case OrderItemType::POINT:
  487. return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::INCLUDED);
  488. default:
  489. return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::EXCLUDED);
  490. }
  491. }
  492. }