src/Controller/BookingApi/ApiOrderControllerApi.php line 329

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by Elements.at New Media Solutions GmbH
  4.  *
  5.  */
  6. namespace App\Controller\BookingApi;
  7. use App\Ecommerce\AvailabilitySystem\Event\AvailabilitySystem;
  8. use App\Ecommerce\CartManager\CartPriceModificator\CashbackInsurance;
  9. use App\Ecommerce\Checkout\Step\ConfirmStep;
  10. use App\Ecommerce\Checkout\Step\PaymentStep;
  11. use App\Ecommerce\Checkout\Step\ProductData;
  12. use App\Model\BookingApi\Cart;
  13. use App\Model\BookingApi\Exception\DataMissingException;
  14. use App\Model\BookingApi\Order;
  15. use App\Model\Shop\Checkout\ConfirmData;
  16. use App\Model\Shop\Checkout\PaymentData;
  17. use App\Model\Shop\Event\EventProduct;
  18. use App\Model\Type\TenantType;
  19. use App\Service\BookingApi\ApiService;
  20. use App\Service\Shop\ShopService;
  21. use App\Service\SiteConfigService;
  22. use App\Traits\EnvironmentTrait;
  23. use Carbon\Carbon;
  24. use Elements\Bundle\SkidataTicketingSwebBundle\Service\OrderService;
  25. use Elements\Bundle\TicketShopFrameworkBundle\Ecommerce\Checkout\Step\Address;
  26. use Elements\Bundle\TicketShopFrameworkBundle\Model\Shop\Cart\TicketShopCartInterface;
  27. use Elements\Bundle\TicketShopFrameworkBundle\Service\Swisspass\SwisspassService;
  28. use Elements\Bundle\TicketShopFrameworkBundle\Service\VoucherCodeService;
  29. use OpenApi\Annotations as OA;
  30. use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\CartInterface;
  31. use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
  32. use Pimcore\Model\Asset;
  33. use Pimcore\Model\DataObject\OnlineShopOrder;
  34. use Pimcore\Model\DataObject\TicketProduct;
  35. use Pimcore\Tool\Session;
  36. use Symfony\Component\HttpFoundation\JsonResponse;
  37. use Symfony\Component\HttpFoundation\Request;
  38. use Symfony\Component\Routing\Annotation\Route;
  39. use Symfony\Component\Routing\RouterInterface;
  40. use Symfony\Component\Validator\Validator\ValidatorInterface;
  41. /**
  42.  * Class ApiOrderControllerApi
  43.  *
  44.  * @Route("/bookingAPI/order")
  45.  *
  46.  * @OA\Tag(name="Order")
  47.  */
  48. class ApiOrderControllerApi extends ApiAbstractController
  49. {
  50.     use EnvironmentTrait;
  51.     /**
  52.      *
  53.      *
  54.      * @param Request $request
  55.      *
  56.      * @Route("/orderData", name="order_data", methods={"POST", "GET"})
  57.      *
  58.      * @OA\Post(
  59.      *      path="/bookingAPI/order/orderData",
  60.      *     description="get order data",
  61.      *     tags={"Order"},
  62.      *
  63.      *     @OA\RequestBody(
  64.      *      required=true,
  65.      *
  66.      *           @OA\JsonContent(
  67.      *              required={"orderId"},
  68.      *
  69.      *              @OA\Property( property="orderId", type="int", example=3467593),
  70.      *           )
  71.      *      ),
  72.      *
  73.      *     @OA\Response(
  74.      *          response=200,
  75.      *          description="Successful operation",
  76.      *
  77.      *          @OA\MediaType(
  78.      *              mediaType="application/json",
  79.      *
  80.      *              @OA\Schema(
  81.      *                  type = "object",
  82.      *
  83.      *                  @OA\Property(property="order", type="object", ref="#/components/schemas/Order")
  84.      *              )
  85.      *          )
  86.      *      ),
  87.      *
  88.      *     @OA\Response(
  89.      *          response=500,
  90.      *          description="Error",
  91.      *
  92.      *           @OA\MediaType(
  93.      *               mediaType="application/json",
  94.      *
  95.      *              @OA\Schema(
  96.      *                  type = "object",
  97.      *
  98.      *                   @OA\Property(property="errors", type="array", @OA\Items(type="string")),
  99.      *              )
  100.      *          )
  101.      *      ),
  102.      *     )
  103.      **/
  104.     public function orderData(Request $requestVoucherCodeService $voucherCodeServiceShopService $shopServiceSiteConfigService $siteConfigServiceRouterInterface $router): JsonResponse
  105.     {
  106.         try {
  107.             $body $request->getContent();
  108.             $requestData json_decode($bodytrue);
  109.             if ($request->get('debug')) {
  110.                 //$requestData['orderId'] = 3467593; //event; valid marketplace order
  111.                 $requestData['orderId'] = 3768048;
  112.             }
  113.             if (!$requestData['orderId']) {
  114.                 throw  new DataMissingException('missing orderId in order/orderData request ');
  115.             }
  116.             $order \App\Model\Shop\Order::getById($requestData['orderId']);
  117.             if (!$order) {
  118.                 $this->bookingapiLogger->warning('Requested order with id ' $requestData['orderId'] . ' not found.');
  119.                 return $this->sendErrors(['order not found'], 404);
  120.             } elseif ($order->getTenant() !== 'marketplace') {
  121.                 $this->bookingapiLogger->warning('Requested order with id ' $requestData['orderId'] . ' is not a marketplace order.');
  122.                 return $this->sendErrors(['order does not belong to tenant marketplace'], 404);
  123.             }
  124.             $bookingApiOrder = new Order();
  125.             $bookingApiOrder->setDataFromOnlineShopOrder($order$voucherCodeService$shopService$siteConfigService$router);
  126.             if ($request->get('debug')) {
  127.                 p_r($bookingApiOrder);
  128.                 die();
  129.             }
  130.             return new JsonResponse(
  131.                 [
  132.                     'order' => $bookingApiOrder->jsonSerialize(),
  133.                 ]
  134.             );
  135.         } catch (\Throwable $throwable) {
  136.             if ($request->get('debug')) {
  137.                 throw $throwable;
  138.             }
  139.             $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' $throwable->getTraceAsString());
  140.             return $this->sendErrors([$throwable->getMessage()], 500);
  141.         }
  142.     }
  143.     /**
  144.      *
  145.      *
  146.      * @param Request $request
  147.      *
  148.      * @Route("/submit", name="order_submit", methods={"POST", "GET"})
  149.      *
  150.      * @OA\Post(
  151.      *      path="/bookingAPI/order/submit",
  152.      *     description="Submit order (ticket, event)",
  153.      *     tags={"Order"},
  154.      *
  155.      *     @OA\RequestBody(
  156.      *      required=true,
  157.      *
  158.      *           @OA\JsonContent(
  159.      *              required={"cart"},
  160.      *
  161.      *                   @OA\Property(
  162.      *                      property="cart",
  163.      *                      ref="#/components/schemas/Cart"
  164.      *                  ),
  165.      *                  @OA\Property(
  166.      *                       property="externalTransactionId",
  167.      *                       type="string"
  168.      *                   ),
  169.      *
  170.      *                  @OA\Examples(example="eventExample", value="{""externalTransactionId"":""asdf1234"",""cart"":
  171.     {
  172.     ""userInfo"":{
  173.     ""email"": ""john.doe@example.com"",
  174.     ""address"": {
  175.     ""salutation"": ""male"",
  176.     ""firstName"": ""John"",
  177.     ""lastName"": ""Doe"",
  178.     ""phone"": ""1234567"",
  179.     ""street"": ""Somewhere No.10"",
  180.     ""streetAddition"": """",
  181.     ""city"": ""Over The Rainbow"",
  182.     ""zip"": ""1234"",
  183.     ""country"": ""CH"",
  184.     ""language"": ""de""
  185.     }
  186.     },
  187.     ""eventItems"": [
  188.     {
  189.     ""price"": 0,
  190.     ""eventId"": 17563,
  191.     ""dateTime"": 1713420000000,
  192.     ""ageGroupId"": 3002233,
  193.     ""amount"": 1
  194.     },
  195.     {
  196.     ""price"": 0,
  197.     ""eventId"": 17563,
  198.     ""dateTime"": 1713420000000,
  199.     ""ageGroupId"": 3002234,
  200.     ""amount"": 1
  201.     }
  202.     ],
  203.     ""price"": 79.5,
  204.     ""shipping"": 0,
  205.     ""totalPrice"": 79.5
  206.     }}", summary="a valid example for an event order"),
  207.     @OA\Examples(example="ticketSandboxExampleAdultReload", value="{""cart"":
  208.     {
  209.     ""userInfo"":{
  210.     ""email"": ""john.doe@example.com"",
  211.     ""address"": {
  212.     ""salutation"": ""male"",
  213.     ""firstName"": ""John"",
  214.     ""lastName"": ""Doe"",
  215.     ""phone"": ""1234567"",
  216.     ""street"": ""Somewhere No.10"",
  217.     ""streetAddition"": """",
  218.     ""city"": ""Over The Rainbow"",
  219.     ""zip"": ""1234"",
  220.     ""country"": ""CH"",
  221.     ""language"": ""de""
  222.     }
  223.     },
  224.     ""ticketItems"": [
  225.     {
  226.     ""price"": 30,
  227.     ""ticketId"": 3002546,
  228.     ""catalogId"": 3000188,
  229.     ""consumerCategoryId"": 3000153,
  230.     ""date"": ""2024-04-11"",
  231.     ""deliveryType"": ""reload"",
  232.     ""deliveryCountry"": ""CH"",
  233.     ""firstName"": ""John"",
  234.     ""lastName"": ""Doe"",
  235.     ""birthday"": ""2000-01-01"",
  236.     ""keycardNumber"": ""01-16147133534548799951-9""
  237.     }
  238.     ],
  239.     ""price"": 30,
  240.     ""shipping"": 0,
  241.     ""totalPrice"": 30
  242.     }}", summary="a valid example for a SANDBOX ticket order - adult reload"),
  243.     @OA\Examples(example="ticketSandboxExampleChildRequest", value="{""cart"":
  244.     {
  245.     ""userInfo"":{
  246.     ""email"": ""john.doe@example.com"",
  247.     ""address"": {
  248.     ""salutation"": ""male"",
  249.     ""firstName"": ""John"",
  250.     ""lastName"": ""Doe"",
  251.     ""phone"": ""1234567"",
  252.     ""street"": ""Somewhere No.10"",
  253.     ""streetAddition"": """",
  254.     ""city"": ""Over The Rainbow"",
  255.     ""zip"": ""1234"",
  256.     ""country"": ""CH"",
  257.     ""language"": ""de""
  258.     }
  259.     },
  260.     ""ticketItems"": [
  261.     {
  262.     ""price"": 15,
  263.     ""ticketId"": 3002546,
  264.     ""catalogId"": 3000188,
  265.     ""consumerCategoryId"": 3000159,
  266.     ""date"": ""2024-04-11"",
  267.     ""deliveryType"": ""request"",
  268.     ""deliveryCountry"": ""CH"",
  269.     ""firstName"": ""John"",
  270.     ""lastName"": ""Doe"",
  271.     ""birthday"": ""2005-01-01""
  272.     }
  273.     ],
  274.     ""price"": 15,
  275.     ""shipping"": 3,
  276.     ""totalPrice"": 18
  277.     }}", summary="a valid example for a SANDBOX ticket order - child request")
  278.      *           )
  279.      *      ),
  280.      *     @OA\Response(
  281.      *          response=200,
  282.      *          description="Successful operation",
  283.      *
  284.      *          @OA\MediaType(
  285.      *              mediaType="application/json",
  286.      *
  287.      *              @OA\Schema(
  288.      *                  type = "object",
  289.      *
  290.      *                  @OA\Property(property="success", type="boolean"),
  291.      *                  @OA\Property(property="order", type="object", ref="#/components/schemas/Order")
  292.      *              )
  293.      *          )
  294.      *      ),
  295.      *
  296.      *     @OA\Response(
  297.      *          response=500,
  298.      *          description="Error",
  299.      *
  300.      *           @OA\MediaType(
  301.      *               mediaType="application/json",
  302.      *
  303.      *              @OA\Schema(
  304.      *                  type = "object",
  305.      *
  306.      *                  @OA\Property(property="success", type="boolean"),
  307.      *                   @OA\Property(property="errors", type="array", @OA\Items(type="string")),
  308.      *              )
  309.      *          )
  310.      *      ),
  311.      *     )
  312.      *
  313.      */
  314.     public function submitAction(Request $request,
  315.                                  ApiService $apiService,
  316.                                  OrderService $orderService,
  317.                                  ShopService $shopService,
  318.                                  ValidatorInterface $validator,
  319.                                  VoucherCodeService $voucherCodeService,
  320.                                  SiteConfigService $siteConfigService,
  321.                                  RouterInterface $router,
  322.                                  SwisspassService $swisspassService): JsonResponse
  323.     {
  324.         try {
  325.             $body $request->getContent();
  326.             $requestData json_decode($bodytrue);
  327.             $this->setEnvironmentTenant();
  328.             $cart $this->getCart();
  329.             $this->clearCartData($cart);
  330.             if ($request->get('debug')) {
  331.                 $requestData = [
  332.                     'cart' => [
  333.                         'userInfo' => [
  334.                             'email' => 'john.doe@example.com',
  335.                             'address' => [
  336.                                 'salutation' => 'male',
  337.                                 'firstName' => 'John',
  338.                                 'lastName' => 'Doe',
  339.                                 'phone' => '1234567',
  340.                                 'street' => 'Somewhere No.10',
  341.                                 'streetAddition' => '',
  342.                                 'city' => 'Over The Rainbow',
  343.                                 'zip' => '1234',
  344.                                 'country' => 'CH',
  345.                                 'language' => 'de'],
  346.                         ],
  347.                         'ticketItems' => [
  348.                             [
  349.                               /*  'price' => 30,
  350.                                 'ticketId' => 3002546,
  351.                                 'catalogId' => 3000188,
  352.                                 'consumerCategoryId' => 3000153,*/
  353.                                 'price' => 83,
  354.                                 'ticketId' => 518928,
  355.                                 'catalogId' => 1501805,
  356.                                 'consumerCategoryId' => 502338,
  357.                                 'date' => '2024-04-30',
  358.                                 'deliveryType' => 'reload',
  359.                                 'deliveryCountry' => 'CH',
  360.                                 'firstName' => 'John',
  361.                                 'lastName' => 'Doe',
  362.                                 'birthday' => '2000-01-01',
  363.                                 'keycardNumber' => '01-16147133534874566908-0',
  364.                             ],
  365.                         ],
  366.                         'price' => 83,
  367.                         'shipping' => 0,
  368.                         //"cashbackInsuranceSelected" => true,
  369.                         'totalPrice' => 83,
  370.                     ],
  371.                 ];
  372.             }
  373.             if (!isset($requestData['cart'])) {
  374.                 $this->bookingapiLogger->error("received order submit without cart in payload:");
  375.                 $this->bookingapiLogger->error("####### REQUEST BODY #######");
  376.                 $this->bookingapiLogger->error($body);
  377.                 $this->bookingapiLogger->error("############################");
  378.                 throw new \Exception('endpoint requires a cart in request');
  379.             }
  380.             $externalTransactionId null;
  381.             if(isset($requestData['externalTransactionId'])){
  382.                 $externalTransactionId $requestData['externalTransactionId'];
  383.             }
  384.             $bookingApiCart = new Cart();
  385.             $bookingApiCart->unmarshall($requestData['cart']);
  386.             foreach($bookingApiCart->getTicketItems() as $cartItemTicket){
  387.                 $keycardCheck $this->keyCardNumberShortToLongCheck($cartItemTicket);
  388.                 if(!$keycardCheck){
  389.                     return $this->sendErrors(['could not map keycard short number '.$cartItemTicket->getKeycardShortnumber()], 500);
  390.                 }
  391.             }
  392.             //create session cart
  393.             $errors $this->fillSessionCartFromRequest($cart$bookingApiCart$apiService$orderService$shopService$validator$swisspassService);
  394.             if (!empty($errors)) {
  395.                 $this->bookingapiLogger->warning('Could not create session cart. Sending the following errors: ' implode(','$errors));
  396.                 return $this->sendErrors($errors500);
  397.             }
  398.             //is cart checkoutable and personalisation complete
  399.             if (!$cart->isCheckoutable()) {
  400.                 throw new \Exception('cart is not checkoutable');
  401.             }
  402.             //is cart price correct?
  403.             $cartTotal = (float)$cart->getPriceCalculator()->getGrandTotal()->getAmount()->asNumeric();
  404.             if ($bookingApiCart->getTotalPrice() != $cartTotal) {
  405.                 throw new \Exception('booking api cart received price is wrong. Total should be ' $cartTotal ' but is ' $bookingApiCart->getTotalPrice());
  406.             }
  407.             $checkoutManager Factory::getInstance()->getCheckoutManager($cart);
  408.             /**
  409.              * @var \App\Model\Shop\Order $order
  410.              */
  411.             $order $checkoutManager->getOrder();
  412.             $order->setTenant(TenantType::MARKETPLACE);
  413.             $isSandboxOnly true;
  414.             if (count($bookingApiCart->getTicketItems()) > 0) {
  415.                 foreach ($bookingApiCart->getTicketItems() as $index => $bookingApiCartItemTicket) {
  416.                     //TODO @msteyrer
  417.                     if ($bookingApiCartItemTicket->getCatalogId() != 3000188/* && !$request->get('debug')*/) {
  418.                         $isSandboxOnly false;
  419.                         //throw new \Exception('submitting orders with ticket items is currently only allowed for Skidata Sandbox Tickets from Catalog Id 3000188');
  420.                     }
  421.                     $errors $apiService->validateTicketData($shopService$orderService$validator$bookingApiCartItemTicket);
  422.                     if (!empty($errors)) {
  423.                         $errors[] = 'Error with ticket in index ' $index;
  424.                         return $this->sendErrors($errors500);
  425.                     }
  426.                 }
  427.             }
  428.             $addressStep $checkoutManager->getCheckoutStep(Address::STEP_NAME);
  429.             $productDataStep $checkoutManager->getCheckoutStep(ProductData::STEP_NAME);
  430.             $paymentStep $checkoutManager->getCheckoutStep(PaymentStep::STEP_NAME);
  431.             $confirmOrderStep $checkoutManager->getCheckoutStep(ConfirmStep::STEP_NAME);
  432.             $paymentData = new PaymentData();
  433.             $paymentData->setOrderLanguage($bookingApiCart->getUserInfo()->getAddress()->getLanguage());
  434.             $confirmOrderStepData = new ConfirmData();
  435.             $confirmOrderStepData->setGdprConsent(true);
  436.             $checkoutManager->commitStep($addressStepjson_decode($cart->getCheckoutData('address')));
  437.             $checkoutManager->commitStep($productDataStep, []);
  438.             $checkoutManager->commitStep($paymentStep$paymentData);
  439.             $checkoutManager->commitStep($confirmOrderStep$confirmOrderStepData);
  440.             $order->setOrderLanguage($bookingApiCart->getUserInfo()->getAddress()->getLanguage());
  441.             $order->setOrderDateFinished(Carbon::now());
  442.             //save image and set in order item
  443.             foreach ($bookingApiCart->getTicketItems() as $index => $bookingApiCartItemTicket) {
  444.                 if ($bookingApiCartItemTicket->getImageData() && $bookingApiCartItemTicket->getImageFileFormat()) {
  445.                     //add image to order item checkout data
  446.                     try {
  447.                         //find order item for cart item this is not ideal, but can not be done via Ids
  448.                         $foundItem null;
  449.                         foreach ($order->getItems() as $orderItem) {
  450.                             if ($orderItem->getCustomized()->getOrderItemCustomizedCheckoutData()) {
  451.                                 $checkoutData $orderItem->getCustomized()->getOrderItemCustomizedCheckoutData();
  452.                                 if ($checkoutData->getFirstname() == $bookingApiCartItemTicket->getFirstName() && $checkoutData->getLastname() == $bookingApiCartItemTicket->getLastName() && $orderItem->getProduct()->getParent()->getId() == $bookingApiCartItemTicket->getTicketId() && !$checkoutData->getImage()) {
  453.                                     $foundItem $orderItem;
  454.                                     break;
  455.                                 }
  456.                             }
  457.                         }
  458.                         if (!$foundItem) {
  459.                             throw new \Exception('could not process ticket image data - order item not found');
  460.                         }
  461.                         $checkoutData $foundItem->getCustomized()->getOrderItemCustomizedCheckoutData();
  462.                         //$parentFolderId = 692;
  463.                         $parentFolder $apiService->getTicketImageFolder($order);
  464.                         $parentFolderId $parentFolder->getId();
  465.                         $asset Asset\Image::create($parentFolderId, ['filename' => 'image-' $foundItem->getId() . '.' $bookingApiCartItemTicket->getImageFileFormat(), 'data' => base64_decode($bookingApiCartItemTicket->getImageData())]);
  466.                         /** @phpstan-ignore-next-line */
  467.                         $checkoutData->setImage($asset);
  468.                     } catch (\Exception $e) {
  469.                         $this->bookingapiLogger->error('Could not create image in ticket order: ' $e->getMessage());
  470.                         $errors[] = 'Error with ticket in index ' $index '. Could not process image.';
  471.                         return $this->sendErrors($errors500);
  472.                     }
  473.                 }
  474.             }
  475.             if($externalTransactionId){
  476.                 $order->setPaymentReference($externalTransactionId);
  477.             }
  478.             //skip transmit in dev (dev and stage are in dev environment)
  479.             //no skip transmit at the moment - dev and stage are using skidata sandbox
  480.             /*if(!$isSandboxOnly && $_SERVER["APP_ENV"] == "dev"){
  481.                 //prevent order transmission to skidata unless it is sandbox order
  482.                 $order->getOrCreateCheckoutDataBrick()->setSkipTransmit(true);
  483.             }*/
  484.             $order $checkoutManager->commitOrder();
  485.             $bookingApiOrder = new Order();
  486.             /** @phpstan-ignore-next-line */
  487.             $bookingApiOrder->setDataFromOnlineShopOrder($order$voucherCodeService$shopService$siteConfigService$router);
  488.             $this->bookingapiLogger->info('Submitted order #' $order->getId());
  489.             return new JsonResponse(
  490.                 [
  491.                     'success' => $bookingApiOrder->getOrderState() == 'committed',
  492.                     'order' => $bookingApiOrder->jsonSerialize(),
  493.                 ]
  494.             );
  495.         } catch (\Throwable $throwable) {
  496.             $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' $throwable->getTraceAsString());
  497.             return $this->sendErrors([$throwable->getMessage()], 500);
  498.         }
  499.     }
  500.     /**
  501.      * @Route("/totalPrice", name="order_price", methods={"POST", "GET"})
  502.      *
  503.      * @param Request $request
  504.      *
  505.      * @return JsonResponse
  506.      *
  507.      * @OA\Post(
  508.      *      path="/bookingAPI/order/totalPrice",
  509.      *     description="Get cart with total price for order before submitting the Order",
  510.      *     tags={"Order"},
  511.      *
  512.      *      @OA\RequestBody(
  513.      *      required=true,
  514.      *
  515.      *           @OA\JsonContent(
  516.      *              required={"cart"},
  517.      *
  518.      *              @OA\Property( property="cart", type="object", ref="#/components/schemas/Cart"),
  519.      *
  520.      *              @OA\Examples(example="eventExample", value="{""cart"": {
  521.     ""userInfo"":{
  522.     ""email"": ""john.doe@example.com"",
  523.     ""address"": {
  524.     ""salutation"": ""male"",
  525.     ""firstName"": ""John"",
  526.     ""lastName"": ""Doe"",
  527.     ""phone"": ""1234567"",
  528.     ""street"": ""Somewhere No.10"",
  529.     ""streetAddition"": """",
  530.     ""city"": ""Over The Rainbow"",
  531.     ""zip"": ""1234"",
  532.     ""country"": ""CH"",
  533.     ""language"": ""de""
  534.     }
  535.     },
  536.     ""eventItems"": [
  537.     {
  538.     ""price"": 0,
  539.     ""eventId"": 17563,
  540.     ""dateTime"": 1713420000000,
  541.     ""ageGroupId"": 3002233,
  542.     ""amount"": 1
  543.     },
  544.     {
  545.     ""price"": 0,
  546.     ""eventId"": 17563,
  547.     ""dateTime"": 1713420000000,
  548.     ""ageGroupId"": 3002234,
  549.     ""amount"": 1
  550.     }
  551.     ],
  552.     ""price"": 0,
  553.     ""shipping"": 0,
  554.     ""totalPrice"": 0
  555.     }}", summary="event for one adult + one child"),
  556.      *                  @OA\Examples(example="ticketExampleRequest", value="{""cart"": {
  557.     ""userInfo"":{
  558.     ""email"": ""john.doe@example.com"",
  559.     ""address"": {
  560.     ""salutation"": ""male"",
  561.     ""firstName"": ""John"",
  562.     ""lastName"": ""Doe"",
  563.     ""phone"": ""1234567"",
  564.     ""street"": ""Somewhere No.10"",
  565.     ""streetAddition"": """",
  566.     ""city"": ""Over The Rainbow"",
  567.     ""zip"": ""1234"",
  568.     ""country"": ""CH"",
  569.     ""language"": ""de""
  570.     }
  571.     },
  572.     ""ticketItems"": [
  573.     {
  574.     ""price"": 107,
  575.     ""ticketId"": 518928,
  576.     ""catalogId"": 1501805,
  577.     ""consumerCategoryId"": 502338,
  578.     ""date"": ""2024-04-30"",
  579.     ""deliveryType"": ""request"",
  580.     ""deliveryCountry"": ""CH"",
  581.     ""firstName"": ""John"",
  582.     ""lastName"": ""Doe"",
  583.     ""birthday"": ""2000-01-01"",
  584.     ""insuranceId"": 518668
  585.     }
  586.     ],
  587.     ""price"": 0,
  588.     ""shipping"": 0,
  589.     ""totalPrice"": 0
  590.     }}", summary="ticket for one adult - request"),
  591.      *                  @OA\Examples(example="ticketExampleReloadSandbox", value="{""cart"": {
  592.     ""userInfo"":{
  593.     ""email"": ""john.doe@example.com"",
  594.     ""address"": {
  595.     ""salutation"": ""male"",
  596.     ""firstName"": ""John"",
  597.     ""lastName"": ""Doe"",
  598.     ""phone"": ""1234567"",
  599.     ""street"": ""Somewhere No.10"",
  600.     ""streetAddition"": """",
  601.     ""city"": ""Over The Rainbow"",
  602.     ""zip"": ""1234"",
  603.     ""country"": ""CH"",
  604.     ""language"": ""de""
  605.     }
  606.     },
  607.     ""ticketItems"": [
  608.     {
  609.     ""price"": 107,
  610.     ""ticketId"": 518928,
  611.     ""catalogId"": 1501805,
  612.     ""consumerCategoryId"": 502338,
  613.     ""date"": ""2024-04-30"",
  614.     ""deliveryType"": ""reload"",
  615.     ""deliveryCountry"": ""CH"",
  616.     ""firstName"": ""John"",
  617.     ""lastName"": ""Doe"",
  618.     ""birthday"": ""2000-01-01"",
  619.     ""keycardNumber"": ""01-16147133534548799951-9"",
  620.     ""insuranceId"": 518668
  621.     }
  622.     ],
  623.     ""price"": 0,
  624.     ""shipping"": 0,
  625.     ""totalPrice"": 0
  626.     }}", summary="ticket for one adult - reload"),
  627.     @OA\Examples(example="ticketExampleReload", value="{""cart"": {
  628.     ""userInfo"":{
  629.     ""email"": ""john.doe@example.com"",
  630.     ""address"": {
  631.     ""salutation"": ""male"",
  632.     ""firstName"": ""John"",
  633.     ""lastName"": ""Doe"",
  634.     ""phone"": ""1234567"",
  635.     ""street"": ""Somewhere No.10"",
  636.     ""streetAddition"": """",
  637.     ""city"": ""Over The Rainbow"",
  638.     ""zip"": ""1234"",
  639.     ""country"": ""CH"",
  640.     ""language"": ""de""
  641.     }
  642.     },
  643.     ""ticketItems"": [
  644.     {
  645.     ""price"": 30,
  646.     ""ticketId"": 3002546,
  647.     ""catalogId"": 3000188,
  648.     ""consumerCategoryId"": 3000153,
  649.     ""date"": ""2024-04-11"",
  650.     ""deliveryType"": ""reload"",
  651.     ""deliveryCountry"": ""CH"",
  652.     ""firstName"": ""John"",
  653.     ""lastName"": ""Doe"",
  654.     ""birthday"": ""2000-01-01"",
  655.     ""keycardNumber"": ""01-16147133534548799951-9""
  656.     }
  657.     ],
  658.     ""price"": 0,
  659.     ""shipping"": 0,
  660.     ""totalPrice"": 0
  661.     }}", summary="ticket for one adult - reload SANDBOX")
  662.      *
  663.      *           )
  664.      *      ),
  665.      *     @OA\Response(
  666.      *          response=200,
  667.      *          description="Successful operation",
  668.      *
  669.      *          @OA\MediaType(
  670.      *              mediaType="application/json",
  671.      *
  672.      *              @OA\Schema(
  673.      *                  type = "object",
  674.      *
  675.      *                  @OA\Property(property="success", type="boolean"),
  676.      *                  @OA\Property(property="cart", type="object", ref="#/components/schemas/Cart")
  677.      *              )
  678.      *          )
  679.      *     ),
  680.      *
  681.      *     @OA\Response(
  682.      *          response=500,
  683.      *          description="Error"
  684.      *     )
  685.      * )
  686.      */
  687.     public function getTotalPrice(Request $request,
  688.                                   ApiService $apiService,
  689.                                   OrderService $orderService,
  690.                                   ShopService $shopService,
  691.                                   ValidatorInterface $validator,
  692.                                   SwisspassService $swisspassService): JsonResponse
  693.     {
  694.         try {
  695.             $body $request->getContent();
  696.             $requestData json_decode($bodytrue);
  697.             $this->setEnvironmentTenant();
  698.             $cart $this->getCart();
  699.             //p_r($cart);die();
  700.             $this->clearCartData($cart);
  701.             if ($request->get('debug')) {
  702.                 $requestData = [
  703.                     'cart' => [
  704.                         'userInfo' => [
  705.                             'email' => 'john.doe@example.com',
  706.                             'address' => [
  707.                                 'salutation' => 'male',
  708.                                 'firstName' => 'John',
  709.                                 'lastName' => 'Doe',
  710.                                 'phone' => '1234567',
  711.                                 'street' => 'Somewhere No.10',
  712.                                 'streetAddition' => '',
  713.                                 'city' => 'Over The Rainbow',
  714.                                 'zip' => '1234',
  715.                                 'country' => 'CH',
  716.                                 'language' => 'de'],
  717.                         ],
  718.                         "eventItems" => [
  719.                             [
  720.                                 "price" => 0,
  721.                                 "eventId" => 36661,
  722.                                 "dateTime" => 1711407600000,
  723.                                 "ageGroupId" => 3002389,
  724.                                 "amount" => 1
  725.                             ]
  726.                         ],
  727.                         /*
  728.                         "ticketItems" => [
  729.                             [
  730.                                 "price" => 107,
  731.                                 "ticketId" => 518928,
  732.                                 "catalogId" => 1501805,
  733.                                 "consumerCategoryId" => 502338,
  734.                                 "date" => "2024-04-30",
  735.                                 "deliveryType" => "reload",
  736.                                 "deliveryCountry" => "CH",
  737.                                 "firstName" => "John",
  738.                                 "lastName" => "Doe",
  739.                                 "birthday" => "2000-01-01",
  740.                                 //        "keycardNumber"=> "01-16147133534548799951-9",
  741.                                         "keycardShortnumber"=> "U123123",
  742.                                 //        "swisspassNumber"=> "",
  743.                                 //       "swisspassZip"=> "",
  744.                                 //"insuranceId" => 518668
  745.                             ]
  746.                         ], */
  747.                /*         'ticketItems' => [
  748.                             [
  749.                                 'price' => 30,
  750.                                 'ticketId' => 3002546,
  751.                                 'catalogId' => 3000188,
  752.                                 'consumerCategoryId' => 3000153,
  753.                                 'date' => '2024-04-11',
  754.                                 'deliveryType' => 'reload',
  755.                                 'deliveryCountry' => 'CH',
  756.                                 'firstName' => 'John',
  757.                                 'lastName' => 'Doe',
  758.                                 'birthday' => '2000-01-01',
  759.                                 'keycardNumber' => '01-16147133534548799951-9',
  760.                             ],
  761.                         ], */
  762.                         'price' => 0,
  763.                         'shipping' => 0,
  764.                         //"cashbackInsuranceSelected" => true,
  765.                         'totalPrice' => 0,
  766.                     ],
  767.                 ];
  768.             }
  769.             if (!isset($requestData['cart'])) {
  770.                 $this->bookingapiLogger->error("received total price request without cart in payload:");
  771.                 $this->bookingapiLogger->error("####### REQUEST BODY #######");
  772.                 $this->bookingapiLogger->error($body);
  773.                 $this->bookingapiLogger->error("############################");
  774.                 throw new \Exception('endpoint requires a cart in request');
  775.             } else {
  776.                 $this->bookingapiLogger->error("received total price request");
  777.                 $this->bookingapiLogger->error("####### REQUEST BODY #######");
  778.                 $this->bookingapiLogger->error($body);
  779.                 $this->bookingapiLogger->error("############################");
  780.             }
  781.             $bookingApiCart = new Cart();
  782.             $bookingApiCart->unmarshall($requestData['cart']);
  783.             if ($bookingApiCart->getPrice() > || $bookingApiCart->getTotalPrice() > || $bookingApiCart->getShipping() > 0) {
  784.                 $this->bookingapiLogger->warning('Invalid call to order/totalPrice endpoint: cart.price, cart.shipping and cart.totalPrice must be empty for totalPrice endpoint.');
  785.                 return $this->sendErrors(['cart.price, cart.shipping and cart.totalPrice must be empty for totalPrice endpoint. prices will be calculated and returned in response'], 500);
  786.             }
  787.             foreach($bookingApiCart->getTicketItems() as $cartItemTicket){
  788.                 $keycardCheck $this->keyCardNumberShortToLongCheck($cartItemTicket);
  789.                 if(!$keycardCheck){
  790.                     return $this->sendErrors(['could not map keycard short number '.$cartItemTicket->getKeycardShortnumber()], 500);
  791.                 }
  792.             }
  793.             $errors $this->fillSessionCartFromRequest($cart$bookingApiCart$apiService$orderService$shopService$validator$swisspassService);
  794.             if (!empty($errors)) {
  795.                 $this->bookingapiLogger->warning('Could not create session cart. Sending the following errors: ' implode(','$errors));
  796.                 return $this->sendErrors($errors500);
  797.             }
  798.             //convert session cart to new booking api cart for return
  799.             $bookingApiCartReturn = new Cart();
  800.             $bookingApiCartReturn->setDataFromEcommerceCart($cart$this->languages);
  801.             if ($request->get('debug') && \Pimcore::inDebugMode()) {
  802.                 p_r($cart);
  803.                 p_r($bookingApiCartReturn);
  804.                 die();
  805.             }
  806.             return new JsonResponse(
  807.                 [
  808.                     'success' => true,
  809.                     'cart' => $bookingApiCartReturn->jsonSerialize(),
  810.                 ]
  811.             );
  812.         } catch (\Throwable $throwable) {
  813.             if ($request->get('debug')) {
  814.                 throw $throwable;
  815.             } else {
  816.                 $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' $throwable->getTraceAsString());
  817.                 return $this->sendErrors([$throwable->getMessage()], 500);
  818.             }
  819.         }
  820.     }
  821.     /**
  822.      * @param TicketShopCartInterface $cart
  823.      * @param Cart $bookingApiCart
  824.      * @param ApiService $apiService
  825.      * @param OrderService $orderService
  826.      * @param ShopService $shopService
  827.      * @param ValidatorInterface $validator
  828.      *
  829.      * @return array<string>
  830.      *
  831.      * @throws \Exception
  832.      */
  833.     protected function fillSessionCartFromRequest(TicketShopCartInterface $cart,
  834.                                                   Cart $bookingApiCart,
  835.                                                   ApiService $apiService,
  836.                                                   OrderService $orderService,
  837.                                                   ShopService $shopService,
  838.                                                   ValidatorInterface $validator,
  839.                                                   SwisspassService $swisspassService
  840.     ): array
  841.     {
  842.         $errors = [];
  843.         $checkoutDataProduct = [];
  844.         //user info and delivery info
  845.         $userInfo $bookingApiCart->getUserInfo();
  846.         $checkoutDataAddress = [
  847.             'customerSalutation' => $userInfo->getAddress()->getSalutation(),
  848.             'customerFirstname' => $userInfo->getAddress()->getFirstName(),
  849.             'customerLastname' => $userInfo->getAddress()->getLastName(),
  850.             'customerPhone' => $userInfo->getAddress()->getPhone(),
  851.             'customerStreet' => $userInfo->getAddress()->getStreet(),
  852.             'customerStreetAddon' => $userInfo->getAddress()->getStreetAddition(),
  853.             'customerZip' => $userInfo->getAddress()->getZip(),
  854.             'customerCity' => $userInfo->getAddress()->getCity(),
  855.             'customerCountry' => $userInfo->getAddress()->getCountry(),
  856.             'customerLanguage' => $userInfo->getAddress()->getLanguage(),
  857.             'customerEmail' => $userInfo->getEmail(),
  858.             'useDeliveryAddress' => false,
  859.         ];
  860.         if ($bookingApiCart->getDeliveryInfo()) {
  861.             $deliveryInfo $bookingApiCart->getDeliveryInfo();
  862.             $checkoutDataAddress['useDeliveryAddress'] = true;
  863.             $checkoutDataAddress['deliveryCountry'] = $deliveryInfo->getAddress()->getCountry();
  864.             $checkoutDataAddress['deliverySalutation'] = $deliveryInfo->getAddress()->getSalutation();
  865.             $checkoutDataAddress['deliveryFirstname'] = $deliveryInfo->getAddress()->getFirstName();
  866.             $checkoutDataAddress['deliveryLastname'] = $deliveryInfo->getAddress()->getLastName();
  867.             $checkoutDataAddress['deliveryPhone'] = $deliveryInfo->getAddress()->getPhone();
  868.             $checkoutDataAddress['deliveryStreet'] = $deliveryInfo->getAddress()->getStreet();
  869.             $checkoutDataAddress['deliveryStreetAddon'] = $deliveryInfo->getAddress()->getStreetAddition();
  870.             $checkoutDataAddress['deliveryZip'] = $deliveryInfo->getAddress()->getZip();
  871.             $checkoutDataAddress['deliveryCity'] = $deliveryInfo->getAddress()->getCity();
  872.         }
  873.         $cart->setCheckoutData('address'json_encode($checkoutDataAddress));
  874.         //event items
  875.         if ($bookingApiCart->eventItems) {
  876.             foreach ($bookingApiCart->getEventItems() as $index => $bookingApiCartItemEvent) {
  877.                 $product EventProduct::getById($bookingApiCartItemEvent->getEventId());
  878.                 $selectedDate = new Carbon();
  879.                 $selectedDate->setTimestamp($bookingApiCartItemEvent->getDateTime()/1000);
  880.                 $quantity $bookingApiCartItemEvent->getAmount();
  881.                 $priceObj \App\Model\Shop\Event\ShopDynamicPrice::getById($bookingApiCartItemEvent->getAgeGroupId());
  882.                 /**@var $availabilitySystem AvailabilitySystem **/
  883.                 $availabilitySystem $product->getAvailabilitySystemImplementation();
  884.                 /** @phpstan-ignore-next-line */
  885.                 $availability $availabilitySystem->getAvailableQuota($product$selectedDate);
  886.                 if($availability->getAvailableQuota() < $quantity){
  887.                     $errors[] = 'Error with event in index '.$index.': available quota can not satisfy requested quantity';
  888.                     return $errors;
  889.                 }
  890.                 $sessionCartItem $shopService->addEventToCartPerPriceObject($product$priceObj$cart$selectedDate$quantity);
  891.                 if(!$sessionCartItem){
  892.                     $errors[] = 'Error with event in index ' $index ' could not add to cart event Id '.$product->getId().' for date '$selectedDate->format('c');
  893.                     return $errors;
  894.                 }
  895.             }
  896.         }
  897.         //ticket items
  898.         if ($bookingApiCart->ticketItems) {
  899.             foreach ($bookingApiCart->getTicketItems() as $index => $bookingApiCartItemTicket) {
  900.                 $errors $apiService->validateTicketData($shopService$orderService$validator$bookingApiCartItemTicket);
  901.                 if (!empty($errors)) {
  902.                     $errors[] = 'Error with ticket in index ' $index;
  903.                     return $errors;
  904.                 } else {
  905.                     //add ticket to session cart
  906.                     $ticketProduct \Elements\Bundle\TicketShopFrameworkBundle\Model\DataObject\TicketProduct::getById($bookingApiCartItemTicket->getTicketId());
  907.                     $insurance \Elements\Bundle\TicketShopFrameworkBundle\Model\DataObject\TicketProduct::getById($bookingApiCartItemTicket->getInsuranceId());
  908.                     $consumerCategory \Elements\Bundle\TicketShopFrameworkBundle\Model\DataObject\TicketConsumerCategory::getById($bookingApiCartItemTicket->getConsumerCategoryId());
  909.                     $ticketCatalog \App\Model\Shop\Ticket\ShopTicketCatalog::getById($bookingApiCartItemTicket->getCatalogId());
  910.                     $ticketStartDate Carbon::createFromFormat('Y-m-d'$bookingApiCartItemTicket->getDate());
  911.                     $params = [
  912.                         //isUpgradeOption setting is only relevant for ticket shop, not for API booking
  913.                         'isUpgradeOption' => false,
  914.                         'originalProduct' => $ticketProduct,
  915.                     ];
  916.                     $addedItems $shopService->addTicketItemsToCartPerConsumer(
  917.                         $ticketProduct,
  918.                         $consumerCategory,
  919.                         1,
  920.                         $insurance,
  921.                         $ticketCatalog,
  922.                         $cart,
  923.                         $ticketStartDate,
  924.                         $params
  925.                     );
  926.                     if (!isset($addedItems[0])) {
  927.                         $errors[] = 'could not add ticket in index ' $index ' to cart';
  928.                         return $errors;
  929.                     }
  930.                     $cartItemKey $addedItems[0];
  931.                     //acquisition type / delivery
  932.                     $cartProduct $cart->getItem($cartItemKey)->getProduct();
  933.                     /**
  934.                      * @var TicketProduct $cartProduct
  935.                      */
  936.                     $cartProduct->setAcquisitionType($bookingApiCartItemTicket->getDeliveryType());
  937.                     //personalisation and keycard
  938.                     $checkoutDataProduct[$cartItemKey] = [
  939.                         'firstname' => $bookingApiCartItemTicket->getFirstName(),
  940.                         'lastname' => $bookingApiCartItemTicket->getLastName(),
  941.                         'birthday' => $bookingApiCartItemTicket->getBirthday() . 'T00:00:00+0000',
  942.                         'deliveryOption' => $bookingApiCartItemTicket->getDeliveryType(),
  943.                     ];
  944.                     if ($bookingApiCartItemTicket->getKeycardNumber()) {
  945.                         $checkoutDataProduct[$cartItemKey]['keycard'] = $bookingApiCartItemTicket->getKeycardNumber();
  946.                     }
  947.                     if ($bookingApiCartItemTicket->getSwisspassNumber()) {
  948.                         $keycardNr $swisspassService->getKeyCardNumberForSwissPass($bookingApiCartItemTicket->getSwisspassNumber(), $bookingApiCartItemTicket->getSwisspassZip());
  949.                         if(!$keycardNr){
  950.                             $errors "could not get keycard number for SwissPass Id ".$bookingApiCartItemTicket->getSwisspassNumber();
  951.                         }
  952.                         $checkoutDataProduct[$cartItemKey]['isHTAConsumer'] = (bool)$cartProduct->getTicketConsumerCategory()->getIsSwisspassHTAConsumer();
  953.                         $checkoutDataProduct[$cartItemKey]['keycard'] = $keycardNr;
  954.                         $checkoutDataProduct[$cartItemKey]['swisspassId'] = $bookingApiCartItemTicket->getSwisspassNumber();
  955.                         $checkoutDataProduct[$cartItemKey]['zip'] = $bookingApiCartItemTicket->getSwisspassZip();
  956.                     }
  957.                     $cart->setCheckoutData('productData'json_encode($checkoutDataProduct));
  958.                 }
  959.             }
  960.         }
  961.         //cashback
  962.         if ($bookingApiCart->getCashbackInsuranceSelected()) {
  963.             foreach ($cart->getPriceCalculator()->getModificators() as $possibleMod) {
  964.                 if ($possibleMod instanceof CashbackInsurance) {
  965.                     if (!$possibleMod->isOptionAllowed($cart)) {
  966.                         $errors[] = 'cashback insurance not allowed for cart';
  967.                     } else {
  968.                         /** @phpstan-ignore-next-line */
  969.                         $cart->setCheckoutData('cashback'true);
  970.                     }
  971.                     break;
  972.                 }
  973.             }
  974.         }
  975.         $cart->setCheckoutData('apiBooking''');
  976.         return $errors;
  977.     }
  978. }