--- a/fortis-for-woocommerce/assets/js/frontend/blocks.asset.php
+++ b/fortis-for-woocommerce/assets/js/frontend/blocks.asset.php
@@ -1 +1,4 @@
-<?php return array('dependencies' => array('react', 'wc-blocks-registry', 'wc-settings', 'wp-html-entities', 'wp-i18n'), 'version' => 'f57c9a5a613550657d38');
+<?php return array(
+ 'dependencies' => array( 'react', 'wc-blocks-registry', 'wc-settings', 'wp-html-entities', 'wp-i18n' ),
+ 'version' => 'f57c9a5a613550657d38',
+);
--- a/fortis-for-woocommerce/classes/FortisApi.php
+++ b/fortis-for-woocommerce/classes/FortisApi.php
@@ -594,7 +594,7 @@
}
if ( ! $surcharge_item_id ) {
- $order->add_order_note( __( 'No Surcharge fee found to refund.', 'woocommerce' ) );
+ $order->add_order_note( __( 'No Surcharge fee found to refund.', 'fortis-for-woocommerce' ) );
return false;
}
--- a/fortis-for-woocommerce/classes/FortisFrameworkApi.php
+++ b/fortis-for-woocommerce/classes/FortisFrameworkApi.php
@@ -18,8 +18,8 @@
* @package WooCommerce
*/
class FortisframeworkApi extends WC_Payment_Gateway {
- public const DEVELOPER_ID_SANDBOX = 'woo120sb';
- public const DEVELOPER_ID_PRODUCTION = 'woo25120';
+ public const DEVELOPER_ID_SANDBOX = 'woo130sb';
+ public const DEVELOPER_ID_PRODUCTION = 'woo26130';
/**
* Gateway id
--- a/fortis-for-woocommerce/classes/FortisTransactionService.php
+++ b/fortis-for-woocommerce/classes/FortisTransactionService.php
@@ -35,6 +35,142 @@
}
/**
+ * Resolve a Fortis transaction to a WooCommerce order and perform validation.
+ *
+ * @param FortisApi $fortisApi Fortis API instance.
+ * @param string $transaction_id Fortis transaction id.
+ * @param bool $enable_logging Whether to log validation failures.
+ * @param mixed $logger Optional logger (expects add method).
+ *
+ * @return array|null Returns array with 'order', 'fortis_transaction' and 'is_refund' or null on failure.
+ */
+ public function resolveOrderFromTransaction(
+ FortisApi $fortisApi,
+ string $transaction_id,
+ bool $enable_logging = false,
+ $logger = null
+ ): ?array {
+ $fortis_transaction_json = $fortisApi->get_transaction( $transaction_id );
+ $fortis_transaction = json_decode( wp_unslash( $fortis_transaction_json ), true );
+
+ if ( ! is_array( $fortis_transaction ) || ! isset( $fortis_transaction['data'] ) ) {
+ if ( $enable_logging && is_callable( array( $logger, 'add' ) ) ) {
+ $logger->add(
+ 'fortis_ach_notify',
+ 'Failed to fetch transaction from Fortis for ID: ' . $transaction_id
+ );
+ }
+
+ return null;
+ }
+
+ // Use authoritative fields from Fortis
+ $order_id = $fortis_transaction['data']['description'] ?? '';
+
+ // Fall back to matching by transaction_api_id (which we save on order creation) when description is missing.
+ if ( '' === $order_id ) {
+ $tx_api_id = $fortis_transaction['data']['transaction_api_id'] ?? '';
+ if ( $tx_api_id ) {
+ // Attempt to find an order that has the saved transaction_api_id meta.
+ $orders = wc_get_orders(
+ array(
+ 'limit' => 1,
+ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+ 'meta_query' => array(
+ array(
+ 'key' => 'transaction_api_id',
+ 'value' => $tx_api_id,
+ ),
+ ),
+ )
+ );
+
+ if ( ! empty( $orders ) ) {
+ $order = $orders[0];
+ $order_id = $order->get_id();
+ } else {
+ if ( $enable_logging && is_callable( array( $logger, 'add' ) ) ) {
+ $logger->add(
+ 'fortis_ach_notify',
+ "No order description on Fortis txn $transaction_id and no order found with transaction_api_id: $tx_api_id"
+ );
+ }
+
+ return null;
+ }
+ } else {
+ if ( $enable_logging && is_callable( array( $logger, 'add' ) ) ) {
+ $logger->add(
+ 'fortis_ach_notify',
+ 'No order description found on Fortis transaction: ' . $transaction_id
+ );
+ }
+
+ return null;
+ }
+ }
+
+ // If we didn't find the order via transaction_api_id earlier, look it up by the description value.
+ if ( empty( $order ) ) {
+ $order = wc_get_order( $order_id );
+ if ( ! $order ) {
+ if ( $enable_logging && is_callable( array( $logger, 'add' ) ) ) {
+ $logger->add( 'fortis_ach_notify', 'Order not found for ID from Fortis transaction: ' . $order_id );
+ }
+
+ return null;
+ }
+ }
+
+ // Verify location id
+ $remote_location = $fortis_transaction['data']['location_id'] ?? '';
+ if ( $remote_location && $remote_location !== $fortisApi->location_id ) {
+ if ( $enable_logging && is_callable( array( $logger, 'add' ) ) ) {
+ $logger->add(
+ 'fortis_ach_notify',
+ "Fortis transaction location mismatch for txn $transaction_id: expected {$fortisApi->location_id}, got $remote_location"
+ );
+ }
+
+ return null;
+ }
+
+ // Verify amount if present.
+ $remote_amount = $fortis_transaction['data']['transaction_amount'] ?? '';
+ if ( '' !== $remote_amount && is_numeric( $remote_amount ) ) {
+ $remote_amount_str = (string) $remote_amount;
+ $remote_amount_cents = (int) round( floatval( $remote_amount_str ) );
+ $interpretation = 'cents';
+
+ $order_amount_cents = (int) bcmul( (string) $order->get_total(), '100', 0 );
+ if ( $remote_amount_cents !== $order_amount_cents ) {
+ if ( $enable_logging && is_callable( array( $logger, 'add' ) ) ) {
+ $logger->add(
+ 'fortis_ach_notify',
+ "Fortis transaction amount mismatch for txn $transaction_id: expected $order_amount_cents, got $remote_amount_cents (interpreted as $interpretation)"
+ );
+ }
+
+ return null;
+ }
+ }
+
+ $ach_refund_ids = $order->get_meta( 'ach_refund_ids' );
+ if ( '' !== $ach_refund_ids ) {
+ $ach_refund_ids = json_decode( $ach_refund_ids );
+ } else {
+ $ach_refund_ids = array();
+ }
+ $is_refund = in_array( $transaction_id, $ach_refund_ids, true );
+
+ return array(
+ 'order' => $order,
+ 'fortis_transaction' => $fortis_transaction,
+ 'is_refund' => $is_refund,
+ );
+ }
+
+ /**
* @param $id
* @param $subtotal
* @param $taxAmount
@@ -166,17 +302,20 @@
* @throws Exception
*/
public function getBillingData(): array {
- $verify = wp_verify_nonce(
- parse_str( $_POST['fields'], $fields )
- ?? $fields['woocommerce-process-checkout-nonce'] ?? '',
- $_REQUEST['action'] ?? ''
- );
+ // Prepare and sanitize posted fields
+ $raw_fields = sanitize_text_field( wp_unslash( $_POST['fields'] ?? '' ) );
+ parse_str( $raw_fields, $fields_raw );
+ $fields = is_array( $fields_raw ) ? array_map( 'sanitize_text_field', $fields_raw ) : array();
+
+ $nonce = $fields['woocommerce-process-checkout-nonce'] ?? '';
+ $action = sanitize_text_field( wp_unslash( $_REQUEST['action'] ?? '' ) );
+ $verify = wp_verify_nonce( $nonce, $action );
if ( $verify ) {
http_response_code( 417 );
exit();
}
- if ( ! empty( $_POST['fields'] ) ) {
- parse_str( filter_var( wp_unslash( $_POST['fields'] ), FILTER_UNSAFE_RAW ), $post_data );
+ if ( ! empty( $raw_fields ) ) {
+ $post_data = $fields;
$termsAccepted = false;
if ( ! empty( $post_data['terms'] ) && in_array(
--- a/fortis-for-woocommerce/classes/WC_Gateway_Fortis.php
+++ b/fortis-for-woocommerce/classes/WC_Gateway_Fortis.php
@@ -30,7 +30,7 @@
public const ID = 'fortis';
- public const VERSION = '1.2.0';
+ public const VERSION = '1.3.0';
public const TITLE = 'title';
public const DESCRIPTION = 'description';
@@ -159,7 +159,7 @@
* @return void
*/
public static function show_cart_messages() {
- $nonce = $_REQUEST['_wpnonce'] ?? '';
+ $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ?? '' ) );
$verify = wp_verify_nonce( $nonce, 'show_cart_messages' );
if ( $verify ) {
http_response_code( 417 );
@@ -199,6 +199,7 @@
$order_notes = wp_cache_get( $cache_key );
if ( $order_notes === false ) {
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Cached query for better performance
$order_notes = $wpdb->get_results(
$wpdb->prepare(
"SELECT comment_content FROM {$wpdb->comments} WHERE comment_post_ID = %d
@@ -219,12 +220,12 @@
* @throws WC_Data_Exception WC_Data_Exception.
*/
public function check_fortis_response() {
- $nonce = $_REQUEST['_wpnonce'] ?? '';
+ $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ?? '' ) );
$wcsession = WC()->session;
if ( empty( $nonce ) ) {
if ( $wcsession->get( 'post' ) ) {
$blockpost = $wcsession->get( 'post' );
- $nonce = $blockpost['fortis-nonce'] ?? '';
+ $nonce = sanitize_text_field( $blockpost['fortis-nonce'] ?? '' );
}
}
$action = 'check_fortis_response';
@@ -342,7 +343,12 @@
true
);
} elseif ( isset( $_POST['fortis_billing_data'] ) ) {
- parse_str( $_POST['fortis_billing_data'], $billingData );
+ $raw_fortis_billing = filter_var( wp_unslash( $_POST['fortis_billing_data'] ?? '' ), FILTER_UNSAFE_RAW );
+ parse_str( $raw_fortis_billing, $billingData_raw );
+ $billingData = is_array( $billingData_raw ) ? array_map(
+ 'sanitize_text_field',
+ $billingData_raw
+ ) : array();
} else {
$billingData = array();
}
@@ -355,11 +361,11 @@
if ( $status !== 1 && $status !== 5 ) {
$message = match ( $status ) {
- 2 => __( 'The transaction was declined', 'woocommerce' ),
- 3 => __( 'The transaction had an error', 'woocommerce' ),
- 4 => __( 'The transaction was canceled', 'woocommerce' ),
- 5 => __( 'The transaction is pending', 'woocommerce' ),
- default => __( 'Payment failed.', 'woocommerce' ),
+ 2 => __( 'The transaction was declined', 'fortis-for-woocommerce' ),
+ 3 => __( 'The transaction had an error', 'fortis-for-woocommerce' ),
+ 4 => __( 'The transaction was canceled', 'fortis-for-woocommerce' ),
+ 5 => __( 'The transaction is pending', 'fortis-for-woocommerce' ),
+ default => __( 'Payment failed.', 'fortis-for-woocommerce' ),
};
// Re-draft pending order if on Blocks checkout and failed tokenized payment
@@ -836,7 +842,7 @@
*
* @since 1.0.0
*/
- $this->form_fields = apply_filters( 'fnb_fortis_settings', $form_fields ) ?? $form_fields;
+ $this->form_fields = apply_filters( 'fortis_for_woocommerce_settings', $form_fields ) ?? $form_fields;
}
/**
@@ -1040,11 +1046,11 @@
$show_validation_animation = 'yes' === $this->settings[ FortisApi::SHOWVALIDATIONANIMATION ] ? 'true' : 'false';
$hide_agreement_checkbox = 'yes' === $this->settings[ FortisApi::HIDEAGREEMENTCHECKBOX ] ? 'true' : 'false';
- $verify = wp_verify_nonce(
- parse_str( $_POST['post_data'] ?? '', $fields )
- ?? $fields['woocommerce-process-checkout-nonce'] ?? '',
- 'checkout'
- );
+ $raw_post_data = filter_var( wp_unslash( $_POST['post_data'] ?? '' ), FILTER_UNSAFE_RAW );
+ parse_str( $raw_post_data, $fields_raw );
+ $fields = is_array( $fields_raw ) ? array_map( 'sanitize_text_field', $fields_raw ) : array();
+ $nonce = $fields['woocommerce-process-checkout-nonce'] ?? '';
+ $verify = wp_verify_nonce( $nonce, 'checkout' );
if ( $verify ) {
http_response_code( 417 );
exit();
@@ -1169,7 +1175,7 @@
$wcsession = WC()->session;
$customer_id = $wcsession->get( 'customer' )['id'];
- $nonce = $_REQUEST['_wpnonce'] ?? '';
+ $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ?? '' ) );
$verify = wp_verify_nonce( $nonce, - 1 );
if ( $verify ) {
http_response_code( 417 );
@@ -1313,7 +1319,7 @@
* @throws WC_Data_Exception WC_Data_Exception.
*/
public function process_review_payment() {
- $nonce = $_REQUEST['_wpnonce'] ?? '';
+ $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ?? '' ) );
$verify = wp_verify_nonce( $nonce, 'process_review_payment' );
if ( $verify ) {
http_response_code( 417 );
@@ -1341,7 +1347,7 @@
*
* @since 1.0.0
*/
- return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id );
+ return apply_filters( 'fortis_for_woocommerce_gateway_icon', $icon, $this->id );
}
/**
@@ -1354,7 +1360,7 @@
* @since 1.0.0
*/
public function process_payment( $order_id ) {
- $nonce = $_REQUEST['woocommerce-process-checkout-nonce'] ?? '';
+ $nonce = sanitize_text_field( wp_unslash( $_REQUEST['woocommerce-process-checkout-nonce'] ?? '' ) );
$verify = wp_verify_nonce( $nonce, 'process_review_payment' );
if ( $verify ) {
http_response_code( 417 );
@@ -1631,7 +1637,7 @@
*
* @since 1.0.0
*/
- $notice_types = apply_filters( 'woocommerce_notice_types', array( 'error', 'success', 'notice' ) );
+ $notice_types = apply_filters( 'fortis_for_woocommerce_notice_types', array( 'error', 'success', 'notice' ) );
// Buffer output.
ob_start();
@@ -1668,15 +1674,11 @@
* @return void
* @throws WC_Data_Exception WC_Data_Exception.
*/
+ // Webhook endpoint: nonces are not applicable for external webhook POSTs.
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Webhook POSTs cannot use WP nonces and are authenticated via Fortis signatures/transaction data.
public function check_fortis_notify_response(): void {
- $nonce = $_REQUEST['_wpnonce'] ?? '';
- $action = 'check_fortis_notify_response';
- $verify = wp_verify_nonce( $nonce, $action );
- if ( $verify ) {
- http_response_code( 417 );
- exit();
- }
// Log notify response for debugging purposes.
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
if ( $this->logging ) {
self::$wc_logger->add(
'fortis_ach_notify',
@@ -1684,33 +1686,58 @@
);
self::$wc_logger->add(
'fortis_ach_notify',
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
'Notify GET: ' . wp_json_encode( array_map( 'sanitize_text_field', $_GET ) )
);
}
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- External webhook POST; validated by Fortis and sanitized below.
if ( ! isset( $_POST['data'] ) ) {
http_response_code( 417 );
exit();
}
- $fortis_data = json_decode( wp_unslash( sanitize_text_field( wp_unslash( $_POST['data'] ) ) ) );
- $order_id = $fortis_data->description;
- $order = wc_get_order( $order_id );
- $transaction_id = $fortis_data->id;
- $ach_refund_ids = $order->get_meta( 'ach_refund_ids' );
- if ( '' !== $ach_refund_ids ) {
- $ach_refund_ids = json_decode( $ach_refund_ids );
- } else {
- $ach_refund_ids = array();
+ $fortis_data = json_decode( wp_unslash( sanitize_text_field( wp_unslash( $_POST['data'] ) ) ) );
+ // phpcs:enable WordPress.Security.NonceVerification.Missing
+
+ $transaction_id = $fortis_data->id ?? '';
+
+ // If Fortis sends a CREATE event it often arrives before the description is populated.
+ // Acknowledge CREATE events with HTTP 200 and do not attempt to process them yet.
+ if ( isset( $fortis_data->type ) && 'CREATE' === strtoupper( $fortis_data->type ) ) {
+ if ( $this->logging ) {
+ self::$wc_logger->add(
+ 'fortis_ach_notify',
+ 'Received CREATE webhook for txn ' . $transaction_id . ' - acknowledging and skipping processing.'
+ );
+ }
+ http_response_code( 200 );
+ exit();
}
- $is_refund = false;
- if ( in_array( $transaction_id, $ach_refund_ids, true ) ) {
- $is_refund = true;
+
+ $transactionService = new FortisTransactionService();
+ $resolved = $transactionService->resolveOrderFromTransaction(
+ $this->fortis_api,
+ $transaction_id,
+ $this->logging,
+ self::$wc_logger
+ );
+ if ( null === $resolved ) {
+ http_response_code( 417 );
+ exit();
}
+
+ $order = $resolved['order'];
+ $is_refund = $resolved['is_refund'];
+ $fortis_transaction = $resolved['fortis_transaction'];
+
+ // Use authoritative transaction data for downstream processing
+ $fortis_data = json_decode( wp_json_encode( $fortis_transaction['data'] ) );
+
$order->set_payment_method( self::ID );
if ( ! $is_refund ) {
- switch ( $fortis_data->status_id ) {
+ switch ( $fortis_data->status_code ) {
case 131: // Pending Origination.
// It's not possible to update an order status to a non-valid status, so set meta.
$order->update_meta_data( 'order_status', 'Pending Origination' );
@@ -1729,7 +1756,7 @@
break;
}
} else {
- switch ( $fortis_data->status_id ) {
+ switch ( $fortis_data->status_code ) {
case 131: // Pending Origination.
// It's not possible to update an order status to a non-valid status, so set meta.
$order->update_meta_data( 'order_status', 'Pending Refund Origination' );
@@ -1952,15 +1979,21 @@
* Encrypt API keys before saving to the database.
*/
public function process_admin_options() {
+ // Ensure the user has appropriate capability before processing options.
+ if ( ! current_user_can( 'manage_options' ) ) {
+ return;
+ }
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is called by WooCommerce admin options save where nonce is verified by the settings page parent::process_admin_options();
parent::process_admin_options();
$fields = array(
FortisApi::PRODUCTION_USER_API_KEY,
FortisApi::SANDBOX_USER_API_KEY,
);
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
foreach ( $fields as $field ) {
$post_key = $this->plugin_id . $this->id . '_' . $field;
- if ( ! empty( $_POST[ $post_key ] ) ) {
- $raw_key = sanitize_text_field( $_POST[ $post_key ] );
+ if ( ! empty( sanitize_text_field( wp_unslash( $_POST[ $post_key ] ?? '' ) ) ) ) {
+ $raw_key = sanitize_text_field( wp_unslash( $_POST[ $post_key ] ) );
$current_encrypted = get_option( $this->get_option_key_name( $field ), '' );
$current_decrypted = '';
if ( ! empty( $current_encrypted ) ) {
@@ -1971,10 +2004,11 @@
update_option( $this->get_option_key_name( $field ), $encrypted );
update_option( $this->get_option_key_name( $field ) . '_last_updated', current_time( 'mysql' ) );
}
- } elseif ( isset( $_POST[ $post_key ] ) && $_POST[ $post_key ] === '' ) {
+ } elseif ( isset( $_POST[ $post_key ] ) && sanitize_text_field( wp_unslash( $_POST[ $post_key ] ) ) === '' ) {
delete_option( $this->get_option_key_name( $field ) );
delete_option( $this->get_option_key_name( $field ) . '_last_updated' );
}
+ // phpcs:enable WordPress.Security.NonceVerification.Missing
}
}
--- a/fortis-for-woocommerce/classes/WooCommercePaymentService.php
+++ b/fortis-for-woocommerce/classes/WooCommercePaymentService.php
@@ -1,5 +1,9 @@
<?php
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
class WooCommercePaymentService {
/**
@@ -62,7 +66,12 @@
return true;
} catch ( Exception $e ) {
- error_log( 'Error adding surcharge: ' . $e->getMessage() );
+ if ( function_exists( 'wc_get_logger' ) ) {
+ wc_get_logger()->error(
+ 'Error adding surcharge: ' . $e->getMessage(),
+ array( 'source' => 'fortis-for-woocommerce' )
+ );
+ }
return false;
}
@@ -201,36 +210,56 @@
WC()->customer->set_billing_phone( $billingData['billing_phone'] );
}
- // Update customer shipping address if provided
- if ( isset( $billingData['shipping_first_name'] ) ) {
+ // Update customer shipping address if provided, otherwise use billing address
+ if ( isset( $billingData['shipping_first_name'] ) && ! empty( $billingData['shipping_first_name'] ) ) {
WC()->customer->set_shipping_first_name( $billingData['shipping_first_name'] );
+ } else {
+ WC()->customer->set_shipping_first_name( $billingData['billing_first_name'] ?? '' );
}
- if ( isset( $billingData['shipping_last_name'] ) ) {
+ if ( isset( $billingData['shipping_last_name'] ) && ! empty( $billingData['shipping_last_name'] ) ) {
WC()->customer->set_shipping_last_name( $billingData['shipping_last_name'] );
+ } else {
+ WC()->customer->set_shipping_last_name( $billingData['billing_last_name'] ?? '' );
}
- if ( isset( $billingData['shipping_company'] ) ) {
+ if ( isset( $billingData['shipping_company'] ) && ! empty( $billingData['shipping_company'] ) ) {
WC()->customer->set_shipping_company( $billingData['shipping_company'] );
+ } else {
+ WC()->customer->set_shipping_company( $billingData['billing_company'] ?? '' );
}
- if ( isset( $billingData['shipping_address_1'] ) ) {
+ if ( isset( $billingData['shipping_address_1'] ) && ! empty( $billingData['shipping_address_1'] ) ) {
WC()->customer->set_shipping_address_1( $billingData['shipping_address_1'] );
+ } else {
+ WC()->customer->set_shipping_address_1( $billingData['billing_address_1'] ?? '' );
}
- if ( isset( $billingData['shipping_address_2'] ) ) {
+ if ( isset( $billingData['shipping_address_2'] ) && ! empty( $billingData['shipping_address_2'] ) ) {
WC()->customer->set_shipping_address_2( $billingData['shipping_address_2'] );
+ } else {
+ WC()->customer->set_shipping_address_2( $billingData['billing_address_2'] ?? '' );
}
- if ( isset( $billingData['shipping_city'] ) ) {
+ if ( isset( $billingData['shipping_city'] ) && ! empty( $billingData['shipping_city'] ) ) {
WC()->customer->set_shipping_city( $billingData['shipping_city'] );
+ } else {
+ WC()->customer->set_shipping_city( $billingData['billing_city'] ?? '' );
}
- if ( isset( $billingData['shipping_state'] ) ) {
+ if ( isset( $billingData['shipping_state'] ) && ! empty( $billingData['shipping_state'] ) ) {
WC()->customer->set_shipping_state( $billingData['shipping_state'] );
+ } else {
+ WC()->customer->set_shipping_state( $billingData['billing_state'] ?? '' );
}
- if ( isset( $billingData['shipping_postcode'] ) ) {
+ if ( isset( $billingData['shipping_postcode'] ) && ! empty( $billingData['shipping_postcode'] ) ) {
WC()->customer->set_shipping_postcode( $billingData['shipping_postcode'] );
+ } else {
+ WC()->customer->set_shipping_postcode( $billingData['billing_postcode'] ?? '' );
}
- if ( isset( $billingData['shipping_country'] ) ) {
+ if ( isset( $billingData['shipping_country'] ) && ! empty( $billingData['shipping_country'] ) ) {
WC()->customer->set_shipping_country( $billingData['shipping_country'] );
+ } else {
+ WC()->customer->set_shipping_country( $billingData['billing_country'] ?? '' );
}
- if ( isset( $billingData['shipping_phone'] ) ) {
+ if ( isset( $billingData['shipping_phone'] ) && ! empty( $billingData['shipping_phone'] ) ) {
WC()->customer->set_shipping_phone( $billingData['shipping_phone'] );
+ } else {
+ WC()->customer->set_shipping_phone( $billingData['billing_phone'] ?? '' );
}
if ( isset( $billingData['customer_note'] ) && ! empty( $billingData['customer_note'] ) ) {
$order->set_customer_note( sanitize_text_field( $billingData['customer_note'] ) );
@@ -252,6 +281,14 @@
return $order;
}
+ // Verify nonce for non-Blocks requests to ensure form submission is valid.
+ if ( ! $isBlocks ) {
+ $nonce = sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ?? $_REQUEST['_wpnonce'] ?? '' ) );
+ if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'check_fortis_response' ) ) {
+ return new WP_Error( 'invalid_nonce', __( 'Invalid request.', 'fortis-for-woocommerce' ) );
+ }
+ }
+
$this->validateCheckoutBillingData( $billingData );
$checkout = WC()->checkout();
@@ -280,7 +317,21 @@
}
}
- public function validateCheckoutBillingData( array $billingData ): void {
+ public function validateCheckoutBillingData( array &$billingData ): void {
+ // First, populate empty shipping fields with billing data
+ $shipping_fields = array( 'first_name', 'last_name', 'company', 'address_1', 'address_2', 'city', 'state', 'postcode', 'country', 'phone' );
+
+ foreach ( $shipping_fields as $field ) {
+ $shipping_key = "shipping_$field";
+ $billing_key = "billing_$field";
+
+ // If shipping field is empty or not set, use billing field value
+ if ( ( ! isset( $billingData[ $shipping_key ] ) || empty( $billingData[ $shipping_key ] ) )
+ && isset( $billingData[ $billing_key ] ) ) {
+ $billingData[ $shipping_key ] = $billingData[ $billing_key ];
+ }
+ }
+
$errors = new WP_Error();
$checkout = WC()->checkout();
@@ -305,7 +356,8 @@
);
$errors->add(
$field_key,
- sprintf( __( '%s is a required field.', 'woocommerce' ), $field_label )
+ // translators: %s is the human-readable label for the checkout field.
+ sprintf( __( '%s is a required field.', 'fortis-for-woocommerce' ), $field_label )
);
}
}
@@ -313,52 +365,55 @@
}
if ( empty( $billingData['billing_first_name'] ) ) {
- $errors->add( 'billing_first_name', __( 'First name is required.', 'woocommerce' ) );
+ $errors->add( 'billing_first_name', __( 'First name is required.', 'fortis-for-woocommerce' ) );
}
if ( empty( $billingData['billing_last_name'] ) ) {
- $errors->add( 'billing_last_name', __( 'Last name is required.', 'woocommerce' ) );
+ $errors->add( 'billing_last_name', __( 'Last name is required.', 'fortis-for-woocommerce' ) );
}
if ( empty( $billingData['billing_email'] ) || ! is_email( $billingData['billing_email'] ) ) {
- $errors->add( 'billing_email', __( 'A valid email address is required.', 'woocommerce' ) );
+ $errors->add( 'billing_email', __( 'A valid email address is required.', 'fortis-for-woocommerce' ) );
}
if ( empty( $billingData['billing_address_1'] ) ) {
- $errors->add( 'billing_address_1', __( 'Billing address is required.', 'woocommerce' ) );
+ $errors->add( 'billing_address_1', __( 'Billing address is required.', 'fortis-for-woocommerce' ) );
}
if ( empty( $billingData['billing_city'] ) ) {
- $errors->add( 'billing_city', __( 'City is required.', 'woocommerce' ) );
+ $errors->add( 'billing_city', __( 'City is required.', 'fortis-for-woocommerce' ) );
}
if ( empty( $billingData['billing_postcode'] ) ) {
- $errors->add( 'billing_postcode', __( 'Postcode is required.', 'woocommerce' ) );
+ $errors->add( 'billing_postcode', __( 'Postcode is required.', 'fortis-for-woocommerce' ) );
}
if ( empty( $billingData['billing_country'] ) ) {
- $errors->add( 'billing_country', __( 'Country is required.', 'woocommerce' ) );
+ $errors->add( 'billing_country', __( 'Country is required.', 'fortis-for-woocommerce' ) );
}
// Validate payment method
if ( empty( $billingData['payment_method'] ) ) {
- $errors->add( 'payment_method', __( 'Please select a payment method.', 'woocommerce' ) );
+ $errors->add( 'payment_method', __( 'Please select a payment method.', 'fortis-for-woocommerce' ) );
} else {
$available_gateways = WC()->payment_gateways()->get_available_payment_gateways();
if ( ! isset( $available_gateways[ $billingData['payment_method'] ] ) ) {
- $errors->add( 'payment_method', __( 'Invalid payment method selected.', 'woocommerce' ) );
+ $errors->add( 'payment_method', __( 'Invalid payment method selected.', 'fortis-for-woocommerce' ) );
}
}
if ( WC()->cart->is_empty() ) {
- $errors->add( 'cart', __( 'Your cart is empty.', 'woocommerce' ) );
+ $errors->add( 'cart', __( 'Your cart is empty.', 'fortis-for-woocommerce' ) );
}
if ( function_exists( 'wc_terms_and_conditions_checkbox_enabled' ) && wc_terms_and_conditions_checkbox_enabled() ) {
if ( empty( $billingData['terms'] ) || $billingData['terms'] !== 'on' ) {
- $errors->add( 'terms', __( 'You must accept the Terms and Conditions to proceed.', 'woocommerce' ) );
+ $errors->add(
+ 'terms',
+ __( 'You must accept the Terms and Conditions to proceed.', 'fortis-for-woocommerce' )
+ );
}
}
// Trigger WooCommerce checkout validation hooks
- do_action( 'woocommerce_checkout_process' );
+ do_action( 'fortis_for_woocommerce_checkout_process' );
// Allow other plugins to add validation errors
- $errors = apply_filters( 'woocommerce_checkout_validation_errors', $errors, $billingData );
+ $errors = apply_filters( 'fortis_for_woocommerce_checkout_validation_errors', $errors, $billingData );
if ( $errors->has_errors() ) {
wc_clear_notices();
--- a/fortis-for-woocommerce/fortis-for-woocommerce.php
+++ b/fortis-for-woocommerce/fortis-for-woocommerce.php
@@ -4,12 +4,12 @@
* Description: Receive payments using the Fortis payments provider.
* Author: Fortis Payment Systems
* Author URI: https://fortispay.com/
- * Version: 1.2.0
+ * Version: 1.3.0
* Requires at least: 6.0
* Tested up to: 6.9
*
* Woo: 18734003307187:5af1fe7212d9675f3bea8d98af3207eb
- * WC tested up to: 10.3.6
+ * WC tested up to: 10.5.0
* WC requires at least: 7.0
*
* @package Fortis for WooCommerce
@@ -149,7 +149,7 @@
*/
function fortis_get_billing_data() {
// Nonce check
- $nonce = $_POST['fortis_nonce'] ?? '';
+ $nonce = sanitize_text_field( wp_unslash( $_POST['fortis_nonce'] ?? '' ) );
$action = 'check_fortis_response';
if ( ! wp_verify_nonce( $nonce, $action ) ) {
wp_send_json_error(
@@ -161,6 +161,16 @@
wp_die();
}
+ if ( ! isset( $_POST['fields'] ) ) {
+ wp_send_json_error(
+ array(
+ 'error' => true,
+ 'messages' => 'Fields data is missing.',
+ )
+ );
+ wp_die();
+ }
+
parse_str( filter_var( wp_unslash( $_POST['fields'] ), FILTER_UNSAFE_RAW ), $post_data );
$fortisTransactionService = new FortisTransactionService();
@@ -185,7 +195,7 @@
*/
function fortis_get_token_surcharges() {
// Nonce check
- $nonce = $_POST['fortis_nonce'] ?? '';
+ $nonce = sanitize_text_field( wp_unslash( $_POST['fortis_nonce'] ?? '' ) );
$action = 'check_fortis_response';
if ( ! wp_verify_nonce( $nonce, $action ) ) {
wp_send_json_error(
@@ -197,8 +207,8 @@
wp_die();
}
- $postcode = sanitize_text_field( $_POST['postcode'] ?? '' );
- $shipping_method = sanitize_text_field( $_POST['shipping_method'] ?? '' );
+ $postcode = sanitize_text_field( wp_unslash( $_POST['postcode'] ?? '' ) );
+ $shipping_method = sanitize_text_field( wp_unslash( $_POST['shipping_method'] ?? '' ) );
$customer_id = 0;
if ( is_user_logged_in() ) {
@@ -416,34 +426,34 @@
add_action( 'before_woocommerce_init', 'fortis_declare_hpos_compatibility' );
// Add "Complete Auth" button on the order detail screen
-add_action( 'woocommerce_order_item_add_action_buttons', 'add_complete_auth_button_to_order_details' );
-function add_complete_auth_button_to_order_details( $order ) {
+add_action( 'woocommerce_order_item_add_action_buttons', 'fortis_add_complete_auth_button_to_order_details' );
+function fortis_add_complete_auth_button_to_order_details( $order ) {
// Display the button only if the order has 'auth-only' meta
if ( 'auth-only' === $order->get_meta( 'action' ) && 'on-hold' == $order->get_status() ) {
- echo '<button type="button" class="button complete-auth-button" data-order-id="' . esc_attr( $order->get_id() ) . '">' . __(
+ echo '<button type="button" class="button complete-auth-button" data-order-id="' . esc_attr( $order->get_id() ) . '">' . esc_html__(
'Complete Auth',
- 'fortis'
+ 'fortis-for-woocommerce'
) . '</button>';
}
}
// Add "Complete Auth" option to the Order Actions dropdown if the order has 'auth-only' meta
-add_filter( 'woocommerce_order_actions', 'conditionally_add_complete_auth_order_action' );
-function conditionally_add_complete_auth_order_action( $actions ) {
+add_filter( 'woocommerce_order_actions', 'fortis_conditionally_add_complete_auth_order_action' );
+function fortis_conditionally_add_complete_auth_order_action( $actions ) {
global $theorder;
// Check if we're on the WooCommerce Order Edit screen and have a valid order
if ( $theorder && 'auth-only' === $theorder->get_meta( 'action' ) && 'on-hold' == $theorder->get_status() ) {
- $actions['complete_auth'] = __( 'Complete Auth', 'fortis' );
+ $actions['complete_auth'] = __( 'Complete Auth', 'fortis-for-woocommerce' );
}
return $actions;
}
// Handle the "Complete Auth" action from the Order Actions dropdown
-add_action( 'woocommerce_order_action_complete_auth', 'process_complete_auth_order_action' );
-function process_complete_auth_order_action( $order ) {
- $result = complete_auth_for_order( $order->get_id() );
+add_action( 'woocommerce_order_action_complete_auth', 'fortis_process_complete_auth_order_action' );
+function fortis_process_complete_auth_order_action( $order ) {
+ $result = fortis_complete_auth_for_order( $order->get_id() );
// Display a notice in the admin area
if ( $result['success'] ) {
@@ -466,8 +476,8 @@
}
// Add Complete Auth Column
-add_filter( 'manage_woocommerce_page_wc-orders_columns', 'add_wc_order_list_custom_column' );
-function add_wc_order_list_custom_column( $columns ) {
+add_filter( 'manage_woocommerce_page_wc-orders_columns', 'fortis_add_wc_order_list_custom_column' );
+function fortis_add_wc_order_list_custom_column( $columns ) {
$reordered_columns = array();
// Inserting columns to a specific location
@@ -476,7 +486,7 @@
if ( 'order_status' === $key ) {
// Inserting after "Status" column
- $reordered_columns['complete_auth'] = __( '', 'fortis' );
+ $reordered_columns['complete_auth'] = __( 'Complete Auth', 'fortis-for-woocommerce' );
}
}
@@ -484,20 +494,25 @@
}
// Display the 'Complete Auth' button on order list
-add_action( 'manage_woocommerce_page_wc-orders_custom_column', 'display_wc_order_list_custom_column_content', 10, 2 );
-function display_wc_order_list_custom_column_content( $column, $order ) {
+add_action(
+ 'manage_woocommerce_page_wc-orders_custom_column',
+ 'fortis_display_wc_order_list_custom_column_content',
+ 10,
+ 2
+);
+function fortis_display_wc_order_list_custom_column_content( $column, $order ) {
// Display the 'Complete Auth' button only if the order is not already complete
if ( $column === 'complete_auth' && 'auth-only' === $order->get_meta( 'action' ) && 'on-hold' === $order->get_status() ) {
- echo '<button class="button complete-auth-button" data-order-id="' . esc_attr( $order->get_id() ) . '">' . __(
+ echo '<button class="button complete-auth-button" data-order-id="' . esc_attr( $order->get_id() ) . '">' . esc_html__(
'Complete Auth',
- 'fortis'
+ 'fortis-for-woocommerce'
) . '</button>';
}
}
// Enqueue JavaScript to handle button click
-add_action( 'admin_enqueue_scripts', 'enqueue_complete_auth_js' );
-function enqueue_complete_auth_js() {
+add_action( 'admin_enqueue_scripts', 'fortis_enqueue_complete_auth_js' );
+function fortis_enqueue_complete_auth_js() {
wp_enqueue_script(
'complete-auth-js',
plugin_dir_url( __FILE__ ) . 'assets-non-blocks/js/complete-auth.js',
@@ -518,10 +533,13 @@
}
// Handle the Ajax request to process Complete Auth
-add_action( 'wp_ajax_complete_auth_action', 'complete_auth_action' );
-function complete_auth_action() {
+add_action( 'wp_ajax_complete_auth_action', 'fortis_complete_auth_action' );
+function fortis_complete_auth_action() {
// Verify nonce
- if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'complete_auth_nonce' ) ) {
+ if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce(
+ sanitize_text_field( wp_unslash( $_POST['nonce'] ) ),
+ 'complete_auth_nonce'
+ ) ) {
wp_send_json_error( array( 'message' => 'Nonce verification failed.' ) );
}
@@ -531,7 +549,7 @@
$order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0;
if ( $order_id > 0 ) {
- $result = complete_auth_for_order( $order_id );
+ $result = fortis_complete_auth_for_order( $order_id );
if ( $result['success'] ) {
wp_send_json_success( array( 'message' => $result['message'] ) );
} else {
@@ -543,7 +561,7 @@
}
// Reusable function to complete auth for an order
-function complete_auth_for_order( $order_id ) {
+function fortis_complete_auth_for_order( $order_id ) {
$order = wc_get_order( $order_id );
if ( $order && 'auth-only' === $order->get_meta( 'action' ) ) {
@@ -558,18 +576,18 @@
return array(
'success' => true,
- 'message' => __( 'Order completed successfully via Complete Auth.', 'fortis' ),
+ 'message' => __( 'Order completed successfully via Complete Auth.', 'fortis-for-woocommerce' ),
);
} else {
return array(
'success' => false,
- 'message' => __( 'Order could not be completed via Complete Auth.', 'fortis' ),
+ 'message' => __( 'Order could not be completed via Complete Auth.', 'fortis-for-woocommerce' ),
);
}
} else {
return array(
'success' => false,
- 'message' => __( 'Invalid or non-auth-only order selected.', 'fortis' ),
+ 'message' => __( 'Invalid or non-auth-only order selected.', 'fortis-for-woocommerce' ),
);
}
}
--- a/fortis-for-woocommerce/includes/blocks/class-wc-fortis-payments-blocks.php
+++ b/fortis-for-woocommerce/includes/blocks/class-wc-fortis-payments-blocks.php
@@ -73,7 +73,7 @@
? require $script_asset_path
: array(
'dependencies' => array(),
- 'version' => '1.2.0',
+ 'version' => '1.3.0',
);
$script_url = WC_Fortis_Payments::plugin_url() . $script_path;
@@ -115,7 +115,7 @@
$wcsession = WC()->session;
$customer = $wcsession->get( 'customer' );
- $customer_id = $customer['id'];
+ $customer_id = isset( $customer['id'] ) ? (int) $customer['id'] : 0;
$order_id = $wcsession->get( 'store_api_draft_order' );
$stored_order_id = $wcsession->get( 'order_id' );
@@ -175,15 +175,18 @@
$show_validation_animation = 'yes' === $this->settings[ FortisApi::SHOWVALIDATIONANIMATION ] ? 'true' : 'false';
$hide_agreement_checkbox = 'yes' === $this->settings[ FortisApi::HIDEAGREEMENTCHECKBOX ] ? 'true' : 'false';
- $postcode = $customer['postcode'];
+ $postcode = isset( $customer['postcode'] ) ? $customer['postcode'] : '';
$fortisTransactionService = new FortisTransactionService();
- $tokenSurcharges = $fortisTransactionService->getTokenSurcharges(
- $customer_id,
- $subtotal,
- $tax_amount,
- $postcode
- );
+ $tokenSurcharges = null;
+ if ( $customer_id > 0 ) {
+ $tokenSurcharges = $fortisTransactionService->getTokenSurcharges(
+ $customer_id,
+ $subtotal,
+ $tax_amount,
+ $postcode
+ );
+ }
$nonce = wp_create_nonce( 'check_fortis_response' );