add('payplug_gateway', $message) : self::$log->log($level, $message, ['source' => 'payplug_gateway']); } /** * Construct method * * @return void */ public function __construct() { //required plugin id $this->id = 'payplug'; $this->supports = [ 'products', 'refunds', 'tokenization', ]; $payplug_gateways = ['payplug', 'american_express', 'apple_pay', 'bancontact', 'oney_x3_with_fees', 'oney_x3_without_fees', 'oney_x4_with_fees', 'oney_x4_without_fees', 'satispay', 'ideal', 'mybank', 'wero', 'bizum', 'scalapay']; //save buttom admin if ((!empty($_GET['section'])) && (in_array($_GET['section'], $payplug_gateways))) { $GLOBALS['hide_save_button'] = true; } $this->init_settings(); $this->requirements = new PayplugGatewayRequirements($this); if ($this->user_logged_in()) { $this->init_payplug(); } $this->mode = (bool) $this->get_configuration()->get_option('mode') ? 'live' : 'test'; $this->debug = (bool) $this->get_configuration()->get_option('debug'); $this->email = (string) $this->get_configuration()->get_option('email'); //admin form $this->init_form_fields(); add_filter('woocommerce_get_customer_payment_tokens', [$this, 'filter_tokens'], 10, 3); self::$log_enabled = $this->debug; add_filter('woocommerce_get_order_item_totals', [$this, 'customize_gateway_title'], 10, 2); add_action('woocommerce_thankyou', [$this, 'validate_payment']); add_action('the_post', [$this, 'validate_payment']); add_action('woocommerce_available_payment_gateways', [$this, 'check_gateway']); } /** * @param $option * @param $value * * @return bool|void */ public function update_option($option, $value = '') { if ($this->needs_setup()) { wp_send_json_error('needs_setup'); wp_die(); } parent::update_option($option, $value); } /** * this payment gateway cannot be updated on the wooco payment settings * * @return bool */ public function needs_setup() { return true; } /** * Customize gateway title in emails. * * @param array $total_rows * @param \WC_Order $order * * @return array * * @author Clément Boirie */ public function customize_gateway_title($total_rows, $order) { $get_payment_method = $this->id; if (method_exists($order, 'get_payment_method')) { $get_payment_method = $order->get_payment_method(); } $payment_method = PayplugWoocommerceHelper::is_pre_30() ? $order->payment_method : $get_payment_method; if ( $this->id !== $payment_method || !isset($total_rows['payment_method']) ) { return $total_rows; } $total_rows['payment_method']['value'] = __('Credit card', 'payplug'); return $total_rows; } /** * Validate order payment when the user is redirected to the success confirmation page. * * @throws \WC_Data_Exception */ public function validate_payment($id = null, $save_request = true, $ipn = false): void { global $wp; if (!$ipn) { if (!is_wc_endpoint_url('order-received') || (empty($_GET['key']) && empty($id))) { return; } } if (!empty($_GET['order-received'])) { $order_id = (int) ($_GET['order-received']); } elseif (!empty($id) && !is_object($id)) { $order_id = (int) $id; } if (empty($order_id) && (isset($wp->query_vars['order-received']))) { $order_id = apply_filters( 'woocommerce_thankyou_order_id', absint($wp->query_vars['order-received']) ); } if (empty($order_id)) { if (empty($_GET['key']) && empty($id) && !is_object($id)) { return; } $order_id = wc_get_order_id_by_order_key(wc_clean((!empty($_GET['key']) ? $_GET['key'] : (int) $id))); } if (empty($order_id)) { return; } $order = wc_get_order($order_id); if (!$order instanceof \WC_Order) { return; } $payment_method = PayplugWoocommerceHelper::is_pre_30() ? $order->payment_method : $order->get_payment_method(); if (!in_array($payment_method, ['payplug', 'oney_x3_with_fees', 'oney_x4_with_fees', 'oney_x3_without_fees', 'oney_x4_without_fees', 'bancontact', 'apple_pay', 'american_express', 'satispay', 'mybank', 'ideal', 'wero', 'bizum', 'scalapay'])) { return; } if ($payment_method === $this->id) { $transaction_id = PayplugWoocommerceHelper::is_pre_30() ? get_post_meta($order_id, '_transaction_id', true) : $order->get_transaction_id(); if (empty($transaction_id)) { self::log(sprintf('Order #%s : Missing transaction id.', $order_id), 'error'); return; } $lock_id = Lock::handle_insert($save_request, $transaction_id); if (!$lock_id) { return; } try { $payment = $this->payplug_api->payment_retrieve($transaction_id); } catch (\Exception $e) { self::log( sprintf( 'Order #%s : An error occurred while retrieving the payment data with the message : %s', $order_id, $e->getMessage() ) ); return; } $this->response->process_payment($payment); \Payplug\PayplugWoocommerce\Model\Lock::delete_lock($lock_id); $waiting_requests = \Payplug\PayplugWoocommerce\Model\Lock::get_lock_by_payment_id($transaction_id); if ($waiting_requests) { \Payplug\PayplugWoocommerce\Model\Lock::delete_lock_by_payment_id($transaction_id); $this->validate_payment($order_id, false); } } } /** * Check if this gateway is enabled */ public function is_available() { if ('yes' == $this->enabled) { $available = $this->requirements->satisfy_requirements() && !empty($this->get_api_key($this->get_current_mode())); // $this->enabled only reflects the country/context check as it was at gateway construction time, // so re-run it here to catch context that only becomes known later in the request (e.g. order-pay). if ($available && method_exists($this, 'checkGateway')) { $available = $this->checkGateway(); } return $available; } return parent::is_available(); } /** * Load gateway settings. */ public function init_settings(): void { parent::init_settings(); $enabled = !empty($this->settings['enabled']) && (bool) $this->settings['enabled']; $this->enabled = $enabled ? 'yes' : 'no'; } /** * Register gateway settings. */ public function init_form_fields(): void { $anchor = esc_html_x(__('More informations', 'payplug'), 'modal', 'payplug'); $domain = __('support.payplug.com/hc/fr/articles/4408142346002', 'payplug'); $link = sprintf(' %s', $domain, $anchor); $anchor_bancontact = esc_html_x(__('payplug_bancontact_activation_request', 'payplug'), 'modal', 'payplug'); $domain_bancontact = __('payplug_bancontact_activation_url', 'payplug'); $bancontact_call_to_action = sprintf(' %s', $domain_bancontact, $anchor_bancontact); $oney_cfg = $this->get_configuration()->get_option('payment_methods.configuration.oney'); if (!empty($oney_cfg)) { $oney_amount = json_decode($oney_cfg['custom_amounts'], true); $oney_amount['min'] = (float) $oney_amount['min'] / 100; $oney_amount['max'] = (float) $oney_amount['max'] / 100; } else { $oney_amount = [ 'min' => 100, 'max' => 3000, ]; } $fields = [ 'enabled' => [ 'title' => __('Enable/Disable', 'payplug'), 'type' => 'checkbox', 'label' => __('Enable PayPlug', 'payplug'), 'description' => __('Only Euro payments can be processed with PayPlug.', 'payplug'), 'default' => false, ], 'title' => [ 'title' => __('Title', 'payplug'), 'type' => 'text', 'description' => __('The payment solution title displayed to your customers during checkout', 'payplug'), 'default' => _x('Credit card checkout', 'Default gateway title', 'payplug'), 'desc_tip' => false, ], 'description' => [ 'title' => __('Description', 'payplug'), 'type' => 'text', 'description' => __('The payment solution description displayed to your customers during checkout', 'payplug'), 'default' => '', 'desc_tip' => false, ], 'title_connexion' => [ 'title' => __('Connection', 'payplug'), 'type' => 'title', ], 'email' => [ 'type' => 'hidden', 'default' => '', ], 'login' => [ 'type' => 'login', 'default' => '', ], 'title_testmode' => [ 'title' => __('Mode', 'payplug'), 'type' => 'title', ], 'mode' => [ 'title' => '', 'label' => '', 'type' => 'yes_no', 'yes' => 'Live', 'no' => 'Test', 'description' => __('In TEST mode, all payments will be simulations and will not generate real transactions.', 'payplug'), 'default' => false, 'hide_label' => true, ], 'title_settings' => [ 'title' => __('Settings', 'payplug'), 'type' => 'title', ], 'payment_method' => [ 'title' => __('Payment page', 'payplug'), 'type' => 'radio', 'options' => [ 'redirect' => __('Redirect', 'payplug'), 'embedded' => __('Integrated', 'payplug'), ], 'description' => __('Customers will be redirected to a PayPlug payment page to finalize the transaction, or payments will be performed in an embeddable payment form on your website.', 'payplug'), 'default' => 'redirect', 'desc_tip' => false, ], 'debug' => [ 'title' => __('Debug', 'payplug'), 'type' => 'checkbox', 'description' => __('Debug mode saves additional information on your server for each operation done via the PayPlug plugin (Developer setting).', 'payplug'), 'label' => __('Activate debug mode', 'payplug'), 'default' => true, 'desc_tip' => false, ], 'title_advanced_settings' => [ 'title' => __('payplug_advanced_settings', 'payplug'), 'description' => __( 'Your current offer does not allow this option. Try it on TEST mode. More information here.', 'payplug' ), 'type' => 'title', ], 'save_card' => [ 'title' => __('One Click Payment', 'payplug'), 'type' => 'checkbox', 'label' => __('Activate', 'payplug'), 'description' => __('Allow your customers to save their credit card information for later purchases.', 'payplug'), 'default' => false, 'desc_tip' => false, ], 'bancontact' => [ 'title' => __('payplug_bancontact_activate_title', 'payplug'), 'type' => 'checkbox', 'label' => __('Activate', 'payplug'), 'description' => '

' . __('payplug_bancontact_testmode_description', 'payplug') . '

' . '

' . __('payplug_bancontact_livemode_description_disabled', 'payplug') . '

' . $bancontact_call_to_action, 'default' => false, ], 'apple_pay' => [ 'title' => __('payplug_apple_pay_activate_title', 'payplug'), 'type' => 'checkbox', 'label' => __('Activate', 'payplug'), 'description' => '

' . __('payplug_apple_pay_testmode_description', 'payplug') . '

' . '

' . __('payplug_apple_pay_livemode_description', 'payplug') . '

', 'default' => false, ], 'american_express' => [ 'title' => __('payplug_amex_title', 'payplug'), 'type' => 'checkbox', 'label' => __('payplug_amex_activate', 'payplug'), 'description' => '

' . __('payplug_amex_testmode_description', 'payplug') . '

' . '

' . __('payplug_amex_livemode_description', 'payplug') . '

', 'default' => false, ], 'oney' => [ 'title' => __('3x 4x Oney payments', 'payplug'), 'type' => 'checkbox', 'label' => __('Activate', 'payplug'), // TRAD 'description' => sprintf(__('Allow your customers to split payments into 3 or 4 installments, for orders between %s€ and %s€', 'payplug'), $this->min_oney_price, $this->max_oney_price) . $link, 'default' => false, 'desc_tip' => false, ], 'oney_type' => [ 'title' => '', 'type' => 'oney_type', 'options' => [ 'with_fees' => __('Oney with fees', 'payplug'), 'without_fees' => __('Oney without fees', 'payplug'), ], 'descriptions' => [ 'with_fees' => __('The fees are split between you and your customers', 'payplug'), 'without_fees' => __('You pay the fees', 'payplug'), ], 'description' => '', 'default' => 'with_fees', 'desc_tip' => false, ], 'oney_thresholds' => [ 'title' => '', 'type' => 'oney_thresholds', 'description' => sprintf( __('I would like to offer guaranteed payment in installments for amounts between %s€ and %s€.', 'payplug'), '' . $oney_amount['min'] . '', '' . $oney_amount['max'] . '' ), 'desc_tip' => false, ], 'oney_product_animation' => [ 'title' => __('oney_installments_pop_up', 'payplug'), 'description' => __('display_the_oney_installments_pop_up_on_the_product_page', 'payplug'), 'label' => __('Activate', 'payplug'), 'default' => false, 'desc_tip' => false, 'type' => 'oney_product_animation', ], ]; if ($this->user_logged_in()) { if ($this->permissions->has_permissions(PayplugPermissions::SAVE_CARD)) { unset($fields['title_advanced_settings']); } elseif ('live' === $this->get_current_mode()) { $fields['save_card']['disabled'] = true; } } /** * Filter PayPlug gateway settings. * * @param array $fields */ $fields = apply_filters('payplug_gateway_settings', $fields); $this->form_fields = $fields; } /** * Set global configuration for PayPlug instance. */ public function init_payplug(): void { $this->payplug_api = new PayplugApi($this); $this->payplug_api->init(); // init() just resolved this for the same mode - reuse it instead of asking // Service\Api::get_bearer_token() to do so again right after. $this->permissions = new PayplugPermissions($this, $this->payplug_api->get_current_bearer_token()); $this->response = new PayplugResponse($this); // Register IPN handler new PayplugIpnResponse($this); } /** * Filter saved tokens for the gateway. * * A token will be removed if : * - it doesn't match the current merchant logged in, * - or it doesn't match the current gateway mode, * - or it is expired. * * @param array $tokens * @param int $user_id * @param string $gateway_id * * @return array */ public function filter_tokens($tokens, $user_id, $gateway_id) { if (!is_user_logged_in() || !class_exists('WC_Payment_Gateway_CC')) { return $tokens; } $saved_card = $this->get_configuration()->get_option('payment_methods.configuration.payplug.save_card') && is_user_logged_in(); if (!$saved_card) { foreach ($tokens as $token_id => $token) { if ('payplug' == (string) $token->get_gateway_id()) { unset($tokens[$token_id]); } } return $tokens; } /* @var \WC_Payment_Token_CC $token */ foreach ($tokens as $k => $token) { if ($this->id !== $token->get_gateway_id()) { continue; } // check if token is associated with a merchant id and if it match the current one $token_merchant_id = $token->get_meta('payplug_account', true); if (empty($token_merchant_id) || $this->get_merchant_id() !== $token_merchant_id) { unset($tokens[$k]); continue; } // check if token is available for the current gateway mode if ($this->mode != $token->get_meta('mode', true)) { unset($tokens[$k]); continue; } // check if token is not expired $current_month = \absint(date('n')); $current_year = \absint(date('Y')); if ($current_year > (int) $token->get_expiry_year()) { unset($tokens[$k]); continue; } if ($current_year === (int) $token->get_expiry_year() && $current_month > (int) $token->get_expiry_month()) { unset($tokens[$k]); continue; } } return $tokens; } /** * extra payment fields */ public function payment_fields(): void { $description = $this->get_description(); if (!empty($description)) { echo wpautop(wptexturize($description)); } if ($this->save_card_available()) { $this->tokenization_script(); $this->saved_payment_methods(); } } /** * Handle admin display. */ public function admin_options(): void { /************ VUE Code *************/ wp_enqueue_script('chunk-vendors.js', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/js/chunk-vendors-' . PAYPLUG_GATEWAY_VERSION . '.js', [], PAYPLUG_GATEWAY_VERSION); wp_enqueue_script('app.js', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/js/app-' . PAYPLUG_GATEWAY_VERSION . '.js', [], PAYPLUG_GATEWAY_VERSION); wp_enqueue_style('app.css', PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/css/app-' . PAYPLUG_GATEWAY_VERSION . '.css', [], PAYPLUG_GATEWAY_VERSION); wp_localize_script( 'app.js', 'payplug_admin_config', [ 'img_path' => esc_url(PAYPLUG_GATEWAY_PLUGIN_URL . 'assets/dist/'), 'ajax_url' => get_home_url(), 'rest_url' => get_home_url() . '/?rest_route=/payplug_api/', ] ); ?>
customer_user : $order->get_customer_id(); $amount = (int) PayplugWoocommerceHelper::get_payplug_amount($order->get_total()); $amount = $this->validate_order_amount($amount); if (is_wp_error($amount)) { self::log(sprintf('Invalid amount %s for the order.', $order->get_total()), 'error'); throw new \Exception($amount->get_error_message()); } $payment_token_id = (isset($_POST['wc-' . $this->id . '-payment-token']) && 'new' !== $_POST['wc-' . $this->id . '-payment-token']) ? wc_clean($_POST['wc-' . $this->id . '-payment-token']) : false; if ($payment_token_id && (int) $customer_id > 0) { self::log(sprintf('Payment token found.', $amount)); return $this->process_payment_with_token($order, $amount, $customer_id, $payment_token_id); } return $this->process_standard_payment($order, $amount, $customer_id); } /** * Whether the current request is repaying an existing order (order-pay page, or one of * the order-pay AJAX flows). The order_key/order_pay_key posted by those AJAX flows must * match the order's own key: their mere presence isn't proof of anything, since any * request can set them. * * @param \WC_Order|null $order * * @return bool */ protected function is_order_pay_request($order): bool { if (is_wc_endpoint_url('order-pay')) { return true; } $posted_key = $_POST['order_pay_key'] ?? $_POST['order_key'] ?? ''; if (empty($posted_key)) { return false; } return $order instanceof \WC_Order && hash_equals($order->get_order_key(), wc_clean(wp_unslash($posted_key))); } /** * if payment was generated by an intend, we shouldn't generate another one and try to pay it, this would generate duplications * * @param $order * * @throws \Exception * * @return array|null */ private function process_standard_intent_payment($order) { // This can run from AJAX endpoints whose own request URL never carries the order-pay // query var, so is_wc_endpoint_url() alone can't detect that context here: fall back // to the order_key/order_pay_key sent by the order-pay AJAX flows. $is_order_pay = $this->is_order_pay_request($order); //no order-pay page, no ajax_on_order_review_page if (!$is_order_pay && PayplugWoocommerceHelper::is_checkout_block() && ( ('payplug' == $this->id && in_array($this->embedded_mode, ['integrated', 'popup'])) || ('american_express' == $this->id && 'popup' == $this->embedded_mode) ) && ($_GET['wc-ajax'] ?? '') !== 'payplug_order_review_url' && !empty($order->get_transaction_id()) ) { $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id(); try { $payment = $this->payplug_api->payment_retrieve($order->get_transaction_id()); if (ob_get_length() > 0) { ob_clean(); } // Save transaction id for the order PayplugWoocommerceHelper::is_pre_30() ? update_post_meta($order_id, '_transaction_id', $payment->id) : $order->set_transaction_id($payment->id); if ($payment->is_paid) { $finished_status = wc_get_is_paid_statuses(); $order->set_status($finished_status[0]); } if (is_callable([$order, 'save'])) { $order->save(); } /** * Fires once a payment has been created. * * @param int $order_id Order ID * @param PaymentResource $payment Payment resource */ \do_action('payplug_gateway_payment_created', $order_id, $payment); $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment); PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata); self::log(sprintf('Payment intent created for order #%s', $order_id)); $return_url = esc_url_raw($order->get_checkout_order_received_url()); $result = [ 'payment_id' => $payment->id, 'result' => 'success', 'redirect' => !empty($payment->hosted_payment->payment_url) ? $payment->hosted_payment->payment_url : $return_url, 'cancel' => !empty($payment->hosted_payment->cancel_url) ? $payment->hosted_payment->cancel_url : null, ]; // wp_send_json_success() calls die(), which is only safe for the classic // wc-ajax request this was written for: the Store API checkout flow (used by // the checkout block) calls process_payment() through the REST framework, // and killing the process mid-request there produces a broken response. if (wp_doing_ajax()) { wp_send_json_success($result); } return $result; } catch (HttpException $e) { self::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error'); throw new \Exception(__('Payment processing failed. Please retry.', 'payplug')); } catch (\Exception $e) { self::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error'); throw new \Exception(__('Payment processing failed. Please retry.', 'payplug')); } } return null; } /** * @param \WC_Order $order * @param int $amount * @param int $customer_id * * @throws \Exception * * @return array */ public function process_standard_payment($order, $amount, $customer_id) { $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id(); $intent = $this->process_standard_intent_payment($order); if (!empty($intent)) { return $intent; } try { $address_data = PayplugAddressData::from_order($order); $return_url = esc_url_raw($order->get_checkout_order_received_url()); if (!(substr($return_url, 0, 4) === 'http')) { $return_url = get_site_url() . $return_url; } $payment_data = [ 'amount' => $amount, 'currency' => get_woocommerce_currency(), 'allow_save_card' => $this->save_card_available() && (int) $customer_id > 0, 'billing' => $address_data->get_billing(), 'shipping' => $address_data->get_shipping(), 'hosted_payment' => [ 'return_url' => $return_url, 'cancel_url' => esc_url_raw($order->get_cancel_order_url_raw()), ], 'notification_url' => esc_url_raw(WC()->api_request_url('PayplugGateway')), 'metadata' => [ 'order_id' => $order_id, 'customer_id' => ((int) $customer_id > 0) ? $customer_id : 'guest', 'domain' => $this->limit_length(esc_url_raw(home_url()), 500), ], ]; if (PayplugWoocommerceHelper::is_checkout_block() && is_checkout()) { $payment_data['metadata']['woocommerce_block'] = 'CHECKOUT'; } elseif (PayplugWoocommerceHelper::is_cart_block() && is_cart()) { $payment_data['metadata']['woocommerce_block'] = 'CART'; } //IP request required variables if ('integrated' == $this->embedded_mode) { $payment_data['initiator'] = 'PAYER'; $payment_data['integration'] = 'INTEGRATED_PAYMENT'; unset($payment_data['hosted_payment']['cancel_url']); } //for subscriptions the card needs to be saved $is_subscription = PayplugWoocommerceHelper::is_subscription(); if (!empty($is_subscription) && $is_subscription === true) { $payment_data['allow_save_card'] = false; $payment_data['save_card'] = true; $payment_data['force_3ds'] = true; $payment_data['metadata']['subscription'] = 'subscription'; } /** * Filter the payment data before it's used * * @param array $payment_data * @param int $order_id * @param array $customer_details * @param PayplugAddressData $address_data */ $payment_data = apply_filters('payplug_gateway_payment_data', $payment_data, $order_id, [], $address_data); $payment = $this->payplug_api->payment_create($payment_data); // Save transaction id for the order PayplugWoocommerceHelper::is_pre_30() ? update_post_meta($order_id, '_transaction_id', $payment->id) : $order->set_transaction_id($payment->id); $order->set_payment_method($this->id); $order->set_payment_method_title($this->method_title); if (is_callable([$order, 'save'])) { $order->save(); } /** * Fires once a payment has been created. * * @param int $order_id Order ID * @param PaymentResource $payment Payment resource */ \do_action('payplug_gateway_payment_created', $order_id, $payment); $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment); PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata); self::log(sprintf('Payment creation complete for order #%s', $order_id)); if (ob_get_length() > 0) { ob_clean(); } return [ 'payment_id' => $payment->id, 'result' => 'success', 'redirect' => !empty($payment->hosted_payment->payment_url) ? $payment->hosted_payment->payment_url : $return_url, 'cancel' => !empty($payment->hosted_payment->cancel_url) ? $payment->hosted_payment->cancel_url : null, ]; } catch (HttpException $e) { self::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error'); throw new \Exception(__('Payment processing failed. Please retry.', 'payplug')); } catch (\Exception $e) { self::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error'); throw new \Exception(__('Payment processing failed. Please retry.', 'payplug')); } } /** * @param \WC_Order $order * @param int $amount * @param int $customer_id * @param string $token_id * * @throws \Exception * * @return array */ public function process_payment_with_token($order, $amount, $customer_id, $token_id) { $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id(); $payment_token = WC_Payment_Tokens::get($token_id); if (!$payment_token || (int) $customer_id !== (int) $payment_token->get_user_id()) { self::log('Could not find the payment token or the payment doesn\'t belong to the current user.', 'error'); throw new \Exception(__('Invalid payment method.', 'payplug')); } try { $address_data = PayplugAddressData::from_order($order); $return_url = esc_url_raw($order->get_checkout_order_received_url()); if (!(substr($return_url, 0, 4) === 'http')) { $return_url = get_site_url() . $return_url; } $payment_data = [ 'amount' => $amount, 'currency' => get_woocommerce_currency(), 'payment_method' => $payment_token->get_token(), 'allow_save_card' => false, 'billing' => $address_data->get_billing(), 'shipping' => $address_data->get_shipping(), 'initiator' => 'PAYER', 'hosted_payment' => [ 'return_url' => $return_url, 'cancel_url' => esc_url_raw($order->get_cancel_order_url_raw()), ], 'notification_url' => esc_url_raw(WC()->api_request_url('PayplugGateway')), 'metadata' => [ 'order_id' => $order_id, 'customer_id' => ((int) $customer_id > 0) ? $customer_id : 'guest', 'domain' => $this->limit_length(esc_url_raw(home_url()), 500), 'woocommerce_block' => \WC_Blocks_Utils::has_block_in_page(wc_get_page_id('checkout'), 'woocommerce/checkout'), ], ]; $is_subscription = PayplugWoocommerceHelper::is_subscription(); if (!empty($is_subscription) && $is_subscription === true) { $payment_data['metadata']['subscription'] = 'subscription'; } /** This filter is documented in src/Gateway/PayplugGateway */ $payment_data = apply_filters('payplug_gateway_payment_data', $payment_data, $order_id, [], $address_data); $payment = $this->payplug_api->payment_create($payment_data); // Save transaction id for the order PayplugWoocommerceHelper::is_pre_30() ? update_post_meta($order_id, '_transaction_id', $payment->id) : $order->set_transaction_id($payment->id); if (is_callable([$order, 'save'])) { $order->save(); } /** This action is documented in src/Gateway/PayplugGateway */ \do_action('payplug_gateway_payment_created', $order_id, $payment); $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment); PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata); $this->response->process_payment($payment, true); if (($payment->__get('is_paid'))) { $redirect = $order->get_checkout_order_received_url(); } elseif (isset($payment->__get('hosted_payment')->payment_url)) { $redirect = $payment->__get('hosted_payment')->payment_url; } else { $redirect = $return_url; } return [ 'payment_id' => $payment->id, 'result' => 'success', 'is_paid' => $payment->__get('is_paid'), // Use for path redirect before DSP2 'redirect' => $redirect, ]; } catch (HttpException $e) { self::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error'); throw new \Exception(__('Payment processing failed. Please retry.', 'payplug')); } catch (\Exception $e) { self::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error'); throw new \Exception(__('Payment processing failed. Please retry.', 'payplug')); } } /** * Process refund for an order paid with PayPlug gateway. * * @param int $order_id * @param null $amount * @param string $reason * * @return bool|\WP_Error */ public function process_refund($order_id, $amount = null, $reason = '') { self::log(sprintf('Processing refund for order #%s', $order_id)); if (!$this->user_logged_in()) { self::log(__('You must be logged in with your PayPlug account.', 'payplug'), 'error'); return new \WP_Error('process_refund_error', __('You must be logged in with your PayPlug account.', 'payplug')); } $order = wc_get_order($order_id); if (!$order instanceof \WC_Order) { self::log(sprintf('The order #%s does not exist.', $order_id), 'error'); return new \WP_Error('process_refund_error', sprintf(__('The order %s does not exist.', 'payplug'), $order_id)); } if ($order->get_status() === 'cancelled') { self::log(sprintf('The order #%s cannot be refund.', $order_id), 'error'); return new \WP_Error('process_refund_error', sprintf(__('The order %s cannot be refund.', 'payplug'), $order_id)); } $transaction_id = PayplugWoocommerceHelper::is_pre_30() ? get_post_meta($order_id, '_transaction_id', true) : $order->get_transaction_id(); if (empty($transaction_id)) { self::log(sprintf('The order #%s does not have PayPlug transaction ID associated with it.', $order_id), 'error'); return new \WP_Error('process_refund_error', __('No PayPlug transaction was found for this order. The refund could not be processed.', 'payplug')); } $customer_id = PayplugWoocommerceHelper::is_pre_30() ? $order->customer_user : $order->get_customer_id(); $data = [ 'metadata' => [ 'order_id' => $order_id, 'customer_id' => ((int) $customer_id > 0) ? $customer_id : 'guest', 'refund_from' => 'woocommerce', ], ]; if (!is_null($amount)) { $data['amount'] = PayplugWoocommerceHelper::get_payplug_amount($amount); } if (!empty($reason)) { $data['metadata']['reason'] = $reason; } /** * Filter the refund data before it's used. * * @param array $data * @param int $order_id * @param string $transaction_id */ $data = apply_filters('payplug_gateway_refund_data', $data, $order_id, $transaction_id); try { $refund = $this->payplug_api->refund_create($transaction_id, $data); /** * Fires once a refund has been created. * * @param int $order_id Order ID * @param RefundResource $refund Refund resource * @param string $transaction_id Transaction id */ \do_action('payplug_gateway_refund_created', $order_id, $refund, $transaction_id); $refund_meta_key = sprintf('_pr_%s', wc_clean($refund->id)); if (PayplugWoocommerceHelper::is_pre_30()) { update_post_meta($order_id, $refund_meta_key, $refund->id); } else { $order->add_meta_data($refund_meta_key, $refund->id, true); $order->save(); } $note = sprintf(__('Refund %s : Refunded %s', 'payplug'), wc_clean($refund->id), wc_price(((int) $refund->amount) / 100)); if (!empty($refund->metadata['reason'])) { $note .= sprintf(' (%s)', esc_html($refund->metadata['reason'])); } $order->add_order_note($note); try { $payment = $this->payplug_api->payment_retrieve($transaction_id); $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment); PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata); } catch (\Exception $e) { } self::log('Refund process complete for the order.'); return true; } catch (HttpException $e) { self::log(sprintf('Refund request error for the order %s from PayPlug API : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error'); return new \WP_Error('process_refund_error', __('The transaction could not be refunded. Please try again.', 'payplug')); } catch (\Exception $e) { self::log(sprintf('Refund request error for the order %s : %s', $order_id, wc_clean($e->getMessage())), 'error'); return new \WP_Error('process_refund_error', __('The transaction could not be refunded. Please try again.', 'payplug')); } } /** * Check the order amount to ensure it's on the allowed range. * * @param int $amount * * @return int|\WP_Error */ public function validate_order_amount($amount) { if ( $amount < PayplugWoocommerceHelper::get_minimum_amount() || $amount > PayplugWoocommerceHelper::get_maximum_amount() ) { return new \WP_Error( 'invalid order amount', sprintf(__('Payments for this amount (%s) are not authorised with this payment gateway.', 'payplug'), \wc_price($amount / 100)) ); } return $amount; } /** * Limit string length. * * @param string $value * @param int $maxlength * * @return string */ public function limit_length($value, $maxlength = 100) { return (strlen($value) > $maxlength) ? substr($value, 0, $maxlength) : $value; } /** * Get user's keys. * * @param string $email * @param string $password * * @return array|\WP_Error */ public function retrieve_user_api_keys($email, $password) { if (empty($email) || empty($password)) { return new \WP_Error('missing_login_data', __('Please fill all login fields', 'payplug')); } try { $response = Authentication::getKeysByLogin($email, $password); if (empty($response) || !isset($response['httpResponse']) && 'payplug' === $this->id) { return new \WP_Error('invalid_credentials', __('Invalid credentials.', 'payplug')); } return $response['httpResponse']['secret_keys']; } catch (HttpException $e) { if ('payplug' === $this->id) { return new \WP_Error('invalid_credentials', __('Invalid credentials.', 'payplug')); } } } /** * Get user merchant id. * * This method might be called during the login process before the global PayPlug * configuration is set. In that case you can pass a valid token to make the request. * * @param string|null $key * * @return string */ public function retrieve_merchant_id($key = null) { $merchant_id = ''; try { $response = !is_null($key) && !empty($key) ? Authentication::getAccount(new Payplug($key)) : Authentication::getAccount(); PayplugWoocommerceHelper::set_transient_data($response); $merchant_id = isset($response['httpResponse']['id']) ? $response['httpResponse']['id'] : ''; } catch (ConfigurationException $e) { self::log(sprintf('Missing API key for PayPlug client : %s', wc_print_r($e->getMessage(), true)), 'error'); } catch (HttpException $e) { self::log(sprintf('Account request error from PayPlug API : %s', wc_print_r($e->getErrorObject(), true)), 'error'); PayplugWoocommerceHelper::exception_handler_400_logout($e->getCode(), '', sprintf('Account request error from PayPlug API : %s', wc_print_r($e->getMessage(), true))); } catch (\Exception $e) { self::log(sprintf('Account request error : %s', wc_clean($e->getMessage())), 'error'); } return $merchant_id; } /** * Get PayPlug gateway mode. * * @return string */ public function get_current_mode() { return $this->get_configuration()->get_option('mode'); } /** * Get user API key. * * @param string $mode * * @return string */ public function get_api_key($mode = 'test') { return $this->get_api()->get_bearer_token($mode); } /** * Get current merchant id. * * @return string */ public function get_merchant_id() { return $this->get_configuration()->get_option('company_id'); } /** * Check if user is logged in and we have an API key for TEST mode. * * @return bool */ public function user_logged_in() { $options = $this->get_configuration()->get_options(); if (empty($options) || !isset($options['api_key']) || !isset($options['jwt'])) { return false; } $jwt = json_decode($options['jwt'], true); if (!empty($jwt) && isset($jwt['test']) && isset($jwt['test']['access_token'])) { return true; } $api_key = json_decode($options['api_key'], true); return !empty($api_key) && isset($api_key['test']) && !empty($api_key['test']); } /** * Check if oneclick payment is activated and merchant can use it. * * @return bool */ public function save_card_available() { return 'payplug' == $this->id && $this->user_logged_in() && $this->save_card && $this->permissions->has_permissions(PayplugPermissions::SAVE_CARD); } /** * Check if the gatteway is allowed for the order amount * * @param array * * @return array */ public function check_gateway($gateways) { if (!empty(WC()->cart) && isset($gateways[$this->id]) && $gateways[$this->id]->id == $this->id) { $order_amount = $this->get_order_total(); foreach ($gateways[$this->id]->settings['payment_methods']['permissions'] as $key => &$permission) { $method_amounts = json_decode($permission['amounts'], true); if ($order_amount < $method_amounts['min']['EUR'] / 100 || $order_amount > $method_amounts['max']['EUR'] / 100) { unset($gateways[$key]); } } } if ((bool) $this->get_configuration()->get_option('payment_methods.configuration.oney.with_fees')) { unset($gateways['oney_x3_without_fees']); unset($gateways['oney_x4_without_fees']); } else { unset($gateways['oney_x3_with_fees']); unset($gateways['oney_x4_with_fees']); } return $gateways; } /** * Can the order be refunded via this gateway? * * * @param WC_Order $order Order object. * * @return bool If false, the automatic refund button is hidden in the UI. */ public function can_refund_order($order) { $status = $order->get_status(); return $order && $this->supports('refunds') && $status !== 'cancelled' && $status !== 'failed'; } }