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 ""; 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); } }