Passed
Push — master ( d2fcbc...0a49bb )
by Aimeos
02:57
created

Standard::limit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 17
rs 10
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2014
6
 * @copyright Aimeos (aimeos.org), 2015-2022
7
 * @package Controller
8
 * @subpackage Jobs
9
 */
10
11
12
namespace Aimeos\Controller\Jobs\Order\Service\Payment;
13
14
15
/**
16
 * Captures the money for authorized orders
17
 *
18
 * @package Controller
19
 * @subpackage Jobs
20
 */
21
class Standard
22
	extends \Aimeos\Controller\Jobs\Base
23
	implements \Aimeos\Controller\Jobs\Iface
24
{
25
	/** controller/jobs/order/service/payment/name
26
	 * Class name of the used order service payment scheduler controller implementation
27
	 *
28
	 * Each default job controller can be replace by an alternative imlementation.
29
	 * To use this implementation, you have to set the last part of the class
30
	 * name as configuration value so the controller factory knows which class it
31
	 * has to instantiate.
32
	 *
33
	 * For example, if the name of the default class is
34
	 *
35
	 *  \Aimeos\Controller\Jobs\Order\Service\Payment\Standard
36
	 *
37
	 * and you want to replace it with your own version named
38
	 *
39
	 *  \Aimeos\Controller\Jobs\Order\Service\Payment\Mypayment
40
	 *
41
	 * then you have to set the this configuration option:
42
	 *
43
	 *  controller/jobs/order/service/payment/name = Mypayment
44
	 *
45
	 * The value is the last part of your own class name and it's case sensitive,
46
	 * so take care that the configuration value is exactly named like the last
47
	 * part of the class name.
48
	 *
49
	 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
50
	 * characters are possible! You should always start the last part of the class
51
	 * name with an upper case character and continue only with lower case characters
52
	 * or numbers. Avoid chamel case names like "MyPayment"!
53
	 *
54
	 * @param string Last part of the class name
55
	 * @since 2014.07
56
	 * @category Developer
57
	 */
58
59
	/** controller/jobs/order/service/payment/decorators/excludes
60
	 * Excludes decorators added by the "common" option from the order service payment controllers
61
	 *
62
	 * Decorators extend the functionality of a class by adding new aspects
63
	 * (e.g. log what is currently done), executing the methods of the underlying
64
	 * class only in certain conditions (e.g. only for logged in users) or
65
	 * modify what is returned to the caller.
66
	 *
67
	 * This option allows you to remove a decorator added via
68
	 * "controller/jobs/common/decorators/default" before they are wrapped
69
	 * around the job controller.
70
	 *
71
	 *  controller/jobs/order/service/payment/decorators/excludes = array( 'decorator1' )
72
	 *
73
	 * This would remove the decorator named "decorator1" from the list of
74
	 * common decorators ("\Aimeos\Controller\Jobs\Common\Decorator\*") added via
75
	 * "controller/jobs/common/decorators/default" to this job controller.
76
	 *
77
	 * @param array List of decorator names
78
	 * @since 2015.09
79
	 * @see controller/jobs/common/decorators/default
80
	 * @see controller/jobs/order/service/payment/decorators/global
81
	 * @see controller/jobs/order/service/payment/decorators/local
82
	 */
83
84
	/** controller/jobs/order/service/payment/decorators/global
85
	 * Adds a list of globally available decorators only to the order service payment controllers
86
	 *
87
	 * Decorators extend the functionality of a class by adding new aspects
88
	 * (e.g. log what is currently done), executing the methods of the underlying
89
	 * class only in certain conditions (e.g. only for logged in users) or
90
	 * modify what is returned to the caller.
91
	 *
92
	 * This option allows you to wrap global decorators
93
	 * ("\Aimeos\Controller\Jobs\Common\Decorator\*") around the job controller.
94
	 *
95
	 *  controller/jobs/order/service/payment/decorators/global = array( 'decorator1' )
96
	 *
97
	 * This would add the decorator named "decorator1" defined by
98
	 * "\Aimeos\Controller\Jobs\Common\Decorator\Decorator1" only to this job controller.
99
	 *
100
	 * @param array List of decorator names
101
	 * @since 2015.09
102
	 * @see controller/jobs/common/decorators/default
103
	 * @see controller/jobs/order/service/payment/decorators/excludes
104
	 * @see controller/jobs/order/service/payment/decorators/local
105
	 */
106
107
	/** controller/jobs/order/service/payment/decorators/local
108
	 * Adds a list of local decorators only to the order service payment controllers
109
	 *
110
	 * Decorators extend the functionality of a class by adding new aspects
111
	 * (e.g. log what is currently done), executing the methods of the underlying
112
	 * class only in certain conditions (e.g. only for logged in users) or
113
	 * modify what is returned to the caller.
114
	 *
115
	 * This option allows you to wrap local decorators
116
	 * ("\Aimeos\Controller\Jobs\Order\Service\Payment\Decorator\*") around this job controller.
117
	 *
118
	 *  controller/jobs/order/service/payment/decorators/local = array( 'decorator2' )
119
	 *
120
	 * This would add the decorator named "decorator2" defined by
121
	 * "\Aimeos\Controller\Jobs\Order\Service\Payment\Decorator\Decorator2" only to this job
122
	 * controller.
123
	 *
124
	 * @param array List of decorator names
125
	 * @since 2015.09
126
	 * @see controller/jobs/common/decorators/default
127
	 * @see controller/jobs/order/service/payment/decorators/excludes
128
	 * @see controller/jobs/order/service/payment/decorators/global
129
	 */
130
131
132
	/**
133
	 * Returns the localized name of the job.
134
	 *
135
	 * @return string Name of the job
136
	 */
137
	public function getName() : string
138
	{
139
		return $this->context()->translate( 'controller/jobs', 'Capture authorized payments' );
140
	}
141
142
143
	/**
144
	 * Returns the localized description of the job.
145
	 *
146
	 * @return string Description of the job
147
	 */
148
	public function getDescription() : string
149
	{
150
		return $this->context()->translate( 'controller/jobs', 'Authorized payments of orders will be captured after dispatching or after a configurable amount of time' );
151
	}
152
153
154
	/**
155
	 * Executes the job.
156
	 *
157
	 * @throws \Aimeos\Controller\Jobs\Exception If an error occurs
158
	 */
159
	public function run()
160
	{
161
		$context = $this->context();
162
		$manager = \Aimeos\MShop::create( $context, 'service' );
163
164
		$filter = $manager->filter()->add( ['service.type' => 'payment'] );
165
		$cursor = $manager->cursor( $filter );
166
167
		while( $items = $manager->iterate( $cursor ) )
168
		{
169
			foreach( $items as $item )
170
			{
171
				try
172
				{
173
					$provider = $manager->getProvider( $item, $item->getType() );
174
175
					if( $provider->isImplemented( \Aimeos\MShop\Service\Provider\Payment\Base::FEAT_CAPTURE ) ) {
176
						$this->orders( $provider );
177
					}
178
				}
179
				catch( \Exception $e )
180
				{
181
					$str = 'Error while capturing payments for service with ID "%1$s": %2$s';
182
					$msg = sprintf( $str, $item->getId(), $e->getMessage() . "\n" . $e->getTraceAsString() );
183
					$context->logger()->error( $msg, 'order/service/payment' );
184
				}
185
			}
186
		}
187
	}
188
189
190
	/**
191
	 * Returns the date after the payments are captured
192
	 *
193
	 * @return string|null Date/time in "YYYY-MM-DD HH:mm:ss" format or NULL if not configured
194
	 */
195
	protected function capture() : ?string
196
	{
197
		/** controller/jobs/order/service/payment/capture-days
198
		 * Automatically capture payments after the configured amount of days
199
		 *
200
		 * You can capture authorized payments after a configured amount of days
201
		 * even if the parcel for the order wasn't dispatched yet. This is useful
202
		 * for payment methods like credit cards where autorizations are revoked
203
		 * by the aquirers after some time (usually seven days).
204
		 *
205
		 * @param integer Number of days
206
		 * @since 2014.07
207
		 * @category User
208
		 * @category Developer
209
		 */
210
		$days = $this->context()->config()->get( 'controller/jobs/order/service/payment/capture-days', null );
211
		return $days ? date( 'Y-m-d 00:00:00', time() - 86400 * $days ) : null;
212
	}
213
214
215
	/**
216
	 * Returns the domains that should be fetched together with the order data
217
	 *
218
	 * @return array List of domain names
219
	 */
220
	protected function domains() : array
221
	{
222
		/** controller/jobs/order/service/payment/domains
223
		 * Associated items that should be available too in the order
224
		 *
225
		 * Orders consist of address, coupons, products and services. They can be
226
		 * fetched together with the order items and passed to the payment service
227
		 * providers. Available domains for those items are:
228
		 *
229
		 * - order/base
230
		 * - order/base/address
231
		 * - order/base/coupon
232
		 * - order/base/product
233
		 * - order/base/service
234
		 *
235
		 * @param array Referenced domain names
236
		 * @since 2022.04
237
		 * @see controller/jobs/order/email/payment/limit-days
238
		 * @see controller/jobs/order/service/payment/capture-days
239
		 */
240
		$domains = ['order/base', 'order/base/address', 'order/base/coupon', 'order/base/product', 'order/base/service'];
241
		return $this->context()->config()->get( 'controller/jobs/order/service/delivery/domains', $domains );
242
	}
243
244
245
	/**
246
	 * Returns the date until orders should be processed
247
	 *
248
	 * @return string Date/time in "YYYY-MM-DD HH:mm:ss" format
249
	 */
250
	protected function limit() : string
251
	{
252
		/** controller/jobs/order/service/payment/limit-days
253
		 * Only start capturing payments of orders that were created in the past within the configured number of days
254
		 *
255
		 * Capturing payments is normally done immediately after the delivery
256
		 * status changed to "dispatched" or "delivered". This option prevents
257
		 * payments from being captured in case anything went wrong and payments
258
		 * of old orders would be captured now.
259
		 *
260
		 * @param integer Number of days
261
		 * @since 2014.07
262
		 * @category User
263
		 * @category Developer
264
		 */
265
		$days = $this->context()->config()->get( 'controller/jobs/order/service/payment/limit-days', 90 );
266
		return date( 'Y-m-d 00:00:00', time() - 86400 * $days );
267
	}
268
269
270
	/**
271
	 * Returns the maximum number of orders processed at once
272
	 *
273
	 * @return int Maximum number of items
274
	 */
275
	protected function max() : int
276
	{
277
		/** controller/jobs/order/service/delivery/batch-max
278
		 * Maximum number of orders processed at once by the delivery service provider
279
		 *
280
		 * Orders are sent in batches if the delivery service provider supports it.
281
		 * This setting configures the maximum orders that will be handed over to
282
		 * the delivery service provider at once. Bigger batches an improve the
283
		 * performance but requires more memory.
284
		 *
285
		 * @param integer Number of orders
286
		 * @since 2018.07
287
		 * @see controller/jobs/order/service/delivery/domains
288
		 * @see controller/jobs/order/service/delivery/limit-days
289
		 */
290
		return $this->context()->config()->get( 'controller/jobs/order/service/delivery/batch-max', 100 );
291
	}
292
293
294
	/**
295
	 * Fetches and processes the order items
296
	 *
297
	 * @param \Aimeos\MShop\Service\Provider\Iface $provider Service provider for processing the orders
298
	 */
299
	protected function orders( \Aimeos\MShop\Service\Provider\Iface $provider )
300
	{
301
		$context = $this->context();
302
		$domains = $this->domains();
303
304
		$item = $provider->getServiceItem();
305
		$manager = \Aimeos\MShop::create( $context, 'order' );
306
307
		$filter = $manager->filter()->slice( 0, $this->max() );
308
		$filter->add( $filter->and( [
309
			$filter->compare( '>=', 'order.datepayment', $this->limit() ),
310
			$filter->compare( '>=', 'order.statuspayment', \Aimeos\MShop\Order\Item\Base::PAY_AUTHORIZED ),
311
			$filter->compare( '==', 'order.base.service.code', $item->getCode() ),
312
			$filter->compare( '==', 'order.base.service.type', 'payment' ),
313
		] ) );
314
		$cursor = $manager->cursor( $filter );
315
316
		if( ( $capture = $this->capture() ) !== null ) {
317
			$filter->add( $filter->compare( '<=', 'order.datepayment', $capture ) );
318
		} else {
319
			$status = [\Aimeos\MShop\Order\Item\Base::STAT_DISPATCHED, \Aimeos\MShop\Order\Item\Base::STAT_DELIVERED];
320
			$filter->add( $filter->compare( '==', 'order.statusdelivery', $status ) );
321
		}
322
323
		while( $items = $manager->iterate( $cursor, $domains ) )
324
		{
325
			foreach( $items as $item )
326
			{
327
				try
328
				{
329
					$provider->capture( $item );
0 ignored issues
show
Bug introduced by
The method capture() does not exist on Aimeos\MShop\Service\Provider\Iface. It seems like you code against a sub-type of said class. However, the method does not exist in Aimeos\MShop\Service\Provider\Delivery\Iface or Aimeos\MShop\Service\Provider\Decorator\Iface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

329
					$provider->/** @scrutinizer ignore-call */ 
330
                capture( $item );
Loading history...
330
				}
331
				catch( \Exception $e )
332
				{
333
					$str = 'Error while capturing payment for order with ID "%1$s": %2$s';
334
					$msg = sprintf( $str, $item->getId(), $e->getMessage() . "\n" . $e->getTraceAsString() );
335
					$context->logger()->error( $msg, 'order/service/payment' );
336
				}
337
			}
338
		}
339
	}
340
}
341