id = self::GATEWAY_ID; $this->method_title = __( 'Revolut Gateway - Credit/Debit Cards', 'revolut-gateway-for-woocommerce' ); $this->tab_title = __( 'Credit/Debit Cards', 'revolut-gateway-for-woocommerce' ); $this->default_title = __( 'Pay with card', 'revolut-gateway-for-woocommerce' ); /* translators:%1s: %$2s: */ $this->method_description = sprintf( __( 'Accept card payments easily and securely via %1$sRevolut%2$s.', 'revolut-gateway-for-woocommerce' ), '', '' ); $this->title = $this->get_option( 'title' ); $this->description = $this->get_option( 'description' ); $this->save_payment_method_for = 'merchant'; parent::__construct(); if ( get_option( 'woocommerce_revolut_cc_settings' ) === false ) { $this->add_default_options(); } $this->init_scripts(); $this->revolut_saved_cards = 'yes' === $this->get_option( 'revolut_saved_cards' ); add_action( 'wp_enqueue_scripts', array( $this, 'load_payment_scripts' ) ); add_filter( 'wc_revolut_settings_nav_tabs', array( $this, 'admin_nav_tab' ), 4 ); if ( class_exists( 'WC_Subscriptions_Order' ) ) { add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 10, 2 ); add_action( 'woocommerce_subscriptions_change_payment_before_submit', array( $this, 'differentiate_change_payment_method_form' ) ); add_action( 'wcs_resubscribe_order_created', array( $this, 'delete_resubscribe_meta' ), 10 ); // display the credit card used for a subscription in the "My Subscriptions" table. add_filter( 'woocommerce_my_subscriptions_payment_method', array( $this, 'maybe_render_subscription_payment_method' ), 10, 2 ); add_action( 'woocommerce_subscription_failing_payment_method_updated_' . $this->id, array( $this, 'update_failing_payment_method' ), 10, 2 ); add_action( 'woocommerce_subscription_token_changed', array( $this, 'update_changed_subscription_token' ), 10, 2 ); } } /** * Load required payment scripts. */ public function load_payment_scripts() { $this->tokenization_script(); } /** * Supported functionality */ public function init_supports() { parent::init_supports(); $this->supports = array( 'refunds', 'tokenization', 'add_payment_method', 'subscriptions', 'subscription_cancellation', 'subscription_suspension', 'subscription_reactivation', 'subscription_amount_changes', 'subscription_date_changes', 'subscription_payment_method_change', 'subscription_payment_method_change_customer', 'subscription_payment_method_change_admin', 'multiple_subscriptions', 'pre-orders', ); } /** * Initialize Gateway Settings Form Fields */ public function init_form_fields() { $this->form_fields = array( 'enabled' => array( 'title' => __( 'Enable/Disable', 'revolut-gateway-for-woocommerce' ), 'label' => __( 'Enable ', 'revolut-gateway-for-woocommerce' ) . $this->method_title, 'type' => 'checkbox', 'description' => __( 'This controls whether or not this gateway is enabled within WooCommerce.', 'revolut-gateway-for-woocommerce' ), 'default' => 'yes', 'desc_tip' => true, ), 'title' => array( 'title' => __( 'Title', 'revolut-gateway-for-woocommerce' ), 'type' => 'text', 'description' => __( 'This controls the title that the user sees during checkout.', 'revolut-gateway-for-woocommerce' ), 'default' => $this->default_title, 'desc_tip' => true, ), 'card_widget_type' => array( 'title' => __( 'Card widget type', 'revolut-gateway-for-woocommerce' ), 'type' => 'select', 'description' => __( 'Select the card widget type.', 'revolut-gateway-for-woocommerce' ), 'default' => 'card_field', 'desc_tip' => true, 'options' => array( 'card_field' => __( 'Card field', 'revolut-gateway-for-woocommerce' ), 'popup' => __( 'Popup', 'revolut-gateway-for-woocommerce' ), ), ), 'revolut_saved_cards' => array( 'title' => __( 'Save Cards', 'revolut-gateway-for-woocommerce' ), 'label' => __( 'Enable Payment with Saved Cards', 'revolut-gateway-for-woocommerce' ), 'type' => 'checkbox', 'description' => __( 'If enabled, users will be able to save their cards and use them to checkout in subsequent purchases. Card details are stored securely on Revolut servers', 'revolut-gateway-for-woocommerce' ), 'default' => 'yes', 'desc_tip' => true, ), 'enable_cardholder_name' => array( 'title' => __( 'Cardholder\'s Name Field', 'revolut-gateway-for-woocommerce' ), 'label' => __( 'Enable', 'revolut-gateway-for-woocommerce' ), 'type' => 'checkbox', 'default' => 'no', 'description' => __( 'When enabled, input for Cardholder\'s Name will be activated.', 'revolut-gateway-for-woocommerce' ), 'desc_tip' => true, ), 'styling_title' => array( 'title' => __( 'Card widget style', 'revolut-gateway-for-woocommerce' ), 'type' => 'title', ), 'widget_styling' => array( 'title' => __( 'Customize card widget style', 'revolut-gateway-for-woocommerce' ), 'label' => __( 'Enable', 'revolut-gateway-for-woocommerce' ), 'type' => 'checkbox', 'description' => __( 'By enabling this option you can customize the style of the Revolut card widget', 'revolut-gateway-for-woocommerce' ), 'default' => 'no', 'class' => 'revolut_styling_option_enable', 'desc_tip' => true, ), 'widget_background_color' => array( 'title' => __( 'Card widget background color', 'revolut-gateway-for-woocommerce' ), 'type' => 'color', 'description' => __( 'This controls the background color of the Revolut card widget', 'revolut-gateway-for-woocommerce' ), 'default' => WC_REVOLUT_CARD_WIDGET_BG_COLOR, 'class' => 'revolut_styling_option', 'desc_tip' => true, ), 'widget_text_color' => array( 'title' => __( 'Card widget font color', 'revolut-gateway-for-woocommerce' ), 'type' => 'color', 'description' => __( 'This controls the text color of the Revolut Card widget', 'revolut-gateway-for-woocommerce' ), 'default' => WC_REVOLUT_CARD_WIDGET_TEXT_COLOR, 'class' => 'revolut_styling_option', 'desc_tip' => true, ), 'revolut_logo_color' => array( 'title' => __( 'Revolut logo theme', 'revolut-gateway-for-woocommerce' ), 'type' => 'select', 'default' => '#7a7a7a', 'class' => 'revolut_styling_option', 'options' => array( '#7a7a7a' => __( 'Dark', 'revolut-gateway-for-woocommerce' ), '#cccccc' => __( 'Light', 'revolut-gateway-for-woocommerce' ), ), 'description' => __( 'This controls the color of the Revolut', 'revolut-gateway-for-woocommerce' ), 'desc_tip' => true, ), 'restore_button' => array( 'title' => '', 'type' => 'text', 'description' => '', ), ); } /** * Scheduled_subscription_payment function. * * @param float $amount_to_charge The amount to charge. * @param WC_Order $renewal_order WC_Order order. * * @throws Exception Exception. */ public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ) { try { $wc_order_id = $renewal_order->get_id(); $payment_token_id = $renewal_order->get_meta( '_payment_token_id', true ); if ( empty( $payment_token_id ) ) { throw new Exception( "Subscription order payment token is missing: $payment_token_id" ); } $wc_customer_id = $renewal_order->get_meta( '_wc_customer_id', true ); if ( empty( $wc_customer_id ) ) { throw new Exception( "Subscription customer ID is missing: $payment_token_id" ); } $revolut_customer_id = $this->get_revolut_customer_id( $wc_customer_id ); if ( empty( $revolut_customer_id ) ) { throw new Exception( "Can not find Subscription Revolut customer ID: $revolut_customer_id" ); } $descriptor = new WC_Revolut_Order_Descriptor( $amount_to_charge, $renewal_order->get_currency(), $revolut_customer_id ); $revolut_payment_public_id = $this->create_revolut_order( $descriptor ); // resolve revolut_public_id into revolut_order_id. $revolut_order_id = $this->get_revolut_order_by_public_id( $revolut_payment_public_id ); if ( empty( $revolut_order_id ) ) { throw new Exception( 'Can not find Revolut order ID' ); } // update internal table to avoid piggybacking on already paid order. $this->save_wc_order_id( $revolut_payment_public_id, $revolut_order_id, $wc_order_id ); // make the payment with saved card $payment_method_id. $wc_token = WC_Payment_Tokens::get( $payment_token_id ); $this->pay_by_saved_method( $revolut_order_id, $wc_token ); // payment should be processed until this point, if not throw an error. $this->check_payment_processed( $revolut_order_id ); // payment process began... $renewal_order->update_status( 'on-hold' ); $renewal_order->add_order_note( 'Payment has been successfully authorized (Order ID: ' . $revolut_order_id . ').' ); // check payment result and update order status. $this->handle_revolut_order_result( $renewal_order, $revolut_order_id ); $message = sprintf( 'Subscription charge successfully completed by Revolut (Order ID: %s)', $revolut_order_id ); $renewal_order->add_order_note( $message ); $renewal_order->set_transaction_id( $revolut_order_id ); WC_Subscriptions_Manager::process_subscription_payments_on_order( $renewal_order ); } catch ( Exception $e ) { WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $renewal_order ); $renewal_order->update_status( 'failed', 'An error occurred while Payment processing: ' . $e->getMessage() ); $this->log_error( $e->getMessage() ); return false; } return true; } /** * Process the payment and return the result. * * @param int $wc_order_id WooCommerce Order order id. * * @throws Exception Exception. */ public function process_payment( $wc_order_id ) { if ( $this->has_subscription( $wc_order_id ) ) { if ( $this->is_subs_change_payment() ) { return $this->change_subs_payment_method( $wc_order_id ); } // Regular payment with force customer enabled. return parent::process_payment( $wc_order_id ); } else { return parent::process_payment( $wc_order_id ); } } /** * Render an input in the "Change payment method" form which does not appear in the "Pay for order" page */ public function differentiate_change_payment_method_form() { echo wp_kses( '', array( 'input' => array( 'id' => array(), 'type' => array(), 'name' => array(), 'value' => array(), 'checked' => array(), 'class' => array(), 'placeholder' => array(), 'style' => array(), ), ), ); } /** * Process the payment method change for subscriptions. * * @param int $wc_order_id WooCommerce order id. * * @throws Exception Exception. */ public function change_subs_payment_method( $wc_order_id ) { try { $subscription = wc_get_order( $wc_order_id ); $revolut_payment_public_id = $this->get_post_request_data( 'revolut_public_id' ); if ( empty( $revolut_payment_public_id ) ) { throw new Exception( 'Missing revolut_public_id parameter' ); } // resolve revolut_public_id into revolut_order_id. $revolut_order_id = $this->get_revolut_order_by_public_id( $revolut_payment_public_id ); if ( empty( $revolut_order_id ) ) { throw new Exception( 'Can not find Revolut order ID' ); } if ( $this->is_using_saved_payment_method() ) { $wc_token = $this->get_selected_payment_token(); } else { $wc_token = $this->save_payment_method( $revolut_order_id ); if ( null === $wc_token ) { throw new Exception( 'An error occurred while saving payment method' ); } } $this->save_payment_token_to_order( $subscription, $wc_token, get_current_user_id() ); $this->handle_add_payment_method( $subscription, $wc_token, get_current_user_id() ); return array( 'result' => 'success', 'redirect' => $this->get_return_url( $subscription ), ); } catch ( Exception $e ) { $this->log_error( $e->getMessage() ); wc_add_notice( $e->getMessage(), 'error' ); return false; } } /** * Updates all active subscriptions payment method. * * @param WC_Subscription $current_subscription WooCommerce Subscription. * @param object $wc_token WooCommerce Payment Token. * @param int $wc_customer_id WooCommerce Customer id. * @return bool */ public function handle_add_payment_method( $current_subscription, $wc_token, $wc_customer_id ) { // remove public ID after saving the card. $this->unset_revolut_public_id(); if ( $this->update_all_subscriptions_payment_method() ) { $all_subs = wcs_get_users_subscriptions(); if ( ! empty( $all_subs ) ) { foreach ( $all_subs as $sub ) { $this->update_payment_subscription_method( $sub, $wc_token, $wc_customer_id ); } } return true; } if ( null !== $current_subscription ) { return $this->update_payment_subscription_method( $current_subscription, $wc_token, $wc_customer_id ); } return true; } /** * Updates active subscription payment method. * * @param WC_Subscription $subscription WooCommerce Subscription. * @param object $wc_token WooCommerce Payment Token id. * @param int $wc_customer_id WooCommerce Customer id. * @return bool */ public function update_payment_subscription_method( $subscription, $wc_token, $wc_customer_id ) { if ( $subscription->has_status( 'active' ) ) { WC_Subscriptions_Change_Payment_Gateway::update_payment_method( $subscription, $this->id, array( 'post_meta' => array( '_payment_token' => array( 'value' => $wc_token->get_token() ), '_payment_token_id' => array( 'value' => $wc_token->get_id() ), '_wc_customer_id' => array( 'value' => $wc_customer_id ), ), ) ); } return true; } /** * Check is $order_id a subscription. * * @param int $order_id WooCommerce Order id. * @return boolean */ public function has_subscription( $order_id ) { return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) ); } /** * Remove previous subscription information from resubscribe orders. * * @param WC_Order $resubscribe_order instance of a order object. * @return void */ public function delete_resubscribe_meta( $resubscribe_order ) { delete_post_meta( $resubscribe_order->get_id(), '_payment_token' ); delete_post_meta( $resubscribe_order->get_id(), '_payment_token_id' ); delete_post_meta( $resubscribe_order->get_id(), '_wc_customer_id' ); } /** * Render the payment method used for a subscription in the "My Subscriptions" table * * @param string $payment_method_to_display the default payment method text to display. * @param WC_Subscription $subscription the subscription details. * @return string the subscription payment method. */ public function maybe_render_subscription_payment_method( $payment_method_to_display, $subscription ) { $customer_user = $subscription->get_customer_id(); // fail for other payment methods. if ( $subscription->get_payment_method() !== $this->id || ! $customer_user ) { return $payment_method_to_display; } $revolut_payment_token_id = get_post_meta( $subscription->get_id(), '_payment_token_id', true ); $payment_method_to_display = __( 'N/A', 'revolut-gateway-for-woocommerce' ); $wc_token = WC_Payment_Tokens::get( $revolut_payment_token_id ); if ( $wc_token ) { /* translators:%1s: Card type %$2s: Card last four digits */ $payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'revolut-gateway-for-woocommerce' ), $wc_token->get_card_type(), $wc_token->get_last4() ); } return $payment_method_to_display; } /** * Update the customer_id for a subscription after using Revolut to complete a payment to make up for * an automatic renewal payment which previously failed. * * @param WC_Subscription $subscription The subscription for which the failing payment method relates. * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment). * @return void */ public function update_failing_payment_method( $subscription, $renewal_order ) { update_post_meta( $subscription->get_id(), '_payment_token', $renewal_order->get_meta( '_payment_token' ) ); update_post_meta( $subscription->get_id(), '_payment_token_id', $renewal_order->get_meta( '_payment_token_id' ) ); update_post_meta( $subscription->get_id(), '_wc_customer_id', $renewal_order->get_meta( '_wc_customer_id' ) ); } /** * Update the subscription payment meta to change from an old payment token to a new one. * * @param WC_Subscription $subscription The subscription to update. * @param WC_Payment_Token $new_token The new payment token. * @return void */ public function update_changed_subscription_token( $subscription, $new_token ) { if ( $new_token->get_gateway_id() === $this->id ) { update_post_meta( $subscription->get_id(), '_payment_token', $new_token->get_token() ); update_post_meta( $subscription->get_id(), '_payment_token_id', $new_token->get_id() ); } } /** * Display payment icons */ public function get_icon() { $icons_str = ''; $icons_str .= 'Amex'; $icons_str .= 'Visa'; $icons_str .= 'MasterCard'; return apply_filters( 'woocommerce_gateway_icon', $icons_str, $this->id ); } /** * Update all subscriptions payment methods * * @return bool */ public function update_all_subscriptions_payment_method() { return $this->check_is_post_data_submitted( 'wc-' . $this->id . '-update-subs-payment-method-card' ) || $this->check_is_post_data_submitted( 'update_all_subscriptions_payment_method' ); } /** * Check if it is using saved payment method * * @return bool */ public function is_using_saved_payment_method() { return ( $this->check_is_post_data_submitted( 'wc-' . $this->id . '-payment-token' ) && ! empty( $this->get_post_request_data( 'wc-' . $this->id . '-payment-token' ) && 'new' !== $this->get_post_request_data( 'wc-' . $this->id . '-payment-token' ) ) ); } /** * Grab selected payment token from Request * * @return string * @throws Exception Exception. */ public function get_selected_payment_token() { $wc_token_id = $this->get_posted_integer_data( 'wc-' . $this->id . '-payment-token' ); $wc_token = WC_Payment_Tokens::get( $wc_token_id ); $payment_method_id = $wc_token->get_token(); if ( empty( $payment_method_id ) || $wc_token->get_user_id() !== get_current_user_id() ) { throw new Exception( 'Can not process payment token' ); } return $wc_token; } /** * Check if save payment method requested */ public function save_payment_method_requested() { return $this->get_posted_integer_data( 'revolut_save_payment_method' ); } /** * Add public_id field and logo on card form * * @param String $public_id Revolut public id. * @param String $merchant_public_key Revolut public key. * @param String $display_tokenization Available saved card tokens. * * @return string */ public function generate_inline_revolut_form( $public_id, $merchant_public_key, $display_tokenization ) { if ( ! in_array( get_woocommerce_currency(), $this->card_payments_currency_list, true ) ) { return get_woocommerce_currency() . ' currency is not available for card payments'; } $total = WC()->cart->get_total( '' ); $currency = get_woocommerce_currency(); $total = $this->get_revolut_order_total( $total, $currency ); $mode = $this->api_settings->get_option( 'mode' ); $hide_fieldset = $this->get_option( 'card_widget_type' ) === 'popup' || $this->get_request_data( 'pay_for_order' ) ? 'height:0px;padding:0' : ''; $shipping_total = $this->get_cart_total_shipping(); $hide_payment_method = ! empty( $hide_fieldset ) && ! $display_tokenization ? true : false; $available_card_brands = $this->get_available_card_brands( $this->get_revolut_public_id() ); $display_banner = $this->api_settings->get_option( 'disable_banner' ) === 'yes' ? '
' : ''; $cardholder_name_field = ''; if ( 'yes' === $this->get_option( 'enable_cardholder_name', 'yes' ) && ! $hide_fieldset ) { $cardholder_name_field .= '

