Changeset 3488088
- Timestamp:
- 03/22/2026 07:26:37 AM (13 days ago)
- Location:
- sagepay-form-gateway-for-woocommerce/trunk
- Files:
-
- 2 added
- 2 edited
-
checkout.js (added)
-
class-block.php (added)
-
readme.txt (modified) (2 diffs)
-
sagepayform.php (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
-
sagepay-form-gateway-for-woocommerce/trunk/readme.txt
r2158331 r3488088 4 4 Requires at least: 4.5 5 5 Tested up to: 5.2.3 6 Stable tag: 1.4. 56 Stable tag: 1.4.7 7 7 License: GPLv2 or later 8 8 … … 46 46 = 1.4.5 = 47 47 * Updated to support WooCommerce 3.7+ and Wordpress 5.2+ 48 49 = 1.4.6 = 50 Security 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 54 Payment 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 61 Fixes 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 = 68 Elavon 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 72 Developer 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 4 4 * Plugin URI: http://www.patsatech.com/ 5 5 * Description: WooCommerce Plugin for accepting payment through SagePay Form Gateway. 6 * Version: 1.4. 56 * Version: 1.4.7 7 7 * Author: PatSaTECH 8 8 * Author URI: http://www.patsatech.com … … 68 68 $this->send_shipping= $this->settings['send_shipping']; 69 69 $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' ) ); 71 71 72 72 // Actions 73 add_action( 'init', array( $this, 'successful_request' ) );74 73 add_action( 'woocommerce_api_woocommerce_sagepayform', array( $this, 'successful_request' ) ); 75 74 add_action( 'woocommerce_receipt_sagepayform', array( $this, 'receipt_page' ) ); … … 266 265 global $woocommerce; 267 266 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 ); 275 280 276 281 $basket = ''; … … 354 359 $time_stamp = date("ymdHis"); 355 360 $orderid = $this->vendor_name . "-" . $time_stamp . "-" . $order_id; 361 362 $order->add_meta_data( '_sagepay_vendor_tx_code', $orderid, false ); 363 $order->save(); 356 364 357 365 $sagepay_arg['ReferrerID'] = 'CC923B06-40D5-4713-85C1-700D690550BF'; … … 412 420 $sagepay_arg['VendorEMail'] = $this->vendoremail; 413 421 $sagepay_arg['SendEMail'] = $this->sendemails; 414 if ( $order->get_shipping_state == 'US' ){422 if ( 'US' === $order->get_billing_country() || 'US' === $order->get_shipping_country() ) { 415 423 $sagepay_arg['eMailMessage'] = $this->emailmessage; 416 424 } … … 468 476 function process_payment( $order_id ) { 469 477 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 } 471 485 472 486 return array( … … 493 507 **/ 494 508 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; 521 640 } 522 641 … … 558 677 559 678 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 ); 564 691 } 565 692 … … 571 698 572 699 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 ); 575 706 return $sagePayResponse; 576 707 } … … 587 718 } 588 719 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 **/ 726 function add_sagepayform_gateway( $methods ) { 727 $methods[] = 'woocommerce_sagepayform'; return $methods; 728 } 729 730 add_filter('woocommerce_payment_gateways', 'add_sagepayform_gateway' ); 731 732 add_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 */ 739 function 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 ); 594 748 } 595 596 add_filter('woocommerce_payment_gateways', 'add_sagepayform_gateway' );597 598 749 }
Note: See TracChangeset
for help on using the changeset viewer.