Completed
Push — master ( fa0b22...3a2b7d )
by Sven
03:18
created

ProductFromShop::buy()   A

Complexity

Conditions 2
Paths 5

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 11
nc 5
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 ?: '',
341
            'additionalAddressLine2' => $address->additionalAddressLine2 ?: '',
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
            if ($since) {
517
                $this->manager->getConnection()->executeQuery(
518
                    'UPDATE s_plugin_connect_items
519
                    SET export_status = ?
520
                    WHERE revision <= ?
521
                    AND ( export_status = ? OR export_status = ? )',
522
                    [$statusSynced, $since, $statusInsert, $statusUpdate]
523
                );
524
525
                $this->manager->getConnection()->executeQuery(
526
                    'UPDATE s_plugin_connect_items
527
                    SET export_status = ?
528
                    WHERE revision <= ?
529
                    AND export_status = ?',
530
                    [null, $since, $statusDelete]
531
                );
532
            } else {
533
                $this->manager->getConnection()->executeQuery(
534
                    'UPDATE s_plugin_connect_items
535
                    SET export_status = ?
536
                    WHERE revision IS NULL
537
                    AND ( export_status = ? OR export_status = ? )',
538
                    [$statusSynced, $statusInsert, $statusUpdate]
539
                );
540
541
                $this->manager->getConnection()->executeQuery(
542
                    'UPDATE s_plugin_connect_items
543
                    SET export_status = ?
544
                    WHERE revision IS NULL
545
                    AND export_status = ?',
546
                    [null, $statusDelete]
547
                );
548
            }
549
550
551
            /** @var \Shopware\Connect\Struct\Change $change */
552
            foreach ($changes as $change) {
553
                if (!$change instanceof Insert && !$change instanceof Update && !$change instanceof Availability) {
554
                    continue;
555
                }
556
557
                $this->manager->getConnection()->executeQuery(
558
                    'UPDATE s_plugin_connect_items
559
                    SET revision = ?
560
                    WHERE source_id = ? AND shop_id IS NULL',
561
                    [$change->revision, $change->sourceId]
562
                );
563
            }
564
565
            $this->manager->getConnection()->commit();
566
        } catch (\Exception $e) {
567
            $this->manager->getConnection()->rollBack();
568
        }
569
570
        try {
571
            $this->markStreamsAsSynced();
572
            $this->markStreamsAsNotExported();
573
        } catch (\Exception $e) {
574
            $this->logger->write(
575
                true,
576
                sprintf('Failed to mark streams as synced! Message: "%s". Trace: "%s"', $e->getMessage(), $e->getTraceAsString()),
577
                null
578
            );
579
        }
580
    }
581
582 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...
583
    {
584
        $streamIds = $this->manager->getConnection()->executeQuery(
585
            'SELECT pcs.stream_id as streamId
586
             FROM s_plugin_connect_streams as pcs
587
             WHERE export_status = ?',
588
            [ProductStreamService::STATUS_DELETE]
589
        )->fetchAll();
590
591
        foreach ($streamIds as $stream) {
592
            $streamId = $stream['streamId'];
593
594
            $notDeleted = $this->manager->getConnection()->executeQuery(
595
                'SELECT pss.id
596
                 FROM s_product_streams_selection as pss
597
                 JOIN s_plugin_connect_items as pci
598
                 ON pss.article_id = pci.article_id
599
                 WHERE pss.stream_id = ?
600
                 AND pci.export_status != ?',
601
                [$streamId, null]
602
            )->fetchAll();
603
604
            if (count($notDeleted) === 0) {
605
                $this->manager->getConnection()->executeQuery(
606
                    'UPDATE s_plugin_connect_streams
607
                     SET export_status = ?
608
                     WHERE stream_id = ?',
609
                    [null, $streamId]
610
                );
611
            }
612
        }
613
    }
614
615 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...
616
    {
617
        $streamIds = $this->manager->getConnection()->executeQuery(
618
            'SELECT pcs.stream_id as streamId
619
             FROM s_plugin_connect_streams as pcs
620
             WHERE export_status = ?',
621
            [ProductStreamService::STATUS_EXPORT]
622
        )->fetchAll();
623
624
        foreach ($streamIds as $stream) {
625
            $streamId = $stream['streamId'];
626
627
            $notExported = $this->manager->getConnection()->executeQuery(
628
                'SELECT pss.id
629
                 FROM s_product_streams_selection as pss
630
                 JOIN s_plugin_connect_items as pci
631
                 ON pss.article_id = pci.article_id
632
                 WHERE pss.stream_id = ?
633
                 AND pci.export_status != ?',
634
                [$streamId, Attribute::STATUS_SYNCED]
635
            )->fetchAll();
636
637
            if (count($notExported) === 0) {
638
                $this->manager->getConnection()->executeQuery(
639
                    'UPDATE s_plugin_connect_streams
640
                     SET export_status = ?
641
                     WHERE stream_id = ?',
642
                    [ProductStreamService::STATUS_SYNCED, $streamId]
643
                );
644
            }
645
        }
646
    }
647
648
    /**
649
     * @param Address $address
650
     */
651
    private function validateBilling(Address $address)
652
    {
653
        if (!$address->email) {
654
            throw new \RuntimeException('Billing address should contain email');
655
        }
656
657
        if (!$address->firstName) {
658
            throw new \RuntimeException('Billing address should contain first name');
659
        }
660
661
        if (!$address->surName) {
662
            throw new \RuntimeException('Billing address should contain last name');
663
        }
664
665
        if (!$address->zip) {
666
            throw new \RuntimeException('Billing address should contain zip');
667
        }
668
669
        if (!$address->city) {
670
            throw new \RuntimeException('Billing address should contain city');
671
        }
672
673
        if (!$address->phone) {
674
            throw new \RuntimeException('Billing address should contain phone');
675
        }
676
    }
677
}
678