# multisafepay/trunk/src/Services/OrderService.php

MultiSafepay plugin for WooCommerce, version trunk. 326 lines.

- Page: https://pluginprobe.com/plugins/multisafepay/trunk/code/src/Services/OrderService.php
- Raw: https://pluginprobe.com/plugins/multisafepay/trunk/raw/src/Services/OrderService.php
- Modified: 2026-05-21T07:26:10+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/multisafepay/trunk/code/src/Services/OrderService.php#L10-L20`.

```php
<?php declare(strict_types=1);

namespace MultiSafepay\WooCommerce\Services;

use MultiSafepay\Api\Transactions\Gateways as GatewaysSdk;
use MultiSafepay\Api\Transactions\OrderRequest;
use MultiSafepay\Api\Transactions\OrderRequest\Arguments\GatewayInfo\Wallet;
use MultiSafepay\Api\Transactions\OrderRequest\Arguments\PaymentOptions;
use MultiSafepay\Api\Transactions\OrderRequest\Arguments\PluginDetails;
use MultiSafepay\Api\Transactions\OrderRequest\Arguments\SecondChance;
use MultiSafepay\Api\Transactions\OrderRequest\Arguments\TaxTable\TaxRate;
use MultiSafepay\Api\Transactions\OrderRequest\Arguments\TaxTable\TaxRule;
use MultiSafepay\Exception\InvalidArgumentException;
use MultiSafepay\WooCommerce\Utils\MoneyUtil;
use MultiSafepay\WooCommerce\Services\Blocks\BlocksContextService;
use WC_Order;

/**
 * Class OrderService
 *
 * @package MultiSafepay\WooCommerce\Services
 */
class OrderService {

    /**
     * @var CustomerService
     */
    public $customer_service;

    /**
     * @var ShoppingCartService
     */
    public $shopping_cart_service;

    /**
     * @var PaymentMethodService
     */
    public $payment_method_service;

    /**
     * @var BlocksPaymentDataService
     */
    public $blocks_payment_data_service;

    /**
     * OrderService constructor.
     */
    public function __construct() {
        $this->customer_service            = new CustomerService();
        $this->shopping_cart_service       = new ShoppingCartService();
        $this->payment_method_service      = new PaymentMethodService();
        $this->blocks_payment_data_service = new BlocksPaymentDataService();
    }

    /**
     * Wallet payloads (Google Pay token, etc.) are JSON strings.
     * Treat them as opaque: do not run sanitize_text_field() to avoid corrupting JSON.
     *
     * @param string $value
     * @return string
     */
    private function normalize_wallet_payload( string $value ): string {
        $value = trim( $value );
        if ( '' === $value ) {
            return '';
        }

        // Remove null bytes to avoid storage / transport issues.
        $value = str_replace( "\0", '', $value );

        // Defensive limit: Google Pay tokens are ~1-3KB; allow plenty.
        if ( strlen( $value ) > 20000 ) {
            $value = substr( $value, 0, 20000 );
        }

        return $value;
    }

    /**
     * Get a wallet payment token from the current request or from Blocks order meta.
     *
     * Wallet Direct (Apple Pay / Google Pay) can send the token either as:
     * - $_POST['payment_token'] (legacy)
     * - $_POST['<payment_method_id>_payment_token'] (Blocks/JS)
     * - Order meta '_multisafepay_blocks_payment_data[<payment_method_id>_payment_token]' (Blocks persisted)
     *
     * @param WC_Order $order
     * @return string
     */
    public function get_wallet_payment_token( WC_Order $order ): string {
        $payment_method_id = (string) $order->get_payment_method();

        $is_store_api_request = ( new BlocksContextService() )->is_store_api_request();
        $read_request_value   = static function ( string $key ) use ( $is_store_api_request ): string {
            if ( ! isset( $_POST[ $key ] ) ) {
                return '';
            }

            // Store API requests are already unslashed by the REST layer.
            // Do not modify opaque wallet payloads (tokens/JSON).
            // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
            $value = '';

            if ( $is_store_api_request ) {
                $value = (string) $_POST[ $key ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
            }

            if ( ! $is_store_api_request ) {
                $value = (string) wp_unslash( $_POST[ $key ] );
            }
            // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

            return $value;
        };

        $payment_token = $this->normalize_wallet_payload( $read_request_value( 'payment_token' ) );
        if ( ! empty( $payment_token ) ) {
            return $payment_token;
        }

        $payment_method_wallet_key     = $payment_method_id . '_payment_token';
        $payment_token_from_method_key = $this->normalize_wallet_payload( $read_request_value( $payment_method_wallet_key ) );
        if ( ! empty( $payment_token_from_method_key ) ) {
            return $payment_token_from_method_key;
        }

        $token_from_blocks_method_key = $this->normalize_wallet_payload(
            (string) $this->blocks_payment_data_service->get_blocks_payment_data_value( $order, $payment_method_wallet_key )
        );
        if ( ! empty( $token_from_blocks_method_key ) ) {
            return $token_from_blocks_method_key;
        }

        // Legacy-compatible fallback for Blocks: some flows can send/store just `payment_token`.
        return $this->normalize_wallet_payload(
            (string) $this->blocks_payment_data_service->get_blocks_payment_data_value( $order, 'payment_token' )
        );
    }

    /**
     * @param WC_Order $order
     * @param string   $gateway_code
     * @param string   $type
     * @return OrderRequest
     * @throws InvalidArgumentException
     */
    public function create_order_request( WC_Order $order, string $gateway_code, string $type ): OrderRequest {
        $order_request = new OrderRequest();
        $order_request
            ->addOrderId( $order->get_order_number() )
            ->addMoney( MoneyUtil::create_money( (float) ( $order->get_total() ), $order->get_currency() ) )
            ->addGatewayCode( $gateway_code )
            ->addType( $type )
            ->addPluginDetails( $this->create_plugin_details() )
            ->addDescriptionText( $this->get_order_description_text( $order->get_order_number() ) )
            ->addCustomer( $this->customer_service->create_customer_details( $order ) )
            ->addPaymentOptions( $this->create_payment_options( $order ) )
            ->addSecondsActive( $this->get_seconds_active() )
            ->addSecondChance( ( new SecondChance() )->addSendEmail( (bool) get_option( 'multisafepay_second_chance', false ) ) )
            ->addData( array( 'var2' => $order->get_id() ) );

        if ( $order->needs_shipping_address() && $order->has_shipping_address() ) {
            $order_request->addDelivery( $this->customer_service->create_delivery_details( $order ) );
        }

        if ( ! get_option( 'multisafepay_disable_shopping_cart', false ) || in_array( $gateway_code, GatewaysSdk::SHOPPING_CART_REQUIRED_GATEWAYS, true ) ) {
            $order_request->addShoppingCart( $this->shopping_cart_service->create_shopping_cart( $order, $order->get_currency(), $gateway_code ) );
        }

        $payment_method_id             = $order->get_payment_method();
        $payment_component_payload_key = $payment_method_id . '_payment_component_payload';

        $payment_component_payload = sanitize_text_field( wp_unslash( $_POST[ $payment_component_payload_key ] ?? '' ) );
        if ( empty( $payment_component_payload ) ) {
            $payment_component_payload = $this->blocks_payment_data_service->get_blocks_payment_data_value( $order, $payment_component_payload_key );
        }

        if ( ! empty( $payment_component_payload ) ) {
            $order_request->addType( 'direct' );
            $order_request->addData(
                array(
                    'payment_data' => array(
                        'payload' => $payment_component_payload,
                    ),
                )
            );
        }

        $payment_token = $this->get_wallet_payment_token( $order );
        if ( ! empty( $payment_token ) && ( ( 'APPLEPAY' === $gateway_code ) || ( 'GOOGLEPAY' === $gateway_code ) ) ) {
            $order_request->addType( 'direct' );
            $order_request->addGatewayInfo( ( new Wallet() )->addPaymentToken( $payment_token ) );
        }

        // Force BILLINK B2B and B2C transactions to be direct
        if ( 'BILLINK' === $gateway_code ) {
            $payment_method_id = $order->get_payment_method();
            $payment_method    = $this->payment_method_service->get_woocommerce_payment_gateway_by_id( $payment_method_id );

            if ( $payment_method ) {
                $payment_component_option = $payment_method->get_option( 'payment_component', 'no' );

                if ( 'P' === $payment_component_option || 'B' === $payment_component_option ) {
                    $order_request->addType( 'direct' );
                }
            }
        }

        $order_request = $this->add_none_tax_rate( $order_request );

        return apply_filters( 'multisafepay_order_request', $order_request );
    }

    /**
     * @return PluginDetails
     */
    protected function create_plugin_details(): PluginDetails {
        $plugin_details = new PluginDetails();
        global $wp_version;
        return $plugin_details
            ->addApplicationName( 'Wordpress-WooCommerce' )
            ->addApplicationVersion( 'WordPress version: ' . $wp_version . '. WooCommerce version: ' . WC_VERSION )
            ->addPluginVersion( MULTISAFEPAY_PLUGIN_VERSION )
            ->addShopRootUrl( get_bloginfo( 'url' ) );
    }

    /**
     * @param WC_Order $order
     * @return PaymentOptions
     * @throws InvalidArgumentException
     */
    private function create_payment_options( WC_Order $order ): PaymentOptions {
        $payment_options = new PaymentOptions();
        $payment_options->addNotificationUrl( get_rest_url( get_current_blog_id(), 'multisafepay/v1/notification' ) );

        $cancel_endpoint = ( get_option( 'multisafepay_redirect_after_cancel', 'cart' ) === 'cart' ? '' : wc_get_checkout_url() );
        $cancel_url      = wp_specialchars_decode( $order->get_cancel_order_url( $cancel_endpoint ) );

        if ( is_wc_endpoint_url( 'order-pay' ) ) {
            $cancel_url = wp_specialchars_decode( $order->get_checkout_payment_url() );
        }

        $payment_options->addCancelUrl( $cancel_url );
        $payment_options->addRedirectUrl( $order->get_checkout_order_received_url() );
        if ( ! apply_filters( 'multisafepay_post_notification', true ) ) {
            $payment_options->addNotificationUrl( add_query_arg( 'wc-api', 'multisafepay', home_url( '/' ) ) );
            $payment_options->addNotificationMethod( 'GET' );
        }

        // Add BILLINK specific settings
        $payment_method_id = $order->get_payment_method();
        $payment_method    = $this->payment_method_service->get_woocommerce_payment_gateway_by_id( $payment_method_id );

        if ( $payment_method && 'BILLINK' === $payment_method->get_payment_method_gateway_code() ) {
            $payment_component_option = $payment_method->get_option( 'payment_component', 'no' );

            if ( 'P' === $payment_component_option || 'B' === $payment_component_option ) {
                $payment_options->addSettings( array( 'gateways' => array( 'BILLINK' => array( 'type' => $payment_component_option ) ) ) );
            }
        }

        return $payment_options;
    }

    /**
     * Return the order description.
     *
     * @param string $order_number
     * @return string $order_description
     */
    protected function get_order_description_text( string $order_number ): string {
        /* translators: %s: order id */
        $order_description = sprintf( __( 'Payment for order: %s', 'multisafepay' ), $order_number );
        if ( get_option( 'multisafepay_order_request_description', false ) ) {
            $order_description = str_replace( '{order_number}', $order_number, get_option( 'multisafepay_order_request_description', false ) );
        }
        return $order_description;
    }

    /**
     * Return the time active in seconds defined in the plugin settings page
     *
     * @return int
     */
    protected function get_seconds_active(): int {
        $time_active      = get_option( 'multisafepay_time_active', '30' );
        $time_active_unit = get_option( 'multisafepay_time_unit', 'days' );
        if ( 'days' === $time_active_unit ) {
            $time_active = $time_active * 24 * 60 * 60;
        }
        if ( 'hours' === $time_active_unit ) {
            $time_active = $time_active * 60 * 60;
        }
        return $time_active;
    }

    /**
     * This method adds a tax rate of 0, in case is not being created automatically by the shopping cart.
     * This is required to process refunds, based on shopping cart items
     *
     * @param OrderRequest $order_request
     * @return OrderRequest
     * @throws InvalidArgumentException
     */
    public function add_none_tax_rate( OrderRequest $order_request ): OrderRequest {
        if ( $order_request->getShoppingCart() === null ) {
            return $order_request;
        }
        if ( $order_request->getCheckoutOptions()->getTaxTable() === null ) {
            return $order_request;
        }
        $shopping_cart = $order_request->getShoppingCart()->getData();
        if ( isset( $shopping_cart['items'] ) ) {
            foreach ( $shopping_cart['items'] as $item ) {
                if ( '0' === $item['tax_table_selector'] ) {
                    return $order_request;
                }
            }
        }
        $tax_rate = ( new TaxRate() )->addRate( 0 );
        $tax_rule = ( new TaxRule() )->addTaxRate( $tax_rate )->addName( '0' );
        $order_request->getCheckoutOptions()->getTaxTable()->addTaxRule( $tax_rule );
        return $order_request;
    }
}

```
