<?php
/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* http://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Eccube\Service;
use Detection\MobileDetect;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface;
use Eccube\Entity\Cart;
use Eccube\Entity\CartItem;
use Eccube\Entity\Customer;
use Eccube\Entity\Master\DeviceType;
use Eccube\Entity\Master\OrderItemType;
use Eccube\Entity\Master\OrderStatus;
use Eccube\Entity\Order;
use Eccube\Entity\OrderItem;
use Eccube\Entity\Shipping;
use Eccube\Entity\Master\TaxDisplayType;
use Eccube\EventListener\SecurityListener;
use Eccube\Repository\DeliveryRepository;
use Eccube\Repository\Master\DeviceTypeRepository;
use Eccube\Repository\Master\OrderItemTypeRepository;
use Eccube\Repository\Master\OrderStatusRepository;
use Eccube\Repository\Master\PrefRepository;
use Eccube\Repository\OrderRepository;
use Eccube\Repository\PaymentRepository;
use Eccube\Util\StringUtil;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class OrderHelper
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var string 非会員情報を保持するセッションのキー
*/
public const SESSION_NON_MEMBER = 'eccube.front.shopping.nonmember';
/**
* @var string 非会員の住所情報を保持するセッションのキー
*/
public const SESSION_NON_MEMBER_ADDRESSES = 'eccube.front.shopping.nonmember.customeraddress';
/**
* @var string 受注IDを保持するセッションのキー
*/
public const SESSION_ORDER_ID = 'eccube.front.shopping.order.id';
/**
* @var string カートが分割されているかどうかのフラグ. 購入フローからのログイン時にカートが分割された場合にtrueがセットされる.
*
* @see SecurityListener
*/
public const SESSION_CART_DIVIDE_FLAG = 'eccube.front.cart.divide';
/**
* @var SessionInterface
*/
protected $session;
/**
* @var PrefRepository
*/
protected $prefRepository;
/**
* @var OrderRepository
*/
protected $orderRepository;
/**
* @var OrderItemTypeRepository
*/
protected $orderItemTypeRepository;
/**
* @var OrderStatusRepository
*/
protected $orderStatusRepository;
/**
* @var DeliveryRepository
*/
protected $deliveryRepository;
/**
* @var PaymentRepository
*/
protected $paymentRepository;
/**
* @var DeviceTypeRepository
*/
protected $deviceTypeRepository;
/**
* @var MobileDetector
*/
protected $mobileDetector;
/**
* @var EntityManagerInterface
*/
protected $entityManager;
public function __construct(
ContainerInterface $container,
EntityManagerInterface $entityManager,
OrderRepository $orderRepository,
OrderItemTypeRepository $orderItemTypeRepository,
OrderStatusRepository $orderStatusRepository,
DeliveryRepository $deliveryRepository,
PaymentRepository $paymentRepository,
DeviceTypeRepository $deviceTypeRepository,
PrefRepository $prefRepository,
MobileDetect $mobileDetector,
SessionInterface $session
) {
$this->container = $container;
$this->orderRepository = $orderRepository;
$this->orderStatusRepository = $orderStatusRepository;
$this->orderItemTypeRepository = $orderItemTypeRepository;
$this->deliveryRepository = $deliveryRepository;
$this->paymentRepository = $paymentRepository;
$this->deviceTypeRepository = $deviceTypeRepository;
$this->entityManager = $entityManager;
$this->prefRepository = $prefRepository;
$this->mobileDetector = $mobileDetector;
$this->session = $session;
}
/**
* 購入処理中の受注を生成する.
*
* @param Customer $Customer
* @param $CartItems
*
* @return Order
*/
public function createPurchaseProcessingOrder(Cart $Cart, Customer $Customer)
{
$OrderStatus = $this->orderStatusRepository->find(OrderStatus::PROCESSING);
$Order = new Order($OrderStatus);
$preOrderId = $this->createPreOrderId();
$Order->setPreOrderId($preOrderId);
// 顧客情報の設定
$this->setCustomer($Order, $Customer);
$DeviceType = $this->deviceTypeRepository->find($this->mobileDetector->isMobile() ? DeviceType::DEVICE_TYPE_MB : DeviceType::DEVICE_TYPE_PC);
$Order->setDeviceType($DeviceType);
// 明細情報の設定
$OrderItems = $this->createOrderItemsFromCartItems($Cart->getCartItems());
$OrderItemsGroupBySaleType = array_reduce($OrderItems, function ($result, $item) {
/* @var OrderItem $item */
$saleTypeId = $item->getProductClass()->getSaleType()->getId();
$result[$saleTypeId][] = $item;
return $result;
}, []);
foreach ($OrderItemsGroupBySaleType as $OrderItems) {
$Shipping = $this->createShippingFromCustomer($Customer);
$Shipping->setOrder($Order);
$this->addOrderItems($Order, $Shipping, $OrderItems);
$this->setDefaultDelivery($Shipping);
$this->entityManager->persist($Shipping);
$Order->addShipping($Shipping);
}
$this->setDefaultPayment($Order);
$this->entityManager->persist($Order);
return $Order;
}
/**
* @param Cart $Cart
*
* @return bool
*/
public function verifyCart(Cart $Cart)
{
if (count($Cart->getCartItems()) > 0) {
$divide = $this->session->get(self::SESSION_CART_DIVIDE_FLAG);
if ($divide) {
log_info('ログイン時に販売種別が異なる商品がカートと結合されました。');
return false;
}
return true;
}
log_info('カートに商品が入っていません。');
return false;
}
/**
* 注文手続き画面でログインが必要かどうかの判定
*
* @return bool
*/
public function isLoginRequired()
{
// フォームログイン済はログイン不要
if ($this->isGranted('IS_AUTHENTICATED_FULLY')) {
return false;
}
// Remember Meログイン済の場合はフォームからのログインが必要
if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
return true;
}
// 未ログインだがお客様情報を入力している場合はログイン不要
if (!$this->getUser() && $this->getNonMember()) {
return false;
}
return true;
}
/**
* 購入処理中の受注を取得する.
*
* @param string|null $preOrderId
*
* @return Order|null
*/
public function getPurchaseProcessingOrder($preOrderId = null)
{
if (null === $preOrderId) {
return null;
}
return $this->orderRepository->findOneBy([
'pre_order_id' => $preOrderId,
'OrderStatus' => OrderStatus::PROCESSING,
]);
}
/**
* セッションに保持されている非会員情報を取得する.
* 非会員購入時に入力されたお客様情報を返す.
*
* @param string $session_key
*
* @return Customer|null
*/
public function getNonMember($session_key = self::SESSION_NON_MEMBER)
{
$data = $this->session->get($session_key);
if (empty($data)) {
return null;
}
$Customer = new Customer();
$Customer
->setName01($data['name01'])
->setName02($data['name02'])
->setKana01($data['kana01'])
->setKana02($data['kana02'])
->setCompanyName($data['company_name'])
->setEmail($data['email'])
->setPhonenumber($data['phone_number'])
->setPostalcode($data['postal_code'])
->setAddr01($data['addr01'])
->setAddr02($data['addr02']);
if (!empty($data['pref'])) {
$Pref = $this->prefRepository->find($data['pref']);
$Customer->setPref($Pref);
}
return $Customer;
}
/**
* @param Cart $Cart
* @param Customer $Customer
*
* @return Order|null
*/
public function initializeOrder(Cart $Cart, Customer $Customer)
{
// 購入処理中の受注情報を取得
if ($Order = $this->getPurchaseProcessingOrder($Cart->getPreOrderId())) {
return $Order;
}
// 受注情報を作成
$Order = $this->createPurchaseProcessingOrder($Cart, $Customer);
$Cart->setPreOrderId($Order->getPreOrderId());
return $Order;
}
public function removeSession()
{
$this->session->remove(self::SESSION_ORDER_ID);
$this->session->remove(self::SESSION_NON_MEMBER);
$this->session->remove(self::SESSION_NON_MEMBER_ADDRESSES);
}
/**
* 会員情報の更新日時が受注の作成日時よりも新しければ, 受注の注文者情報を更新する.
*
* @param Order $Order
* @param Customer $Customer
*/
public function updateCustomerInfo(Order $Order, Customer $Customer)
{
if ($Order->getCreateDate() < $Customer->getUpdateDate()) {
$this->setCustomer($Order, $Customer);
}
}
public function createPreOrderId()
{
// ランダムなpre_order_idを作成
do {
$preOrderId = sha1(StringUtil::random(32));
$Order = $this->orderRepository->findOneBy(
[
'pre_order_id' => $preOrderId,
]
);
} while ($Order);
return $preOrderId;
}
protected function setCustomer(Order $Order, Customer $Customer)
{
if ($Customer->getId()) {
$Order->setCustomer($Customer);
}
$Order->copyProperties(
$Customer,
[
'id',
'create_date',
'update_date',
'del_flg',
]
);
}
/**
* @param Collection|ArrayCollection|CartItem[] $CartItems
*
* @return OrderItem[]
*/
protected function createOrderItemsFromCartItems($CartItems)
{
$ProductItemType = $this->orderItemTypeRepository->find(OrderItemType::PRODUCT);
return array_map(function ($item) use ($ProductItemType) {
/* @var $item CartItem */
/* @var $ProductClass \Eccube\Entity\ProductClass */
$ProductClass = $item->getProductClass();
/* @var $Product \Eccube\Entity\Product */
$Product = $ProductClass->getProduct();
$OrderItem = new OrderItem();
$OrderItem
->setProduct($Product)
->setProductClass($ProductClass)
->setProductName($Product->getName())
->setProductCode($ProductClass->getCode())
->setPrice($ProductClass->getPrice02())
->setQuantity($item->getQuantity())
->setOrderItemType($ProductItemType);
$ClassCategory1 = $ProductClass->getClassCategory1();
if (!is_null($ClassCategory1)) {
$OrderItem->setClasscategoryName1($ClassCategory1->getName());
$OrderItem->setClassName1($ClassCategory1->getClassName()->getName());
}
$ClassCategory2 = $ProductClass->getClassCategory2();
if (!is_null($ClassCategory2)) {
$OrderItem->setClasscategoryName2($ClassCategory2->getName());
$OrderItem->setClassName2($ClassCategory2->getClassName()->getName());
}
return $OrderItem;
}, $CartItems instanceof Collection ? $CartItems->toArray() : $CartItems);
}
/**
* @param Customer $Customer
*
* @return Shipping
*/
protected function createShippingFromCustomer(Customer $Customer)
{
$Shipping = new Shipping();
$Shipping
->setName01($Customer->getName01())
->setName02($Customer->getName02())
->setKana01($Customer->getKana01())
->setKana02($Customer->getKana02())
->setCompanyName($Customer->getCompanyName())
->setPhoneNumber($Customer->getPhoneNumber())
->setPostalCode($Customer->getPostalCode())
->setPref($Customer->getPref())
->setAddr01($Customer->getAddr01())
->setAddr02($Customer->getAddr02());
return $Shipping;
}
/**
* @param Shipping $Shipping
*/
protected function setDefaultDelivery(Shipping $Shipping)
{
// 配送商品に含まれる販売種別を抽出.
$OrderItems = $Shipping->getOrderItems();
$SaleTypes = [];
/** @var OrderItem $OrderItem */
foreach ($OrderItems as $OrderItem) {
$ProductClass = $OrderItem->getProductClass();
$SaleType = $ProductClass->getSaleType();
$SaleTypes[$SaleType->getId()] = $SaleType;
}
// 販売種別に紐づく配送業者を取得.
$Deliveries = $this->deliveryRepository->getDeliveries($SaleTypes);
// 初期の配送業者を設定
$Delivery = current($Deliveries);
$Shipping->setDelivery($Delivery);
$Shipping->setShippingDeliveryName($Delivery->getName());
}
/**
* @param Order $Order
*/
protected function setDefaultPayment(Order $Order)
{
$OrderItems = $Order->getOrderItems();
// 受注明細に含まれる販売種別を抽出.
$SaleTypes = [];
/** @var OrderItem $OrderItem */
foreach ($OrderItems as $OrderItem) {
$ProductClass = $OrderItem->getProductClass();
if (is_null($ProductClass)) {
// 商品明細のみ対象とする. 送料明細等はスキップする.
continue;
}
$SaleType = $ProductClass->getSaleType();
$SaleTypes[$SaleType->getId()] = $SaleType;
}
// 販売種別に紐づく配送業者を抽出
$Deliveries = $this->deliveryRepository->getDeliveries($SaleTypes);
// 利用可能な支払い方法を抽出.
// ここでは支払総額が決まっていないため、利用条件に合致しないものも選択対象になる場合がある
$Payments = $this->paymentRepository->findAllowedPayments($Deliveries, true);
// 初期の支払い方法を設定.
$Payment = current($Payments);
if ($Payment) {
$Order->setPayment($Payment);
$Order->setPaymentMethod($Payment->getMethod());
}
}
/**
* @param Order $Order
* @param Shipping $Shipping
* @param array $OrderItems
*/
protected function addOrderItems(Order $Order, Shipping $Shipping, array $OrderItems)
{
foreach ($OrderItems as $OrderItem) {
$Shipping->addOrderItem($OrderItem);
$Order->addOrderItem($OrderItem);
$OrderItem->setOrder($Order);
$OrderItem->setShipping($Shipping);
}
}
/**
* @see Symfony\Bundle\FrameworkBundle\Controller\AbstractController
*/
private function isGranted($attribute, $subject = null): bool
{
return $this->container->get('security.authorization_checker')->isGranted($attribute, $subject);
}
/**
* @see Symfony\Bundle\FrameworkBundle\Controller\AbstractController
*/
private function getUser(): ?UserInterface
{
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return null;
}
if (!\is_object($user = $token->getUser())) {
return null;
}
return $user;
}
/**
* 税表示区分を取得する.
*
* - 商品: 税抜
* - 送料: 税込
* - 値引き: 税抜
* - 手数料: 税込
* - ポイント値引き: 税込
*
* @param $OrderItemType
*
* @return TaxDisplayType
*/
public function getTaxDisplayType($OrderItemType)
{
if ($OrderItemType instanceof OrderItemType) {
$OrderItemType = $OrderItemType->getId();
}
switch ($OrderItemType) {
case OrderItemType::PRODUCT:
return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::EXCLUDED);
case OrderItemType::DELIVERY_FEE:
return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::INCLUDED);
case OrderItemType::DISCOUNT:
return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::EXCLUDED);
case OrderItemType::CHARGE:
return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::INCLUDED);
case OrderItemType::POINT:
return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::INCLUDED);
default:
return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::EXCLUDED);
}
}
}