Programmatically use custom shipping methods during order creation - Magento 2.4.3-p1
P粉300541798
P粉300541798 2024-03-26 13:45:22
0
1
407

I'm trying to use a custom shipping method when importing an order using a console command. Everything works fine except the backend doesn't show the shipping method and when I check the order afterwards: screenshot

The module basic structure and shipping method are generated using mage2gen.com and works on both frontend and backend if I create the order manually.

Interestingly, if I use $shippingAddress->setShippingMethod('flatrate_flatrate'); after collecting shipping, the shipping method is set correctly, but I can't get it to work with any other shipping method or my Used with custom shipping methods.

Help would be greatly appreciated! Please tell me if I should add more code.

Thanks! (Quite new to Mage2)

System.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="carriers" sortOrder="1000" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
            <group id="hellocash" sortOrder="10" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
                <label>HelloCash</label>
                <field id="active" type="select" sortOrder="10" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="name" type="text" sortOrder="20" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
                    <label>Method Name</label>
                </field>
                <field id="price" type="text" sortOrder="30" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
                    <label>Price</label>
                    <validate>validate-number validate-zero-or-greater</validate>
                </field>
                <field id="sort_order" type="text" sortOrder="40" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
                    <label>Sort Order</label>
                </field>
                <field id="title" type="text" sortOrder="50" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
                    <label>Title</label>
                </field>
                <field id="sallowspecific" type="select" sortOrder="60" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
                    <label>Ship to Applicable Countries</label>
                    <frontend_class>shipping-applicable-country</frontend_class>
                    <source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model>
                </field>
                <field id="specificcountry" type="multiselect" sortOrder="70" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
                    <label>Ship to Specific Countries</label>
                    <can_be_empty>1</can_be_empty>
                    <source_model>Magento\Directory\Model\Config\Source\Country</source_model>
                </field>
                <field id="specificerrmsg" type="textarea" sortOrder="80" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
                    <label>Displayed Error Message</label>
                </field>
            </group>
        </section>
    </system>
</config>

Carrier model:

<?php
/**
 * Copyright © Dan All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Dan\HelloCash\Model\Carrier;

use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Shipping\Model\Rate\Result;

class HelloCash extends \Magento\Shipping\Model\Carrier\AbstractCarrier implements
    \Magento\Shipping\Model\Carrier\CarrierInterface
{

    protected $_code = 'hellocash';

    protected $_isFixed = true;

    protected $_rateResultFactory;

    protected $_rateMethodFactory;

    /**
     * Constructor
     *
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory
     * @param \Psr\Log\LoggerInterface $logger
     * @param \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory
     * @param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory,
        \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory,
        array $data = []
    ) {
        $this->_rateResultFactory = $rateResultFactory;
        $this->_rateMethodFactory = $rateMethodFactory;
        parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);
    }

    /**
     * {@inheritdoc}
     */
    public function collectRates(RateRequest $request)
    {
        if (!$this->getConfigFlag('active')) {
            return false;
        }

        $shippingPrice = $this->getConfigData('price');

        $result = $this->_rateResultFactory->create();

        if ($shippingPrice !== false) {
            $method = $this->_rateMethodFactory->create();

            $method->setCarrier($this->_code);
            $method->setCarrierTitle($this->getConfigData('title'));

            $method->setMethod($this->_code);
            $method->setMethodTitle($this->getConfigData('name'));

            if ($request->getFreeShipping() === true) {
                $shippingPrice = '0.00';
            }

            $method->setPrice($shippingPrice);
            $method->setCost($shippingPrice);

            $result->append($method);
        }

        return $result;
    }

    /**
     * getAllowedMethods
     *
     * @return array
     */
    public function getAllowedMethods()
    {
        return [$this->_code => $this->getConfigData('name')];
    }
}

Import order console command:

