Passed
Push — master ( d6b463...6dde4a )
by Aimeos
04:00
created

Standard::addBasketAddresses()   A

Complexity

Conditions 5
Paths 15

Size

Total Lines 31
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 15
nop 3
dl 0
loc 31
rs 9.4555
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-2024
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/address' )->create();
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
		$orderServiceManager = \Aimeos\MShop::create( $context, 'order/service' );
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 = $orderServiceManager->create()->copyFrom( $item );
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
381
			$context->setUser( $customerItem );
382
			$context->setGroups( $customerItem->getGroups() );
383
		}
384
		catch( \Exception $e ) {} // Subscription without account
385
386
		return $context;
387
	}
388
389
390
	/**
391
	 * Creates and stores a new order from the given subscription
392
	 *
393
	 * @param \Aimeos\MShop\ContextIface Context object
394
	 * @param \Aimeos\MShop\Subscription\Item\Iface $subscription Subscription item with associated order
395
	 * @return \Aimeos\MShop\Order\Item\Iface New order item including addresses, coupons, products and services
396
	 */
397
	protected function createOrder( \Aimeos\MShop\ContextIface $context,
398
		\Aimeos\MShop\Subscription\Item\Iface $subscription ) : \Aimeos\MShop\Order\Item\Iface
399
	{
400
		$order = $subscription->getOrderItem();
401
402
		$manager = \Aimeos\MShop::create( $context, 'order' );
403
		$newOrder = $manager->create()->setCustomerId( $order->getCustomerId() )->setChannel( 'subscription' );
404
405
		$newOrder = $this->addBasketAddresses( $context, $newOrder, $order->getAddresses() );
406
		$newOrder = $this->addBasketProducts( $context, $newOrder, $order->getProducts(), $subscription->getOrderProductId() );
407
		$newOrder = $this->addBasketServices( $context, $newOrder, $order->getServices() );
408
		$newOrder = $this->addBasketCoupons( $context, $newOrder, $order->getCoupons()->keys() );
409
410
		return $newOrder->check();
411
	}
412
413
414
	/**
415
	 * Creates a new payment for the given order and invoice
416
	 *
417
	 * @param \Aimeos\MShop\ContextIface Context object
418
	 * @param \Aimeos\MShop\Order\Item\Iface $order Complete order with product, addresses and services
419
	 * @param \Aimeos\MShop\Order\Item\Iface New invoice item associated to the order
0 ignored issues
show
Bug introduced by
The type Aimeos\Controller\Jobs\S...ption\Process\Renew\New 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...
420
	 */
421
	protected function createPayment( \Aimeos\MShop\ContextIface $context, \Aimeos\MShop\Order\Item\Iface $order )
422
	{
423
		$manager = \Aimeos\MShop::create( $context, 'service' );
424
425
		foreach( $order->getService( \Aimeos\MShop\Order\Item\Service\Base::TYPE_PAYMENT ) as $service ) {
426
			$manager->getProvider( $manager->get( $service->getServiceId() ), 'payment' )->repay( $order );
427
		}
428
	}
429
430
431
	/**
432
	 * Returns the domains that should be fetched together with the order data
433
	 *
434
	 * @return array List of domain names
435
	 */
436
	protected function domains() : array
437
	{
438
		/** controller/jobs/subscription/process/domains
439
		 * Associated items that should be available too in the subscription
440
		 *
441
		 * Orders consist of address, coupons, products and services. They can be
442
		 * fetched together with the subscription items and passed to the processor.
443
		 * Available domains for those items are:
444
		 *
445
		 * - order
446
		 * - order/address
447
		 * - order/coupon
448
		 * - order/product
449
		 * - order/service
450
		 *
451
		 * @param array Referenced domain names
452
		 * @since 2022.04
453
		 * @see controller/jobs/subscription/process/processors
454
		 * @see controller/jobs/subscription/process/payment-days
455
		 * @see controller/jobs/subscription/process/payment-status
456
		 */
457
		$domains = ['order', 'order/address', 'order/coupon', 'order/product', 'order/service'];
458
		return $this->context()->config()->get( 'controller/jobs/subscription/process/domains', $domains );
459
	}
