PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.5.3
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.5.3
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
fluent-cart / app / Modules / PaymentMethods / Core / AbstractPaymentGateway.php

AbstractPaymentGateway.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.5.3, at app/Modules/PaymentMethods/Core/AbstractPaymentGateway.php

487 lines 16.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Modules\PaymentMethods\Core;
4
5 use FluentCart\Api\Helper;
6 use FluentCart\Api\Orders;
7 use FluentCart\Api\Resource\FrontendResource\CartResource;
8 use FluentCart\Api\StoreSettings;
9 use FluentCart\App\App;
10 use FluentCart\App\Helpers\CartCheckoutHelper;
11 use FluentCart\App\Helpers\Status;
12 use FluentCart\App\Helpers\StatusHelper;
13 use FluentCart\App\Models\OrderTransaction;
14 use FluentCart\Api\Resource\ActivityResource;
15 use FluentCart\App\Services\Payments\PaymentHelper;
16 use FluentCart\App\Services\Renderer\Receipt\ReceiptRenderer;
17 use FluentCart\Framework\Support\Arr;
18
19 abstract class AbstractPaymentGateway implements PaymentGatewayInterface
20 {
21
22 private $methodSlug = '';
23
24 public array $supportedFeatures = [];
25
26 public StoreSettings $storeSettings;
27
28 public ?AbstractSubscriptionModule $subscriptions;
29
30 public BaseGatewaySettings $settings;
31
32 public function __construct(BaseGatewaySettings $settings, ?AbstractSubscriptionModule $subscriptions = null)
33 {
34 $this->settings = $settings;
35 $this->methodSlug = $this->getMeta('slug');
36
37 if ($subscriptions) {
38 $this->supportedFeatures[] = 'subscriptions';
39 }
40 $this->subscriptions = $subscriptions;
41
42 // register global hooks
43 $this->init();
44 }
45
46 public function init(): void
47 {
48 add_filter('fluent_cart/transaction/url_' . $this->methodSlug, [$this, 'getTransactionUrl'], 10, 2);
49 add_filter('fluent_cart/subscription/url_' . $this->methodSlug, [$this, 'getSubscriptionUrl'], 10, 2);
50 }
51
52 public function has(string $feature): bool
53 {
54 return in_array($feature, $this->supportedFeatures);
55 }
56
57 public function getMeta($key = '')
58 {
59 $meta = $this->meta();
60
61 $gatewaySettings = $this->settings->get();
62
63 if (isset($gatewaySettings['checkout_label']) && !empty($gatewaySettings['checkout_label'])) {
64 $meta['title'] = $gatewaySettings['checkout_label'];
65 }
66
67 if (isset($gatewaySettings['checkout_logo']) && !empty($gatewaySettings['checkout_logo'])) {
68 $meta['logo'] = $gatewaySettings['checkout_logo'];
69 }
70
71 if (isset($gatewaySettings['checkout_instructions']) && !empty($gatewaySettings['checkout_instructions'])) {
72 $meta['instructions'] = $gatewaySettings['checkout_instructions'];
73 }
74
75 if ($key !== '') {
76 return Arr::get($meta, $key, '');
77 }
78 return $meta;
79 }
80
81 public function isUpcoming(): bool
82 {
83 return $this->getMeta('upcoming');
84 }
85
86 public function setStoreSettings(StoreSettings $settings): void
87 {
88 $this->storeSettings = $settings;
89 }
90
91 public function storeSettings(): StoreSettings
92 {
93 return $this->storeSettings;
94 }
95
96 public function isCurrencySupported(): bool
97 {
98 return true;
99 }
100
101 public function isEnabled(): bool
102 {
103 return $this->settings->get('is_active') === 'yes';
104 }
105
106 public static function validateSettings($data): array
107 {
108 return $data;
109 }
110
111 public static function beforeSettingsUpdate($data, $oldSettings): array
112 {
113 return $data;
114 }
115
116 public function updateSettings($data)
117 {
118 if ($this->isUpcoming()) {
119 wp_send_json([
120 'status' => 'failed',
121 'message' => __('Payment method is upcoming! Not available for right now!', 'fluent-cart')
122 ], 422);
123 }
124
125 $oldSettings = $this->settings->get();
126 $settings = wp_parse_args($data, $oldSettings);
127 $settings = Helper::sanitize($settings, $this->fields());
128 $is_active = Arr::get($settings, 'is_active', 'no');
129 // validate if the settings/credentials are correct
130 if ('yes' === $is_active) {
131 $response = static::validateSettings($settings);
132 if (isset($response['status']) && $response['status'] === 'failed') {
133 wp_send_json(
134 [
135 'status' => 'failed',
136 'message' => $response['message'] ? $response['message'] : __('Invalid credentials!', 'fluent-cart'),
137 'data' => []
138 ],
139 422
140 );
141 }
142 }
143
144 $settings = static::beforeSettingsUpdate($settings, $oldSettings);
145 // unset($settings['payment_mode']);
146 unset($settings['provider']);
147 fluent_cart_update_option($this->settings->methodHandler, $settings);
148
149 return $settings;
150 }
151
152 public function getSuccessUrl($transaction, $args = [])
153 {
154 $paymentHelper = new PaymentHelper($this->getMeta('route'));
155 return $paymentHelper->successUrl($transaction->uuid, $args);
156 }
157
158 public static function getCancelUrl(): string
159 {
160 $checkoutPage = (new StoreSettings())->getCheckoutPage();
161 // get cart hash from url
162 $cartHash = App::request()->get('fct_cart_hash', '');
163 if ($cartHash) {
164 return add_query_arg([
165 'fct_cart_hash' => $cartHash
166 ], $checkoutPage);
167 }
168 return $checkoutPage;
169 }
170
171 public function paymentFailedNote($content, $data)
172 {
173 $request = Arr::get($data, 'request');
174 $trx_hash = $request->getSafe('trx_hash', 'sanitize_text_field');
175 $transaction = OrderTransaction::query()->where('uuid', $trx_hash)->first();
176 if (!$transaction) {
177 return __('Transaction not found!', 'fluent-cart');
178 }
179
180 $order = (new Orders())->getById($transaction->order_id);
181
182 if (!$order || $transaction->status === Status::TRANSACTION_SUCCEEDED) {
183 return '';
184 }
185
186 $failedtitle = __('Payment Failed', 'fluent-cart');
187 $hasLog = ActivityResource::getQuery()->where('module_id', $order->id)
188 ->where('module_name', 'Order')
189 ->where('status', 'error')
190 ->where('title', $failedtitle)
191 ->count();
192
193 if (!$hasLog) {
194 $content = 'Payment Failed Reason: ' . $request->getSafe('reason', 'sanitize_text_field');
195 fluent_cart_error_log($failedtitle, $content, [
196 'module_id' => $order->id,
197 'module_name' => 'Order'
198 ]);
199 }
200
201 ob_start();
202 (new ReceiptRenderer())->renderConfirmationError([
203 'order' => $order,
204 'failed_reason' => $request->getSafe('reason', 'sanitize_text_field'),
205 'custom_payment_url' => PaymentHelper::getCustomPaymentLink($order->uuid)
206 ]);
207 return ob_get_clean();
208 }
209
210 protected function getListenerUrl($args = null)
211 {
212 return (new PaymentHelper($this->getMeta('route')))->listenerUrl($args);
213 }
214
215 public function getOrderByHash($orderHash)
216 {
217 return (new Orders())->getByHash($orderHash);
218 }
219
220 public function validateSubscriptions($items): bool
221 {
222 $hasSubscription = (new CartResource())->hasSubscriptionProduct($items);
223
224 if ($hasSubscription && !$this->has('subscriptions')) {
225 wp_send_json([
226 'status' => 'failed',
227 'message' => __('Subscription payment is not avalable for this gateway. Please choose another payment method!', 'fluent-cart')
228 ], 422);
229 }
230 return $hasSubscription;
231 }
232
233 public function validatePaymentMethod($data)
234 {
235 $isZeroPayment = Arr::get($data, 'isZeroPayment', false);
236 if (!$this->isEnabled() && !$isZeroPayment) {
237 return [
238 'isValid' => false,
239 'reason' => sprintf(
240 /* translators: %s is the payment method name */
241 __('Selected payment method %s is not active!', 'fluent-cart'),
242 $this->getMeta('route')
243 )
244 ];
245 }
246
247 $hasSubscriptions = (CartCheckoutHelper::make())->hasSubscription();
248 if ($hasSubscriptions === 'yes' && !$this->has('subscriptions')) {
249 return [
250 'isValid' => false,
251 'reason' => sprintf(
252 /* translators: %s is the payment method name */
253 __('Subscription is not active for Selected payment method %s!', 'fluent-cart'),
254 $this->getMeta('route')
255 )
256 ];
257 }
258
259 return [
260 'isValid' => true,
261 ];
262 }
263
264 public function updateOrderDataByOrder($order, $transactionData, $transaction)
265 {
266 if ($order == null) {
267 return;
268 }
269
270 $transaction->fill($transactionData);
271 $transaction->save();
272
273
274 $paymentStatus = Status::syncPaymentStatus(Arr::get($transactionData, 'status'));
275 $orderStatus = !in_array($paymentStatus, [Status::TRANSACTION_SUCCEEDED, Status::PAYMENT_PAID]) ? $paymentStatus : Status::ORDER_PROCESSING;
276
277 $statusHelper = (new StatusHelper())->setOrder($order);
278 $statusHelper->updateTransactionData($transactionData, $transaction);
279
280 if ($amount = Arr::get($transactionData, 'total')) {
281 $statusHelper->updateTotalPaid($amount);
282 }
283
284 $statusHelper->changeOrderStatus($orderStatus, $paymentStatus, $this->getMeta('title'), $this->getMeta('slug'));
285
286 //If product is digital and processing then trigger status to completed
287 if ($order->fulfillment_type == 'digital' && $orderStatus === Status::ORDER_PROCESSING && $order->total_amount <= $order->total_paid) {
288 $statusHelper->changeOrderStatus(Status::ORDER_COMPLETED, $paymentStatus, $this->getMeta('title'), $this->getMeta('slug'));
289 }
290
291 do_action('fluent_cart/payments/after_payment_' . $paymentStatus, [
292 'order' => $order
293 ]);
294 }
295
296 public function getCheckoutItems(): array
297 {
298 return (CartCheckoutHelper::make())->getItems();
299 }
300
301 public function getSettings(): BaseGatewaySettings
302 {
303 return $this->settings;
304 }
305
306 public function renderStoreModeNotice(): string
307 {
308 if ((new StoreSettings())->get('order_mode') == 'test') {
309 return '<div class="mt-5"><span class="text-warning-500">' . __('Your Store is in test mode, change Store\'s \'Order Mode\' to \'Live\' and update related settings to enable live payment!', 'fluent-cart') . '</span></div>';
310 }
311 return '<div class="mt-5"><span class="text-success-500">' . __('Your Store is in Live mode', 'fluent-cart') . '</span></div>';
312 }
313
314 public function beforeRenderPaymentMethod($hasSubscription): void
315 {
316 $this->enqueue($hasSubscription);
317 }
318
319 public function getEnqueueVersion()
320 {
321 return FLUENTCART_VERSION;
322 }
323
324 public function getEnqueueScriptSrc($hasSubscription): array
325 {
326 return [];
327 }
328
329 public function getEnqueueStyleSrc(): array
330 {
331 return [];
332 }
333
334 public function getTransactionUrl($url, $data)
335 {
336 return $url;
337 }
338
339 public function getSubscriptionUrl($url, $data)
340 {
341 return $url;
342 }
343
344 public function processRefund($transaction, $amount, $args)
345 {
346 return new \WP_Error('not_implemented', __('Refund process is not implemented for this payment gateway.', 'fluent-cart'));
347 }
348
349 public function enqueue($hasSubscription): void
350 {
351 $styles = $this->getEnqueueStyleSrc();
352 $scripts = $this->getEnqueueScriptSrc($hasSubscription);
353
354 foreach ($styles as $style) {
355 wp_enqueue_style(
356 Arr::get($style, 'handle'),
357 Arr::get($style, 'src'),
358 Arr::get($style, 'deps', null),
359 Arr::get($style, 'version', $this->getEnqueueVersion())
360 );
361 }
362
363 $handleToEnqueue = '';
364 $scriptHandles = [];
365 foreach ($scripts as $script) {
366 $handle = Arr::get($script, 'handle');
367 if (empty($handleToEnqueue)) {
368 $handleToEnqueue = $handle;
369 }
370 $scriptHandles[] = $handle;
371 wp_enqueue_script(
372 $handle,
373 Arr::get($script, 'src'),
374 Arr::get($script, 'deps', null),
375 Arr::get($script, 'version', $this->getEnqueueVersion()),
376 Arr::get($script, 'in_footer', false),
377 );
378 }
379
380 // Add filter to prevent consent plugins from blocking payment gateway scripts
381 if (!empty($scriptHandles) && is_array($scriptHandles)) {
382 $gatewayInstance = $this;
383
384 add_filter('script_loader_tag', function($tag, $handle, $src) use ($scriptHandles, $gatewayInstance) {
385
386 if (!in_array($handle, $scriptHandles, true)) {
387 return $tag;
388 }
389
390 // This makes payment scripts load as "necessary" cookies
391 $attributes = [
392 'data-category="necessary"',
393 'data-consent-category="necessary"',
394 'data-cookieconsent="ignore"',
395 'data-no-optimize="1"',
396 'data-cfasync="false"',
397 ];
398
399 $serviceName = '';
400
401 if (is_object($gatewayInstance)) {
402 if (method_exists($gatewayInstance, 'getConsentServiceName')) {
403 $serviceName = (string) $gatewayInstance->getConsentServiceName();
404 }
405
406 if (empty($serviceName) && method_exists($gatewayInstance, 'getMeta')) {
407 $serviceName = (string) $gatewayInstance->getMeta('title');
408 }
409 }
410
411 if (!empty($serviceName)) {
412 $serviceName = esc_attr($serviceName);
413 $attributes[] = 'data-usercentrics="' . $serviceName . '"';
414 $attributes[] = 'data-service="' . $serviceName . '"';
415 }
416
417 $tag = preg_replace(
418 '/<script(\s+)/',
419 '<script$1' . implode(' ', $attributes) . ' ',
420 $tag,
421 1
422 );
423
424 return $tag;
425 }, 10, 3);
426 }
427
428 if (!empty($handleToEnqueue)) {
429 foreach ($this->getLocalizeData() as $key => $val) {
430 wp_localize_script($handleToEnqueue, $key, $val);
431 }
432 }
433
434
435 }
436
437 public function prepare($mode, $hasSubscription)
438 {
439 $this->beforeRenderPaymentMethod($hasSubscription);
440 $this->render($mode);
441 $route = $this->getMeta('route');
442 do_action_deprecated('fluent-cart/after_render_payment_method_' . $route, [], '1.3.16', 'fluent_cart/after_render_payment_method_' . $route, 'Use fluent_cart/after_render_payment_method_' . $route . ' instead of fluent-cart/after_render_payment_method_' . $route . '. It will be removed in v1.4.3.');
443 do_action('fluent_cart/after_render_payment_method_' . $route);
444 }
445
446 public function render($mode = 'logo')
447 {
448 $content = '';
449 if ($mode === 'logo') {
450 $content .= '<img src="' . esc_url($this->getMeta('logo') ?? '') . '"alt="' . esc_attr($this->getMeta('title')) . '"/>';
451 } elseif ($mode === 'radio') {
452 $content .= '<span class="checkmark">' . '</span>';
453 } else {
454 $content .= '<span>' . $this->getMeta('title') . '</span>';
455 }
456 echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
457 }
458
459 public function getLocalizeData(): array
460 {
461 // $example = [
462 // 'var_name' => [
463 // 'key' => 'value'
464 // ],
465 //
466 // 'var_name_two' => [
467 // 'key' => 'value'
468 // ],
469 // ];
470 return [];
471 }
472
473 /**
474 * Get the consent service name for this payment gateway
475 * Used by consent management plugins (Usercentrics, CookieYes, etc.)
476 * Override in child classes to specify the service name
477 *
478 * @return string|null Service name or null to use gateway slug
479 */
480 public function getConsentServiceName(): ?string
481 {
482 // By default, return null and let the system use gateway meta title
483 return null;
484 }
485
486 }
487