PluginProbe
seQura / 3.2.0
seQura v3.2.0
4.3.4 4.3.3 4.3.2 4.3.1 trunk 2.0.0 2.0.10 2.0.11 2.0.12 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0.0 3.0.2 3.0.5 3.0.6 3.0.7 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 4.0.0 All 30 releases
sequra / src / Services / Payment / class-payment-method-service.php

class-payment-method-service.php in seQura 3.2.0, at src/Services/Payment/class-payment-method-service.php

445 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Payment method service
4 *
5 * @package SeQura/WC
6 * @subpackage SeQura/WC/Services
7 */
8
9 namespace SeQura\WC\Services\Payment;
10
11 use SeQura\Core\BusinessLogic\AdminAPI\AdminAPI;
12 use SeQura\Core\BusinessLogic\CheckoutAPI\CheckoutAPI;
13 use SeQura\Core\BusinessLogic\CheckoutAPI\Solicitation\Response\SolicitationResponse;
14 use SeQura\Core\BusinessLogic\Domain\Order\Models\SeQuraForm;
15 use SeQura\Core\Infrastructure\Logger\LogContextData;
16 use SeQura\Core\Infrastructure\ORM\Interfaces\RepositoryInterface;
17 use SeQura\Core\Infrastructure\ORM\QueryFilter\Operators;
18 use SeQura\Core\Infrastructure\ORM\QueryFilter\QueryFilter;
19 use SeQura\WC\Core\Extension\BusinessLogic\DataAccess\PaymentMethods\Entities\Payment_Method;
20 use SeQura\WC\Core\Extension\BusinessLogic\DataAccess\PaymentMethods\Entities\Payment_Methods;
21 use SeQura\WC\Core\Extension\BusinessLogic\Domain\Order\Builders\Interface_Create_Order_Request_Builder;
22 use SeQura\WC\Core\Extension\Infrastructure\Configuration\Configuration;
23 use SeQura\WC\Dto\Payment_Method_Data;
24 use SeQura\WC\Dto\Payment_Method_Option;
25 use SeQura\WC\Services\Interface_Logger_Service;
26 use SeQura\WC\Services\Order\Interface_Order_Service;
27 use Throwable;
28 use WC_Order;
29
30 /**
31 * Handle use cases related to payment methods
32 */
33 class Payment_Method_Service implements Interface_Payment_Method_Service {
34
35 /**
36 * Part payment
37 */
38 const PART_PAYMENT = 'part_payment';
39
40 /**
41 * Configuration
42 *
43 * @var Configuration
44 */
45 private $configuration;
46
47 /**
48 * Payment methods
49 *
50 * @var Payment_Method_Option[]
51 */
52 private $payment_methods;
53
54 /**
55 * Create order request builder
56 *
57 * @var Interface_Create_Order_Request_Builder
58 */
59 private $create_order_request_builder;
60
61 /**
62 * Order service
63 *
64 * @var Interface_Order_Service
65 */
66 private $order_service;
67
68 /**
69 * Logger
70 *
71 * @var Interface_Logger_Service
72 */
73 private $logger;
74
75 /**
76 * Payment methods repository
77 *
78 * @var RepositoryInterface
79 */
80 private $payment_methods_repo;
81
82 /**
83 * Constructor
84 */
85 public function __construct(
86 Configuration $configuration,
87 Interface_Create_Order_Request_Builder $create_order_request_builder,
88 Interface_Order_Service $order_service,
89 Interface_Logger_Service $logger,
90 RepositoryInterface $payment_methods_repo
91 ) {
92 $this->configuration = $configuration;
93 $this->create_order_request_builder = $create_order_request_builder;
94 $this->order_service = $order_service;
95 $this->logger = $logger;
96 $this->payment_methods_repo = $payment_methods_repo;
97 }
98
99 /**
100 * Request solicitation
101 */
102 private function request_solicitation( ?WC_Order $order = null ): ?SolicitationResponse {
103 try {
104 $this->create_order_request_builder->set_current_order( $order );
105
106 if ( ! $this->create_order_request_builder->is_allowed() ) {
107 $ctx = $order ? array( new LogContextData( 'order_id', $order->get_id() ) ) : array();
108 $this->logger->log_debug( 'Order is not allowed for solicitation', __FUNCTION__, __CLASS__, $ctx );
109 return null;
110 }
111
112 $response = CheckoutAPI::get()
113 ->solicitation( $this->configuration->get_store_id() )
114 ->solicitFor( $this->create_order_request_builder );
115
116 if ( ! $response->isSuccessful() ) {
117 $ctx = $order ? array( new LogContextData( 'order_id', $order->get_id() ) ) : array();
118 $ctx[] = new LogContextData( 'response', $response->toArray() );
119 $this->logger->log_error( 'Solicitation response is not successful', __FUNCTION__, __CLASS__, $ctx );
120 }
121
122 return $response instanceof SolicitationResponse ? $response : null;
123 } catch ( Throwable $e ) {
124 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
125 return null;
126 }
127 }
128
129 /**
130 * Get identification form
131 */
132 public function get_identification_form( WC_Order $order ): ?SeQuraForm {
133
134 $response = $this->request_solicitation( $order );
135
136 if ( ! $response || ! $response->isSuccessful() ) {
137 return null;
138 }
139
140 /**
141 * Filter the options to be sent to seQura if needed
142 *
143 * @since 2.0.0
144 * */
145 $opts = \apply_filters(
146 'wc_sequra_pumbaa_options',
147 array(
148 'product' => $this->order_service->get_product( $order ),
149 'campaign' => $this->order_service->get_campaign( $order ),
150 ),
151 $order,
152 array()
153 );
154
155 try {
156 $cart_info = $this->order_service->get_cart_info( $order );
157
158 if ( ! $cart_info ) {
159 $this->logger->log_error( 'Cart info is null', __FUNCTION__, __CLASS__, array( new LogContextData( 'order_id', $order->get_id() ) ) );
160 return null;
161 }
162
163 $response = CheckoutAPI::get()
164 ->solicitation( $this->configuration->get_store_id() )
165 ->getIdentificationForm(
166 $cart_info->ref,
167 isset( $opts['product'] ) && '' !== $opts['product'] ? $opts['product'] : null,
168 isset( $opts['campaign'] ) && '' !== $opts['campaign'] ? $opts['campaign'] : null
169 );
170
171 if ( ! $response ) {
172 $this->logger->log_error( 'Identification form response is null', __FUNCTION__, __CLASS__, array( new LogContextData( 'order_id', $order->get_id() ) ) );
173 return null;
174 }
175
176 if ( ! $response->isSuccessful() ) {
177 $this->logger->log_error(
178 'Identification form response is not successful',
179 __FUNCTION__,
180 __CLASS__,
181 array(
182 new LogContextData( 'response', $response->toArray() ),
183 new LogContextData( 'order_id', $order->get_id() ),
184 )
185 );
186 return null;
187 }
188
189 return $response->getIdentificationForm();
190 } catch ( Throwable $e ) {
191 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
192 return null;
193 }
194 }
195
196 /**
197 * Get payment methods
198 *
199 * @return Payment_Method_Option[]
200 */
201 public function get_payment_methods( ?WC_Order $order = null ) {
202 if ( null !== $this->payment_methods ) {
203 return $this->payment_methods;
204 }
205 // TODO: Implement a cache system to avoid multiple requests with the same payload.
206 $response = $this->request_solicitation( $order );
207
208 if ( ! $response || ! $response->isSuccessful() ) {
209 return array();
210 }
211 $this->payment_methods = array();
212 foreach ( (array) ( $response->toArray()['availablePaymentMethods'] ?? array() ) as $raw ) {
213 $this->payment_methods[] = Payment_Method_Option::from_array( $raw );
214 }
215 return $this->payment_methods;
216 }
217
218 /**
219 * Get all payment methods from cache
220 *
221 * @param string $store_id The store ID.
222 * @param string $merchant The merchant ID.
223 * @return Payment_Methods
224 */
225 private function get_payment_methods_from_cache( $store_id, $merchant ) {
226 /**
227 * Payment methods entity
228 *
229 * @var Payment_Methods|null $payment_methods
230 */
231 $payment_methods = $this->payment_methods_repo->selectOne(
232 ( new QueryFilter() )
233 ->where( 'storeId', Operators::EQUALS, $store_id )
234 ->where( 'merchantId', Operators::EQUALS, $merchant )
235 );
236
237 return $payment_methods;
238 }
239
240 /**
241 * Save payment methods to cache
242 *
243 * @param Payment_Methods $payment_methods The payment methods.
244 */
245 private function save_payment_methods_to_cache( $payment_methods ) {
246 /**
247 * Payment methods entity
248 *
249 * @var Payment_Methods|null $payment_methods
250 */
251 $entity = $this->get_payment_methods_from_cache(
252 $payment_methods->getStoreId(),
253 $payment_methods->getMerchantId()
254 );
255 if ( null === $entity ) {
256 $this->payment_methods_repo->save( $payment_methods );
257 } else {
258 $entity->setPaymentMethods( $payment_methods->getPaymentMethods() );
259 $this->payment_methods_repo->update( $entity );
260 }
261 }
262
263 /**
264 * Get all payment methods from the API
265 *
266 * @param string $store_id The store ID.
267 * @param string $merchant The merchant ID.
268 * @return Payment_Methods|null
269 */
270 private function get_payment_methods_from_api( $store_id, $merchant ) {
271 try {
272 $response = AdminAPI::get()->paymentMethods( $store_id )->getPaymentMethods( $merchant );
273 if ( ! $response->isSuccessful() ) {
274 return null;
275 }
276 $payment_methods = new Payment_Methods();
277 $payment_methods->setStoreId( $store_id );
278 $payment_methods->setMerchantId( $merchant );
279 $payment_methods->setPaymentMethods( Payment_Method::fromBatch( $response->toArray() ) );
280 return $payment_methods;
281 } catch ( Throwable $e ) {
282 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__, array( new LogContextData( 'store_id', $store_id ), new LogContextData( 'merchant', $merchant ) ) );
283 return null;
284 }
285 }
286
287 /**
288 * Get a list of all payment methods defined for store and merchant
289 *
290 * @throws Throwable
291 *
292 * @param bool $cache Use cache if available.
293 * @return array<string, string>[]
294 */
295 public function get_all_payment_methods( ?string $store_id, ?string $merchant, $cache = true ): array {
296 if ( ! $store_id || ! $merchant ) {
297 return array();
298 }
299
300 $merchant = strval( $merchant );
301 $store_id = strval( $store_id );
302 $payment_methods = $cache ? $this->get_payment_methods_from_cache( $store_id, $merchant ) : null;
303
304 if ( ! $payment_methods ) {
305 $payment_methods = $this->get_payment_methods_from_api( $store_id, $merchant );
306 if ( $payment_methods ) {
307 // Save the payment methods in the cache.
308 $this->save_payment_methods_to_cache( $payment_methods );
309 }
310 }
311
312 if ( ! $payment_methods ) {
313 return array();
314 }
315
316 $arr = array();
317 foreach ( $payment_methods->getPaymentMethods() as $method ) {
318 $arr[] = $method->toArray();
319 }
320
321 return $arr;
322 }
323
324 /**
325 * Look for available payment methods which can be used with the widget
326 *
327 * @throws Throwable
328 *
329 * @return array<string, string>[]
330 */
331 public function get_all_widget_compatible_payment_methods( string $store_id, ?string $merchant ): array {
332 $compatible_payment_methods = array();
333 foreach ( $this->get_all_payment_methods( $store_id, $merchant ) as $method ) {
334 if ( $method['supportsWidgets'] ) {
335 $compatible_payment_methods[] = $method;
336 }
337 }
338 return $compatible_payment_methods;
339 }
340
341 /**
342 * Look for available payment methods that can be used with part payments
343 *
344 * @throws Throwable
345 *
346 * @return array<string, string>[]
347 */
348 public function get_all_mini_widget_compatible_payment_methods( string $store_id, ?string $merchant ): array {
349 $compatible_payment_methods = array();
350 foreach ( $this->get_all_payment_methods( $store_id, $merchant ) as $method ) {
351 if ( $method['supportsInstallmentPayments'] ) {
352 $compatible_payment_methods[] = $method;
353 }
354 }
355 return $compatible_payment_methods;
356 }
357
358 /**
359 * Look for available payment methods which can be used with the widget
360 *
361 * @throws Throwable
362 *
363 * @return array<string, string>[]
364 */
365 public function get_cart_widget( string $store_id, ?string $merchant ): array {
366 $compatible_payment_methods = array();
367 foreach ( $this->get_all_payment_methods( $store_id, $merchant ) as $method ) {
368 if ( $method['supportsWidgets'] ) {
369 $compatible_payment_methods[] = $method;
370 }
371 }
372 return $compatible_payment_methods;
373 }
374
375 /**
376 * Check if the payment method can be displayed in the widget
377 *
378 * @param array<string, string> $method the payment method. Must contain 'product' at least.
379 */
380 private function supports_widgets( array $method ): bool {
381 return isset( $method['product'] ) && in_array( $method['product'], array( 'i1', 'pp5', 'pp3', 'pp6', 'pp9', 'sp1' ), true );
382 }
383
384 /**
385 * Check if the payment method represents a part payment
386 *
387 * @param array<string, string> $method the payment method. Must contain 'product' at least.
388 */
389 private function supports_installment_payments( array $method ): bool {
390 // phpcs:ignore
391 // return isset( $method['product'] ) && in_array( $method['product'], array( 'pp5', 'pp3', 'pp6', 'pp9', 'sp1' ), true );
392 return isset( $method['product'] ) && in_array( $method['product'], array( 'pp3' ), true );
393 }
394
395 /**
396 * Check if the payment method data matches a valid payment method.
397 */
398 public function is_payment_method_data_valid( ?Payment_Method_Data $data ): bool {
399 if ( null !== $data ) {
400 foreach ( $this->get_payment_methods() as $pm ) {
401 if ( $pm->match( $data ) ) {
402 return true;
403 }
404 }
405 }
406 return false;
407 }
408
409 /**
410 * Check if the current page is the order pay page
411 */
412 public function is_order_pay_page(): bool {
413 return ( (int) strval( \get_query_var( 'order-pay' ) ) ) > 0;
414 }
415
416 /**
417 * Check if the current page is the checkout page
418 */
419 public function is_checkout(): bool {
420 /**
421 * Check if current page is checkout
422 *
423 * @since 3.0.0
424 */
425 return (bool) \apply_filters( 'sequra_is_checkout', \is_checkout() || $this->is_order_pay_page() || $this->is_store_api_request() );
426 }
427
428 /**
429 * Returns true if the request is a store REST API request.
430 *
431 * @return bool
432 */
433 private function is_store_api_request() {
434 // @phpstan-ignore-next-line
435 if ( method_exists( 'WooCommerce', 'is_store_api_request' ) ) {
436 return WC()->is_store_api_request();
437 }
438 if ( empty( $_SERVER['REQUEST_URI'] ) ) {
439 return false;
440 }
441 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
442 return false !== strpos( $_SERVER['REQUEST_URI'], trailingslashit( rest_get_url_prefix() ) . 'wc/store/' );
443 }
444 }
445