<?php
/**
 * Copyright © DAN All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Dan\HelloCash\Console\Command;

use Dan\HelloCash\Api\Data\SyncLogInterfaceFactory;
use Dan\HelloCash\Api\HelloCash as HelloCashApi;
use Dan\HelloCash\Enum\SyncLogType;
use Dan\HelloCash\Exception\SyncException;
use Dan\HelloCash\Exception\SyncOrderException;
use Dan\HelloCash\Model\SyncLogRepository;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\ProductFactory;
use Magento\Config\Model\Config;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\App\State;
use Magento\Framework\DB\Transaction;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\View\Result\PageFactory;
use Magento\Quote\Model\Quote\Address\Rate as ShippingRate;
use Magento\Quote\Model\QuoteManagement;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Customer\Model\Customer\Interceptor as CustomerInterceptor;
use Magento\Customer\Model\CustomerFactory;
use Magento\Framework\App\Area;
use Magento\Framework\Encryption\Encryptor;
use Magento\Quote\Model\QuoteFactory;
use Magento\Store\Model\StoreManagerInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Magento\Sales\Model\Service\InvoiceService;
use Magento\Sales\Model\Order\Email\Sender\InvoiceSender;

class SyncOrders extends SyncCommand
{
    const NAME_ARGUMENT = 'name';
    const NAME_OPTION = 'option';


    protected CustomerFactory $customerFactory;
    protected CustomerInterceptor $customer;
    protected OutputInterface $output;
    protected StoreInterface $store;
    protected StoreManagerInterface $storeManager;
    protected QuoteFactory $quoteFactory;

    /**
     * @var PageFactory
     */
    protected PageFactory $resultPageFactory;
    /**
     * @var OrderRepositoryInterface
     */
    protected OrderRepositoryInterface $orderRepository;
    /**
     * @var ProductRepositoryInterface
     */
    protected ProductRepositoryInterface $productRepository;
    /**
     * @var ProductRepositoryInterface
     */
    protected $customerRepository;
    /**
     * @var QuoteFactory
     */
    protected QuoteFactory $quote;
    /**
     * @var QuoteManagement
     */
    protected QuoteManagement $quoteManagement;
    /**
     * @var OrderSender
     */
    protected OrderSender $orderSender;
    protected ProductFactory $productFactory;
    protected ObjectManagerInterface $objectManager;
    protected ShippingRate $shippingRate;
    protected InvoiceService $invoiceService;
    protected Transaction $transaction;
    protected InvoiceSender $invoiceSender;
    private string $customerEmail;
    private string $customerPassword;
    private string $customerPhone;


    protected function __construct(
        HelloCashApi          $api,
        Config                $config,
        Encryptor             $encryptor,
        SyncLogRepository $syncLogRepository,
        SyncLogInterfaceFactory $syncLogFactory,

        CustomerFactory       $customerFactory,
        StoreManagerInterface $storeManager,
        CustomerRepositoryInterface $customerRepository,
        QuoteFactory $quote,
        ProductFactory $productFactory,
        ObjectManagerInterface $objectManager,
        ShippingRate $shippingRate,
        State                    $state,
        Transaction $transaction
    )
    {
        $this->customerFactory = $customerFactory;
        $this->storeManager = $storeManager;
        $this->customerRepository = $customerRepository;
        $this->quote = $quote;
        $this->productFactory = $productFactory;
        $this->state = $state;
        $this->objectManager = $objectManager;
        $this->shippingRate = $shippingRate;
        $this->transaction = $transaction;

        parent::__construct($api, $config, $encryptor, $syncLogRepository, $syncLogFactory);

        $this->customerEmail = (string)$this->config->getConfigDataValue('hello_cash_general/sync_order/customer_email');
        $this->customerPassword = $this->encryptor->decrypt($this->config->getConfigDataValue('hello_cash_general/sync_order/customer_password'));
        $this->customerPhone = (string)$this->config->getConfigDataValue('hello_cash_general/sync_order/customer_phone');
    }


    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->setName('dan_hellocash:sync-orders');
        $this->setDescription('Imports the HelloCash orders into your Magento shop.');
        $this->setDefinition([
            new InputArgument(self::NAME_ARGUMENT, InputArgument::OPTIONAL, 'Name'),
            new InputOption(self::NAME_OPTION, '-a', InputOption::VALUE_NONE, 'Option functionality')
        ]);
        parent::configure();
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(
        InputInterface  $input,
        OutputInterface $output
    )
    {
        try {
            $this->output = $output;
            $this->state->setAreaCode(Area::AREA_FRONTEND);

            $this->store = $this->storeManager->getStore();
            $this->websiteId = $this->storeManager->getWebsite()->getId();

            $this->createSyncLog(SyncLogType::ORDERS);

            $ordersHellocash = $this->getOrders();

            $this->syncLog->setTotal((int)$ordersHellocash->count);
            $this->syncLogRepository->save($this->syncLog);


            foreach ($ordersHellocash->invoices as $orderHellocash) {
                try {
                    if ('1' === $orderHellocash->invoice_cancellation) {
                        continue;
                    }

                    $orderHellocash = $this->getOrder((int)$orderHellocash->invoice_id);
                    $this->createOrder($orderHellocash);
                } catch (SyncOrderException $exception) {
                    $this->handleOrderError($orderHellocash, $exception);
                }  finally {
                    $this->syncLog->setProcessed($this->syncLog->getProcessed() + 1);
                    $this->syncLogRepository->save($this->syncLog);
                }
            }

            $this->handleSuccess();
        } catch (SyncException $exception) {
            $this->handleFatalError('A problem occurred calling the HelloCash API. (status: ' . $exception->getStatusCode() . ', message: "' . $exception->getMessage() . '")');
        } finally {
            $this->finishSync();
        }
    }

    protected function createOrder(\stdClass $orderHelloCash)
    {
        $this->createCustomer($orderHelloCash);

        $quote = $this->quote->create();
        $quote->setStore($this->store);
        $quote->setCurrency();

        /**
         * Registered Customer
         */
        $customer = $this->customerRepository->getById($this->customer->getId());
        $quote->assignCustomer($customer);

        foreach ($orderHelloCash->items as $item) {
            $product = $this->productFactory
                ->create()
                ->loadByAttribute('hellocash_article_id', (int)$item->item_article_id);
            if (!$product) {
                throw new SyncOrderException('Product not found. (HelloCash ID: ' . $item->item_article_id . ', name: "' . $item->item_name . '")');
            }

            $product->setPrice((float)$item->item_price);
            $quote->addProduct(
                $product,
                (float)$item->item_quantity
            );
        }

        $addressData = $this->createAddressData($orderHelloCash->company);
        $quote->getBillingAddress()->addData($addressData);
        $quote->getShippingAddress()->addData($addressData);

        $this->shippingRate
            ->setCode('hellocash_hellocash')
            ->getPrice();

        $shippingAddress = $quote
            ->getShippingAddress()
            ->addShippingRate($this->shippingRate);

        $shippingAddress->setCollectShippingRates(true);
        $shippingAddress->collectShippingRates();
        $shippingAddress->setShippingMethod('hellocash_hellocash');

        $quote->setPaymentMethod('hellocash');
        $quote->setInventoryProcessed(true);
        $quote->save();

        $quote->getPayment()->importData(['method' => 'hellocash']);
        $quote->collectTotals()->save();


        $quoteManagement = $this->objectManager->create(QuoteManagement::class);
        $order = $quoteManagement->submit($quote);
        $order
            ->setHellocashInvoiceId((int)$orderHelloCash->invoice_id)
            ->setEmailSent(1)
            ->setStatus('pending')
            ->save();

        if (!$order->getEntityId()) {
            throw new SyncOrderException('Failed to get the order ID after saving.');
        }

        $this->createInvoice($order);

        $order
            ->setStatus('complete')
            ->save();


        return $order;
    }

    protected function createCustomer(\stdClass $invoiceHellocash): CustomerInterceptor
    {
        if (isset($this->customer)) {
            return $this->customer;
        }

        $this->verifyCustomerCredentials();

        $this->customer = $this->customerFactory
            ->create()
            ->setWebsiteId($this->websiteId)
            ->setStore($this->store);

        if (!$this->customer->loadByEmail($this->customerEmail)->getId()) {
            $this->customer
                ->setFirstname('Cashier')
                ->setLastname('HelloCash')
                ->setEmail($this->customerEmail)
                ->setPassword($this->customerPassword);

            $this->customer->save();
            /**
             * @TODO: Ask Daniel why this is not working.
             */
//        $this->customerRepository->save($this->customer);
        }
        return $this->customer;
    }

    protected function createAddressData(\stdClass $companyHellocash): array
    {
        $address = [
            'firstname' => $companyHellocash->name,
            'lastname' => 'HelloCash',
            'country_id' => 'AT',
            'city' => $companyHellocash->city,
            'street' => $companyHellocash->street,
            //'region' => $companyHellocash->city,
            'postcode' => $companyHellocash->postalCode,
            'telephone' => $this->customerPhone,
//            'fax' => $companyHellocash->postalCode,
            'save_in_address_book' => 1
        ];

        return $address;
    }

    public function createInvoice($order)
    {
        $this->invoiceService = $this->objectManager->create(InvoiceService::class);
        $invoice = $this->invoiceService
            ->prepareInvoice($order)
            ->register()
            ->save();

        $this->transaction
            ->addObject($invoice)
            ->addObject($invoice->getOrder())
            ->save();

        $this->invoiceSender = $this->objectManager->create(InvoiceSender::class);
        $this->invoiceSender->send($invoice);

        $order
            ->addCommentToStatusHistory(__('Notified customer about invoice creation #%1.', $invoice->getId()))
            ->setIsCustomerNotified(true)
            ->save();

        return $invoice;
    }

    protected function verifyCustomerCredentials()
    {
        if (empty($this->customerEmail)) {
            throw new SyncException('The customer email is not set in the module\'s admin configuration! Please set it.');
        }

        if (empty($this->customerPassword)) {
            throw new SyncException('The customer password is not set in the module\'s admin configuration! Please set it.');
        }

        if (empty($this->customerPhone)) {
            throw new SyncException('The customer phone number is not set in the module\'s admin configuration! Please set it.');
        }
    }
}

P粉300541798
P粉300541798

reply all(1)
P粉032977207

The problem lies with a conflicting, poorly written shipping plugin. So that's okay, thanks to everyone who took the time to read my code.

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template