'; $cardholder_name_field .= ''; $cardholder_name_field .= '

'; } return '
' . $cardholder_name_field . '
' . $this->getSvgImage() . $display_banner . '
'; } /** * Check if cart contains subscription. */ public function cart_contains_subscription() { try { $cart_contains_subscription = WC_Subscriptions_Cart::cart_contains_subscription(); } catch ( Exception $e ) { $cart_contains_subscription = false; } return $cart_contains_subscription; } /** * Check is payment method available. */ public function is_available() { if ( is_add_payment_method_page() && ! $this->revolut_saved_cards ) { return false; } return 'yes' === $this->enabled; } /** * Check currency support for card payments */ public function check_currency_support() { if ( ! in_array( get_woocommerce_currency(), $this->card_payments_currency_list, true ) ) { return false; } return true; } /** * Check if saving payment method is mandatory */ public function is_save_payment_method_mandatory() { if ( is_add_payment_method_page() ) { return true; } if ( ! class_exists( 'WC_Subscriptions_Order' ) ) { return false; } return $this->check_is_get_data_submitted( 'change_payment_method' ) || $this->cart_contains_subscription(); } /** * Displays a checkbox to allow users to save payment methods. */ public function save_payment_method_checkbox() { if ( $this->get_option( 'card_widget_type' ) !== 'popup' && ! $this->is_save_payment_method_mandatory() && $this->revolut_saved_cards && $this->check_currency_support() && get_current_user_id() ) { return '

'; } return ''; } /** * Displays a checkbox to allow users to update all subs payments with new payment. */ public function display_update_subs_payment_checkout() { if ( ! class_exists( 'WC_Subscriptions_Order' ) ) { return false; } if ( wcs_user_has_subscription( get_current_user_id(), '', array( 'active' ) ) && is_add_payment_method_page() ) { $label = wp_kses_post( __( 'Update the Payment Method used for all of my active subscriptions.', 'revolut-gateway-for-woocommerce' ) ); $id = sprintf( 'wc-%1$s-update-subs-payment-method-card', $this->id ); woocommerce_form_field( $id, array( 'type' => 'checkbox', 'label' => $label, 'default' => false, ) ); } } /** * Revolut logo in SVG format. */ public function getSvgImage() { return ' '; } }