Standard::createOrder()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 2
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2018-2025
6
 * @package Controller
7
 * @subpackage Jobs
8
 */
9
10
11
namespace Aimeos\Controller\Jobs\Subscription\Process\Renew;
12
13
14
/**
15
 * Job controller for subscription processs renew.
16
 *
17
 * @package Controller
18
 * @subpackage Jobs
19
 */
20
class Standard
21
	extends \Aimeos\Controller\Jobs\Subscription\Process\Base
22
	implements \Aimeos\Controller\Jobs\Iface
23
{
24
	/** controller/jobs/subscription/process/renew/name
25
	 * Class name of the used subscription suggestions scheduler controller implementation
26
	 *
27
	 * Each default job controller can be replace by an alternative imlementation.
28
	 * To use this implementation, you have to set the last part of the class
29
	 * name as configuration value so the controller factory knows which class it
30
	 * has to instantiate.
31
	 *
32
	 * For example, if the name of the default class is
33
	 *
34
	 *  \Aimeos\Controller\Jobs\Subscription\Process\Renew\Standard
35
	 *
36
	 * and you want to replace it with your own version named
37
	 *
38
	 *  \Aimeos\Controller\Jobs\Subscription\Process\Renew\Myrenew
39
	 *
40
	 * then you have to set the this configuration option:
41
	 *
42
	 *  controller/jobs/subscription/process/renew/name = Myrenew
43
	 *
44
	 * The value is the last part of your own class name and it's case sensitive,
45
	 * so take care that the configuration value is exactly named like the last
46
	 * part of the class name.
47
	 *
48
	 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
49
	 * characters are possible! You should always start the last part of the class
50
	 * name with an upper case character and continue only with lower case characters
51
	 * or numbers. Avoid chamel case names like "MyRenew"!
52
	 *
53
	 * @param string Last part of the class name
54
	 * @since 2018.04
55
	 */
56
57
	/** controller/jobs/subscription/process/renew/decorators/excludes
58
	 * Excludes decorators added by the "common" option from the subscription process CSV job controller
59
	 *
60
	 * Decorators extrenew the functionality of a class by adding new aspects
61
	 * (e.g. log what is currently done), executing the methods of the underlying
62
	 * class only in certain conditions (e.g. only for logged in users) or
63
	 * modify what is returned to the caller.
64
	 *
65
	 * This option allows you to remove a decorator added via
66
	 * "controller/jobs/common/decorators/default" before they are wrapped
67
	 * around the job controller.
68
	 *
69
	 *  controller/jobs/subscription/process/renew/decorators/excludes = array( 'decorator1' )
70
	 *
71
	 * This would remove the decorator named "decorator1" from the list of
72
	 * common decorators ("\Aimeos\Controller\Jobs\Common\Decorator\*") added via
73
	 * "controller/jobs/common/decorators/default" to the job controller.
74
	 *
75
	 * @param array List of decorator names
76
	 * @since 2018.04
77
	 * @see controller/jobs/common/decorators/default
78
	 * @see controller/jobs/subscription/process/renew/decorators/global
79
	 * @see controller/jobs/subscription/process/renew/decorators/local
80
	 */
81
82
	/** controller/jobs/subscription/process/renew/decorators/global
83
	 * Adds a list of globally available decorators only to the subscription process CSV job controller
84
	 *
85
	 * Decorators extrenew the functionality of a class by adding new aspects
86
	 * (e.g. log what is currently done), executing the methods of the underlying
87
	 * class only in certain conditions (e.g. only for logged in users) or
88
	 * modify what is returned to the caller.
89
	 *
90
	 * This option allows you to wrap global decorators
91
	 * ("\Aimeos\Controller\Jobs\Common\Decorator\*") around the job controller.
92
	 *
93
	 *  controller/jobs/subscription/process/renew/decorators/global = array( 'decorator1' )
94
	 *
95
	 * This would add the decorator named "decorator1" defined by
96
	 * "\Aimeos\Controller\Jobs\Common\Decorator\Decorator1" only to the job controller.
97
	 *
98
	 * @param array List of decorator names
99
	 * @since 2018.04
100
	 * @see controller/jobs/common/decorators/default
101
	 * @see controller/jobs/subscription/process/renew/decorators/excludes
102
	 * @see controller/jobs/subscription/process/renew/decorators/local
103
	 */
104
105
	/** controller/jobs/subscription/process/renew/decorators/local
106
	 * Adds a list of local decorators only to the subscription process CSV job controller
107
	 *
108
	 * Decorators extrenew the functionality of a class by adding new aspects
109
	 * (e.g. log what is currently done), executing the methods of the underlying
110
	 * class only in certain conditions (e.g. only for logged in users) or
111
	 * modify what is returned to the caller.
112
	 *
113
	 * This option allows you to wrap local decorators
114
	 * ("\Aimeos\Controller\Jobs\Subscription\Process\Renew\Decorator\*") around the job
115
	 * controller.
116
	 *
117
	 *  controller/jobs/subscription/process/renew/decorators/local = array( 'decorator2' )
118
	 *
119
	 * This would add the decorator named "decorator2" defined by
120
	 * "\Aimeos\Controller\Jobs\Subscription\Process\Renew\Decorator\Decorator2"
121
	 * only to the job controller.
122
	 *
123
	 * @param array List of decorator names
124
	 * @since 2018.04
125
	 * @see controller/jobs/common/decorators/default
126
	 * @see controller/jobs/subscription/process/renew/decorators/excludes
127
	 * @see controller/jobs/subscription/process/renew/decorators/global
128
	 */
129
130
131
	/**
132
	 * Returns the localized name of the job.
133
	 *
134
	 * @return string Name of the job
135
	 */
136
	public function getName() : string
137
	{
138
		return $this->context()->translate( 'controller/jobs', 'Subscription process renew' );
139
	}
140
141
142
	/**
143
	 * Returns the localized description of the job.
144
	 *
145
	 * @return string Description of the job
146
	 */
147
	public function getDescription() : string
148
	{
149
		return $this->context()->translate( 'controller/jobs', 'Renews subscriptions at next date' );
150
	}
151
152
153
	/**
154
	 * Executes the job.
155
	 *
156
	 * @throws \Aimeos\Controller\Jobs\Exception If an error occurs
157
	 */
158
	public function run()
159
	{
160
		$date = date( 'Y-m-d H:i:s' );
161
		$context = $this->context();
162
		$domains = $this->domains();
163
164
		$processors = $this->getProcessors( $this->names() );
165
		$manager = \Aimeos\MShop::create( $context, 'subscription' );
166
167
		$search = $manager->filter( true )->add( 'subscription.datenext', '<=', $date )->slice( 0, $this->max() );
168
		$search->add( $search->or( [
169
			$search->compare( '==', 'subscription.dateend', null ),
170
			$search->compare( '>', 'subscription.dateend', $date ),
171
		] ) );
172
		$cursor = $manager->cursor( $search );
173
174
		while( $items = $manager->iterate( $cursor, $domains ) )
175
		{
176
			foreach( $items as $item )
177
			{
178
				$manager->begin();
179
180
				try
181
				{
182
					$manager->save( $this->process( $item, $processors ) );
183
					$manager->commit();
184
				}
185
				catch( \Exception $e )
186
				{
187
					$manager->rollback();
188
189
					$str = 'Unable to renew subscription with ID "%1$s": %2$s';
190
					$msg = sprintf( $str, $item->getId(), $e->getMessage() . "\n" . $e->getTraceAsString() );
191
					$context->logger()->error( $msg, 'subscription/process/renew' );
192
				}
193
			}
194
		}
195
	}
196
197
198
	/**
199
	 * Adds the given addresses to the order
200
	 *
201
	 * @param \Aimeos\MShop\ContextIface Context object
0 ignored issues
show
Bug introduced by
The type Aimeos\Controller\Jobs\S...n\Process\Renew\Context was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
202
	 * @param \Aimeos\MShop\Order\Item\Iface $newOrder Order object to add the addresses to
203
	 * @param \Aimeos\Map $addresses List of type as key and address object implementing \Aimeos\MShop\Order\Item\Address\Iface as value
204
	 * @return \Aimeos\MShop\Order\Item\Iface Order with addresses added
205
	 */
206
	protected function addBasketAddresses( \Aimeos\MShop\ContextIface $context,
207
		\Aimeos\MShop\Order\Item\Iface $newOrder, \Aimeos\Map $addresses ) : \Aimeos\MShop\Order\Item\Iface
208
	{
209
		foreach( $addresses as $type => $orderAddresses )
210
		{
211
			$idx = 0;
212
213
			foreach( $orderAddresses as $orderAddress ) {
214
				$newOrder->addAddress( ( clone $orderAddress )->setId( null ), $type, $idx );
215
			}
216
		}
217
218
		if( !$newOrder->getCustomerId() ) {
219
			return $newOrder;
220
		}
221
222
		try
223
		{
224
			$customer = \Aimeos\MShop::create( $context, 'customer' )->get( $newOrder->getCustomerId() );
225
			$address = \Aimeos\MShop::create( $context, 'order' )->createAddress();
0 ignored issues
show
Bug introduced by
The method createAddress() does not exist on Aimeos\MShop\Common\Manager\Iface. Did you maybe mean create()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

225
			$address = \Aimeos\MShop::create( $context, 'order' )->/** @scrutinizer ignore-call */ createAddress();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
226
227
			$type = \Aimeos\MShop\Order\Item\Address\Base::TYPE_PAYMENT;
228
			$newOrder->addAddress( $address->copyFrom( $customer->getPaymentAddress() ), $type, 0 );
229
		}
230
		catch( \Exception $e )
231
		{
232
			$msg = sprintf( 'Unable to add current address for customer with ID "%1$s"', $newOrder->getCustomerId() );
233
			$context->logger()->info( $msg, 'subscription/process/renew' );
234
		}
235
236
		return $newOrder;
237
	}
238
239
240
	/**
241
	 * Adds the given coupon codes to the order if enabled
242
	 *
243
	 * @param \Aimeos\MShop\ContextIface Context object
244
	 * @param \Aimeos\MShop\Order\Item\Iface $newOrder Order including product and addresses
245
	 * @param \Aimeos\Map $codes List of coupon codes that should be added to the given order
246
	 * @return \Aimeos\MShop\Order\Item\Iface Basket, maybe with coupons added
247
	 */
248
	protected function addBasketCoupons( \Aimeos\MShop\ContextIface $context,
249
		\Aimeos\MShop\Order\Item\Iface $newOrder, \Aimeos\Map $codes ) : \Aimeos\MShop\Order\Item\Iface
250
	{
251
		/** controller/jobs/subscription/process/renew/use-coupons
252
		 * Applies the coupons of the previous order also to the new one
253
		 *
254
		 * Reuse coupon codes added to the order by the customer the first time
255
		 * again in new subscription orders. If they have any effect depends on
256
		 * the codes still being active (status, time frame and count) and the
257
		 * decorators added to the coupon providers in the admin interface.
258
		 *
259
		 * @param boolean True to reuse coupon codes, false to remove coupons
260
		 * @since 2018.10
261
		 */
262
		if( $context->config()->get( 'controller/jobs/subscription/process/renew/use-coupons', false ) )
263
		{
264
			foreach( $codes as $code )
265
			{
266
				try {
267
					$newOrder->addCoupon( $code );
268
				} catch( \Aimeos\MShop\Plugin\Provider\Exception | \Aimeos\MShop\Coupon\Exception $e ) {
269
					$newOrder->deleteCoupon( $code );
270
				}
271
			}
272
		}
273
274
		return $newOrder;
275
	}
276
277
278
	/**
279
	 * Adds the given products to the order
280
	 *
281
	 * @param \Aimeos\MShop\ContextIface Context object
282
	 * @param \Aimeos\MShop\Order\Item\Iface $order Order to add the products to
283
	 * @param \Aimeos\Map $orderProducts List of product items Implementing \Aimeos\MShop\Order\Item\Product\Iface
284
	 * @param string $orderProductId Unique ID of the ordered subscription product
285
	 * @return \Aimeos\MShop\Order\Item\Iface Order with products added
286
	 */
287
	protected function addBasketProducts( \Aimeos\MShop\ContextIface $context,
288
		\Aimeos\MShop\Order\Item\Iface $newOrder, \Aimeos\Map $orderProducts, $orderProductId ) : \Aimeos\MShop\Order\Item\Iface
289
	{
290
		foreach( $orderProducts as $orderProduct )
291
		{
292
			if( $orderProduct->getId() == $orderProductId )
293
			{
294
				$orderProduct = clone $orderProduct;
295
				$orderProduct->getAttributeItems()->setId( null );
296
297
				$newOrder->addProduct( $orderProduct->setId( null ) );
298
			}
299
		}
300
301
		return $newOrder;
302
	}
303
304
305
	/**
306
	 * Adds a matching delivery and payment service to the order
307
	 *
308
	 * @param \Aimeos\MShop\ContextIface Context object
309
	 * @param \Aimeos\MShop\Order\Item\Iface $order Order to add the services to
310
	 * @param \Aimeos\Map $services Associative list of type as key and list of service objects implementing \Aimeos\MShop\Order\Item\Service\Iface as values
311
	 * @return \Aimeos\MShop\Order\Item\Iface Order with delivery and payment service added
312
	 */
313
	protected function addBasketServices( \Aimeos\MShop\ContextIface $context,
314
		\Aimeos\MShop\Order\Item\Iface $newOrder, \Aimeos\Map $services ) : \Aimeos\MShop\Order\Item\Iface
315
	{
316
		$type = \Aimeos\MShop\Order\Item\Service\Base::TYPE_PAYMENT;
317
318
		if( isset( $services[$type] ) )
319
		{
320
			$idx = 0;
321
322
			foreach( $services[$type] as $orderService )
323
			{
324
				$orderService = clone $orderService;
325
				$orderService->getAttributeItems()->setId( null );
326
327
				$newOrder->addService( $orderService->setId( null ), $type, $idx++ );
328
			}
329
		}
330
331
		$idx = 0;
332
		$type = \Aimeos\MShop\Order\Item\Service\Base::TYPE_DELIVERY;
333
334
		$serviceManager = \Aimeos\MShop::create( $context, 'service' );
335
		$orderManager = \Aimeos\MShop::create( $context, 'order' );
336
337
		$search = $serviceManager->filter( true );
338
		$search->setSortations( [$search->sort( '+', 'service.position' )] );
339
		$search->setConditions( $search->compare( '==', 'service.type', $type ) );
340
341
		foreach( $serviceManager->search( $search, ['media', 'price', 'text'] ) as $item )
342
		{
343
			$provider = $serviceManager->getProvider( $item, $item->getType() );
344
345
			if( $provider->isAvailable( $newOrder ) === true )
346
			{
347
				$orderServiceItem = $orderManager->createService()->copyFrom( $item );
0 ignored issues
show
Bug introduced by
The method createService() does not exist on Aimeos\MShop\Common\Manager\Iface. Did you maybe mean create()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

347
				$orderServiceItem = $orderManager->/** @scrutinizer ignore-call */ createService()->copyFrom( $item );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
348
				return $newOrder->addService( $orderServiceItem, $type, $idx++ );
349
			}
350
		}
351
352
		return $newOrder;
353
	}
354
355
356
	/**
357
	 * Creates a new context based on the order and the customer the subscription belongs to
358
	 *
359
	 * @param \Aimeos\MShop\Subscription\Item\Iface $order Subscription item with associated order
360
	 * @return \Aimeos\MShop\ContextIface New context object
361
	 */
362
	protected function createContext( \Aimeos\MShop\Subscription\Item\Iface $subscription ) : \Aimeos\MShop\ContextIface
363
	{
364
		$context = clone $this->context();
365
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
366
367
		$order = $subscription->getOrderItem();
368
		$sitecode = $order->getSiteCode();
369
		$locale = $order->locale();
370
371
		$manager = \Aimeos\MShop::create( $context, 'locale' );
372
		$locale = $manager->bootstrap( $sitecode, $locale->getLanguageId(), $locale->getCurrencyId(), false, $level );
373
374
		$context->setLocale( $locale );
375
376
		try
377
		{
378
			$manager = \Aimeos\MShop::create( $context, 'customer' );
379
			$customerItem = $manager->get( $order->getCustomerId(), ['group'] );
380
			$context->setUser( $customerItem );
381
382
			$manager = \Aimeos\MShop::create( $context, 'group' );
383
			$filter = $manager->filter( true )->add( ['group.id' => $customerItem->getGroups()] );
384
			$groupItems = $manager->search( $filter->slice( 0, count( $customerItem->getGroups() ) ) )->all();
385
			$context->setGroups( $groupItems );
386
		}
387
		catch( \Exception $e ) {} // Subscription without account
388
389
		return $context;
390
	}
391
392
393
	/**
394
	 * Creates and stores a new order from the given subscription
395
	 *
396
	 * @param \Aimeos\MShop\ContextIface Context object
397
	 * @param \Aimeos\MShop\Subscription\Item\Iface $subscription Subscription item with associated order
398
	 * @return \Aimeos\MShop\Order\Item\Iface New order item including addresses, coupons, products and services
399
	 */
400
	protected function createOrder( \Aimeos\MShop\ContextIface $context,
401
		\Aimeos\MShop\Subscription\Item\Iface $subscription ) : \Aimeos\MShop\Order\Item\Iface
402
	{
403
		$order = $subscription->getOrderItem();
404
405
		$manager = \Aimeos\MShop::create( $context, 'order' );
406
		$newOrder = $manager->create()->setCustomerId( $order->getCustomerId() )->setChannel( 'subscription' );
407
408
		$newOrder = $this->addBasketAddresses( $context, $newOrder, $order->getAddresses() );
409
		$newOrder = $this->addBasketProducts( $context, $newOrder, $order->getProducts(), $subscription->getOrderProductId() );
410
		$newOrder = $this->addBasketServices( $context, $newOrder, $order->getServices() );
411
		$newOrder = $this->addBasketCoupons( $context, $newOrder, $order->getCoupons()->keys() );
412
413
		return $newOrder->check();
414
	}
415
416
417
	/**
418
	 * Creates a new payment for the given order and invoice
419
	 *
420
	 * @param \Aimeos\MShop\ContextIface Context object
421
	 * @param \Aimeos\MShop\Order\Item\Iface $order Complete order with product, addresses and services
422
	 * @return \Aimeos\MShop\Order\Item\Iface Updated order item
423
	 */
424
	protected function createPayment( \Aimeos\MShop\ContextIface $context, \Aimeos\MShop\Order\Item\Iface $order ) : \Aimeos\MShop\Order\Item\Iface
425
	{
426
		$manager = \Aimeos\MShop::create( $context, 'service' );
427
428
		foreach( $order->getService( \Aimeos\MShop\Order\Item\Service\Base::TYPE_PAYMENT ) as $service ) {
429
			$manager->getProvider( $manager->get( $service->getServiceId() ), 'payment' )->repay( $order );
430
		}
431
432
		return $order;
433
	}
434
435
436
	/**
437
	 * Returns the domains that should be fetched together with the order data
438
	 *
439
	 * @return array List of domain names
440
	 */
441
	protected function domains() : array
442
	{
443
		/** controller/jobs/subscription/process/domains
444
		 * Associated items that should be available too in the subscription
445
		 *
446
		 * Orders consist of address, coupons, products and services. They can be
447
		 * fetched together with the subscription items and passed to the processor.
448
		 * Available domains for those items are:
449
		 *
450
		 * - order
451
		 * - order/address
452
		 * - order/coupon
453
		 * - order/product
454
		 * - order/service
455
		 *
456
		 * @param array Referenced domain names
457
		 * @since 2022.04
458
		 * @see controller/jobs/subscription/process/processors
459
		 * @see controller/jobs/subscription/process/payment-days
460
		 * @see controller/jobs/subscription/process/payment-status
461
		 */
462
		$domains = ['order', 'order/address', 'order/coupon', 'order/product', 'order/service'];
463
		return $this->context()->config()->get( 'controller/jobs/subscription/process/domains', $domains );
464
	}
465
466
467
	/**
468
	 * Returns if subscriptions should end if payment couldn't be captured
469
	 *
470
	 * @return bool TRUE if subscription should end, FALSE if not
471
	 */
472
	protected function ends() : bool
473
	{
474
		/** controller/jobs/subscription/process/payment-ends
475
		 * Subscriptions ends if payment couldn't be captured
476
		 *
477
		 * By default, a subscription ends automatically if the next payment couldn't
478
		 * be captured. When setting this configuration to FALSE, the subscription job
479
		 * controller will try to capture the payment at the next run again until the
480
		 * subscription is deactivated manually.
481
		 *
482
		 * @param bool TRUE if payment failures ends the subscriptions, FALSE if not
483
		 * @since 2019.10
484
		 * @see controller/jobs/subscription/process/processors
485
		 * @see controller/jobs/subscription/process/payment-days
486
		 * @see controller/jobs/subscription/process/payment-status
487
		 */
488
		return (bool) $this->context()->config()->get( 'controller/jobs/subscription/process/payment-ends', true );
489
	}
490
491
492
	/**
493
	 * Returns the maximum number of orders processed at once
494
	 *
495
	 * @return int Maximum number of items
496
	 */
497
	protected function max() : int
498
	{
499
		/** controller/jobs/subscription/process/batch-max
500
		 * Maximum number of subscriptions processed at once by the subscription process job
501
		 *
502
		 * This setting configures the maximum number of subscriptions including
503
		 * orders that will be processed at once. Bigger batches an improve the
504
		 * performance but requires more memory.
505
		 *
506
		 * @param integer Number of subscriptions
507
		 * @since 2023.04
508
		 * @see controller/jobs/subscription/process/domains
509
		 * @see controller/jobs/subscription/process/names
510
		 * @see controller/jobs/subscription/process/payment-days
511
		 * @see controller/jobs/subscription/process/payment-status
512
		 */
513
		return $this->context()->config()->get( 'controller/jobs/subscription/process/batch-max', 100 );
514
	}
515
516
517
	/**
518
	 * Returns the names of the subscription processors
519
	 *
520
	 * @return array List of processor names
521
	 */
522
	protected function names() : array
523
	{
524
		/** controller/jobs/subscription/process/processors
525
		 * List of processor names that should be executed for subscriptions
526
		 *
527
		 * For each subscription a number of processors for different tasks can be executed.
528
		 * They can for example add a group to the customers' account during the customer
529
		 * has an active subscribtion.
530
		 *
531
		 * @param array List of processor names
532
		 * @since 2018.04
533
		 * @see controller/jobs/subscription/process/domains
534
		 * @see controller/jobs/subscription/process/max
535
		 * @see controller/jobs/subscription/process/payment-days
536
		 * @see controller/jobs/subscription/process/payment-status
537
		 */
538
		return (array) $this->context()->config()->get( 'controller/jobs/subscription/process/processors', [] );
539
	}
540
541
542
	/**
543
	 * Runs the subscription processors for the passed item
544
	 *
545
	 * @param \Aimeos\MShop\Subscription\Item\Iface $item Subscription item
546
	 * @param iterable $processors List of processor objects to run on the item
547
	 * @return \Aimeos\MShop\Subscription\Item\Iface Updated subscription item
548
	 */
549
	protected function process( \Aimeos\MShop\Subscription\Item\Iface $item, iterable $processors
550
		) : \Aimeos\MShop\Subscription\Item\Iface
551
	{
552
		$context = $this->context();
553
		$orderManager = \Aimeos\MShop::create( $context, 'order' );
554
555
		$context = $this->createContext( $item );
556
		$newOrder = $this->createOrder( $context, $item );
557
558
		foreach( $processors as $processor ) {
559
			$processor->renewBefore( $item, $newOrder );
560
		}
561
562
		$newOrder = $orderManager->save( $newOrder->check() );
563
564
		try
565
		{
566
			$newOrder = $orderManager->save( $this->createPayment( $context, $newOrder ) );
0 ignored issues
show
Bug introduced by
It seems like $newOrder can also be of type Aimeos\Map; however, parameter $order of Aimeos\Controller\Jobs\S...andard::createPayment() does only seem to accept Aimeos\MShop\Order\Item\Iface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

566
			$newOrder = $orderManager->save( $this->createPayment( $context, /** @scrutinizer ignore-type */ $newOrder ) );
Loading history...
567
568
			$interval = new \DateInterval( $item->getInterval() );
569
			$date = date_create( (string) $item->getDateNext() )->add( $interval )->format( 'Y-m-d H:i:s' );
570
571
			$item->setDateNext( $date )->setPeriod( $item->getPeriod() + 1 )->setReason( null );
572
		}
573
		catch( \Exception $e )
574
		{
575
			if( $e->getCode() < 1 ) // not a soft error
576
			{
577
				$item->setReason( \Aimeos\MShop\Subscription\Item\Iface::REASON_PAYMENT );
578
579
				if( $this->ends() ) {
580
					$item->setDateEnd( date_create()->format( 'Y-m-d H:i:s' ) );
581
				}
582
			}
583
584
			throw $e;
585
		}
586
		finally // will be always executed, even if exception is rethrown in catch()
587
		{
588
			foreach( $processors as $processor ) {
589
				$processor->renewAfter( $item, $newOrder );
590
			}
591
		}
592
593
		return $item;
594
	}
595
}
596