460
461
462
	/**
463
	 * Returns if subscriptions should end if payment couldn't be captured
464
	 *
465
	 * @return bool TRUE if subscription should end, FALSE if not
466
	 */
467
	protected function ends() : bool
468
	{
469
		/** controller/jobs/subscription/process/payment-ends
470
		 * Subscriptions ends if payment couldn't be captured
471
		 *
472
		 * By default, a subscription ends automatically if the next payment couldn't
473
		 * be captured. When setting this configuration to FALSE, the subscription job
474
		 * controller will try to capture the payment at the next run again until the
475
		 * subscription is deactivated manually.
476
		 *
477
		 * @param bool TRUE if payment failures ends the subscriptions, FALSE if not
478
		 * @since 2019.10
479
		 * @see controller/jobs/subscription/process/processors
480
		 * @see controller/jobs/subscription/process/payment-days
481
		 * @see controller/jobs/subscription/process/payment-status
482
		 */
483
		return (bool) $this->context()->config()->get( 'controller/jobs/subscription/process/payment-ends', true );
484
	}
485
486
487
	/**
488
	 * Returns the maximum number of orders processed at once
489
	 *
490
	 * @return int Maximum number of items
491
	 */
492
	protected function max() : int
493
	{
494
		/** controller/jobs/subscription/process/batch-max
495
		 * Maximum number of subscriptions processed at once by the subscription process job
496
		 *
497
		 * This setting configures the maximum number of subscriptions including
498
		 * orders that will be processed at once. Bigger batches an improve the
499
		 * performance but requires more memory.
500
		 *
501
		 * @param integer Number of subscriptions
502
		 * @since 2023.04
503
		 * @see controller/jobs/subscription/process/domains
504
		 * @see controller/jobs/subscription/process/names
505
		 * @see controller/jobs/subscription/process/payment-days
506
		 * @see controller/jobs/subscription/process/payment-status
507
		 */
508
		return $this->context()->config()->get( 'controller/jobs/subscription/process/batch-max', 100 );
509
	}
510
511
512
	/**
513
	 * Returns the names of the subscription processors
514
	 *
515
	 * @return array List of processor names
516
	 */
517
	protected function names() : array
518
	{
519
		/** controller/jobs/subscription/process/processors
520
		 * List of processor names that should be executed for subscriptions
521
		 *
522
		 * For each subscription a number of processors for different tasks can be executed.
523
		 * They can for example add a group to the customers' account during the customer
524
		 * has an active subscribtion.
525
		 *
526
		 * @param array List of processor names
527
		 * @since 2018.04
528
		 * @see controller/jobs/subscription/process/domains
529
		 * @see controller/jobs/subscription/process/max
530
		 * @see controller/jobs/subscription/process/payment-days
531
		 * @see controller/jobs/subscription/process/payment-status
532
		 */
533
		return (array) $this->context()->config()->get( 'controller/jobs/subscription/process/processors', [] );
534
	}
535
536
537
	/**
538
	 * Runs the subscription processors for the passed item
539
	 *
540
	 * @param \Aimeos\MShop\Subscription\Item\Iface $item Subscription item
541
	 * @param iterable $processors List of processor objects to run on the item
542
	 * @return \Aimeos\MShop\Subscription\Item\Iface Updated subscription item
543
	 */
544
	protected function process( \Aimeos\MShop\Subscription\Item\Iface $item, iterable $processors
545
		) : \Aimeos\MShop\Subscription\Item\Iface
546
	{
547
		$context = $this->context();
548
		$orderManager = \Aimeos\MShop::create( $context, 'order' );
549
550
		$context = $this->createContext( $item );
551
		$newOrder = $this->createOrder( $context, $item );
552
553
		foreach( $processors as $processor ) {
554
			$processor->renewBefore( $item, $newOrder );
555
		}
556
557
		$newOrder = $orderManager->save( $newOrder->check() );
558
559
		try
560
		{
561
			$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

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