Completed
Pull Request — master (#373)
by Jonas
03:04
created

ProductFromShop::calculatePrice()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * (c) shopware AG <[email protected]>
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
namespace ShopwarePlugins\Connect\Components;
9
10
use Enlight_Event_EventManager;
11
use Shopware\Connect\Gateway;
12
use Shopware\Connect\ProductFromShop as ProductFromShopBase;
13
use Shopware\Connect\Struct\Order;
14
use Shopware\Connect\Struct\Product;
15
use Shopware\Connect\Struct\Address;
16
use Shopware\Models\Order as OrderModel;
17
use Shopware\Models\Attribute\OrderDetail as OrderDetailAttributeModel;
18
use Shopware\Models\Customer as CustomerModel;
19
use Shopware\Components\Model\ModelManager;
20
use Shopware\Components\Random;
21
use Shopware\Connect\Struct\Change\FromShop\Availability;
22
use Shopware\Connect\Struct\Change\FromShop\Insert;
23
use Shopware\Connect\Struct\Change\FromShop\Update;
24
use Shopware\Connect\Struct\PaymentStatus;
25
use Shopware\Connect\Struct\Shipping;
26
use Shopware\CustomModels\Connect\Attribute;
27
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamService;
28
29
/**
30
 * The interface for products exported *to* connect *from* the local shop
31
 *
32
 * @category  Shopware
33
 * @package   Shopware\Plugins\SwagConnect
34
 */
35
class ProductFromShop implements ProductFromShopBase
36
{
37
    /**
38
     * @var Helper
39
     */
40
    private $helper;
41
42
    /**
43
     * @var ModelManager
44
     */
45
    private $manager;
46
47
    /**
48
     * @var \Shopware\Connect\Gateway
49
     */
50
    private $gateway;
51
52
    /**
53
     * @var Logger
54
     */
55
    private $logger;
56
57
    /**
58
     * @var Enlight_Event_EventManager
59
     */
60
    private $eventManager;
61
62
    /**
63
     * @param Helper $helper
64
     * @param ModelManager $manager
65
     * @param Gateway $gateway
66
     * @param Logger $logger
67
     * @param Enlight_Event_EventManager $eventManager
68
     */
69
    public function __construct(
70
        Helper $helper,
71
        ModelManager $manager,
72
        Gateway $gateway,
73
        Logger $logger,
74
        Enlight_Event_EventManager $eventManager
75
    ) {
76
        $this->helper = $helper;
77
        $this->manager = $manager;
78
        $this->gateway = $gateway;
79
        $this->logger = $logger;
80
        $this->eventManager = $eventManager;
81
    }
82
83
    /**
84
     * Get product data
85
     *
86
     * Get product data for all the source IDs specified in the given string
87
     * array.
88
     *
89
     * @param string[] $sourceIds
90
     * @return Product[]
91
     */
92
    public function getProducts(array $sourceIds)
93
    {
94
        return $this->helper->getLocalProduct($sourceIds);
95
    }
96
97
    /**
98
     * Get all IDs of all exported products
99
     *
100
     * @throws \BadMethodCallException
101
     * @return string[]
102
     */
103
    public function getExportedProductIDs()
104
    {
105
        throw new \BadMethodCallException('Not implemented');
106
    }
107
108
    /**
109
     * Reserve a product in shop for purchase
110
     *
111
     * @param Order $order
112
     * @throws \Exception Abort reservation by throwing an exception here.
113
     * @return void
114
     */
115
    public function reserve(Order $order)
116
    {
117
        $this->eventManager->notify(
118
            'Connect_Supplier_Reservation_Before',
119
            [
120
                'subject' => $this,
121
                'order' => $order
122
            ]
123
        );
124
    }
125
126
    /**
127
     * Create order in shopware
128
     * Wraps the actual order process into a transaction
129
     *
130
     *
131
     * @param Order $order
132
     * @throws \Exception Abort buy by throwing an exception,
133
     *                    but only in very important cases.
134
     *                    Do validation in {@see reserve} instead.
135
     * @return string
136
     */
137
    public function buy(Order $order)
138
    {
139
        $this->manager->beginTransaction();
140
        try {
141
            $order = $this->eventManager->filter('Connect_Components_ProductFromShop_Buy_OrderFilter', $order);
142
143
            $this->validateBilling($order->billingAddress);
144
            $orderNumber = $this->doBuy($order);
145
146
            $this->manager->commit();
147
        } catch (\Exception $e) {
148
            $this->manager->rollback();
149
            throw $e;
150
        }
151
152
        return $orderNumber;
153
    }
154
155
    /**
156
     * Actually creates the remote order in shopware.
157
     *
158
     * @param Order $order
159
     * @return string
160
     */
161
    public function doBuy(Order $order)
162
    {
163
        $this->manager->clear();
164
165
        $detailStatus = $this->manager->find('Shopware\Models\Order\DetailStatus', 0);
166
        $status = $this->manager->find('Shopware\Models\Order\Status', 0);
167
        $shop = $this->manager->find('Shopware\Models\Shop\Shop', 1);
168
        $number = 'SC-' . $order->orderShop . '-' . $order->localOrderId;
169
170
        $repository = $this->manager->getRepository('Shopware\Models\Payment\Payment');
171
        $payment = $repository->findOneBy([
172
            'name' => 'invoice',
173
        ]);
174
175
        // todo: Create the OrderModel without previous plain SQL
176
        //$model = new OrderModel\Order();
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
177
        $sql = 'INSERT INTO `s_order` (`ordernumber`, `cleared`) VALUES (?, 17);';
178
        Shopware()->Db()->query($sql, [$number]);
179
        $modelId = Shopware()->Db()->lastInsertId();
180
        /** @var $model \Shopware\Models\Order\Order */
181
        $model = $this->manager->find('Shopware\Models\Order\Order', $modelId);
182
183
        $attribute = new \Shopware\Models\Attribute\Order;
184
        $attribute->setConnectOrderId($order->localOrderId);
185
        $attribute->setConnectShopId($order->orderShop);
186
        $model->setAttribute($attribute);
187
188
        $model->fromArray([
189
            'number' => $number,
190
            'invoiceShipping' => $order->grossShippingCosts,
0 ignored issues
show
Bug introduced by
The property grossShippingCosts does not seem to exist. Did you mean shipping?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
191
            'invoiceShippingNet' => $order->shippingCosts,
0 ignored issues
show
Bug introduced by
The property shippingCosts does not seem to exist. Did you mean shipping?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
192
            'currencyFactor' => 1,
193
            'orderStatus' => $status,
194
            'shop' => $shop,
195
            'languageSubShop' => $shop,
196
            'payment' => $payment,
197
            'currency' => 'EUR',
198
            'orderTime' => 'now'
199
        ]);
200
        $items = [];
201
        $connectAttributeRepository = $this->manager->getRepository('Shopware\CustomModels\Connect\Attribute');
202
203
        /** @var \Shopware\Connect\Struct\OrderItem $orderItem */
204
        foreach ($order->products as $orderItem) {
205
            $product = $orderItem->product;
206
            /** @var \Shopware\CustomModels\Connect\Attribute $connectAttribute */
207
            $connectAttribute = $connectAttributeRepository->findOneBy([
208
                'sourceId' => $product->sourceId,
209
                'shopId' => null,
210
            ]);
211
            if (!$connectAttribute) {
212
                $this->logger->write(
213
                    true,
214
                    sprintf('Detail with sourceId: %s does not exist', $product->sourceId),
215
                    null,
216
                    true
217
                );
218
                continue;
219
            }
220
221
            /** @var $detail \Shopware\Models\Article\Detail */
222
            $detail = $connectAttribute->getArticleDetail();
223
            /** @var $productModel \Shopware\Models\Article\Article */
224
            $productModel = $detail->getArticle();
225
            $item = new OrderModel\Detail();
226
            $item->fromArray([
227
                'articleId' => $productModel->getId(),
228
                'quantity' => $orderItem->count,
229
                'orderId' => $model->getId(),
230
                'number' => $model->getNumber(),
231
                'articleNumber' => $detail->getNumber(),
232
                'articleName' => $product->title,
233
                'price' => $this->calculatePrice($product),
234
                'taxRate' => $product->vat * 100,
235
                'status' => $detailStatus,
236
                'attribute' => new OrderDetailAttributeModel()
237
            ]);
238
            $items[] = $item;
239
        }
240
        $model->setDetails($items);
241
242
        $email = $order->billingAddress->email;
243
244
        $password = Random::getAlphanumericString(30);
245
246
        $repository = $this->manager->getRepository('Shopware\Models\Customer\Customer');
247
        $customer = $repository->findOneBy([
248
            'email' => $email
249
        ]);
250
        if ($customer === null) {
251
            $customer = new CustomerModel\Customer();
252
            $customer->fromArray([
253
                'active' => true,
254
                'email' => $email,
255
                'password' => $password,
256
                'accountMode' => 1,
257
                'shop' => $shop,
258
                'paymentId' => $payment->getId(),
259
            ]);
260
        }
261
        if ($customer->getBilling() === null) {
262
            $billing = new CustomerModel\Billing();
263
            $customer->setBilling($billing);
264
        } else {
265
            $billing = $customer->getBilling();
266
        }
267
268
        $billing->fromArray($this->getAddressData(
269
            $order->billingAddress
270
        ));
271
        $this->manager->persist($customer);
272
273
        $model->setCustomer($customer);
274
275
        $billing = new OrderModel\Billing();
276
        $billing->setCustomer($customer);
277
        $billing->fromArray($this->getAddressData(
278
                $order->billingAddress
279
        ));
280
        $model->setBilling($billing);
281
282
        $shipping = new OrderModel\Shipping();
283
        $shipping->setCustomer($customer);
284
        $shipping->fromArray($this->getAddressData(
285
            $order->deliveryAddress
286
        ));
287
        $model->setShipping($shipping);
288
289
        $model->calculateInvoiceAmount();
290
291
        $dispatchRepository = $this->manager->getRepository('Shopware\Models\Dispatch\Dispatch');
292
        $dispatch = $dispatchRepository->findOneBy([
293
            'name' => $order->shipping->service
294
        ]);
295
        if ($dispatch) {
296
            $model->setDispatch($dispatch);
297
        }
298
299
        $this->eventManager->notify(
300
            'Connect_Supplier_Buy_Before',
301
            [
302
                'subject' => $this,
303
                'order' => $order
304
            ]
305
        );
306
307
        $this->manager->flush();
308
309
        return $model->getNumber();
310
    }
311
312
    /**
313
     * Calculate the price (including VAT) that the from shop needs to pay.
314
     *
315
     * This is most likely NOT the price the customer itself has to pay.
316
     *
317
     * @return float
318
     */
319
    private function calculatePrice($product)
320
    {
321
        return $product->purchasePrice * ($product->vat + 1);
322
    }
323
324
    /**
325
     * @param Address $address
326
     * @return array
327
     */
328
    private function getAddressData(Address $address)
329
    {
330
        $repository = 'Shopware\Models\Country\Country';
331
        $repository = $this->manager->getRepository($repository);
332
        /** @var $country \Shopware\Models\Country\Country */
333
        $country = $repository->findOneBy([
334
            'iso3' => $address->country
335
        ]);
336
337
        return [
338
            'company' => $address->company ?: '',
339
            'department' => $address->department ?: '',
340
            'additionalAddressLine1' => $address->additionalAddressLine1 ?: '',
0 ignored issues
show
Bug introduced by
The property additionalAddressLine1 does not seem to exist. Did you mean additionalAddressLine?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
341
            'additionalAddressLine2' => $address->additionalAddressLine2 ?: '',
0 ignored issues
show
Bug introduced by
The property additionalAddressLine2 does not seem to exist. Did you mean additionalAddressLine?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
342
            'salutation' => 'mr',
343
            'lastName' => $address->surName,
344
            'firstName' => $address->firstName,
345
            'city' => $address->city,
346
            'zipCode' => $address->zip,
347
            'street' => $address->street,
348
            'streetNumber' => $address->streetNumber,
349
            'phone' => $address->phone,
350
            'country' => $country
351
        ];
352
    }
353
354
    public function updatePaymentStatus(PaymentStatus $status)
355
    {
356
        // $paymentStatus->localOrderId is actually ordernumber for this shop
357
        // e.g. BP-35-20002
358
        $repository = $this->manager->getRepository('Shopware\Models\Order\Order');
359
        $order = $repository->findOneBy(['number' => $status->localOrderId]);
360
361
        if ($order) {
362
            $paymentStatusRepository = $this->manager->getRepository('Shopware\Models\Order\Status');
363
            /** @var \Shopware\Models\Order\Status $orderPaymentStatus */
364
            $orderPaymentStatus = $paymentStatusRepository->findOneBy(
365
                ['name' => 'sc_' . $status->paymentStatus]
366
            );
367
368
            if ($orderPaymentStatus) {
369
                $order->setPaymentStatus($orderPaymentStatus);
370
371
                $this->eventManager->notify(
372
                    'Connect_Supplier_Update_PaymentStatus_Before',
373
                    [
374
                        'subject' => $this,
375
                        'paymentStatus' => $status,
376
                        'order' => $order
377
                    ]
378
                );
379
380
                $this->manager->persist($order);
381
                $this->manager->flush();
382
            } else {
383
                $this->logger->write(
384
                    true,
385
                    sprintf(
386
                        'Payment status "%s" not found',
387
                        $status->paymentStatus
388
                    ),
389
                    sprintf(
390
                        'Order with id "%s"',
391
                        $status->localOrderId
392
                    )
393
                );
394
            }
395
        } else {
396
            $this->logger->write(
397
                true,
398
                sprintf(
399
                    'Order with id "%s" not found',
400
                    $status->localOrderId
401
                ),
402
                serialize($status)
403
            );
404
        }
405
    }
406
407
    public function calculateShippingCosts(Order $order)
408
    {
409
        if (!$order->deliveryAddress) {
410
            return new Shipping(['isShippable' => false]);
411
        }
412
413
        $countryIso3 = $order->deliveryAddress->country;
414
        $country = $this->manager->getRepository('Shopware\Models\Country\Country')->findOneBy(['iso3' => $countryIso3]);
415
416
        if (!$country) {
417
            return new Shipping(['isShippable' => false]);
418
        }
419
420
        if (count($order->orderItems) == 0) {
421
            throw new \InvalidArgumentException(
422
                'ProductList is not allowed to be empty'
423
            );
424
        }
425
426
        /* @var \Shopware\Models\Shop\Shop $shop */
427
        $shop = $this->manager->getRepository('Shopware\Models\Shop\Shop')->getActiveDefault();
428
        if (!$shop) {
429
            return new Shipping(['isShippable' => false]);
430
        }
431
        $shop->registerResources(Shopware()->Container()->get('bootstrap'));
432
433
        /** @var /Enlight_Components_Session_Namespace $session */
0 ignored issues
show
Documentation introduced by
The doc-type /Enlight_Components_Session_Namespace could not be parsed: Unknown type name "/Enlight_Components_Session_Namespace" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
434
        $session = Shopware()->Session();
435
        $sessionId = uniqid('connect_remote');
436
        $session->offsetSet('sSESSION_ID', $sessionId);
437
438
        /** @var \Shopware\Models\Dispatch\Dispatch $shipping */
439
        $shipping = $this->manager->getRepository('Shopware\Models\Dispatch\Dispatch')->findOneBy([
440
            'type' => 0 // standard shipping
441
        ]);
442
443
        // todo: if products are not shippable with default shipping
444
        // todo: do we need to check with other shipping methods
445
        if (!$shipping) {
446
            return new Shipping(['isShippable' => false]);
447
        }
448
449
        $session->offsetSet('sDispatch', $shipping->getId());
450
451
        $repository = $this->manager->getRepository('Shopware\CustomModels\Connect\Attribute');
452
        $products = [];
453
        /** @var \Shopware\Connect\Struct\OrderItem $orderItem */
454
        foreach ($order->orderItems as $orderItem) {
455
            $attributes = $repository->findBy(['sourceId' => [$orderItem->product->sourceId], 'shopId' => null]);
456
            if (count($attributes) === 0) {
457
                continue;
458
            }
459
460
            $products[] = [
461
                'ordernumber' => $attributes[0]->getArticleDetail()->getNumber(),
462
                'quantity' => $orderItem->count,
463
            ];
464
        }
465
466
        /** @var \Shopware\CustomModels\Connect\Attribute $attribute */
467
        foreach ($products as $product) {
468
            Shopware()->Modules()->Basket()->sAddArticle($product['ordernumber'], $product['quantity']);
469
        }
470
471
        $result = Shopware()->Modules()->Admin()->sGetPremiumShippingcosts(['id' => $country->getId()]);
472
        if (!is_array($result)) {
473
            return new Shipping(['isShippable' => false]);
474
        }
475
476
        $sql = 'DELETE FROM s_order_basket WHERE sessionID=?';
477
        Shopware()->Db()->executeQuery($sql, [
478
            $sessionId,
479
        ]);
480
481
        $shippingReturn = new Shipping([
482
            'shopId' => $this->gateway->getShopId(),
483
            'service' => $shipping->getName(),
484
            'shippingCosts' => floatval($result['netto']),
485
            'grossShippingCosts' => floatval($result['brutto']),
486
        ]);
487
488
        $this->eventManager->notify(
489
            'Connect_Supplier_Get_Shipping_After',
490
            [
491
                'subject' => $this,
492
                'shipping' => $shippingReturn,
493
                'order' => $order
494
            ]
495
        );
496
497
        return $shippingReturn;
498
    }
499
500
    /**
501
     * Perform sync changes to fromShop
502
     *
503
     * @param string $since
504
     * @param \Shopware\Connect\Struct\Change[] $changes
505
     * @return void
506
     */
507
    public function onPerformSync($since, array $changes)
508
    {
509
        $this->manager->getConnection()->beginTransaction();
510
511
        $statusSynced = Attribute::STATUS_SYNCED;
512
        $statusInsert = Attribute::STATUS_INSERT;
513
        $statusUpdate = Attribute::STATUS_UPDATE;
514
        $statusDelete = Attribute::STATUS_DELETE;
515
        try {
516
            $this->manager->getConnection()->executeQuery(
517
                "UPDATE s_plugin_connect_items
518
                SET export_status = '$statusSynced'
519
                WHERE revision <= ?
520
                AND ( export_status = '$statusInsert' OR export_status = '$statusUpdate' )",
521
                [$since]
522
            );
523
524
            $this->manager->getConnection()->executeQuery(
525
                "UPDATE s_plugin_connect_items
526
                SET export_status = ?
527
                WHERE revision <= ?
528
                AND export_status = '$statusDelete'",
529
                [null, $since]
530
            );
531
532
            /** @var \Shopware\Connect\Struct\Change $change */
533
            foreach ($changes as $change) {
534
                if (!$change instanceof Insert && !$change instanceof Update && !$change instanceof Availability) {
535
                    continue;
536
                }
537
538
                $this->manager->getConnection()->executeQuery(
539
                    'UPDATE s_plugin_connect_items
540
                    SET revision = ?
541
                    WHERE source_id = ? AND shop_id IS NULL',
542
                    [$change->revision, $change->sourceId]
543
                );
544
            }
545
546
            $this->manager->getConnection()->commit();
547
        } catch (\Exception $e) {
548
            $this->manager->getConnection()->rollBack();
549
        }
550
551
        try {
552
            $this->markStreamsAsSynced();
553
            $this->markStreamsAsNotExported();
554
        } catch (\Exception $e) {
555
            $this->logger->write(
556
                true,
557
                sprintf('Failed to mark streams as synced! Message: "%s". Trace: "%s"', $e->getMessage(), $e->getTraceAsString()),
558
                null
559
            );
560
        }
561
    }
562
563 View Code Duplication
    private function markStreamsAsNotExported()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
564
    {
565
        $streamIds = $this->manager->getConnection()->executeQuery(
566
            'SELECT pcs.stream_id as streamId
567
             FROM s_plugin_connect_streams as pcs
568
             WHERE export_status = ?',
569
            [ProductStreamService::STATUS_DELETE]
570
        )->fetchAll();
571
572
        foreach ($streamIds as $stream) {
573
            $streamId = $stream['streamId'];
574
575
            $notDeleted = $this->manager->getConnection()->executeQuery(
576
                'SELECT pss.id
577
                 FROM s_product_streams_selection as pss
578
                 JOIN s_plugin_connect_items as pci
579
                 ON pss.article_id = pci.article_id
580
                 WHERE pss.stream_id = ?
581
                 AND pci.export_status != ?',
582
                [$streamId, null]
583
            )->fetchAll();
584
585
            if (count($notDeleted) === 0) {
586
                $this->manager->getConnection()->executeQuery(
587
                    'UPDATE s_plugin_connect_streams
588
                     SET export_status = ?
589
                     WHERE stream_id = ?',
590
                    [null, $streamId]
591
                );
592
            }
593
        }
594
    }
595
596 View Code Duplication
    private function markStreamsAsSynced()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
597
    {
598
        $streamIds = $this->manager->getConnection()->executeQuery(
599
            'SELECT pcs.stream_id as streamId
600
             FROM s_plugin_connect_streams as pcs
601
             WHERE export_status = ?',
602
            [ProductStreamService::STATUS_EXPORT]
603
        )->fetchAll();
604
605
        foreach ($streamIds as $stream) {
606
            $streamId = $stream['streamId'];
607
608
            $notExported = $this->manager->getConnection()->executeQuery(
609
                'SELECT pss.id
610
                 FROM s_product_streams_selection as pss
611
                 JOIN s_plugin_connect_items as pci
612
                 ON pss.article_id = pci.article_id
613
                 WHERE pss.stream_id = ?
614
                 AND pci.export_status != ?',
615
                [$streamId, Attribute::STATUS_SYNCED]
616
            )->fetchAll();
617
618
            if (count($notExported) === 0) {
619
                $this->manager->getConnection()->executeQuery(
620
                    'UPDATE s_plugin_connect_streams
621
                     SET export_status = ?
622
                     WHERE stream_id = ?',
623
                    [ProductStreamService::STATUS_SYNCED, $streamId]
624
                );
625
            }
626
        }
627
    }
628
629
    /**
630
     * @param Address $address
631
     */
632
    private function validateBilling(Address $address)
633
    {
634
        if (!$address->email) {
635
            throw new \RuntimeException('Billing address should contain email');
636
        }
637
638
        if (!$address->firstName) {
639
            throw new \RuntimeException('Billing address should contain first name');
640
        }
641
642
        if (!$address->surName) {
643
            throw new \RuntimeException('Billing address should contain last name');
644
        }
645
646
        if (!$address->zip) {
647
            throw new \RuntimeException('Billing address should contain zip');
648
        }
649
650
        if (!$address->city) {
651
            throw new \RuntimeException('Billing address should contain city');
652
        }
653
654
        if (!$address->phone) {
655
            throw new \RuntimeException('Billing address should contain phone');
656
        }
657
    }
658
}
659