bindConfig($config); // phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace if (!empty($services = $config['services'] ?? null)) { $this->registerServices($services); } } /** * @param float $totalAmount * @param int $numberOfInstalments * * @return array */ public static function calculateInstalmentPlan(float $totalAmount, int $numberOfInstalments = 3): array { $totalAmount = $totalAmount * 100; $modAmount = $totalAmount % $numberOfInstalments; $downPayment = round(floatval((($totalAmount - $modAmount) / $numberOfInstalments / 100) + ($modAmount / 100)), 2); $instalment = ($totalAmount - $modAmount) / $numberOfInstalments / 100; return [ static::DOWN_PAYMENT => $downPayment, static::INSTALMENT => $instalment, ]; } /** * Register service providers set in config * * @param $services */ protected function registerServices($services) { foreach ($services as $serviceClassname => $serviceConfig) { $this->singleton( $serviceClassname, function ($container) use ($serviceClassname, $serviceConfig) { $serviceInstance = new $serviceClassname(); if (method_exists($serviceInstance, 'bindConfig')) { $serviceInstance->bindConfig($serviceConfig); } if (in_array(ServiceTrait::class, class_uses($serviceInstance))) { $serviceInstance->setContainer($container); $serviceInstance->init(); } return $serviceInstance; } ); } } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * @param string $alias * * @return mixed * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getService($alias) { return $this->make($alias); } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get the `view` service * * @return ViewService * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public static function getServiceView() { return static::getInstance()->getService(ViewService::class); } /** * @param $config * * @throws Exception */ public static function initInstanceWithConfig($config) { if (is_null(static::$instance)) { static::setInstance(new static($config)); } // phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace if (!static::getInstance() instanceof static) { throw new Exception('No plugin initialized.'); } } /** * Initialize all needed things for this plugin: hooks, assignments... */ public function initPlugin(): void { add_action('init', [$this, 'checkWooCommerceExistence']); if (!class_exists('WooCommerce')) { return; } // Load text domain add_action('init', [$this, 'tamaraLoadTextDomain']); // Register new Tamara custom statuses add_action('init', [$this, 'registerTamaraCustomOrderStatuses']); // For Admin add_action('admin_enqueue_scripts', [$this, 'enqueueAdminSettingScripts']); // Handle refund when a refund is created add_action('woocommerce_create_refund', [$this, 'tamaraRefundPayment'], 10, 2); // Add Tamara custom statuses to wc order status list add_filter('wc_order_statuses', [$this, 'addTamaraCustomOrderStatuses']); // Add note on Refund add_action('woocommerce_order_item_add_action_buttons', [$this, 'addRefundNote']); add_filter('woocommerce_rest_prepare_shop_order_object', [$this, 'updateTamaraCheckoutDataToOrder'], 10, 3); add_action('init', [$this, 'addCustomRewriteRules']); add_action('init', [$this, 'addTamaraAuthoriseFailedMessage'], 1000); add_action('parse_request', [$this, 'handleTamaraApi'], 1000); add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']); // add_filter('woocommerce_checkout_fields', [$this, 'adjustBillingPhoneDescription']); add_filter('woocommerce_payment_gateways', [$this, 'registerTamaraPaymentGateway']); add_filter('woocommerce_available_payment_gateways', [$this, 'adjustTamaraPaymentTypesOnCheckout'], 9998, 1); add_action('woocommerce_update_options_checkout_'.static::TAMARA_GATEWAY_ID, [$this, 'onSaveSettings'], 10, 1); add_action($this->getTamaraPopupWidgetPosition(), [$this, 'showTamaraProductPopupWidget']); add_action($this->getTamaraCartPopupWidgetPosition(), [$this, 'showTamaraCartProductPopupWidget']); add_action('wp_ajax_tamara_perform_cron', [$this, 'performCron']); add_action('wp_ajax_tamara-authorise', [$this, 'tamaraAuthoriseHandler']); add_action('wp_ajax_nopriv_tamara-authorise', [$this, 'tamaraAuthoriseHandler']); add_action('wp_head', [$this, 'tamaraCheckoutParams']); add_action('woocommerce_checkout_update_order_review', [$this, 'getUpdatedPhoneNumberOnCheckout']); if ($this->isCronjobEnabled() && rand(0,20) === 1) { add_action('admin_footer', [$this, 'addCronJobTriggerScript']); } add_shortcode('tamara_show_popup', [$this, 'tamaraProductPopupWidget']); add_shortcode('tamara_show_cart_popup', [$this, 'tamaraCartPopupWidget']); add_shortcode('tamara_authorise_order', [$this, 'doAuthoriseOrderAction']); // For Rest Api add_filter('rest_pre_dispatch', [$this, 'populateRestApiRequest'], 1, 3); // Update Settings Url in admin for Pay By Instalments add_action('admin_head', [$this, 'updatePayByInstalmentSettingUrl']); add_filter('woocommerce_billing_fields', [$this, 'forceRequireBillingPhone'], 1001, 2); add_action('wp_ajax_tamara-get-instalment-plan', [$this, 'getInstalmentPlanAccordingToProductVariation']); add_action('wp_ajax_nopriv_tamara-get-instalment-plan', [$this, 'getInstalmentPlanAccordingToProductVariation']); add_action('wp_ajax_update-tamara-checkout-params', [$this, 'updateTamaraCheckoutParams']); add_action('wp_ajax_nopriv_update-tamara-checkout-params', [$this, 'updateTamaraCheckoutParams']); add_action('get_header', [$this, 'overrideWcClearCart'], 8); add_action('wp_loaded', [$this, 'cancelOrder'], 21); // Add Tamara Note on Order Received page add_filter('woocommerce_thankyou_order_received_text', [$this, 'tamaraOrderReceivedText'], 10, 2); } /** * Populate Rest Api Request * * @param mixed $result * @param \WP_REST_Server $restApiServer * @param \WP_REST_Request $restApiRequest * * @return mixed */ public function populateRestApiRequest($result, $restApiServer, $restApiRequest) { $this->setRestApiRequest($restApiRequest); return $result; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Add Tamara Note after successful payment * * @param string $str * @param \Automattic\WooCommerce\Admin\Overrides\Order $order * * @return string * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function tamaraOrderReceivedText($str, $order) { if (empty($order)) { return $str; } $payment_method = $order->get_payment_method(); if (!empty($payment_method) && $this->isTamaraGateway($payment_method)) { return $str.$this->getServiceView()->render('views/woocommerce/checkout/tamara-order-received-button', [ 'textDomain' => $this->textDomain, ]); } return $str; } /** * Handle Tamara log message * * @param string $message * */ public function logMessage($message) { if ($this->isCustomLogMessageEnabled()) { if (is_array($message)) { $message = json_encode($message); } $fileHandle = fopen($this->logMessageFilePath(), "a"); fwrite($fileHandle, "[".gmdate('Y-m-d h:i:s')."] ".$message."\n"); fclose($fileHandle); } } /** * Update order status and add order note wrapper * * @param WC_Order $wcOrder * @param string $orderNote * @param string $newOrderStatus * @param string $updateOrderStatusNote * */ public function updateOrderStatusAndAddOrderNote($wcOrder, $orderNote, $newOrderStatus, $updateOrderStatusNote) { if ($wcOrder) { $this->logMessage(sprintf("Tamara - Prepare to Update Order Status - Order ID: %s, Order Note: %s, new order status: %s, order status note: %s", $wcOrder->get_id(), $orderNote, $newOrderStatus, $updateOrderStatusNote)); try { $wcOrder->add_order_note($orderNote); $wcOrder->update_status($newOrderStatus, $updateOrderStatusNote, true); } catch (Exception $exception) { $this->logMessage(sprintf("Tamara - Failed to Update Order Status - Order ID: %s, Order Note: %s, new order status: %s, order status note: %s. Error Message: %s", $wcOrder->get_id(), $orderNote, $newOrderStatus, $updateOrderStatusNote, $exception->getMessage())); } } } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get WC Tamara Gateway Pay By Later class * * @return WCTamaraGateway * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getWCTamaraGatewayService() { return $this->getService(WCTamaraGateway::class); } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get WC Tamara Gateway Pay Now class * * @return WCTamaraGatewayPayNow * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getWCTamaraGatewayPayNowService() { return $this->getService(WCTamaraGatewayPayNow::class); } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get WC Tamara Gateway Pay By Instalments class * * @return WCTamaraGateway * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getWCTamaraGatewayPayByInstalmentsService() { return $this->getService(WCTamaraGatewayPayByInstalments::class); } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get WC Tamara Gateway Pay In X class * * @param $instalment * * @return WCTamaraGateway * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getWCTamaraGatewayPayInXService($instalment) { $instalmentService = 'Tamara\Wp\Plugin\Services\WCTamaraGatewayPayIn'.$instalment; return $this->getService($instalmentService); } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get WC Tamara Gateway Single Checkout class * * @return WCTamaraGateway * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getWCTamaraGatewayCheckoutService() { return $this->getService(WCTamaraGatewayCheckout::class); } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get WC Tamara Gateway Pay Next Month class * * @return WCTamaraGateway * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getWCTamaraGatewayPayNextMonthService() { return $this->getService(WCTamaraGatewayPayNextMonth::class); } /** * Get Tamara Popup Widget postion */ public function getTamaraPopupWidgetPosition() { return $this->getWCTamaraGatewayOptions()['popup_widget_position'] ?? 'woocommerce_before_add_to_cart_form'; } /** * Get Tamara Cart Popup Widget postion */ public function getTamaraCartPopupWidgetPosition() { return $this->getWCTamaraGatewayOptions()['cart_popup_widget_position'] ?? 'woocommerce_proceed_to_checkout'; } /** * Check if Payment type Pay By Later is enabled in admin settings */ public function isPayByLaterEnabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['pay_by_later_enabled'] ?? 'no'); } /** * Check if Payment type Pay Now is enabled in admin settings */ public function isPayNowEnabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['pay_now_enabled'] ?? 'no'); } /** * Check if Payment type Pay By Instalments is enabled in admin settings */ public function isPayByInstalmentsEnabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['pay_by_instalments_enabled'] ?? 'no'); } /** * Check if a specific Pay In X payment type is enabled in admin settings * * @param $instalment * @param $countryCode * * @return bool */ public function isPayInXEnabled($instalment, $countryCode) { return 'yes' === ($this->getWCTamaraGatewayOptions()['pay_in_'.$instalment.'_'.$countryCode] ?? 'no'); } /** * Check if Tamara Gateway is enabled in admin settings */ public function isTamaraGatewayEnabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['enabled'] ?? 'no'); } /** * Check if Tamara custom log message is enabled in admin settings */ public function isCustomLogMessageEnabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['custom_log_message_enabled'] ?? 'no'); } /** * Check if Tamara force billing phone option is enabled in admin settings */ public function isForceBillingPhoneEnabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['force_billing_phone'] ?? 'no'); } /** * Check if Cronjob is enabled in admin settings */ public function isCronjobEnabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['crobjob_enabled'] ?? 'no'); } /** * Check if Tamara Pay Later popup widget is enabled in admin settings */ public function isPayLaterPDPEnabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['pay_later_popup_widget_enabled'] ?? 'no'); } /** * Check if Always Show Popup Widget is enabled in admin settings */ public function isAlwaysShowWidgetPopupEnabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['always_show_popup_widget_enabled'] ?? 'no'); } /** * Check if Showing Popup Widget is disabled in admin settings */ public function isWidgetPopupDisabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['popup_widget_disabled'] ?? 'no'); } /** * Check if Showing Popup Widget in Cart page is disabled in admin settings */ public function isCartWidgetPopupDisabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['cart_popup_widget_disabled'] ?? 'no'); } /** * Check if Credit Precheck is enabled in admin settings */ public function isCreditPrecheckEnabled() { return 'yes' === ($this->getWCTamaraGatewayOptions()['credit_precheck_enabled'] ?? 'no'); } /** * Get WC Tamara Gateway options */ public function getWCTamaraGatewayOptions() { return get_option($this->getWCTamaraGatewayOptionKey(), null); } /** * Get WC Tamara Gateway options */ public function getWCTamaraGatewayOptionKey() { return 'woocommerce_'.static::TAMARA_GATEWAY_ID.'_settings'; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get on save settings method from WC Tamara Gateway * * @param $settings * * @return void * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function onSaveSettings($settings) { return $this->getWCTamaraGatewayService()->onSaveSettings($settings); } /** * Tamara Log File Path */ public function logMessageFilePath() { return (defined('UPLOADS') ? UPLOADS : (WP_CONTENT_DIR.'/uploads/').static::MESSAGE_LOG_FILE_NAME); } /** * Tamara Log File Url */ public function logMessageFileUrl() { return wp_upload_dir()['baseurl'].'/'.static::MESSAGE_LOG_FILE_NAME; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Force pending capture payments within 180 days to be captured * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function forceCaptureTamaraOrder() { $tamaraCapturePaymentStatus = $this->getWCTamaraGatewayService()->tamaraStatus['payment_capture'] ?? 'wc-completed'; $customerOrders = [ 'fields' => 'ids', 'post_type' => 'shop_order', 'post_status' => $tamaraCapturePaymentStatus, 'date_query' => [ 'before' => date('Y-m-d', strtotime('-14 days')), 'after' => date('Y-m-d', strtotime('-180 days')), 'inclusive' => true, ], 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_tamara_order_id', 'compare' => 'EXISTS', ], [ 'key' => '_tamara_capture_id', 'compare' => 'NOT EXISTS', ], [ 'key' => '_tamara_force_capture_checked', 'compare' => 'NOT EXISTS', ], ], ]; $customerOrdersQuery = new \WP_Query($customerOrders); $wcOrderIds = $customerOrdersQuery->posts; foreach ($wcOrderIds as $wcOrderId) { update_post_meta($wcOrderId, '_tamara_force_capture_checked', 1); if (static::TAMARA_FULLY_CAPTURED_STATUS === TamaraCheckout::getInstance()->getTamaraOrderStatus($wcOrderId)) { $wcOrder = wc_get_order($wcOrderId); $wcOrder->add_order_note(__('Tamara - The payment has been captured successfully.', $this->textDomain)); $tamaraCaptureId = $this->getWCTamaraGatewayService()->getTamaraCaptureId($wcOrderId); update_post_meta($wcOrderId, '_tamara_capture_id', $tamaraCaptureId); return true; } else { $this->getWCTamaraGatewayService()->captureWcOrder($wcOrderId); } } } /** * Force pending authorise payments within 180 days to be authorised * */ public function forceAuthoriseTamaraOrder() { $toAuthoriseStatus = 'wc-pending'; $customerOrders = [ 'fields' => 'ids', 'post_type' => 'shop_order', 'post_status' => $toAuthoriseStatus, 'date_query' => [ 'before' => date('Y-m-d', strtotime('-3 hours')), 'after' => date('Y-m-d', strtotime('-180 days')), 'inclusive' => true, ], 'meta_query' => [ 'relation' => 'AND', [ 'key' => 'tamara_checkout_session_id', 'compare' => 'EXISTS', ], [ 'key' => '_tamara_order_id', 'compare' => 'NOT EXISTS', ], [ 'key' => '_tamara_force_authorise_checked', 'compare' => 'NOT EXISTS', ], ], ]; $customerOrdersQuery = new \WP_Query($customerOrders); $wcOrderIds = $customerOrdersQuery->posts; foreach ($wcOrderIds as $wcOrderId) { update_post_meta($wcOrderId, '_tamara_force_authorise_checked', 1); if (!$this->isOrderAuthorised($wcOrderId)) { $this->authoriseOrder($wcOrderId); } } } /** * Add Tamara Refund Note * * @param WC_Order $order */ public function addRefundNote($order) { if ($this->isTamaraGateway($order->get_payment_method())) { echo '
'.__('This order is paid via Tamara Pay Later.', $this->textDomain); echo '
'.''.__('You need to refund the full shipping amount.', $this->textDomain).''; } } /** * Register Tamara new statuses */ public function registerTamaraCustomOrderStatuses() { register_post_status('wc-tamara-p-canceled', [ 'label' => _x('Tamara Payment Cancelled', 'Order status', $this->textDomain), 'public' => true, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop('Tamara Payment Cancelled (%s)', 'Tamara Payment Cancelled (%s)', $this->textDomain), ]); register_post_status('wc-tamara-p-failed', [ 'label' => _x('Tamara Payment Failed', 'Order status', $this->textDomain), 'public' => true, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop('Tamara Payment Failed (%s)', 'Tamara Payment Failed (%s)', $this->textDomain), ]); register_post_status('wc-tamara-c-failed', [ 'label' => _x('Tamara Capture Failed', 'Order status', $this->textDomain), 'public' => true, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop('Tamara Capture Failed (%s)', 'Tamara Capture Failed (%s)', $this->textDomain), ]); register_post_status('wc-tamara-a-done', [ 'label' => _x('Tamara Authorise Success', 'Order status', $this->textDomain), 'public' => true, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop('Tamara Authorise Success (%s)', 'Tamara Authorise Success (%s)', $this->textDomain), ]); register_post_status('wc-tamara-a-failed', [ 'label' => _x('Tamara Authorise Failed', 'Order status', $this->textDomain), 'public' => true, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop('Tamara Authorise Failed (%s)', 'Tamara Authorise Failed (%s)', $this->textDomain), ]); register_post_status('wc-tamara-o-canceled', [ 'label' => _x('Tamara Order Cancelled', 'Order status', $this->textDomain), 'public' => true, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop('Tamara Order Cancelled (%s)', 'Tamara Order Cancelled (%s)', $this->textDomain), ]); register_post_status('wc-tamara-p-capture', [ 'label' => _x('Tamara Payment Capture', 'Order status', $this->textDomain), 'public' => true, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop('Tamara Payment Capture (%s)', 'Tamara Payment Capture (%s)', $this->textDomain), ]); } /** * Add Tamara Statuses to the list of WC Order statuses * * @param array $order_statuses * * @return array $order_statuses */ public function addTamaraCustomOrderStatuses($order_statuses) { $order_statuses['wc-tamara-p-canceled'] = _x('Tamara Payment Cancelled', 'Order status', $this->textDomain); $order_statuses['wc-tamara-p-failed'] = _x('Tamara Payment Failed', 'Order status', $this->textDomain); $order_statuses['wc-tamara-c-failed'] = _x('Tamara Capture Failed', 'Order status', $this->textDomain); $order_statuses['wc-tamara-a-done'] = _x('Tamara Authorise Done', 'Order status', $this->textDomain); $order_statuses['wc-tamara-a-failed'] = _x('Tamara Authorise Failed', 'Order status', $this->textDomain); $order_statuses['wc-tamara-o-canceled'] = _x('Tamara Order Cancelled', 'Order status', $this->textDomain); $order_statuses['wc-tamara-p-capture'] = _x('Tamara Payment Capture', 'Order status', $this->textDomain); return $order_statuses; } /** * Localize the plugin */ public function tamaraLoadTextDomain() { $locale = determine_locale(); $mofile = $locale.'.mo'; load_textdomain($this->textDomain, $this->basePath.'/languages/'.$this->textDomain.'-'.$mofile); } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Handle process for Tamara endpoint slug returned * * @param WP $wp * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function handleTamaraApi($wp) { $pagename = $wp->query_vars['pagename'] ?? null; $tamaraPageSlugs = [ WCTamaraGateway::IPN_SLUG, WCTamaraGateway::WEBHOOK_SLUG, WCTamaraGateway::PAYMENT_SUCCESS_SLUG, WCTamaraGateway::PAYMENT_CANCEL_SLUG, WCTamaraGateway::PAYMENT_FAIL_SLUG, ]; if (in_array($pagename, $tamaraPageSlugs)) { $this->logMessage(sprintf('Pagename: %s', $pagename)); } if (WCTamaraGateway::IPN_SLUG === $pagename) { /** @var TamaraNotificationService $tamara_notification_service */ $tamara_notification_service = $this->getService(TamaraNotificationService::class); $tamara_notification_service->handleIpnRequest(); exit; } // Handle webhook elseif (WCTamaraGateway::WEBHOOK_SLUG === $pagename) { /** @var TamaraNotificationService $tamara_notification_service */ $tamara_notification_service = $this->getService(TamaraNotificationService::class); $tamara_notification_service->handleWebhook(); exit; } elseif (WCTamaraGateway::PAYMENT_CANCEL_SLUG === $pagename) { $this->handleTamaraCancelUrl(); do_action('after_tamara_cancel'); exit; } elseif (WCTamaraGateway::PAYMENT_FAIL_SLUG === $pagename) { $this->handleTamaraFailureUrl(); do_action('after_tamara_failure'); exit; } } /** * Detect if an order is authorised or not * * @param $wcOrderId * * @return bool */ public function isOrderAuthorised($wcOrderId) { return !!get_post_meta($wcOrderId, 'tamara_authorized', true); } /** * Prevent an order is cancelled from FE if its payment has been authorised from Tamara * * @param WC_Order $wcOrder * @param int $wcOrderId * */ protected function preventOrderCancelAction($wcOrder, $wcOrderId) { $orderNote = 'This order can not be cancelled because the payment was authorised from Tamara. Order ID: '.$wcOrderId; $wcOrder->add_order_note($orderNote); $this->logMessage($orderNote); wp_redirect(wc_get_cart_url()); } /** * Add needed params for Tamara checkout success url */ public function tamaraCheckoutParams() { $storeCurrency = get_woocommerce_currency(); $publicKey = $this->getWCTamaraGatewayService()->getPublicKey() ?? ''; $siteLocale = substr(get_locale(), 0, 2) ?? "en"; $countryCode = $this->getWCTamaraGatewayService()->getCurrentCountryCode(); ?> getWCTamaraGatewayService()->isLiveMode()) { ?> getTamaraOrderByWcOrderId($wcOrderId); if ($tamaraOrder && 'approved' === $tamaraOrder->getStatus()) { return true; } return false; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get Tamara order id by WC order Id * * @param int $wcOrderId * * @return string * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function getTamaraOrderId($wcOrderId) { $tamaraOrder = $this->getTamaraOrderByWcOrderId($wcOrderId); if ($tamaraOrder) { return $tamaraOrder->getOrderId(); } return null; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get Tamara order by WC order Id * * @param int $wcOrderId * * @return null|Dependencies\Tamara\Response\Order\GetOrderByReferenceIdResponse * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getTamaraOrderByWcOrderId($wcOrderId) { $tamaraClient = $this->getWCTamaraGatewayService()->tamaraClient; try { $tamaraOrderResponse = $tamaraClient->getOrderByReferenceId(new GetOrderByReferenceIdRequest($wcOrderId)); $this->logMessage(sprintf("Tamara Get Order by Reference ID Response: %s", print_r($tamaraOrderResponse, true))); if ($tamaraOrderResponse->isSuccess()) { return $tamaraOrderResponse; } } catch (Exception $tamaraOrderResponseException) { $this->logMessage( sprintf( "Tamara Get Order by Reference ID Failed Response.\nError message: ' %s'.\nTrace: %s", $tamaraOrderResponseException->getMessage(), $tamaraOrderResponseException->getTraceAsString() ) ); } return null; } /** * If the order is not authorised from Tamara, do it on the Tamara Success Url returned */ public function tamaraAuthoriseHandler() { $wcOrderId = filter_input(INPUT_POST, 'wcOrderId', FILTER_SANITIZE_NUMBER_INT); $authoriseSuccessResponse = [ 'message' => 'authorise_success', ]; if ($this->isOrderAuthorised($wcOrderId) || $this->authoriseOrder($wcOrderId)) { wp_send_json($authoriseSuccessResponse); } wp_send_json( [ 'message' => 'authorise_failed', ] ); } /** * Do authorise order with order id from payload returning from Tamara */ public function doAuthoriseOrderAction() { $wcOrderId = filter_input(INPUT_GET, 'wcOrderId', FILTER_SANITIZE_NUMBER_INT); $wcOrderId || $wcOrderId = filter_input(INPUT_POST, 'wcOrderId', FILTER_SANITIZE_NUMBER_INT); $this->authoriseOrder($wcOrderId); } /** * @param $wcOrderId * * @return bool true if an authorise action is made successfully, false if failed * or already authorised */ public function authoriseOrder($wcOrderId) { $wcOrder = wc_get_order($wcOrderId); try { if (!$this->isOrderAuthorised($wcOrderId) && $wcOrder && ($this->isOrderTamaraApproved($wcOrderId))) { $tamaraOrderId = $this->getTamaraOrderId($wcOrderId); /** @var TamaraNotificationService $tamaraNotificationService */ $tamaraNotificationService = $this->getService(TamaraNotificationService::class); $tamaraNotificationService->authoriseOrder($wcOrderId, $tamaraOrderId); if ($this->isOrderAuthorised($wcOrderId)) { return true; } } } catch (Exception $exception) { } return false; } /** * Add Tamara Authorise Failed Message on cart page */ public function addTamaraAuthoriseFailedMessage() { $tamaraAuthoriseParam = filter_input(INPUT_GET, 'tamara_authorise'); if ('failed' === $tamaraAuthoriseParam && !static::isRestRequest()) { if (function_exists('wc_add_notice')) { wc_add_notice(__('We are unable to authorise your payment from Tamara. Please contact us if you need assistance.', $this->textDomain), 'error'); } } } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Do needed things on Tamara Cancel Url returned * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function handleTamaraCancelUrl() { $orderId = filter_input(INPUT_GET, 'wcOrderId', FILTER_SANITIZE_NUMBER_INT); $wcOrder = wc_get_order($orderId); if ($this->isOrderAuthorised($orderId)) { $this->preventOrderCancelAction($wcOrder, $orderId); } elseif (!empty($orderId)) { $newOrderStatus = $this->getWCTamaraGatewayService()->tamaraStatus['payment_cancelled']; $orderNote = 'The payment for this order has been cancelled from Tamara.'; $this->updateOrderStatusAndAddOrderNote($wcOrder, $orderNote, $newOrderStatus, ''); $cancelUrlFromTamara = add_query_arg( [ 'tamara_custom_status' => 'tamara-p-canceled', 'redirect_from' => 'tamara', 'cancel_order' => 'true', 'order' => $wcOrder->get_order_key(), 'order_id' => $orderId, '_wpnonce' => wp_create_nonce('woocommerce-cancel_order'), ], $wcOrder->get_cancel_order_url_raw() ); wp_redirect($cancelUrlFromTamara); } } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Do needed things on Tamara Failure Url returned * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function handleTamaraFailureUrl() { $orderId = filter_input(INPUT_GET, 'wcOrderId', FILTER_SANITIZE_NUMBER_INT); $wcOrder = wc_get_order($orderId); if ($this->isOrderAuthorised($orderId)) { $this->preventOrderCancelAction($wcOrder, $orderId); } elseif (!empty($orderId)) { $newOrderStatus = $this->getWCTamaraGatewayService()->tamaraStatus['payment_failed']; $orderNote = 'The payment for this order has been declined from Tamara.'; $this->updateOrderStatusAndAddOrderNote($wcOrder, $orderNote, $newOrderStatus, ''); $failureUrlFromTamara = add_query_arg( [ 'tamara_custom_status' => 'tamara-p-failed', 'redirect_from' => 'tamara', 'cancel_order' => 'true', 'order' => $wcOrder->get_order_key(), 'order_id' => $orderId, '_wpnonce' => wp_create_nonce('woocommerce-cancel_order'), ], $wcOrder->get_cancel_order_url_raw() ); wp_redirect($failureUrlFromTamara); } } /** * Do some needed things when activate plugin */ public function activatePlugin() { if (!class_exists('WooCommerce')) { die(sprintf(__('Plugin `%s` needs Woocommerce to be activated', $this->textDomain), 'Tamara Checkout')); } } /** * @noinspection PhpUnusedDeclarationInspection */ public function deactivatePlugin() { // The problem with calling flush_rewrite_rules() is that the rules instantly get regenerated, while your plugin's hooks are still active. delete_option('rewrite_rules'); } /** * Add rewrite rule for Tamara IPN and Webhook response page */ public function addCustomRewriteRules() { add_rewrite_rule(WCTamaraGateway::IPN_SLUG.'/?$', 'index.php?pagename='.WCTamaraGateway::IPN_SLUG, 'top'); add_rewrite_rule(WCTamaraGateway::WEBHOOK_SLUG.'/?$', 'index.php?pagename='.WCTamaraGateway::WEBHOOK_SLUG, 'top'); add_rewrite_rule(WCTamaraGateway::PAYMENT_SUCCESS_SLUG.'/?$', 'index.php?pagename='.WCTamaraGateway::PAYMENT_SUCCESS_SLUG, 'top'); add_rewrite_rule(WCTamaraGateway::PAYMENT_CANCEL_SLUG.'/?$', 'index.php?pagename='.WCTamaraGateway::PAYMENT_CANCEL_SLUG, 'top'); add_rewrite_rule(WCTamaraGateway::PAYMENT_FAIL_SLUG.'/?$', 'index.php?pagename='.WCTamaraGateway::PAYMENT_FAIL_SLUG, 'top'); } /** * Run this method under the "init" action */ public function checkWooCommerceExistence() { if (class_exists('WooCommerce')) { // Add "Settings" link when the plugin is active add_filter('plugin_action_links_tamara-checkout/tamara-checkout.php', [$this, 'addSettingsLinks']); } else { require_once(ABSPATH.'wp-admin/includes/plugin.php'); // Throw a notice if WooCommerce is NOT active deactivate_plugins(plugin_basename($this->pluginFilename)); add_action('admin_notices', [$this, 'noticeNonWooCommerce']); } } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Add more links to plugin settings * * @param $pluginLinks * * @return array * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function addSettingsLinks($pluginLinks) { $pluginLinks[] = ''.esc_html__('Settings', $this->textDomain).''; return $pluginLinks; } /** * Throw a notice if WooCommerce is NOT active */ public function noticeNonWooCommerce() { $class = 'notice notice-warning'; $message = sprintf(__('Plugin `%s` deactivated because WooCommerce is not active. Please activate WooCommerce first.', $this->textDomain), 'Tamara Checkout'); printf('

%2$s

', $class, $message); } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Add Tamara Payment Gateway * * @param $gateways * * @return array * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function registerTamaraPaymentGateway($gateways) { $gateways[] = $this->getWCTamaraGatewayService(); return $gateways; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Adjust Tamara payment types on checkout page based on Tamara settings * * @param $availableGateways * * @return array * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function adjustTamaraPaymentTypesOnCheckout($availableGateways) { if ($this->isTamaraGatewayEnabled() && is_checkout()) { if ($this->getWCTamaraGatewayService()->isSingleCheckoutEnabled()) { $availableGateways = $this->possiblyAddTamaraSingleCheckout($availableGateways); } else { for ($i = 12; $i >= 2; $i--) { $tamaraPayLaterKey = static::TAMARA_GATEWAY_ID; $tamaraPayLaterOffset = array_search($tamaraPayLaterKey, array_keys(WC()->payment_gateways->payment_gateways())); $availableGateways = array_merge( array_slice($availableGateways, 0, $tamaraPayLaterOffset), array($this->getWCTamaraGatewayPayInXService($i)->id => $this->getWCTamaraGatewayPayInXService($i)), array_slice($availableGateways, $tamaraPayLaterOffset, null) ); } $payNextMonthService = [static::TAMARA_GATEWAY_PAY_NEXT_MONTH => $this->getWCTamaraGatewayPayNextMonthService()]; $availableGateways = $this->mergeTamaraPaymentMethodsAfterPayLaterOffset($payNextMonthService, $availableGateways); $payNowService = [static::TAMARA_GATEWAY_PAY_NOW => $this->getWCTamaraGatewayPayNowService()]; $availableGateways = $this->mergeTamaraPaymentMethodsAfterPayLaterOffset($payNowService, $availableGateways); } } return $availableGateways; } /** * Enqueue admin scripts for settings */ public function enqueueAdminSettingScripts() { // Only enqueue the setting scripts on the Tamara Checkout settings screen. if ($this->isTamaraAdminSettingsScreen()) { wp_enqueue_script('tamara-checkout-settings-js', $this->baseUrl.'/assets/dist/js/admin.js', ['jquery'], $this->version, true); wp_enqueue_style('tamara-admin-css', $this->baseUrl.'/assets/dist/css/admin.css', [], $this->version); } // Load the admin stylesheet on shop order screen elseif (isset($_GET['post_type']) && ('shop_order' === $_GET['post_type'])) { wp_enqueue_style('tamara-admin-css', $this->baseUrl.'/assets/dist/css/admin.css', [], $this->version); } } /** * Add some help text for billing phone when using Tamara payment * * @param $checkoutFields * * @return mixed */ public function adjustBillingPhoneDescription($checkoutFields) { if (isset($checkoutFields['billing'], $checkoutFields['billing']['billing_phone'])) { $checkoutFields['billing']['billing_phone']['description'] = __('If you use Tamara Payment, this should be your full Tamara registered phone number (e.g. +966504449999 for KSA, +97150888444 for UAE)', $this->textDomain); } return $checkoutFields; } /** * Enqueue FE stylesheet and scripts */ public function enqueueScripts() { if ($this->getWCTamaraGatewayService()->isLiveMode()) { if (is_checkout()) { wp_enqueue_script('tamara-information-widget', static::TAMARA_INFORMATION_WIDGET_URL, [], $this->version, true); wp_enqueue_script('tamara-installment-plan-widget', static::TAMARA_INSTALLMENT_PLAN_WIDGET_URL, [], $this->version, true); } wp_enqueue_script('tamara-product-widget', static::TAMARA_PRODUCT_WIDGET_URL, [], $this->version, true); } else { if (is_checkout()) { wp_enqueue_script('tamara-information-sandbox-widget', static::TAMARA_INFORMATION_WIDGET_SANDBOX_URL, [], $this->version, true); wp_enqueue_script('tamara-installment-plan-sandbox-widget', static::TAMARA_INSTALLMENT_PLAN_WIDGET_SANDBOX_URL, [], $this->version, true); } wp_enqueue_script('tamara-product-sandbox-widget', static::TAMARA_PRODUCT_WIDGET_SANDBOX_URL, [], $this->version, true); } wp_enqueue_style('tamara-checkout', $this->baseUrl.'/assets/dist/css/main.css', [], $this->version . '&' . time()); wp_enqueue_script('tamara-checkout', $this->baseUrl.'/assets/dist/js/main.js', ['jquery'], $this->version . '&' . time(), true); } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Add relevant links to plugins page * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getAdminSettingLink() { if (version_compare(WC()->version, '2.6', '>=')) { $sectionSlug = $this->getWCTamaraGatewayService()->id; } else { $sectionSlug = strtolower(WCTamaraGateway::class); } return admin_url('admin.php?page=wc-settings&tab=checkout§ion='.$sectionSlug); } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * @param \WC_Order_Refund $wcOrderRefund * @param $args * * @throws \Illuminate\Contracts\Container\BindingResolutionException * @throws Exception */ public function tamaraRefundPayment($wcOrderRefund, $args) { $wcOrder = wc_get_order($args['order_id']); $wcOrderId = $args['order_id']; $payment_method = $wcOrder->get_payment_method(); if ($this->isTamaraGateway($payment_method)) { $tamaraOrderId = $this->getWCTamaraGatewayService()->getTamaraOrderId($wcOrderId); $captureId = $this->getWCTamaraGatewayService()->getTamaraCaptureId($wcOrderId); $refundCollection = []; $wcOrderTotal = new Money(MoneyHelper::formatNumber(abs($wcOrderRefund->get_amount())), $wcOrder->get_currency()); $wcShippingTotal = new Money(MoneyHelper::formatNumber(abs($wcOrderRefund->get_shipping_total())), $wcOrder->get_currency()); $wcTaxTotal = new Money(MoneyHelper::formatNumber(abs($wcOrderRefund->get_total_tax())), $wcOrder->get_currency()); $wcDiscountTotal = new Money(MoneyHelper::formatNumber($wcOrderRefund->get_discount_total()), $wcOrder->get_currency()); $wcOrderItemsRefund = $this->getWCTamaraGatewayService()->populateTamaraRefundOrderItems($wcOrderRefund); try { $refundItem = new Refund($captureId, $wcOrderTotal, $wcShippingTotal, $wcTaxTotal, $wcDiscountTotal, $wcOrderItemsRefund); array_push($refundCollection, $refundItem); $refundResponse = $this->getWCTamaraGatewayService()->tamaraClient->refund(new RefundRequest($tamaraOrderId, $refundCollection)); $this->logMessage(sprintf("Tamara Refund Response Data: %s", print_r($refundResponse, true))); } catch (Exception $tamaraRefundException) { $this->logMessage(sprintf("Tamara Service timeout or disconnected.\nError message: '%s'.\nTrace: %s", $tamaraRefundException->getMessage(), $tamaraRefundException->getTraceAsString())); } if (isset($refundResponse) && $refundResponse->isSuccess()) { $wcOrder->add_order_note( /* translators: Refund ID */ sprintf(__('Order has been refunded successfully - Refund ID: #%1$s', $this->textDomain), $wcOrderRefund->get_id())); } else { $errorMessage = null; if (isset($tamaraRefundException) && $tamaraRefundException instanceof Exception) { $errorMessage = $tamaraRefundException->getMessage(); $this->logMessage($errorMessage); } elseif (isset($refundResponse)) { if ('refund.shipping_amount_invalid' === $refundResponse->getMessage()) { throw new Exception(__('You need to enter the full shipping amount to refund.', $this->textDomain)); } elseif ('items_is_empty' === $refundResponse->getErrors()[0]['error_code']) { throw new Exception(__('Refund item is empty. Please choose your item to refund.', $this->textDomain)); } elseif ('refund.capture_not_found' === $refundResponse->getMessage()) { $captureNotFoundMessage = __('Tamara Capture ID not found. Please capture the payment before making a refund.', $this->textDomain); $wcOrder->add_order_note($captureNotFoundMessage); throw new Exception($captureNotFoundMessage); } } throw new Exception(__('Error! Tamara is having a problem. Please contact Tamara and try again later', $this->textDomain)); } } } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Tamara show widget popup shortcode callback method * * @param $attributes * * @return bool|string|void|null * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function tamaraProductPopupWidget($attributes) { extract(shortcode_atts(array( 'price' => '', 'currency' => '', 'language' => '', ), $attributes)); $dataPrice = !empty($price) ? $price : $this->getDisplayedProductPrice(); $dataCurrency = !empty($currency) ? $currency : get_woocommerce_currency(); $dataLanguage = !empty($language) ? $language : substr(get_locale(), 0, 2); if ($this->isWidgetPopupDisabled() || (!empty($this->getDisplayedProductId()) && $this->isExcludedProduct($this->getDisplayedProductId())) || (!empty($this->getDisplayedProductCategoryIds()) && $this->isExcludedProductCategory($this->getDisplayedProductCategoryIds()))) { return false; } else { $itemPrice = is_array($dataPrice) ? $this->getAppropriateVariationProductPrice($dataPrice) : $dataPrice; return $this->getServiceView()->render('views/woocommerce/checkout/tamara-popup-widget', [ 'dataPrice' => $itemPrice ?? 0, 'dataCurrency' => $dataCurrency, 'dataLanguage' => $dataLanguage ?? 'en', 'inlineType' => static::TAMARA_INLINE_TYPE_PRODUCT_WIDGET_INT, ]); } } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Tamara show cart widget popup shortcode callback method * * @param $attributes * * @return bool|string|void|null * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function tamaraCartPopupWidget($attributes) { extract(shortcode_atts(array( 'price' => '', 'currency' => '', 'language' => '', ), $attributes)); $getPrice = is_cart() ? WC()->cart->get_total( null ) : $this->getDisplayedProductPrice(); $dataPrice = !empty($price) ? $price : $getPrice; $dataCurrency = !empty($currency) ? $currency : get_woocommerce_currency(); $dataLanguage = !empty($language) ? $language : substr(get_locale(), 0, 2); if ($this->isCartWidgetPopupDisabled() || (!empty($this->getDisplayedProductId()) && $this->isExcludedProduct($this->getDisplayedProductId())) || (!empty($this->getDisplayedProductCategoryIds()) && $this->isExcludedProductCategory($this->getDisplayedProductCategoryIds()))) { return false; } else { $itemPrice = is_array($dataPrice) ? $this->getAppropriateVariationProductPrice($dataPrice) : $dataPrice; return $this->getServiceView()->render('views/woocommerce/checkout/tamara-popup-widget', [ 'dataPrice' => $itemPrice ?? 0, 'dataCurrency' => $dataCurrency, 'dataLanguage' => $dataLanguage ?? 'en', 'inlineType' => static::TAMARA_INLINE_TYPE_PRODUCT_WIDGET_INT, ]); } } /** * Show Tamara popup widget */ public function showTamaraProductPopupWidget() { if ($this->isTamaraGatewayEnabled()) { echo do_shortcode('[tamara_show_popup]'); } } /** * Show Tamara popup widget on Cart page */ public function showTamaraCartProductPopupWidget() { if ($this->isTamaraGatewayEnabled() && !$this->isCartWidgetPopupDisabled()) { echo do_shortcode('[tamara_show_cart_popup]'); } } /** * Get displayed product price on FE */ public function getDisplayedProductPrice() { global $product; if ($product) { if ($product instanceof \WC_Product) { if ($product instanceof \WC_Product_Variable) { return $product->get_variation_prices(true)['price']; } else { return wc_get_price_to_display($product); } } } return null; } /** * Get displayed product id on FE */ public function getDisplayedProductId() { global $product; if ($product) { if ($product instanceof \WC_Product) { return $product->get_id(); } } return null; } /** * Get all category ids of displayed product on FE */ public function getDisplayedProductCategoryIds() { global $product; if ($product) { if ($product instanceof \WC_Product) { $productId = $product->get_id(); return wc_get_product_cat_ids($productId); } } return null; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * @param $productPrice * * @return bool * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function isProductPriceValid($productPrice) { // Force pull country payment types from remote api $countryPaymentTypes = $this->getWCTamaraGatewayService()->getCountryPaymentTypes(); $paymentTypes = $this->getWCTamaraGatewayService()->getPaymentTypes(); if (!$this->isAlwaysShowWidgetPopupEnabled() && !$this->getWCTamaraGatewayService()->isSingleCheckoutEnabled()) { if (is_array($productPrice)) { foreach ($productPrice as $item => $variationPrice) { if (($this->getWCTamaraGatewayService()->populateMinLimit() <= $variationPrice && $this->getWCTamaraGatewayService()->populateMaxLimit() >= $variationPrice) || ($this->populatePayInXsLimitAmountBasedOnProductPrice($variationPrice)['instalmentMinAmount'] <= $variationPrice && $this->populatePayInXsLimitAmountBasedOnProductPrice($variationPrice)['instalmentMaxAmount'] >= $variationPrice) || ($this->populatePayNextMonthMinLimit() <= $variationPrice && $this->populatePayNextMonthMaxLimit() >= $variationPrice) ) { return true; break; } } } else { return ($this->getWCTamaraGatewayService()->populateMinLimit() <= $productPrice && $this->getWCTamaraGatewayService()->populateMaxLimit() >= $productPrice) || ($this->populatePayInXsLimitAmountBasedOnProductPrice($productPrice)['instalmentMinAmount'] <= $productPrice && $this->populatePayInXsLimitAmountBasedOnProductPrice($productPrice)['instalmentMaxAmount'] >= $productPrice) || ($this->populatePayNextMonthMinLimit() <= $productPrice && $this->populatePayNextMonthMaxLimit() >= $productPrice); } } else { return true; } } /** * Modify which total amount should be used to display on checkout page * * @param $amount * * @return mixed */ public function getTotalToCalculate($amount) { if (is_checkout_pay_page()) { global $wp; if (isset($wp->query_vars['order-pay']) && absint($wp->query_vars['order-pay']) > 0) { $orderId = absint($wp->query_vars['order-pay']); $wcOrder = wc_get_order($orderId); if ($wcOrder) { return $wcOrder->get_total(); } } } return $amount; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Update Tamara checkout data to order meta data created via rest api * * @param \WP_REST_Response $response The response object. * @param \WP_Post $post Post object. * @param \WP_REST_Request $request Request object. * * @return mixed * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function updateTamaraCheckoutDataToOrder($response, $post, $request) { $wcOrder = wc_get_order($post); if (!empty($wcOrder) && $this->isTamaraGateway($wcOrder->get_payment_method())) { $wcOrderId = $wcOrder->get_id(); $hasTamaraCheckoutUrl = !!get_post_meta($wcOrderId, 'tamara_checkout_url', true); $hasTamaraCheckoutSessionId = !!get_post_meta($wcOrderId, 'tamara_checkout_session_id', true); if (!$hasTamaraCheckoutUrl && !$hasTamaraCheckoutSessionId) { $tamaraCheckoutResponse = $this->getWCTamaraGatewayService()->tamaraCheckoutSession($wcOrderId); if ($tamaraCheckoutResponse) { $response_data = $response->get_data(); $metaData = [ [ 'key' => 'tamara_checkout_session_id', 'value' => $tamaraCheckoutResponse['tamaraCheckoutSessionId'] ?: null, ], [ 'key' => 'tamara_checkout_url', 'value' => $tamaraCheckoutResponse['tamaraCheckoutUrl'] ?: null, ], ]; $response_data['meta_data'] = $response_data['meta_data'] + $metaData; $response->set_data($response_data); } } } return $response; } /** * Check a request a Rest API request or not * @return bool */ public static function isRestRequest() { $requestUri = !empty($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null; return $requestUri && strpos($requestUri, 'wp-json') !== false; } /** * Set Rest Api Request to $restApiRequest variable * * @param $restApiRequest */ public function setRestApiRequest($restApiRequest) { $this->restApiRequest = $restApiRequest; } /** * Return the Rest Api Request * * @return \WP_REST_Request */ public function getRestApiRequest() { return $this->restApiRequest; } /** * Redirect Pay By Instalments settings page to Pay By Later settings page */ public function updatePayByInstalmentSettingUrl() { if (is_admin() && isset($_GET['page'], $_GET['tab'], $_GET['section']) && ('wc-settings' === $_GET['page']) && ('checkout' === $_GET['tab']) && (in_array($_GET['section'], $this->getPayInXIds()))) { wp_redirect(admin_url('admin.php?page=wc-settings&tab=checkout§ion='.strtolower(static::TAMARA_GATEWAY_ID))); } } /** * @param $fields * @param $country * * @return mixed */ public function forceRequireBillingPhone($fields, $country) { if (is_wc_endpoint_url('edit-address') || !$this->isForceBillingPhoneEnabled() || !empty($fields['billing_phone'])) { return $fields; } elseif ($this->isForceBillingPhoneEnabled() && empty($fields['billing_phone'])) { $fields['billing_phone'] = [ 'label' => __('Phone', 'woocommerce'), 'required' => true, ]; return $fields; } return $fields; } /** * Fire an ajax request without waiting for response */ public function addCronJobTriggerScript() { $ajaxCronjobUrl = esc_attr(add_query_arg([ 'action' => 'tamara_perform_cron', ], admin_url('admin-ajax.php'))); echo << SCRIPT; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * @return false|string * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function performCron() { if (current_user_can('publish_posts')) { $this->forceAuthoriseTamaraOrder(); $this->forceCaptureTamaraOrder(); return json_encode(true); } return json_encode(false); } /** * @param string $url * * @return string */ public function removeTrailingSlashes($url) { return rtrim(trim($url), '/'); } /** * Get the array of Pay In X Ids * * @return array */ public function getPayInXIds() { return [ static::TAMARA_GATEWAY_CHECKOUT_ID, static::TAMARA_GATEWAY_PAY_IN_2, static::TAMARA_GATEWAY_PAY_IN_3, static::TAMARA_GATEWAY_PAY_IN_4, static::TAMARA_GATEWAY_PAY_IN_5, static::TAMARA_GATEWAY_PAY_IN_6, static::TAMARA_GATEWAY_PAY_IN_7, static::TAMARA_GATEWAY_PAY_IN_8, static::TAMARA_GATEWAY_PAY_IN_9, static::TAMARA_GATEWAY_PAY_IN_10, static::TAMARA_GATEWAY_PAY_IN_11, static::TAMARA_GATEWAY_PAY_IN_12, ]; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Populate the array of enabled Pay In Xs min amount * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function populateMinAmountArrayOfEnabledPayInXs() { $countryCode = $this->getWCTamaraGatewayService()->getCurrentCountryCode(); $payInXMinAmount = []; for ($i = 12; $i >= 2; $i--) { if ($this->getWCTamaraGatewayService()->isSingleCheckoutEnabled()) { $payInXMaxAmount[$i] = $this->populateInstalmentPayInXMaxLimit($i, $countryCode); } else { if ($this->isPayInXEnabled($i, $countryCode)) { $payInXMinAmount[$i] = $this->populateInstalmentPayInXMinLimit($i, $countryCode); } } } return $payInXMinAmount; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Populate the array of enabled Pay In Xs max amount * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function populateMaxAmountArrayOfEnabledPayInXs() { $countryCode = $this->getWCTamaraGatewayService()->getCurrentCountryCode(); $payInXMaxAmount = []; for ($i = 12; $i >= 2; $i--) { if ($this->getWCTamaraGatewayService()->isSingleCheckoutEnabled()) { $payInXMaxAmount[$i] = $this->populateInstalmentPayInXMaxLimit($i, $countryCode); } else { if ($this->isPayInXEnabled($i, $countryCode)) { $payInXMaxAmount[$i] = $this->populateInstalmentPayInXMaxLimit($i, $countryCode); } } } return $payInXMaxAmount; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get min amount priority instalment period * * @return int | null * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getMinAmountOfEnabledPriorityInstalment() { $minAmountArr = $this->populateMinAmountArrayOfEnabledPayInXs() ?? []; if (!empty($minAmountArr)) { $filterNullVarArr = array_filter($minAmountArr, function ($v) { return !is_null($v); }); if (!empty($filterNullVarArr)) { return $minAmountArr[max(array_keys($filterNullVarArr))]; } } return null; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get max amount priority instalment period * * @return int | null * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getMaxAmountOfEnabledPriorityInstalment() { $maxAmountArr = $this->populateMaxAmountArrayOfEnabledPayInXs() ?? []; if (!empty($maxAmountArr)) { $filterNullVarArr = array_filter($maxAmountArr, function ($v) { return !is_null($v); }); if (!empty($filterNullVarArr)) { return $maxAmountArr[max(array_keys($filterNullVarArr))]; } } return null; } /** @noinspection PhpFullyQualifiedNameUsageInspection */ /** * Get priority instalment period amongs enabled Pay In Xs * * @return int | null * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getPriorityInstalmentPeriod() { $maxAmountArr = $this->populateMaxAmountArrayOfEnabledPayInXs() ?? []; if (!empty($maxAmountArr)) { $filterNullVarArr = array_filter($maxAmountArr, function ($v) { return !is_null($v); }); return max(array_keys($filterNullVarArr)); } return null; } /** * Check if current screen is Tamara Admin Settings page * * @return bool */ public function isTamaraAdminSettingsScreen() { return (is_admin() && isset($_GET['page'], $_GET['tab'], $_GET['section']) && ('wc-settings' === $_GET['page']) && ('checkout' === $_GET['tab']) && (static::TAMARA_GATEWAY_ID === $_GET['section'])); } /** * Return the array of Tamara Gateway Ids * * @return array */ public function getAllTamaraGatewayIds() { return [ static::TAMARA_GATEWAY_ID, static::TAMARA_GATEWAY_PAY_NOW, static::TAMARA_GATEWAY_PAY_NEXT_MONTH, static::TAMARA_GATEWAY_PAY_BY_INSTALMENTS_ID, static::TAMARA_GATEWAY_PAY_IN_2, static::TAMARA_GATEWAY_PAY_IN_3, static::TAMARA_GATEWAY_PAY_IN_4, static::TAMARA_GATEWAY_PAY_IN_5, static::TAMARA_GATEWAY_PAY_IN_6, static::TAMARA_GATEWAY_PAY_IN_7, static::TAMARA_GATEWAY_PAY_IN_8, static::TAMARA_GATEWAY_PAY_IN_9, static::TAMARA_GATEWAY_PAY_IN_10, static::TAMARA_GATEWAY_PAY_IN_11, static::TAMARA_GATEWAY_PAY_IN_12, static::TAMARA_GATEWAY_CHECKOUT_ID, ]; } /** * Check if a payment method is Tamara * * @param $paymentMethodId * * @return bool */ public function isTamaraGateway($paymentMethodId) { return !!in_array($paymentMethodId, $this->getAllTamaraGatewayIds()); } /** * Get all product ids of items in cart, including parent and child ids. * * @return array */ public function getAllProductIdsInCart() { $allCartItems = WC()->cart->get_cart(); $productIds = []; foreach ($allCartItems as $item => $values) { $itemId = $values['data']->get_id() ?? null; $productIds[] = $itemId; $product = wc_get_product($itemId); // Check if a product is a variation add add its parent id to the list. if ($product instanceof \WC_Product_Variation) { $productParentId = $product->get_parent_id() ?? null; if (!in_array($productParentId, $productIds)) { $productIds[] = $productParentId; } } } return $productIds; } /** * Get all category ids of items in cart, including ancestors and subcategories. * * @return array */ public function getAllProductCategoryIdsInCart() { $allCartItems = WC()->cart->get_cart(); $allProductCategoryIds = []; foreach ($allCartItems as $item => $values) { $productId = $values['data']->get_id() ?? null; $allProductCategoryIds = array_merge($allProductCategoryIds, wc_get_product_cat_ids($productId)); } return $allProductCategoryIds; } /** * Get the array of Tamara excluded product ids * * @return array */ public function getExcludedProductIds() { $tamaraExcludedProductsOption = $this->getWCTamaraGatewayOptions()['excluded_products'] ?? ''; return array_map('trim', explode(',', $tamaraExcludedProductsOption)); } /** * Get the array of Tamara excluded product category ids * * @return array */ public function getExcludedProductCategoryIds() { $tamaraExcludedProductCategoriesOption = $this->getWCTamaraGatewayOptions()['excluded_product_categories'] ?? ''; return array_map('trim', explode(',', $tamaraExcludedProductCategoriesOption)); } /** * Check if a product is excluded from using Tamara * * @param int $productId * * @return bool */ public function isExcludedProduct($productId) { return !!(in_array($productId, $this->getExcludedProductIds())); } /** * Check if there's any product category id is excluded from using Tamara * * @param array $productCategoryIds * * @return bool */ public function isExcludedProductCategory($productCategoryIds) { return !!(count(array_intersect($productCategoryIds, $this->getExcludedProductCategoryIds()))); } /** * Get the base url of plugin * * @return string */ public function getBaseUrl() { return $this->baseUrl; } /** * Get appropriate instalment plan according to current product variation price * and return the value through ajax call */ public function getInstalmentPlanAccordingToProductVariation() { $variationPrice = filter_input(INPUT_POST, 'variationPrice', FILTER_SANITIZE_STRING); if (!empty($variationPrice) && !$this->isWidgetPopupDisabled()) { $currency = get_woocommerce_currency(); // Todo: Handle and re-generate if PDP is not initialized when there is no plan for the smallest variation price $PDPWidgetData = $this->populatePDPWidgetBasedOnPrice($variationPrice, $currency) ?? []; if (!empty($PDPWidgetData['paymentType'])) { wp_send_json([ 'message' => 'success', 'data' => $PDPWidgetData, ]); } } wp_send_json( [ 'message' => 'No payment type found for this price', ] ); } /** * Update Tamara Checkout Params on updated_checkout event * and return the value through ajax call */ public function updateTamaraCheckoutParams() { $storeCurrency = get_woocommerce_currency(); $countryCode = $this->getWCTamaraGatewayService()->getCurrentCountryCode(); wp_send_json( [ 'message' => 'success', 'country' => $countryCode, 'currency' => $storeCurrency, ] ); } /** * Return the first value in variation product price array that meets the smallest instalment plan * * @param array $variationPriceArr * * @return mixed */ public function getAppropriateVariationProductPrice($variationPriceArr) { foreach ($variationPriceArr as $item => $variationPrice) { if (($this->getWCTamaraGatewayService()->populateMinLimit() <= $variationPrice && $this->getWCTamaraGatewayService()->populateMaxLimit() >= $variationPrice) || ($this->populatePayInXsLimitAmountBasedOnProductPrice($variationPrice)['instalmentMinAmount'] <= $variationPrice && $this->populatePayInXsLimitAmountBasedOnProductPrice($variationPrice)['instalmentMaxAmount'] >= $variationPrice)) { return $variationPrice; break; } elseif ($this->isAlwaysShowWidgetPopupEnabled() && !$this->isWidgetPopupDisabled()) { return $variationPrice; break; } } return null; } /** * Override Wc Clear Cart function and keep cart for orders with cancelled/failed payments from Tamara */ public function overrideWcClearCart() { remove_action('get_header', 'wc_clear_cart_after_payment'); global $wp; if (!empty($wp->query_vars['order-received'])) { $order_id = absint($wp->query_vars['order-received']); $order_key = isset($_GET['key']) ? wc_clean(wp_unslash($_GET['key'])) : ''; // WPCS: input var ok, CSRF ok. if ($order_id > 0) { $order = wc_get_order($order_id); if ($order && hash_equals($order->get_order_key(), $order_key)) { WC()->cart->empty_cart(); } } } if (WC()->session->order_awaiting_payment > 0) { $order = wc_get_order(WC()->session->order_awaiting_payment); if ($order && $order->get_id() > 0) { // If the order has not failed, or is not pending, the order must have gone through. if (!$order->has_status(array('failed', 'pending', 'cancelled')) && !$this->isTamaraGateway($order->get_payment_method())) { WC()->cart->empty_cart(); } } } } /** * Cancel a pending order and add Tamara payment cancelled/failed notice. */ public function cancelOrder() { if ( isset($_GET['cancel_order']) && isset($_GET['order']) && isset($_GET['order_id']) && (isset($_GET['_wpnonce']) && wp_verify_nonce(wp_unslash($_GET['_wpnonce']), 'woocommerce-cancel_order')) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ) { wc_nocache_headers(); $order_key = wp_unslash($_GET['order']); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $order_id = absint($_GET['order_id']); $order = wc_get_order($order_id); $paymentMethod = $order->get_payment_method(); $user_can_cancel = current_user_can('cancel_order', $order_id); $order_can_cancel = $order->has_status(apply_filters('woocommerce_valid_order_statuses_for_cancel', array('pending', 'failed'), $order)); $redirect = isset($_GET['redirect']) ? wp_unslash($_GET['redirect']) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ($user_can_cancel && !$order_can_cancel && $this->isTamaraGateway($paymentMethod)) { wc_clear_notices(); wc_add_notice(__('Your payment via Tamara has failed, please try again with a different payment method.', $this->textDomain), 'error'); } if ($redirect) { wp_safe_redirect($redirect); exit; } } } /** * Update phone number on every ajax calls on checkout * * @param $postedData * * @return void */ public function getUpdatedPhoneNumberOnCheckout($postedData) { global $woocommerce; // Parsing posted data on checkout $post = array(); $vars = explode('&', $postedData); foreach ($vars as $k => $value) { $v = explode('=', urldecode($value)); $post[$v[0]] = $v[1]; } // Update phone number get from posted data $this->customerPhoneNumber = $post['billing_phone']; } /** * Return customer phone number * * @return string */ public function getCustomerPhoneNumber() { return $this->customerPhoneNumber; } /** * Update WC Order Payment method according to Tamara order * * @param $wcOrderId * @param $wcOrder * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function updateWcOrderPaymentMethodAccordingToTamaraOrder($wcOrderId, $wcOrder) { $tamaraOrder = $this->getTamaraOrderByWcOrderId($wcOrderId); $paymentType = $tamaraOrder->getPaymentType(); update_post_meta($wcOrderId, 'tamara_payment_type', $paymentType); if ($paymentType === $this->getWCTamaraGatewayService()->getPaymentTypeMapping()[static::TAMARA_GATEWAY_ID]) { update_post_meta($wcOrderId, 'payment_method', static::TAMARA_GATEWAY_ID); delete_post_meta($wcOrderId, 'tamara_payment_type_instalment'); $wcOrder->set_payment_method(static::TAMARA_GATEWAY_ID); $wcOrder->set_payment_method_title($this->getWCTamaraGatewayService()::TAMARA_GATEWAY_DEFAULT_TITLE); $wcOrder->save(); } elseif ($paymentType === WCTamaraGateway::PAYMENT_TYPE_PAY_NEXT_MONTH) { update_post_meta($wcOrderId, 'payment_method', static::TAMARA_GATEWAY_ID); delete_post_meta($wcOrderId, 'tamara_payment_type_instalment'); $wcOrder->set_payment_method(static::TAMARA_GATEWAY_ID); $wcOrder->set_payment_method_title($this->getWCTamaraGatewayService()::TAMARA_GATEWAY_PAY_NEXT_MONTH_DEFAULT_TITLE); $wcOrder->save(); } elseif ($paymentType === WCTamaraGateway::PAYMENT_TYPE_PAY_NOW) { update_post_meta($wcOrderId, 'payment_method', static::TAMARA_GATEWAY_ID); delete_post_meta($wcOrderId, 'tamara_payment_type_instalment'); $wcOrder->set_payment_method(static::TAMARA_GATEWAY_ID); $wcOrder->set_payment_method_title($this->getWCTamaraGatewayService()::TAMARA_GATEWAY_PAY_NOW_DEFAULT_TITLE); $wcOrder->save(); } else { $instalment = $tamaraOrder->getInstalments(); update_post_meta($wcOrderId, 'payment_method', static::TAMARA_GATEWAY_PAY_IN_X.$instalment); update_post_meta($wcOrderId, 'tamara_payment_type_instalment', $instalment); $wcOrder->set_payment_method(static::TAMARA_GATEWAY_PAY_IN_X.$instalment); $wcOrder->set_payment_method_title($this->getWCTamaraGatewayService()->getPayInXTitle($instalment)); $wcOrder->save(); } } /** * Add Tamara Single Checkout to existing available gateways * * @param $availableGateways * * @return array * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function possiblyAddTamaraSingleCheckout($availableGateways) { $singleCheckoutService = [static::TAMARA_GATEWAY_CHECKOUT_ID => $this->getWCTamaraGatewayCheckoutService()]; $availableGateways = $this->mergeTamaraPaymentMethodsAfterPayLaterOffset($singleCheckoutService, $availableGateways); $payNowService = [static::TAMARA_GATEWAY_PAY_NOW => $this->getWCTamaraGatewayPayNowService()]; $availableGateways = $this->mergeTamaraPaymentMethodsAfterPayLaterOffset($payNowService, $availableGateways); unset($availableGateways[static::TAMARA_GATEWAY_ID]); return $availableGateways; } /** * Add other Tamara payment methods right after Pay Later offset on checkout * * @param $array * @param $availableGateways * * @return array */ protected function mergeTamaraPaymentMethodsAfterPayLaterOffset($array, $availableGateways) { $tamaraPayLaterKey = static::TAMARA_GATEWAY_ID; $tamaraPayLaterOffset = array_search($tamaraPayLaterKey, array_keys(WC()->payment_gateways->payment_gateways())); return array_merge( array_slice($availableGateways, 0, $tamaraPayLaterOffset), $array, array_slice($availableGateways, $tamaraPayLaterOffset, null) ); } protected function isAfterTamaraAuthorised($orderStatus) { return !!in_array($orderStatus, $this->getAfterTamaraAuthorisedStatuses()); } protected function getAfterTamaraAuthorisedStatuses() { return [ static::TAMARA_CANCELED_STATUS, static::TAMARA_PARTIALLY_CAPTURED_STATUS, static::TAMARA_FULLY_CAPTURED_STATUS, static::TAMARA_PARTIALLY_REFUNDED_STATUS, static::TAMARA_FULLY_REFUNDED_STATUS ]; } protected function isSupportedCountry($countryCode) { $supportedCountries = ['SA', 'AE', 'KW', 'BH']; return !!in_array($countryCode, $supportedCountries); } /** * Get Tamara order status from remote * * @param $wcOrderId * * @return string * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getTamaraOrderStatus($wcOrderId) { $tamaraOrder = $this->getTamaraOrderByWcOrderId($wcOrderId); if ($tamaraOrder) { return $tamaraOrder->getStatus() ?? null; } return null; } }