protected function fetchConditions() { parent::fetchConditions(); $filter_abandoned = $this->getState('filter.abandoned', 0); if ($filter_abandoned) { // abandoned carts are only valid when they have items in them $this->setCondition('items', array('$not' => array('$size' => 0))); // only users $this->setCondition('user_id', array('$nin' => array('', null))); $settings = \Shop\Models\Settings::fetch(); $abandoned_time = $settings->get('abandoned_cart_time') * 60; // set starting date and time for abandoned carts? $filter_abandoned_datetime = $this->getState('filter.abandoned.datetime'); if (!empty($filter_abandoned_datetime)) { $abandoned_time = $filter_abandoned_datetime - $abandoned_time; } else { // or use current timestamp $abandoned_time = time() - $abandoned_time; } $this->setCondition('metadata.last_modified.time', array('$lt' => $abandoned_time)); // only newly abandoned carts $filter_only_new = $this->getState('filter.abandoned_only_new', 0); if ($filter_only_new) { // TODO OR NULL $this->setCondition('abandoned_notifications', array('$size' => 0)); } } }
protected function preSite() { if (class_exists('\\Search\\Factory')) { \Search\Factory::registerSource(new \Search\Models\Source(array('id' => 'shop.products', 'title' => 'Products', 'class' => '\\Shop\\Models\\Products'))); } if (class_exists('\\Minify\\Factory')) { \Minify\Factory::registerPath($this->dir . "/src/"); $files = array('Shop/Assets/js/class.js', 'Shop/Assets/js/validation.js', 'Shop/Assets/js/site.js', 'Shop/Assets/js/jquery.popupoverlay.js', 'Shop/Assets/js/jquery.scrollTo.js', 'Shop/Assets/js/jquery.payment.js', 'Shop/Assets/js/jquery.star-rating.js'); if ($check_campaigns = \Dsc\System::instance()->get('session')->get('shop.check_campaigns')) { $files[] = 'Shop/Assets/js/check_campaigns.js'; } foreach ($files as $file) { \Minify\Factory::js($file); } $files = array('Shop/Assets/css/jquery.star-rating.css'); foreach ($files as $file) { \Minify\Factory::css($file); } } $app = \Base::instance(); $request_kmi = \Dsc\System::instance()->get('input')->get('kmi', null, 'string'); $cookie_kmi = $app->get('COOKIE.kmi'); if (!empty($request_kmi)) { if ($cookie_kmi != $request_kmi) { $app->set('COOKIE.kmi', $request_kmi); } $cart = \Shop\Models\Carts::fetch(); if (empty($cart->user_email)) { $cart->user_email = $request_kmi; $cart->store(); } } // symlink to the public folder if necessary if (!is_dir($this->app->get('PATH_ROOT') . 'public/ShopAssets')) { $public_assets = $this->app->get('PATH_ROOT') . 'public/ShopAssets'; $app_assets = realpath(__DIR__ . '/src/Shop/Assets'); $res = symlink($app_assets, $public_assets); } static::diagnostics(); }
/** * Adds an item to the wishlist * * @param string $variant_id * @param \Shop\Models\Products $product * @param array $post */ public function addItem($variant_id, \Shop\Models\Products $product, array $post) { $wishlistitem = \Shop\Models\Carts::createItem($variant_id, $product, $post); // Is the item already in the wishlist? // if so, inc quantity // otherwise add the wishlistitem $exists = false; foreach ($this->items as $key => $item) { if ($item['hash'] == $wishlistitem->hash) { $exists = true; $wishlistitem->id = $item['id']; $wishlistitem->quantity = $wishlistitem->quantity + $item['quantity']; $this->items[$key] = $wishlistitem->cast(); break; } } if (!$exists) { $this->items[] = $wishlistitem->cast(); } return $this->save(); }
/** * */ public function moveToCart() { $f3 = \Base::instance(); $wishlist_id = $this->inputfilter->clean($f3->get('PARAMS.id'), 'alnum'); $wishlistitem_hash = $this->inputfilter->clean($f3->get('PARAMS.hash'), 'cmd'); $identity = \Dsc\System::instance()->get('auth')->getIdentity(); $session_id = \Dsc\System::instance()->get('session')->id(); $wishlist = (new \Shop\Models\Wishlists())->load(array('_id' => new \MongoId((string) $wishlist_id))); if (empty($wishlist->id)) { if ($f3->get('AJAX')) { return $this->outputJson($this->getJsonResponse(array('result' => false, 'message' => 'Invalid wishlist'))); } else { \Dsc\System::addMessage('Invalid Wishlist', 'error'); $f3->reroute('/shop/wishlist'); return; } } // Validate that this wishlist belongs to the current user if ($identity->id != $wishlist->user_id && $session_id != $wishlist->session_id) { if ($f3->get('AJAX')) { return $this->outputJson($this->getJsonResponse(array('result' => false, 'message' => 'Not your wishlist'))); } else { \Dsc\System::addMessage('Not your wishlist', 'error'); $f3->reroute('/shop/wishlist'); return; } } $cart = \Shop\Models\Carts::fetch(); try { $wishlist->moveToCart($wishlistitem_hash, $cart); } catch (\Exception $e) { if ($f3->get('AJAX')) { return $this->outputJson($this->getJsonResponse(array('result' => false, 'message' => 'Item could not be moved to cart'))); } else { \Dsc\System::addMessage('Item could not be moved to cart', 'error'); \Dsc\System::addMessage($e->getMessage(), 'error'); $f3->reroute('/shop/wishlist/' . $wishlist->id); return; } } if ($f3->get('AJAX')) { return $this->outputJson($this->getJsonResponse(array('result' => true, 'message' => 'Item moved to cart'))); } else { \Dsc\System::addMessage('Item moved to cart'); $f3->reroute('/shop/wishlist/' . $wishlist->id); } }
/** * Determines if this coupon is valid for a cart * * @param \Shop\Models\Carts $cart * @throws \Exception */ public function cartValid(\Shop\Models\Carts $cart) { // Set $this->__is_validated = true if YES, cart can use this coupon // throw an Exception if NO, cart cannot use this coupon /** * is the coupon published? */ if (!$this->published()) { throw new \Exception('This coupon is expired.'); } /** * Only 1 user-submitted coupon per cart, * and if the auto-coupon is exclusive, it can't be added with others */ // If this is a user-submitted coupon && there are other user-submitted coupons in the cart, fail if (empty($this->usage_automatic) && $cart->userCoupons() && $cart->userCoupons()[0]['code'] != $this->code) { throw new \Exception('Only one coupon allowed per cart'); } // if this is an automatic coupon && usage_with_others == 0 && there are other automatic coupons in the cart if ($this->usage_automatic && empty($this->usage_with_others) && $cart->autoCoupons()) { throw new \Exception('This coupon cannot be combined with others'); } // TODO take min_subtotal_amount_currency into account once we have currencies sorted if (!empty($this->min_subtotal_amount) && $cart->subtotal() < $this->min_subtotal_amount) { throw new \Exception('Cart has not met the minimum required subtotal'); } // TODO take min_order_amount_currency into account once we have currencies sorted $total = $cart->subtotal() - $cart->giftCardTotal() - $cart->discountTotal() - $cart->creditTotal(); // Add back the value of this coupon in case it is already applied foreach ($cart->allCoupons() as $coupon) { if ((string) $coupon['_id'] == (string) $this->id) { $total = $total + $coupon['amount']; break; } } if (!empty($this->min_order_amount) && $total < $this->min_order_amount) { throw new \Exception('Cart has not met the minimum required amount'); } /** * check that at least one of the $this->required_products is in the cart */ if (!empty($this->required_products)) { // get the IDs of all products in this cart $product_ids = array(); foreach ($cart->items as $cartitem) { $product_ids[] = (string) \Dsc\ArrayHelper::get($cartitem, 'product_id'); } $intersection = array_intersect($this->required_products, $product_ids); if (empty($intersection)) { throw new \Exception('Coupon does not apply to any products in your cart.'); } } /** * check that at least one of the $this->required_coupons is in the cart */ if (!empty($this->required_coupons)) { // get the IDs of all coupons in this cart $coupon_ids = array(); foreach ($cart->userCoupons() as $coupon) { $coupon_ids[] = (string) $coupon['_id']; } foreach ($cart->autoCoupons() as $coupon) { $coupon_ids[] = (string) $coupon['_id']; } $intersection = array_intersect($this->required_coupons, $coupon_ids); if (empty($intersection)) { throw new \Exception('Cart does not have any of the required coupons'); } } /** * check that at least one of the products from $this->required_collections is in the cart */ if (!empty($this->required_collections)) { // get the IDs of all products in this cart $product_ids = array(); foreach ($cart->items as $cartitem) { $product_ids[] = (string) \Dsc\ArrayHelper::get($cartitem, 'product_id'); } $found = false; foreach ($this->required_collections as $collection_id) { $collection_product_ids = \Shop\Models\Collections::productIds($collection_id); $intersection = array_intersect($collection_product_ids, $product_ids); if (!empty($intersection)) { $found = true; break; // if its found, break the foreach loop } } if (!$found) { throw new \Exception('Coupon does not apply to any products in your cart.'); } } /** * evaluate shopper groups against $this->groups */ if (!empty($this->groups)) { $groups = array(); $user = (new \Users\Models\Users())->setState('filter.id', $cart->user_id)->getItem(); if (empty($cart->user_id) || empty($user->id)) { // Get the default group $group_id = \Shop\Models\Settings::fetch()->{'users.default_group'}; if (!empty($group_id)) { $groups[] = (new \Users\Models\Groups())->setState('filter.id', (string) $group_id)->getItem(); } } elseif (!empty($user->id)) { $groups = $user->groups(); } $group_ids = array(); foreach ($groups as $group) { $group_ids[] = (string) $group->id; } switch ($this->groups_method) { case "none": $intersection = array_intersect($this->groups, $group_ids); if (!empty($intersection)) { throw new \Exception('Your order does not qualify for this discount.'); } break; case "all": // $missing_groups == the ones from $this->groups that are NOT in $group_ids $missing_groups = array_diff($this->groups, $group_ids); if (!empty($missing_groups)) { throw new \Exception('Your order does not qualify for this discount.'); } break; case "one": default: $intersection = array_intersect($this->groups, $group_ids); if (empty($intersection)) { throw new \Exception('Your order does not qualify for this discount.'); } break; } } /** * using geo_address_type (shipping/billing) from the cart, check that it is in geo_countries | geo_regions (if either is set) */ if (!empty($this->geo_countries) || !empty($this->geo_regions)) { // ok, so which of the addresses should we evaluate? switch ($this->geo_address_type) { case "billing": $region = $cart->billingRegion(); $country = $cart->billingCountry(); break; case "shipping": default: $region = $cart->shippingRegion(); $country = $cart->shippingCountry(); break; } if (is_null($region) && !empty($this->geo_regions) || is_null($country) && !empty($this->geo_countries)) { throw new \Exception('Customer cannot use this coupon until we know your address'); } if (!empty($this->geo_countries)) { // eval the country if (!in_array($country, $this->geo_countries)) { throw new \Exception('Shipping address is invalid'); } } if (!empty($this->geo_regions)) { // eval the region if (!in_array($region, $this->geo_regions)) { throw new \Exception('Shipping address is invalid'); } } } /** * Check the usage of the coupon */ if (strlen($this->usage_max)) { // usage_max = number of times TOTAL that the coupon may be used // count the orders with coupon.code $total_count = (new \Shop\Models\Orders())->collection()->count(array('coupons.code' => $this->code)); if ((int) $this->usage_max <= (int) $total_count) { throw new \Exception('Coupon cannot be used any more'); } } if (strlen($this->usage_max_per_customer)) { // usage_max_per_customer = number of times this customer may use this coupon // count the orders with coupon.code for user.id $user_count = (new \Shop\Models\Orders())->collection()->count(array('coupons.code' => $this->code, 'user_id' => $cart->user_id)); if ((int) $this->usage_max_per_customer <= (int) $user_count) { throw new \Exception('You cannot use this coupon any more'); } } /** * Check, if this isn't generated code */ if (!empty($this->generated_code)) { $key = new \MongoRegex('/' . $this->generated_code . '/i'); $result = \Shop\Models\Coupons::collection()->aggregate(array('$match' => array('_id' => new \MongoId((string) $this->id))), array('$unwind' => '$codes.list'), array('$match' => array("codes.list.code" => $key)), array('$group' => array('_id' => '$title', 'used_code' => array('$sum' => '$codes.list.used')))); if (count($result['result'])) { if ($result['result'][0]['used_code']) { throw new \Exception('You cannot use this coupon any more'); } } else { throw new \Exception('Coupon "' . $this->generated_code . '" is no longer available.'); } } /** * if we made it this far, the cart is valid for this coupon */ $this->__is_validated = true; return $this; }
public function completePurchase() { $gateway_id = $this->inputfilter->clean($this->app->get('PARAMS.gateway_id'), 'cmd'); // 1. Get \Shop\Models\Checkout // and the payment data to the checkout model $payment_data = (array) $this->app->get('REQUEST'); $payment_data['payment_method'] = $gateway_id; $checkout = \Shop\Models\Checkout::instance(); // the cart->id is in the $payment_data, so let the PaymentMethod load the cart $cart = \Shop\Models\Carts::fetch(); $checkout->addCart($cart)->addPaymentData($payment_data); // 3. validate the payment data against the cart try { $checkout->validatePayment(); } catch (\Exception $e) { // Log this error message $order = $checkout->order(); $order->setError($e->getMessage())->set('errors', $order->getErrors())->fail(); \Dsc\System::addMessage('Checkout could not complete for the following reason:', 'error'); \Dsc\System::addMessage($e->getMessage(), 'error'); // redirect to the ./shop/checkout/payment page unless a failure redirect has been set in the session (site.shop.checkout.redirect.fail) $redirect = '/shop/checkout/payment'; if ($custom_redirect = \Dsc\System::instance()->get('session')->get('site.shop.checkout.redirect.fail')) { $redirect = $custom_redirect; } \Dsc\System::instance()->get('session')->set('site.shop.checkout.redirect.fail', null); $this->app->reroute($redirect); return; } // 4. since payment was validated, accept the order try { $checkout->acceptOrder(); } catch (\Exception $e) { $checkout->setError($e->getMessage()); } // if the order acceptance fails, let the user know if (!$checkout->orderAccepted() || !empty($checkout->getErrors())) { \Dsc\System::addMessage('Checkout could not be completed. Please try again or contact us if you have further difficulty.', 'error'); // Add the errors to the stack and redirect foreach ($checkout->getErrors() as $exception) { \Dsc\System::addMessage($exception->getMessage(), 'error'); } // redirect to the ./shop/checkout/payment page unless a failure redirect has been set in the session (site.shop.checkout.redirect.fail) $redirect = '/shop/checkout/payment'; if ($custom_redirect = \Dsc\System::instance()->get('session')->get('site.shop.checkout.redirect.fail')) { $redirect = $custom_redirect; } \Dsc\System::instance()->get('session')->set('site.shop.checkout.redirect.fail', null); $this->app->reroute($redirect); return; } // if the order acceptance succeeds, trigger completion event try { // Fire an afterShopCheckout event $event_after = \Dsc\System::instance()->trigger('afterShopCheckout', array('checkout' => $checkout)); } catch (\Exception $e) { \Dsc\System::addMessage($e->getMessage(), 'warning'); } // Redirect to ./shop/checkout/confirmation unless a site.shop.checkout.redirect has been set $redirect = '/shop/checkout/confirmation'; if ($custom_redirect = \Dsc\System::instance()->get('session')->get('site.shop.checkout.redirect')) { $redirect = $custom_redirect; } \Dsc\System::instance()->get('session')->set('site.shop.checkout.redirect', null); $this->app->reroute($redirect); return; }
/** * Calculates the value of this card against the data in a cart * * @param \Shop\Models\Carts $cart * @return number */ public function cartValue(\Shop\Models\Carts $cart) { $value = $this->balance(); // exclude the giftcard total from the following comparison $cart_total = $cart->subtotal() - $cart->discountTotal() - $cart->creditTotal() + $cart->taxTotal() + $cart->shippingTotal(); if ($cart_total < $value) { $value = $cart_total; } if ($value < 0) { $value = 0; } return $value; }
/** * Submits a completed cart checkout processing * */ public function submit() { $cart = \Shop\Models\Carts::fetch(); if ($cart->quantity() <= 0) { $this->app->reroute('/shop/cart'); } $identity = $this->getIdentity(); if (empty($identity->id)) { $flash = \Dsc\Flash::instance(); \Base::instance()->set('flash', $flash); $this->app->set('meta.title', 'Login or Register | Checkout'); $view = \Dsc\System::instance()->get('theme'); echo $view->render('Shop/Site/Views::checkout/identity.php'); return; } $f3 = \Base::instance(); // Update the cart with checkout data from the form $checkout_inputs = $this->input->get('checkout', array(), 'array'); if (!empty($checkout_inputs['billing_address']['same_as_shipping'])) { $checkout_inputs['billing_address']['same_as_shipping'] = true; } else { $checkout_inputs['billing_address']['same_as_shipping'] = false; } $cart_checkout = array_merge((array) $cart->{'checkout'}, $checkout_inputs); $cart->checkout = $cart_checkout; $cart->save(); // Get \Shop\Models\Checkout // Bind the cart and payment data to the checkout model $checkout = \Shop\Models\Checkout::instance(); $checkout->addCart($cart)->addPaymentData($f3->get('POST')); // Fire a beforeShopCheckout event that allows Listeners to hijack the checkout process // Payment processing & authorization could occur at this event, and the Listener would update the checkout object // Add the checkout model to the event $event = new \Dsc\Event\Event('beforeShopCheckout'); $event->addArgument('checkout', $checkout); try { $event = \Dsc\System::instance()->getDispatcher()->triggerEvent($event); } catch (\Exception $e) { $checkout->setError($e->getMessage()); $event->setArgument('checkout', $checkout); } $checkout = $event->getArgument('checkout'); // option 1: ERRORS in checkout from beforeShopCheckout if (!empty($checkout->getErrors())) { // Add the errors to the stack and redirect foreach ($checkout->getErrors() as $exception) { \Dsc\System::addMessage($exception->getMessage(), 'error'); } // redirect to the ./shop/checkout/payment page unless a failure redirect has been set in the session (site.shop.checkout.redirect.fail) $redirect = '/shop/checkout/payment'; if ($custom_redirect = \Dsc\System::instance()->get('session')->get('site.shop.checkout.redirect.fail')) { $redirect = $custom_redirect; } \Dsc\System::instance()->get('session')->set('site.shop.checkout.redirect.fail', null); $f3->reroute($redirect); return; } // option 2: NO ERROR in checkout from beforeShopCheckout // If checkout is not completed, do the standard checkout process // If checkout was completed by a Listener during the beforeShopCheckout process, skip the standard checkout process and go to the afterShopCheckout event if (!$checkout->orderAccepted()) { // the standard checkout process try { // failed payment processing should throw an exception $checkout->processPayment(); } catch (\Exception $e) { \Dsc\System::addMessage($e->getMessage(), 'error'); // redirect to the ./shop/checkout/payment page unless a failure redirect has been set in the session (site.shop.checkout.redirect.fail) $redirect = '/shop/checkout/payment'; if ($custom_redirect = \Dsc\System::instance()->get('session')->get('site.shop.checkout.redirect.fail')) { $redirect = $custom_redirect; } \Dsc\System::instance()->get('session')->set('site.shop.checkout.redirect.fail', null); $this->app->reroute($redirect); return; } try { $checkout->acceptOrder(); } catch (\Exception $e) { $checkout->setError($e->getMessage()); } if (!$checkout->orderAccepted() || !empty($checkout->getErrors())) { \Dsc\System::addMessage('Checkout could not be completed. Please try again or contact us if you have further difficulty.', 'error'); // Add the errors to the stack and redirect foreach ($checkout->getErrors() as $exception) { \Dsc\System::addMessage($exception->getMessage(), 'error'); } // redirect to the ./shop/checkout/payment page unless a failure redirect has been set in the session (site.shop.checkout.redirect.fail) $redirect = '/shop/checkout/payment'; if ($custom_redirect = \Dsc\System::instance()->get('session')->get('site.shop.checkout.redirect.fail')) { $redirect = $custom_redirect; } \Dsc\System::instance()->get('session')->set('site.shop.checkout.redirect.fail', null); $f3->reroute($redirect); return; } } // the order WAS accepted // Fire an afterShopCheckout event $event_after = new \Dsc\Event\Event('afterShopCheckout'); $event_after->addArgument('checkout', $checkout); try { $event_after = \Dsc\System::instance()->getDispatcher()->triggerEvent($event_after); } catch (\Exception $e) { \Dsc\System::addMessage($e->getMessage(), 'warning'); } // Redirect to ./shop/checkout/confirmation unless a site.shop.checkout.redirect has been set $redirect = '/shop/checkout/confirmation'; if ($custom_redirect = \Dsc\System::instance()->get('session')->get('site.shop.checkout.redirect')) { $redirect = $custom_redirect; } \Dsc\System::instance()->get('session')->set('site.shop.checkout.redirect', null); $f3->reroute($redirect); return; }
/** * Remove an item from the cart */ public function removeGiftCard() { $redirect = '/shop/cart'; if ($custom_redirect = \Dsc\System::instance()->get('session')->get('site.removegiftcard.redirect')) { $redirect = $custom_redirect; } \Dsc\System::instance()->get('session')->set('site.removegiftcard.redirect', null); // ----------------------------------------------------- // Start: validation // ----------------------------------------------------- // validate the POST values if (!($code = $this->inputfilter->clean($this->app->get('PARAMS.code'), 'alnum'))) { // if validation fails, respond appropriately if ($this->app->get('AJAX')) { return $this->outputJson($this->getJsonResponse(array('result' => false))); } else { \Dsc\System::addMessage('Invalid Gift Card', 'error'); $this->app->reroute('/shop/cart'); } } // ----------------------------------------------------- // End: validation // ----------------------------------------------------- // get the current user's cart, either based on session_id (visitor) or user_id (logged-in) $cart = \Shop\Models\Carts::fetch(); // remove the item try { $cart->removeGiftCard($code); if ($this->app->get('AJAX')) { return $this->outputJson($this->getJsonResponse(array('result' => true))); } else { \Dsc\System::addMessage('Gift card removed from cart'); $this->app->reroute($redirect); } } catch (\Exception $e) { if ($this->app->get('AJAX')) { return $this->outputJson($this->getJsonResponse(array('result' => false))); } else { \Dsc\System::addMessage($e->getMessage()); $this->app->reroute($redirect); } } }
public function mergeWithCart(\Shop\Models\Carts $cart) { $cart_array = $cart->cast(); unset($cart_array['_id']); unset($cart_array['type']); unset($cart_array['metadata']); unset($cart_array['name']); $this->bind($cart_array); $this->number = $this->createNumber(); $this->grand_total = $cart->total(); $this->sub_total = $cart->subtotal(); $this->tax_total = $cart->taxTotal(); $this->shipping_total = $cart->shippingTotal(); $this->discount_total = $cart->discountTotal(); $this->shipping_discount_total = $cart->shippingDiscountTotal(); $this->credit_total = $cart->creditTotal(); $this->giftcard_total = $cart->giftCardTotal(); $user = (new \Users\Models\Users())->load(array('_id' => $this->user_id)); $this->customer = $user->cast(); $this->customer_name = $user->fullName(); $real_email = $user->email(true); if ($real_email != $this->user_email) { $this->user_email = $real_email; } // $order->is_guest = $cart->isGuest(); ? or is that from the checkout object? // $order->ip_address = $cart->ipAddress(); ? or is that from the checkout object? $this->comments = $cart->{'checkout.order_comments'}; // Shipping fields $this->shipping_required = $cart->shippingRequired(); if ($shipping_method = $cart->shippingMethod()) { $this->shipping_method = $shipping_method->cast(); } $this->shipping_address = $cart->{'checkout.shipping_address'}; // TODO Payment/Billing fields $this->billing_address = $cart->{'checkout.billing_address'}; $this->payment_required = $cart->paymentRequired(); return $this; }