Plugin Directory

Changeset 3488088


Ignore:
Timestamp:
03/22/2026 07:26:37 AM (13 days ago)
Author:
patsatech
Message:

1.4.6

Security and reliability:

  • Return URL follows your site URL (HTTPS when configured) instead of forcing HTTP. Override with filter woocommerce_sagepayform_notify_url.
  • Callback runs only on the WooCommerce API route (wc-api), not on every init request.

Payment verification:

  • Validates decrypted Opayo data before completing an order.
  • Remembers each generated VendorTxCode on the order (supports multiple pending attempts, e.g. refresh or extra tabs); regex fallback for older VendorTxCode formats.
  • Verifies amount and currency against the order; skips duplicate completion if the order is already paid.
  • Records VPSTxId via payment_complete() for the WooCommerce transaction id.
  • Safer decrypt path when crypt is missing, malformed, or invalid.

Fixes and compatibility:

  • US eMailMessage: applies when billing or shipping country is US (fixes incorrect get_shipping_state usage).
  • WooCommerce Blocks: guards when the gateway is missing from the registry.
  • Blocks checkout script text domain aligned with the main plugin (woo-sagepayform-patsatech).
  • Uses wc_get_order() and a paid-status fallback when wc_get_is_paid_statuses() is unavailable.

1.4.7

Elavon Opayo hostname update (Form register endpoint path unchanged):

  • Test: https://sandbox.opayo.eu.elavon.com/gateway/service/vspform-register.vsp (was https://test.sagepay.com/gateway/service/vspform-register.vsp).
  • Live: https://live.opayo.eu.elavon.com/gateway/service/vspform-register.vsp (was https://live.sagepay.com/gateway/service/vspform-register.vsp).

Developer hook:

  • Filter woocommerce_sagepayform_register_url — return a custom URL for each $mode (test or live) for legacy sagepay.com hosts or account-specific endpoints during migration.
Location:
sagepay-form-gateway-for-woocommerce/trunk
Files:
2 added
2 edited

Legend:

