|
@@ -0,0 +1,163 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace Webkul\BagistoApi\State;
|
|
|
|
|
+
|
|
|
|
|
+use ApiPlatform\Metadata\Operation;
|
|
|
|
|
+use ApiPlatform\State\ProcessorInterface;
|
|
|
|
|
+use Illuminate\Support\Facades\Request;
|
|
|
|
|
+use Webkul\BagistoApi\Dto\CartData;
|
|
|
|
|
+use Webkul\BagistoApi\Dto\SaveCheckoutCartInput;
|
|
|
|
|
+use Webkul\BagistoApi\Exception\AuthenticationException;
|
|
|
|
|
+use Webkul\BagistoApi\Exception\OperationFailedException;
|
|
|
|
|
+use Webkul\BagistoApi\Exception\ResourceNotFoundException;
|
|
|
|
|
+use Webkul\BagistoApi\Facades\CartTokenFacade;
|
|
|
|
|
+use Webkul\BagistoApi\Facades\TokenHeaderFacade;
|
|
|
|
|
+use Webkul\CartRule\Repositories\CartRuleCouponRepository;
|
|
|
|
|
+use Webkul\Checkout\Facades\Cart;
|
|
|
|
|
+use Webkul\Shipping\Facades\Shipping;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * GraphQL processor for the SaveCheckoutCart mutation.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Resolves the cart from the Authorization Bearer token, persists the supplied
|
|
|
|
|
+ * shipping method / coupon code / payment method (skipping any field equal to
|
|
|
|
|
+ * the "-1" sentinel) and returns the full cart as CartData.
|
|
|
|
|
+ */
|
|
|
|
|
+class SaveCheckoutCartProcessor implements ProcessorInterface
|
|
|
|
|
+{
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sentinel: a field equal to this value is left unchanged.
|
|
|
|
|
+ */
|
|
|
|
|
+ private const SKIP = '-1';
|
|
|
|
|
+
|
|
|
|
|
+ public function __construct(
|
|
|
|
|
+ protected CartRuleCouponRepository $cartRuleCouponRepository,
|
|
|
|
|
+ ) {}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Process the SaveCheckoutCart mutation.
|
|
|
|
|
+ */
|
|
|
|
|
+ public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
|
|
|
|
|
+ {
|
|
|
|
|
+ if (! $data instanceof SaveCheckoutCartInput) {
|
|
|
|
|
+ throw new OperationFailedException(__('bagistoapi::app.graphql.checkout.invalid-input'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $request = Request::instance() ?? ($context['request'] ?? null);
|
|
|
|
|
+ $token = $request ? TokenHeaderFacade::getAuthorizationBearerToken($request) : null;
|
|
|
|
|
+
|
|
|
|
|
+ if (! $token) {
|
|
|
|
|
+ throw new AuthenticationException(__('bagistoapi::app.graphql.cart.authentication-required'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $cart = CartTokenFacade::getCartByToken($token);
|
|
|
|
|
+
|
|
|
|
|
+ if (! $cart) {
|
|
|
|
|
+ throw new ResourceNotFoundException(__('bagistoapi::app.graphql.cart.invalid-token'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Cart::setCart($cart);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ if ($this->shouldUpdate($data->shippingMethod)) {
|
|
|
|
|
+ $this->applyShippingMethod((string) $data->shippingMethod);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($this->shouldUpdate($data->code)) {
|
|
|
|
|
+ $this->applyPaymentMethod((string) $data->code);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($this->shouldUpdate($data->couponCode)) {
|
|
|
|
|
+ $this->applyCoupon((string) $data->couponCode);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Cart::collectTotals();
|
|
|
|
|
+ } catch (OperationFailedException $e) {
|
|
|
|
|
+ throw $e;
|
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
|
+ throw new OperationFailedException($e->getMessage(), 0, $e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return CartData::fromModel(Cart::getCart());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * A value is updatable when it is present and not the skip sentinel.
|
|
|
|
|
+ */
|
|
|
|
|
+ private function shouldUpdate(?string $value): bool
|
|
|
|
|
+ {
|
|
|
|
|
+ return $value !== null && $value !== self::SKIP;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Validate and persist the shipping method.
|
|
|
|
|
+ */
|
|
|
|
|
+ private function applyShippingMethod(string $code): void
|
|
|
|
|
+ {
|
|
|
|
|
+ if (! Cart::getCart()?->haveStockableItems()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Shipping::collectRates();
|
|
|
|
|
+
|
|
|
|
|
+ if (! Shipping::isMethodCodeExists($code)) {
|
|
|
|
|
+ throw new OperationFailedException(__('bagistoapi::app.graphql.checkout.invalid-shipping-method'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (! Cart::saveShippingMethod($code)) {
|
|
|
|
|
+ throw new OperationFailedException(__('bagistoapi::app.graphql.checkout.shipping-method-save-failed'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Cart::collectTotals();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Validate and persist the payment method.
|
|
|
|
|
+ */
|
|
|
|
|
+ private function applyPaymentMethod(string $code): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $paymentMethodConfig = config('payment_methods.'.$code);
|
|
|
|
|
+
|
|
|
|
|
+ if (! $paymentMethodConfig || ! isset($paymentMethodConfig['class'])) {
|
|
|
|
|
+ throw new OperationFailedException(__('bagistoapi::app.graphql.checkout.invalid-payment-method'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (! Cart::savePaymentMethod(['method' => $code])) {
|
|
|
|
|
+ throw new OperationFailedException(__('bagistoapi::app.graphql.checkout.payment-method-save-failed'));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Apply or remove a coupon. An empty string removes the active coupon.
|
|
|
|
|
+ */
|
|
|
|
|
+ private function applyCoupon(string $code): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $code = trim($code);
|
|
|
|
|
+
|
|
|
|
|
+ if ($code === '') {
|
|
|
|
|
+ Cart::removeCouponCode()->collectTotals();
|
|
|
|
|
+
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (Cart::getCart()?->coupon_code === $code) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $coupon = $this->cartRuleCouponRepository->findOneByField('code', $code);
|
|
|
|
|
+
|
|
|
|
|
+ if (! $coupon) {
|
|
|
|
|
+ throw new OperationFailedException(trans('shop::app.checkout.coupon.invalid'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (! $coupon->cart_rule->status) {
|
|
|
|
|
+ throw new OperationFailedException(trans('shop::app.checkout.coupon.error'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Cart::setCouponCode($coupon->code)->collectTotals();
|
|
|
|
|
+
|
|
|
|
|
+ if (Cart::getCart()?->coupon_code !== $coupon->code) {
|
|
|
|
|
+ throw new OperationFailedException(trans('shop::app.checkout.coupon.error'));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|