PluginProbe
HyperPay Payments / trunk
HyperPay Payments vtrunk
6.6.0 trunk 1.7 1.8 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.1.0 2.2.0 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.5 4.0.0 4.0.2 All 34 releases
hyperpay-gateways / src / Traits / HasTokenization.php

HasTokenization.php in HyperPay Payments trunk, at src/Traits/HasTokenization.php

313 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Hyperpay\Gateways\Traits;
4
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 use Hyperpay\Gateways\Helpers\COF;
10 use Hyperpay\Gateways\Helpers\Http;
11 use Hyperpay\Gateways\Helpers\SubscriptionsManager;
12 use Hyperpay\Gateways\Helpers\TokenManager;
13 use Hyperpay\Gateways\Helpers\View;
14 use WC_Payment_Token;
15 use WC_Payment_Tokens;
16 use WC_Order;
17
18
19 trait HasTokenization
20 {
21 /**
22 * Initializes all hooks and filters for the saved cards feature.
23 */
24 public function bootHasTokenization()
25 {
26 $this->supports = array_merge($this->supports, ['tokenization']);
27
28 add_filter('woocommerce_get_query_vars', [$this, 'add_registration_query_var'], 0);
29 add_action('woocommerce_account_hyperpay-registration_endpoint', [$this, 'handle_registration_endpoint_content']);
30 add_filter('woocommerce_payment_methods_list_item', [$this, 'protect_active_subscription_token'], 10, 2);
31 add_action('woocommerce_payment_token_deleted', [$this, 'handle_wc_token_deleted'], 10, 2);
32 add_action('hyperpay_gateway_success', [$this, 'payment_success'], 10, 2);
33
34 $this->hideTokensInCheckout();
35 }
36
37 /**
38 * Adds 'hyperpay-registration' as a valid My Account query variable.
39 *
40 * @param array $vars
41 * @return array
42 */
43 public function add_registration_query_var(array $vars): array
44 {
45 $vars['hyperpay-registration'] = 'hyperpay-registration';
46 return $vars;
47 }
48
49 /**
50 * Initiates the checkout request to Hyperpay for registration.
51 * This is called when the user clicks 'Add payment method'.
52 *
53 * @return array
54 */
55 public function add_payment_method(): array
56 {
57 $data = $this->getBasicData();
58
59 $agreementId = sprintf('USER%s%s', get_current_user_id(), time());
60 $CIT = COF::build_cit_params($agreementId);
61
62 $data['body'] = \array_merge($data['body'], $CIT, [
63 'createRegistration' => 'true',
64 'paymentType' => $this->trans_type,
65 'customParameters[plugin]' => 'wordpress',
66 'amount' => TokenManager::TOKENIZATION_AMOUNT,
67 'currency' => $this->currency
68 ]);
69
70
71 $response = Http::post($this->ACI_base_url . '/v1/checkouts', $data);
72
73 $checkoutId = $response['id'] ?? '';
74 $redirectUrl = wc_get_endpoint_url('hyperpay-registration', $checkoutId, wc_get_page_permalink('myaccount'));
75
76 if (!$checkoutId) {
77 wc_add_notice(__('Error creating registration session. Please try again.', 'hyperpay-gateways'), 'error');
78
79 return [
80 'result' => 'failure',
81 'redirect' => wc_get_endpoint_url('payment-methods', '', wc_get_page_permalink('myaccount')),
82 ];
83 }
84
85
86
87 return [
88 'result' => 'failed',
89 'redirect' => $redirectUrl,
90 ];
91 }
92
93 /**
94 * Handles the content displayed on the 'hyperpay-registration' My Account endpoint.
95 */
96 public function handle_registration_endpoint_content(): void
97 {
98 if (isset($_GET['resourcePath'], $_GET['nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['nonce'])), 'hyperpay-registration')) {
99 $this->handle_registration_callback(sanitize_text_field(wp_unslash($_GET['resourcePath'])));
100 exit;
101 }
102
103 $checkoutId = get_query_var('hyperpay-registration');
104
105 if (empty($checkoutId)) {
106 wc_add_notice(__('Failed to get checkout id.', 'hyperpay-gateways'), 'error');
107 wp_safe_redirect(wc_get_endpoint_url('payment-methods'));
108 exit;
109 }
110
111 $this->display_registration_form($checkoutId);
112 }
113
114
115 /**
116 * Retrieves the registration result from Hyperpay and saves the token.
117 *
118 * @param string $resourcePath The resource path provided by Hyperpay.
119 */
120 protected function handle_registration_callback(string $resourcePath): void
121 {
122 $paymentMethodsUrl = wc_get_endpoint_url('payment-methods', '', wc_get_page_permalink('myaccount'));
123 $url = $this->ACI_base_url . $resourcePath;
124 $data = $this->getAuthData();
125
126 $result = Http::get($url, $data);
127
128 if (!$this->is_successful_response($result)) {
129 $errorMessage = $result['result']['description'] ?? __('Unknown error.', 'hyperpay-gateways');
130 wc_add_notice(
131 /* translators: %s: error message */
132 sprintf(__('Failed to register payment method: %s', 'hyperpay-gateways'), $errorMessage),
133 'error'
134 );
135 } else {
136 $card = $result['card'] ?? [];
137 $card['paymentBrand'] = $result['paymentBrand'] ?? 'unknown'; // nosemgrep: audit.php.wp.security.xss.sanitized-var-passed-to-method
138
139
140 $agreementId = $result['customParameters']['recurringPaymentAgreement'] ?? '';
141 $initialTxId = $result['CardholderInitiatedTransactionID'] ??
142 $result['standingInstruction']['initialTransactionId'] ??
143 '';
144
145 // Enrich card data for storage.
146 $card['recurringPaymentAgreement'] = $agreementId;
147 $card['initial_tx_id'] = $initialTxId;
148
149
150 $token = TokenManager::save($result['registrationId'], $card, $this->id);
151
152 if ($token) {
153 wc_add_notice(
154 __('Your payment method has been saved successfully!', 'hyperpay-gateways'),
155 'success'
156 );
157 } else {
158 // If token creation fails locally
159 wc_add_notice(__('Failed to save Card.', 'hyperpay-gateways'), 'error');
160 }
161
162 // auto revisal after register the card
163 $this->auto_revisal($result['id']);
164 }
165
166 if (!headers_sent()) {
167 wp_safe_redirect($paymentMethodsUrl);
168 exit;
169 }
170
171 echo "<script>window.location.href='" . esc_url($paymentMethodsUrl) . "';</script>";
172 exit;
173 }
174
175 /**
176 * Renders the registration form using the checkout ID.
177 *
178 * @param string $checkoutId
179 */
180 protected function display_registration_form(string $checkoutId): void
181 {
182 $brands = is_array($this->brands) ? implode(' ', $this->brands) : (string)$this->brands;
183 $postBackURL = wc_get_endpoint_url('hyperpay-registration');
184
185 $dataObj = [
186 'is_arabic' => esc_js($this->is_arabic ?? false),
187 'style' => esc_html($this->payment_style ?? 'card'),
188 'postBackURL' => esc_html($postBackURL . "?nonce=" . esc_html(wp_create_nonce('hyperpay-registration'))),
189 'payment_brands' => esc_html($brands),
190 'custom_style' => esc_html($this->custom_style ?? ''),
191 'scriptURL' => esc_html($this->script_url),
192 'checkoutId' => esc_html($checkoutId),
193 ];
194
195 View::render('registration-form.html', compact('dataObj'));
196 }
197
198 /**
199 * Prevents deletion of a payment token if it's currently linked to an active subscription.
200 *
201 * @param array $item The list item data.
202 * @param WC_Payment_Token $token The token object.
203 * @return array
204 */
205 public function protect_active_subscription_token(array $item, WC_Payment_Token $token): array
206 {
207 // Check only if the token belongs to this gateway
208 if ($token->get_gateway_id() !== $this->id) {
209 return $item;
210 }
211
212 $userId = get_current_user_id();
213 if (!$userId) {
214 return $item;
215 }
216
217 $subscriptions = SubscriptionsManager::getUsersSubscriptions($userId);
218
219 foreach ($subscriptions as $subscription) {
220 /* @var \WC_Subscription $subscription */
221 $subscriptionTokenId = (int)$subscription->get_meta('_payment_token_id', true);
222
223 if ($subscriptionTokenId === (int)$token->get_id()) {
224 // Token is in use by an active subscription, remove the delete action.
225 unset($item['actions']['delete']);
226 break;
227 }
228 }
229
230 return $item;
231 }
232
233 /**
234 * Handles the `woocommerce_payment_token_deleted` action to call the gateway API.
235 *
236 * @param int $tokenId The WooCommerce token ID.
237 * @param WC_Payment_Token $token The token object.
238 */
239 public function handle_wc_token_deleted(int $tokenId, WC_Payment_Token $token): void
240 {
241 // Only act on tokens belonging to this gateway and the current user
242 if ($token->get_gateway_id() !== $this->id || $token->get_user_id() !== get_current_user_id()) {
243 return;
244 }
245
246 $this->delete_registration($token->get_token());
247 }
248
249 /**
250 * Calls the Hyperpay API to delete a stored registration ID.
251 *
252 * @param string $registrationId The ID of the registration to delete.
253 * @return bool True on successful deletion, false otherwise.
254 */
255 private function delete_registration(string $registrationId): bool
256 {
257
258 $url = $this->ACI_base_url . '/v1/registrations/' . $registrationId .
259 "?entityId=" . $this->entityId .
260 "&testMode=" . $this->trans_mode;
261
262
263 $response = Http::delete($url, ['headers' => ["Authorization" => "Bearer {$this->accessToken}"]]);
264
265 return $this->is_successful_response($response);
266 }
267
268 /**
269 * Adds stored registration IDs (tokens) to request body for saved card checkouts.
270 *
271 * @param WC_Order $order The WooCommerce order.
272 * @return array
273 */
274 public function setExtraData(WC_Order $order): array
275 {
276 $customerId = $order->get_customer_id() ?: get_current_user_id();
277 $savedCards = WC_Payment_Tokens::get_customer_tokens($customerId, $this->id);
278 $registrations = [];
279
280
281 foreach (array_values($savedCards) as $index => $card) {
282 $registrations["registrations[{$index}].id"] = $card->get_token();
283 }
284
285 return ['body' => $registrations];
286 }
287
288
289 /**
290 * Hide Saved Cards in checkout page to use our saved cards form
291 *
292 * @see https://hyperpay.docs.oppwa.com/integrations/widget/registration-tokens
293 */
294
295 private function hideTokensInCheckout()
296 {
297 add_filter('woocommerce_get_customer_payment_tokens', function ($tokens, $customer_id, $gateway_id) {
298 global $wp;
299 if (is_checkout() && !absint($wp->query_vars['order-pay'] ?? '')) {
300 $filtered_tokens = array();
301 foreach ($tokens as $token_id => $token) {
302 if ('hyperpay' === $token->get_gateway_id()) {
303 continue;
304 }
305 $filtered_tokens[$token_id] = $token;
306 }
307 return $filtered_tokens;
308 }
309 return $tokens;
310 }, 10, 4);
311 }
312 }
313