# hyperpay-gateways/trunk/src/Traits/HasTokenization.php

HyperPay Payments, version trunk. 313 lines.

- Page: https://pluginprobe.com/plugins/hyperpay-gateways/trunk/code/src/Traits/HasTokenization.php
- Raw: https://pluginprobe.com/plugins/hyperpay-gateways/trunk/raw/src/Traits/HasTokenization.php
- Modified: 2025-12-16T07:47:04+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/hyperpay-gateways/trunk/code/src/Traits/HasTokenization.php#L10-L20`.

```php
<?php

namespace Hyperpay\Gateways\Traits;

if (!defined('ABSPATH')) {
    exit;
}

use Hyperpay\Gateways\Helpers\COF;
use Hyperpay\Gateways\Helpers\Http;
use Hyperpay\Gateways\Helpers\SubscriptionsManager;
use Hyperpay\Gateways\Helpers\TokenManager;
use Hyperpay\Gateways\Helpers\View;
use WC_Payment_Token;
use WC_Payment_Tokens;
use WC_Order;


trait HasTokenization
{
    /**
     * Initializes all hooks and filters for the saved cards feature.
     */
    public function bootHasTokenization()
    {
        $this->supports = array_merge($this->supports, ['tokenization']);

        add_filter('woocommerce_get_query_vars', [$this, 'add_registration_query_var'], 0);
        add_action('woocommerce_account_hyperpay-registration_endpoint', [$this, 'handle_registration_endpoint_content']);
        add_filter('woocommerce_payment_methods_list_item', [$this, 'protect_active_subscription_token'], 10, 2);
        add_action('woocommerce_payment_token_deleted', [$this, 'handle_wc_token_deleted'], 10, 2);
        add_action('hyperpay_gateway_success', [$this, 'payment_success'], 10, 2);

        $this->hideTokensInCheckout();
    }

    /**
     * Adds 'hyperpay-registration' as a valid My Account query variable.
     *
     * @param array $vars
     * @return array
     */
    public function add_registration_query_var(array $vars): array
    {
        $vars['hyperpay-registration'] = 'hyperpay-registration';
        return $vars;
    }

    /**
     * Initiates the checkout request to Hyperpay for registration.
     * This is called when the user clicks 'Add payment method'.
     *
     * @return array
     */
    public function add_payment_method(): array
    {
        $data = $this->getBasicData();

        $agreementId = sprintf('USER%s%s', get_current_user_id(), time());
        $CIT = COF::build_cit_params($agreementId);

        $data['body'] = \array_merge($data['body'], $CIT, [
            'createRegistration' => 'true',
            'paymentType' => $this->trans_type,
            'customParameters[plugin]' => 'wordpress',
            'amount' => TokenManager::TOKENIZATION_AMOUNT,
            'currency' => $this->currency
        ]);


        $response = Http::post($this->ACI_base_url . '/v1/checkouts', $data);

        $checkoutId = $response['id'] ?? '';
        $redirectUrl = wc_get_endpoint_url('hyperpay-registration', $checkoutId, wc_get_page_permalink('myaccount'));

        if (!$checkoutId) {
            wc_add_notice(__('Error creating registration session. Please try again.', 'hyperpay-gateways'), 'error');

            return [
                'result' => 'failure',
                'redirect' => wc_get_endpoint_url('payment-methods', '', wc_get_page_permalink('myaccount')),
            ];
        }



        return [
            'result' => 'failed',
            'redirect' => $redirectUrl,
        ];
    }

    /**
     * Handles the content displayed on the 'hyperpay-registration' My Account endpoint.
     */
    public function handle_registration_endpoint_content(): void
    {
        if (isset($_GET['resourcePath'], $_GET['nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['nonce'])), 'hyperpay-registration')) {
            $this->handle_registration_callback(sanitize_text_field(wp_unslash($_GET['resourcePath'])));
            exit;
        }

        $checkoutId = get_query_var('hyperpay-registration');

        if (empty($checkoutId)) {
            wc_add_notice(__('Failed to get checkout id.', 'hyperpay-gateways'), 'error');
            wp_safe_redirect(wc_get_endpoint_url('payment-methods'));
            exit;
        }

        $this->display_registration_form($checkoutId);
    }


    /**
     * Retrieves the registration result from Hyperpay and saves the token.
     *
     * @param string $resourcePath The resource path provided by Hyperpay.
     */
    protected function handle_registration_callback(string $resourcePath): void
    {
        $paymentMethodsUrl =  wc_get_endpoint_url('payment-methods', '', wc_get_page_permalink('myaccount'));
        $url = $this->ACI_base_url . $resourcePath;
        $data = $this->getAuthData();

        $result = Http::get($url, $data);

        if (!$this->is_successful_response($result)) {
            $errorMessage = $result['result']['description'] ?? __('Unknown error.', 'hyperpay-gateways');
            wc_add_notice(
                /* translators: %s: error message */
                sprintf(__('Failed to register payment method: %s', 'hyperpay-gateways'), $errorMessage),
                'error'
            );
        } else {
            $card = $result['card'] ?? [];
            $card['paymentBrand'] = $result['paymentBrand'] ?? 'unknown'; // nosemgrep: audit.php.wp.security.xss.sanitized-var-passed-to-method


            $agreementId  = $result['customParameters']['recurringPaymentAgreement'] ?? '';
            $initialTxId  = $result['CardholderInitiatedTransactionID'] ??
                $result['standingInstruction']['initialTransactionId'] ??
                '';

            // Enrich card data for storage.
            $card['recurringPaymentAgreement'] = $agreementId;
            $card['initial_tx_id']              = $initialTxId;


            $token = TokenManager::save($result['registrationId'], $card, $this->id);

            if ($token) {
                wc_add_notice(
                    __('Your payment method has been saved successfully!', 'hyperpay-gateways'),
                    'success'
                );
            } else {
                // If token creation fails locally
                wc_add_notice(__('Failed to save Card.', 'hyperpay-gateways'), 'error');
            }

            // auto revisal after register the card
            $this->auto_revisal($result['id']);
        }

        if (!headers_sent()) {
            wp_safe_redirect($paymentMethodsUrl);
            exit;
        }

        echo "<script>window.location.href='" . esc_url($paymentMethodsUrl) . "';</script>";
        exit;
    }

    /**
     * Renders the registration form using the checkout ID.
     *
     * @param string $checkoutId
     */
    protected function display_registration_form(string $checkoutId): void
    {
        $brands = is_array($this->brands) ? implode(' ', $this->brands) : (string)$this->brands;
        $postBackURL = wc_get_endpoint_url('hyperpay-registration');

        $dataObj = [
            'is_arabic' => esc_js($this->is_arabic ?? false),
            'style' => esc_html($this->payment_style ?? 'card'),
            'postBackURL' => esc_html($postBackURL . "?nonce=" . esc_html(wp_create_nonce('hyperpay-registration'))),
            'payment_brands' => esc_html($brands),
            'custom_style' => esc_html($this->custom_style ?? ''),
            'scriptURL' => esc_html($this->script_url),
            'checkoutId' => esc_html($checkoutId),
        ];

        View::render('registration-form.html', compact('dataObj'));
    }

    /**
     * Prevents deletion of a payment token if it's currently linked to an active subscription.
     *
     * @param array $item The list item data.
     * @param WC_Payment_Token $token The token object.
     * @return array
     */
    public function protect_active_subscription_token(array $item, WC_Payment_Token $token): array
    {
        // Check only if the token belongs to this gateway
        if ($token->get_gateway_id() !== $this->id) {
            return $item;
        }

        $userId = get_current_user_id();
        if (!$userId) {
            return $item;
        }

        $subscriptions = SubscriptionsManager::getUsersSubscriptions($userId);

        foreach ($subscriptions as $subscription) {
            /* @var \WC_Subscription $subscription */
            $subscriptionTokenId = (int)$subscription->get_meta('_payment_token_id', true);

            if ($subscriptionTokenId === (int)$token->get_id()) {
                // Token is in use by an active subscription, remove the delete action.
                unset($item['actions']['delete']);
                break;
            }
        }

        return $item;
    }

    /**
     * Handles the `woocommerce_payment_token_deleted` action to call the gateway API.
     *
     * @param int $tokenId The WooCommerce token ID.
     * @param WC_Payment_Token $token The token object.
     */
    public function handle_wc_token_deleted(int $tokenId, WC_Payment_Token $token): void
    {
        // Only act on tokens belonging to this gateway and the current user
        if ($token->get_gateway_id() !== $this->id || $token->get_user_id() !== get_current_user_id()) {
            return;
        }

        $this->delete_registration($token->get_token());
    }

    /**
     * Calls the Hyperpay API to delete a stored registration ID.
     *
     * @param string $registrationId The ID of the registration to delete.
     * @return bool True on successful deletion, false otherwise.
     */
    private function delete_registration(string $registrationId): bool
    {

        $url = $this->ACI_base_url . '/v1/registrations/' . $registrationId .
            "?entityId=" . $this->entityId .
            "&testMode=" . $this->trans_mode;


        $response = Http::delete($url, ['headers' => ["Authorization" => "Bearer {$this->accessToken}"]]);

        return $this->is_successful_response($response);
    }

    /**
     * Adds stored registration IDs (tokens) to request body for saved card checkouts.
     *
     * @param WC_Order $order The WooCommerce order.
     * @return array
     */
    public function setExtraData(WC_Order $order): array
    {
        $customerId  = $order->get_customer_id() ?: get_current_user_id();
        $savedCards  = WC_Payment_Tokens::get_customer_tokens($customerId, $this->id);
        $registrations = [];


        foreach (array_values($savedCards) as $index => $card) {
            $registrations["registrations[{$index}].id"] = $card->get_token();
        }

        return ['body' => $registrations];
    }


    /**
     * Hide Saved Cards in checkout page to use our saved cards form 
     * 
     * @see https://hyperpay.docs.oppwa.com/integrations/widget/registration-tokens
     */

    private function hideTokensInCheckout()
    {
        add_filter('woocommerce_get_customer_payment_tokens', function ($tokens, $customer_id, $gateway_id) {
            global $wp;
            if (is_checkout() && !absint($wp->query_vars['order-pay'] ?? '')) {
                $filtered_tokens = array();
                foreach ($tokens as $token_id => $token) {
                    if ('hyperpay' === $token->get_gateway_id()) {
                        continue;
                    }
                    $filtered_tokens[$token_id] = $token;
                }
                return $filtered_tokens;
            }
            return $tokens;
        }, 10, 4);
    }
}

```