Unmodified
Added
Removed
  • sagepay-form-gateway-for-woocommerce/trunk/readme.txt

    r2158331 r3488088  
    44Requires at least: 4.5
    55Tested up to: 5.2.3
    6 Stable tag: 1.4.5
     6Stable tag: 1.4.7
    77License: GPLv2 or later
    88
     
    4646= 1.4.5 =
    4747* Updated to support WooCommerce 3.7+ and Wordpress 5.2+
     48
     49= 1.4.6 =
     50Security and reliability:
     51* Return URL follows your site URL (HTTPS when configured) instead of forcing HTTP. Override with filter `woocommerce_sagepayform_notify_url`.
     52* Callback runs only on the WooCommerce API route (`wc-api`), not on every `init` request.
     53
     54Payment verification:
     55* Validates decrypted Opayo data before completing an order.
     56* Remembers each generated VendorTxCode on the order (supports multiple pending attempts, e.g. refresh or extra tabs); regex fallback for older VendorTxCode formats.
     57* Verifies amount and currency against the order; skips duplicate completion if the order is already paid.
     58* Records VPSTxId via `payment_complete()` for the WooCommerce transaction id.
     59* Safer decrypt path when `crypt` is missing, malformed, or invalid.
     60
     61Fixes and compatibility:
     62* US eMailMessage: applies when billing or shipping country is US (fixes incorrect `get_shipping_state` usage).
     63* WooCommerce Blocks: guards when the gateway is missing from the registry.
     64* Blocks checkout script text domain aligned with the main plugin (`woo-sagepayform-patsatech`).
     65* Uses `wc_get_order()` and a paid-status fallback when `wc_get_is_paid_statuses()` is unavailable.
     66
     67= 1.4.7 =
     68Elavon Opayo hostname update (Form register endpoint path unchanged):
     69* Test: `https://sandbox.opayo.eu.elavon.com/gateway/service/vspform-register.vsp` (was `https://test.sagepay.com/gateway/service/vspform-register.vsp`).
     70* Live: `https://live.opayo.eu.elavon.com/gateway/service/vspform-register.vsp` (was `https://live.sagepay.com/gateway/service/vspform-register.vsp`).
     71
     72Developer hook:
     73* Filter `woocommerce_sagepayform_register_url` — return a custom URL for each `$mode` (`test` or `live`) for legacy sagepay.com hosts or account-specific endpoints during migration.
  • sagepay-form-gateway-for-woocommerce/trunk/sagepayform.php

    r2158331 r3488088  
    44 * Plugin URI: http://www.patsatech.com/
    55 * Description: WooCommerce Plugin for accepting payment through SagePay Form Gateway.
    6  * Version: 1.4.5
     6 * Version: 1.4.7
    77 * Author: PatSaTECH
    88 * Author URI: http://www.patsatech.com
     
    6868            $this->send_shipping= $this->settings['send_shipping'];
    6969            $this->cardtypes    = $this->settings['cardtypes'];
    70             $this->notify_url   = str_replace( 'https:', 'http:', home_url( '/wc-api/woocommerce_sagepayform' ) );
     70            $this->notify_url   = apply_filters( 'woocommerce_sagepayform_notify_url', home_url( '/wc-api/woocommerce_sagepayform' ) );
    7171
    7272            // Actions
    73             add_action( 'init', array( $this, 'successful_request' ) );
    7473            add_action( 'woocommerce_api_woocommerce_sagepayform', array( $this, 'successful_request' ) );
    7574            add_action( 'woocommerce_receipt_sagepayform', array( $this, 'receipt_page' ) );
     
    266265            global $woocommerce;
    267266
    268             $order = new WC_Order( $order_id );
    269 
    270             if( $this->mode == 'test' ){
    271                 $gateway_url = 'https://test.sagepay.com/gateway/service/vspform-register.vsp';
    272             }else if( $this->mode == 'live' ){
    273                 $gateway_url = 'https://live.sagepay.com/gateway/service/vspform-register.vsp';
    274             }
     267            $order = wc_get_order( $order_id );
     268            if ( ! $order ) {
     269                return '';
     270            }
     271
     272            // Elavon Opayo URL migration: test.sagepay.com / live.sagepay.com → sandbox / live on opayo.eu.elavon.com (same path).
     273            if ( 'test' === $this->mode ) {
     274                $gateway_url = 'https://sandbox.opayo.eu.elavon.com/gateway/service/vspform-register.vsp';
     275            } else {
     276                $gateway_url = 'https://live.opayo.eu.elavon.com/gateway/service/vspform-register.vsp';
     277            }
     278
     279            $gateway_url = apply_filters( 'woocommerce_sagepayform_register_url', $gateway_url, $this->mode );
    275280
    276281            $basket = '';
     
    354359      $time_stamp = date("ymdHis");
    355360      $orderid = $this->vendor_name . "-" . $time_stamp . "-" . $order_id;
     361
     362            $order->add_meta_data( '_sagepay_vendor_tx_code', $orderid, false );
     363            $order->save();
    356364
    357365      $sagepay_arg['ReferrerID']            = 'CC923B06-40D5-4713-85C1-700D690550BF';
     
    412420      $sagepay_arg['VendorEMail']       = $this->vendoremail;
    413421      $sagepay_arg['SendEMail']             = $this->sendemails;
    414             if( $order->get_shipping_state == 'US' ){
     422            if ( 'US' === $order->get_billing_country() || 'US' === $order->get_shipping_country() ) {
    415423                $sagepay_arg['eMailMessage']    = $this->emailmessage;
    416424            }
     
    468476        function process_payment( $order_id ) {
    469477
    470             $order = new WC_Order( $order_id );
     478            $order = wc_get_order( $order_id );
     479            if ( ! $order ) {
     480                return array(
     481                    'result'   => 'failure',
     482                    'redirect' => '',
     483                );
     484            }
    471485
    472486            return array(
     
    493507         **/
    494508        function successful_request() {
    495             global $woocommerce;
    496 
    497             if ( isset($_REQUEST['crypt']) && !empty($_REQUEST['crypt']) ) {
    498 
    499                 $transaction_response = $this->decode(str_replace(' ', '+',$_REQUEST['crypt']));
    500 
    501                 $order_id = explode('-',$transaction_response['VendorTxCode']);
    502 
    503                 if ( $transaction_response['Status'] == 'OK' || $transaction_response['Status'] == 'AUTHENTICATED'|| $transaction_response['Status'] == 'REGISTERED' ) {
    504 
    505                     $order = new WC_Order( $order_id[2] );
    506 
    507                     $order->add_order_note(sprintf(__('SagePay Form Payment Completed. The Reference Number is %s.', 'woo-sagepayform-patsatech'), $transaction_response['VPSTxId']));
    508 
    509                     $order->payment_complete();
    510 
    511                     wp_redirect( $this->get_return_url( $order ) ); exit;
    512 
    513                 }else{
    514 
    515                     wc_add_notice( sprintf(__('Transaction Failed. The Error Message was %s', 'woo-sagepayform-patsatech'), $transaction_response['StatusDetail'] ), $notice_type = 'error' );
    516 
    517                     wp_redirect( get_permalink(get_option( 'woocommerce_checkout_page_id' )) ); exit;
    518 
    519                 }
    520             }
     509
     510            if ( empty( $_REQUEST['crypt'] ) ) {
     511                return;
     512            }
     513
     514            $crypt = str_replace( ' ', '+', wp_unslash( $_REQUEST['crypt'] ) );
     515            $transaction_response = $this->decode( $crypt );
     516
     517            if ( empty( $transaction_response['VendorTxCode'] ) || empty( $transaction_response['Status'] ) ) {
     518                wc_add_notice( __( 'Invalid payment response.', 'woo-sagepayform-patsatech' ), 'error' );
     519                wp_safe_redirect( wc_get_checkout_url() );
     520                exit;
     521            }
     522
     523            $order = $this->get_order_by_vendor_tx_code( $transaction_response['VendorTxCode'] );
     524
     525            if ( ! $order ) {
     526                wc_add_notice( __( 'Order not found for this payment.', 'woo-sagepayform-patsatech' ), 'error' );
     527                wp_safe_redirect( wc_get_checkout_url() );
     528                exit;
     529            }
     530
     531            $success_statuses = array( 'OK', 'AUTHENTICATED', 'REGISTERED' );
     532
     533            if ( in_array( $transaction_response['Status'], $success_statuses, true ) ) {
     534
     535                if ( isset( $transaction_response['Amount'] ) ) {
     536                    $decimals = wc_get_price_decimals();
     537                    $expected = wc_format_decimal( $order->get_total(), $decimals );
     538                    $received = wc_format_decimal( $transaction_response['Amount'], $decimals );
     539                    if ( (string) $expected !== (string) $received ) {
     540                        $order->add_order_note(
     541                            sprintf(
     542                                __( 'Opayo response amount mismatch. Order total %1$s, gateway reported %2$s.', 'woo-sagepayform-patsatech' ),
     543                                $expected,
     544                                $received
     545                            )
     546                        );
     547                        wc_add_notice( __( 'Payment could not be verified. Please contact the store.', 'woo-sagepayform-patsatech' ), 'error' );
     548                        wp_safe_redirect( wc_get_checkout_url() );
     549                        exit;
     550                    }
     551                }
     552
     553                if ( ! empty( $transaction_response['Currency'] ) && strtoupper( $order->get_currency() ) !== strtoupper( $transaction_response['Currency'] ) ) {
     554                    $order->add_order_note(
     555                        sprintf(
     556                            __( 'Opayo response currency mismatch. Order currency %1$s, gateway reported %2$s.', 'woo-sagepayform-patsatech' ),
     557                            $order->get_currency(),
     558                            $transaction_response['Currency']
     559                        )
     560                    );
     561                    wc_add_notice( __( 'Payment could not be verified. Please contact the store.', 'woo-sagepayform-patsatech' ), 'error' );
     562                    wp_safe_redirect( wc_get_checkout_url() );
     563                    exit;
     564                }
     565
     566                $paid_statuses = function_exists( 'wc_get_is_paid_statuses' ) ? wc_get_is_paid_statuses() : array( 'processing', 'completed' );
     567                if ( in_array( $order->get_status(), $paid_statuses, true ) ) {
     568                    wp_safe_redirect( $this->get_return_url( $order ) );
     569                    exit;
     570                }
     571
     572                $vps_id = isset( $transaction_response['VPSTxId'] ) ? $transaction_response['VPSTxId'] : '';
     573
     574                $order->add_order_note(
     575                    sprintf(
     576                        __( 'SagePay Form Payment Completed. The Reference Number is %s.', 'woo-sagepayform-patsatech' ),
     577                        $vps_id
     578                    )
     579                );
     580
     581                $order->payment_complete( $vps_id );
     582
     583                $order->delete_meta_data( '_sagepay_vendor_tx_code' );
     584                $order->save();
     585
     586                wp_safe_redirect( $this->get_return_url( $order ) );
     587                exit;
     588            }
     589
     590            $detail = isset( $transaction_response['StatusDetail'] ) ? $transaction_response['StatusDetail'] : $transaction_response['Status'];
     591            wc_add_notice(
     592                sprintf(
     593                    __( 'Transaction Failed. The Error Message was %s', 'woo-sagepayform-patsatech' ),
     594                    $detail
     595                ),
     596                'error'
     597            );
     598
     599            wp_safe_redirect( wc_get_checkout_url() );
     600            exit;
     601        }
     602
     603        /**
     604         * Resolve order from VendorTxCode (order meta first, then legacy pattern).
     605         *
     606         * @param string $vendor_tx_code Value returned by Opayo in the crypt payload.
     607         * @return WC_Order|false
     608         */
     609        private function get_order_by_vendor_tx_code( $vendor_tx_code ) {
     610
     611            if ( '' === $vendor_tx_code ) {
     612                return false;
     613            }
     614
     615            $orders = wc_get_orders(
     616                array(
     617                    'limit'      => 1,
     618                    'orderby'    => 'none',
     619                    'meta_query' => array(
     620                        array(
     621                            'key'   => '_sagepay_vendor_tx_code',
     622                            'value' => $vendor_tx_code,
     623                        ),
     624                    ),
     625                )
     626            );
     627
     628            if ( ! empty( $orders ) && $orders[0] instanceof WC_Order ) {
     629                return $orders[0];
     630            }
     631
     632            if ( preg_match( '/-(\d{12})-(.+)$/', $vendor_tx_code, $matches ) ) {
     633                $legacy_order = wc_get_order( $matches[2] );
     634                if ( $legacy_order ) {
     635                    return $legacy_order;
     636                }
     637            }
     638
     639            return false;
    521640        }
    522641
     
    558677
    559678        private function decodeAndDecrypt($strIn) {
    560             $strIn = substr($strIn, 1);
    561             $strIn = pack('H*', $strIn);
    562             //return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->vendor_pass, $strIn, MCRYPT_MODE_CBC, $this->vendor_pass);
    563             return openssl_decrypt($strIn, 'AES-128-CBC', $this->vendor_pass, OPENSSL_RAW_DATA, $this->vendor_pass);
     679            if ( '' === $strIn || '@' !== $strIn[0] ) {
     680                return false;
     681            }
     682            $strIn = substr( $strIn, 1 );
     683            if ( '' === $strIn || 0 !== ( strlen( $strIn ) % 2 ) ) {
     684                return false;
     685            }
     686            $binary = @pack( 'H*', $strIn );
     687            if ( false === $binary ) {
     688                return false;
     689            }
     690            return openssl_decrypt( $binary, 'AES-128-CBC', $this->vendor_pass, OPENSSL_RAW_DATA, $this->vendor_pass );
    564691        }
    565692
     
    571698
    572699        public function decode($strIn) {
    573             $decodedString = $this->decodeAndDecrypt($strIn);
    574             parse_str($decodedString, $sagePayResponse);
     700            $decodedString = $this->decodeAndDecrypt( $strIn );
     701            if ( false === $decodedString || '' === $decodedString ) {
     702                return array();
     703            }
     704            $sagePayResponse = array();
     705            parse_str( $decodedString, $sagePayResponse );
    575706            return $sagePayResponse;
    576707        }
     
    587718    }
    588719
    589     /**
    590      * Add the gateway to WooCommerce
    591      **/
    592     function add_sagepayform_gateway( $methods ) {
    593         $methods[] = 'woocommerce_sagepayform'; return $methods;
     720}
     721
     722
     723/**
     724 * Add the gateway to WooCommerce
     725 **/
     726function add_sagepayform_gateway( $methods ) {
     727    $methods[] = 'woocommerce_sagepayform'; return $methods;
     728}
     729
     730add_filter('woocommerce_payment_gateways', 'add_sagepayform_gateway' );
     731
     732add_action( 'woocommerce_blocks_loaded', 'woocommerce_sagepayform_woocommerce_blocks_support' );
     733
     734/**
     735 * Add the gateway to WooCommerce Blocks.
     736 *
     737 * @since 1.4.19
     738 */
     739function woocommerce_sagepayform_woocommerce_blocks_support() {
     740    if ( class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' ) ) {
     741        require_once plugin_dir_path(__FILE__) . 'class-block.php';
     742        add_action(
     743            'woocommerce_blocks_payment_method_type_registration',
     744            function ( Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry $payment_method_registry ) {
     745                $payment_method_registry->register( new woocommerce_sagepayform_blocks() );
     746            }
     747        );
    594748    }
    595 
    596     add_filter('woocommerce_payment_gateways', 'add_sagepayform_gateway' );
    597 
    598749}
Note: See TracChangeset for help on using the changeset viewer.