| 1 |
<?php |
| 2 |
|
| 3 |
namespace Hyperpay\Gateways\Traits; |
| 4 |
if ( ! defined( 'ABSPATH' ) ) exit; |
| 5 |
|
| 6 |
use WC_Validation; |
| 7 |
use Exception; |
| 8 |
use Hyperpay\Gateways\Helpers\Http; |
| 9 |
|
| 10 |
/** |
| 11 |
* Express Payments Blocks integration |
| 12 |
* |
| 13 |
* @since 1.0.3 |
| 14 |
*/ |
| 15 |
trait HyperpayExpressBlock |
| 16 |
{ |
| 17 |
|
| 18 |
public function initialize() |
| 19 |
{ |
| 20 |
add_action('wc_ajax_hyperpay_prepare_checkout', [$this, "initCheckout"]); |
| 21 |
add_action('wc_ajax_hyperpay_update_method', [$this, 'ajax_update_shipping_method']); |
| 22 |
add_action('wc_ajax_hyperpay_update_checkout', [$this, "init_shipping_options"]); |
| 23 |
add_action('wc_ajax_hyperpay_process_checkout', [$this, 'process_checkout']); |
| 24 |
} |
| 25 |
|
| 26 |
|
| 27 |
public function initCheckout() |
| 28 |
{ |
| 29 |
$data = [ |
| 30 |
'headers' => [ |
| 31 |
"Authorization" => "Bearer {$this->accessToken}" |
| 32 |
], |
| 33 |
'body' => [ |
| 34 |
"entityId" => $this->entityId, |
| 35 |
"paymentType" => $this->trans_type, |
| 36 |
] |
| 37 |
]; |
| 38 |
|
| 39 |
|
| 40 |
if ($this->testMode) { |
| 41 |
$data['body']["testMode"] = $this->trans_mode; |
| 42 |
} |
| 43 |
|
| 44 |
if ($this->server_to_server) { |
| 45 |
$data['body']['paymentBrand'] = $this->brands[0]; |
| 46 |
} |
| 47 |
|
| 48 |
|
| 49 |
$response = wp_remote_post($this->token_url, $data); |
| 50 |
$result = json_decode(wp_remote_retrieve_body($response), true); |
| 51 |
|
| 52 |
|
| 53 |
if (is_wp_error($response)) { |
| 54 |
$error = $response->get_error_message(); |
| 55 |
} elseif (wp_remote_retrieve_response_code($response) != 200) { |
| 56 |
$error = $result['result']['description'] ?? "something wrong"; |
| 57 |
} |
| 58 |
|
| 59 |
if (isset($error) || !\preg_match("/^(000\.200)/", $result['result']['code'] ?? '')) { |
| 60 |
return wp_send_json_error($result['result']['description'] ?? $error); |
| 61 |
} |
| 62 |
|
| 63 |
|
| 64 |
return wp_send_json($result['id']); |
| 65 |
} |
| 66 |
|
| 67 |
|
| 68 |
public function init_shipping_options() |
| 69 |
{ |
| 70 |
|
| 71 |
$shipping_inputs = filter_input_array( |
| 72 |
INPUT_POST, |
| 73 |
[ |
| 74 |
'countryCode' => FILTER_SANITIZE_STRING, |
| 75 |
'administrativeArea' => FILTER_SANITIZE_STRING, |
| 76 |
'postalCode' => FILTER_SANITIZE_STRING, |
| 77 |
'locality' => FILTER_SANITIZE_STRING, |
| 78 |
'address' => FILTER_SANITIZE_STRING, |
| 79 |
'address_2' => FILTER_SANITIZE_STRING, |
| 80 |
] |
| 81 |
); |
| 82 |
|
| 83 |
$shipping_address = [ |
| 84 |
'country' => $shipping_inputs['countryCode'], |
| 85 |
'state' => $shipping_inputs['administrativeArea'], |
| 86 |
'postcode' => $shipping_inputs['postalCode'], |
| 87 |
'city' => $shipping_inputs['locality'], |
| 88 |
'address' => $shipping_inputs['address'], |
| 89 |
'address_2' => $shipping_inputs['address_2'], |
| 90 |
]; |
| 91 |
|
| 92 |
|
| 93 |
|
| 94 |
$product_view_options = filter_input_array(INPUT_POST, ['is_product_page' => FILTER_SANITIZE_STRING]); |
| 95 |
$should_show_itemized_view = !isset($product_view_options['is_product_page']) ? true : filter_var($product_view_options['is_product_page'], FILTER_VALIDATE_BOOLEAN); |
| 96 |
|
| 97 |
$shipping_options = $this->get_shipping_options($shipping_address, $should_show_itemized_view); |
| 98 |
return wp_send_json($shipping_options); |
| 99 |
} |
| 100 |
|
| 101 |
/** |
| 102 |
* Update shipping method. |
| 103 |
*/ |
| 104 |
public function ajax_update_shipping_method() |
| 105 |
{ |
| 106 |
|
| 107 |
if (!defined('WOOCOMMERCE_CART')) { |
| 108 |
define('WOOCOMMERCE_CART', true); |
| 109 |
} |
| 110 |
|
| 111 |
$shipping_methods = filter_input(INPUT_POST, 'shipping_method', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY); |
| 112 |
$this->update_shipping_method($shipping_methods); |
| 113 |
|
| 114 |
WC()->cart->calculate_totals(); |
| 115 |
|
| 116 |
$product_view_options = filter_input_array(INPUT_POST, ['is_product_page' => FILTER_SANITIZE_STRING]); |
| 117 |
$should_show_itemized_view = !isset($product_view_options['is_product_page']) ? true : filter_var($product_view_options['is_product_page'], FILTER_VALIDATE_BOOLEAN); |
| 118 |
|
| 119 |
$data = []; |
| 120 |
$data += $this->build_display_items($should_show_itemized_view); |
| 121 |
$data['result'] = 'success'; |
| 122 |
|
| 123 |
wp_send_json($data); |
| 124 |
} |
| 125 |
|
| 126 |
/** |
| 127 |
* Gets shipping options available for specified shipping address |
| 128 |
* |
| 129 |
* @param array $shipping_address Shipping address. |
| 130 |
* @param boolean $itemized_display_items Indicates whether to show subtotals or itemized views. |
| 131 |
* |
| 132 |
* @return array Shipping options data. |
| 133 |
* phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag |
| 134 |
*/ |
| 135 |
public function get_shipping_options($shipping_address, $itemized_display_items = false) |
| 136 |
{ |
| 137 |
try { |
| 138 |
// Set the shipping options. |
| 139 |
$data = []; |
| 140 |
|
| 141 |
// Remember current shipping method before resetting. |
| 142 |
$chosen_shipping_methods = WC()->session->get('chosen_shipping_methods'); |
| 143 |
$this->calculate_shipping($shipping_address); |
| 144 |
|
| 145 |
$packages = WC()->shipping->get_packages(); |
| 146 |
$shipping_rate_ids = []; |
| 147 |
|
| 148 |
if (!empty($packages) && WC()->customer->has_calculated_shipping()) { |
| 149 |
foreach ($packages as $package) { |
| 150 |
if (empty($package['rates'])) { |
| 151 |
throw new Exception(__('Unable to find shipping method for address.', 'hyperpay-gateways')); |
| 152 |
} |
| 153 |
|
| 154 |
foreach ($package['rates'] as $rate) { |
| 155 |
if (in_array($rate->id, $shipping_rate_ids, true)) { |
| 156 |
// The Payment Requests will try to load indefinitely if there are duplicate shipping |
| 157 |
// option IDs. |
| 158 |
throw new Exception(__('Unable to provide shipping options for Payment Requests.', 'hyperpay-gateways')); |
| 159 |
} |
| 160 |
$shipping_rate_ids[] = $rate->id; |
| 161 |
$data['shipping_options'][] = [ |
| 162 |
'identifier' => $rate->id, |
| 163 |
'label' => $rate->label, |
| 164 |
'detail' => '', |
| 165 |
'amount' => (float)($rate->cost), |
| 166 |
]; |
| 167 |
} |
| 168 |
} |
| 169 |
} else { |
| 170 |
throw new Exception(__('Unable to find shipping method for address.', 'hyperpay-gateways')); |
| 171 |
} |
| 172 |
|
| 173 |
// The first shipping option is automatically applied on the client. |
| 174 |
// Keep chosen shipping method by sorting shipping options if the method still available for new address. |
| 175 |
// Fallback to the first available shipping method. |
| 176 |
if (isset($data['shipping_options'][0])) { |
| 177 |
if (isset($chosen_shipping_methods[0])) { |
| 178 |
$chosen_method_id = $chosen_shipping_methods[0]; |
| 179 |
$compare_shipping_options = function ($a, $b) use ($chosen_method_id) { |
| 180 |
if ($a['id'] === $chosen_method_id) { |
| 181 |
return -1; |
| 182 |
} |
| 183 |
|
| 184 |
if ($b['id'] === $chosen_method_id) { |
| 185 |
return 1; |
| 186 |
} |
| 187 |
|
| 188 |
return 0; |
| 189 |
}; |
| 190 |
usort($data['shipping_options'], $compare_shipping_options); |
| 191 |
} |
| 192 |
|
| 193 |
$first_shipping_method_id = $data['shipping_options'][0]['id']; |
| 194 |
$this->update_shipping_method([$first_shipping_method_id]); |
| 195 |
} |
| 196 |
|
| 197 |
WC()->cart->calculate_totals(); |
| 198 |
|
| 199 |
$data += $this->build_display_items($itemized_display_items); |
| 200 |
$data['result'] = 'success'; |
| 201 |
} catch (Exception $e) { |
| 202 |
$data += $this->build_display_items($itemized_display_items); |
| 203 |
$data['result'] = $e->getMessage(); |
| 204 |
} |
| 205 |
|
| 206 |
return $data; |
| 207 |
} |
| 208 |
|
| 209 |
|
| 210 |
/** |
| 211 |
* Updates shipping method in WC session |
| 212 |
* |
| 213 |
* @param array $shipping_methods Array of selected shipping methods ids. |
| 214 |
*/ |
| 215 |
public function update_shipping_method($shipping_methods) |
| 216 |
{ |
| 217 |
$chosen_shipping_methods = WC()->session->get('chosen_shipping_methods'); |
| 218 |
|
| 219 |
if (is_array($shipping_methods)) { |
| 220 |
foreach ($shipping_methods as $i => $value) { |
| 221 |
$chosen_shipping_methods[$i] = wc_clean($value); |
| 222 |
} |
| 223 |
} |
| 224 |
|
| 225 |
WC()->session->set('chosen_shipping_methods', $chosen_shipping_methods); |
| 226 |
} |
| 227 |
|
| 228 |
/** |
| 229 |
* Builds the line items to pass to Payment Request |
| 230 |
* |
| 231 |
* @since 3.1.0 |
| 232 |
* @version 4.0.0 |
| 233 |
*/ |
| 234 |
protected function build_display_items($itemized_display_items = false) |
| 235 |
{ |
| 236 |
if (!defined('WOOCOMMERCE_CART')) { |
| 237 |
define('WOOCOMMERCE_CART', true); |
| 238 |
} |
| 239 |
|
| 240 |
$items = []; |
| 241 |
$lines = []; |
| 242 |
$subtotal = 0; |
| 243 |
$discounts = 0; |
| 244 |
$display_items = !apply_filters('hyperpay_wc_stripe_payment_request_hide_itemization', true) || $itemized_display_items; |
| 245 |
|
| 246 |
foreach (WC()->cart->get_cart() as $cart_item) { |
| 247 |
$subtotal += $cart_item['line_subtotal']; |
| 248 |
$amount = $cart_item['line_subtotal']; |
| 249 |
$quantity_label = 1 < $cart_item['quantity'] ? ' (x' . $cart_item['quantity'] . ')' : ''; |
| 250 |
$product_name = $cart_item['data']->get_name(); |
| 251 |
|
| 252 |
$lines[] = [ |
| 253 |
'label' => $product_name . $quantity_label, |
| 254 |
'amount' => (float)($amount), |
| 255 |
]; |
| 256 |
} |
| 257 |
|
| 258 |
if ($display_items) { |
| 259 |
$items = array_merge($items, $lines); |
| 260 |
} else { |
| 261 |
// Default show only subtotal instead of itemization. |
| 262 |
|
| 263 |
$items[] = [ |
| 264 |
'label' => 'Subtotal', |
| 265 |
'amount' => (float)($subtotal), |
| 266 |
]; |
| 267 |
} |
| 268 |
|
| 269 |
if (version_compare(WC_VERSION, '3.2', '<')) { |
| 270 |
$discounts = wc_format_decimal(WC()->cart->get_cart_discount_total(), WC()->cart->dp); |
| 271 |
} else { |
| 272 |
$applied_coupons = array_values(WC()->cart->get_coupon_discount_totals()); |
| 273 |
|
| 274 |
foreach ($applied_coupons as $amount) { |
| 275 |
$discounts += (float) $amount; |
| 276 |
} |
| 277 |
} |
| 278 |
|
| 279 |
$discounts = wc_format_decimal($discounts, WC()->cart->dp); |
| 280 |
$tax = wc_format_decimal(WC()->cart->tax_total + WC()->cart->shipping_tax_total, WC()->cart->dp); |
| 281 |
$shipping = wc_format_decimal(WC()->cart->shipping_total, WC()->cart->dp); |
| 282 |
$items_total = wc_format_decimal(WC()->cart->cart_contents_total, WC()->cart->dp) + $discounts; |
| 283 |
$order_total = version_compare(WC_VERSION, '3.2', '<') ? wc_format_decimal($items_total + $tax + $shipping - $discounts, WC()->cart->dp) : WC()->cart->get_total(false); |
| 284 |
|
| 285 |
if (wc_tax_enabled()) { |
| 286 |
$items[] = array( |
| 287 |
'label' => esc_html__( 'Tax', 'hyperpay-gateways' ), |
| 288 |
'amount' => (float) ( $tax ), |
| 289 |
); |
| 290 |
} |
| 291 |
|
| 292 |
if (WC()->cart->needs_shipping()) { |
| 293 |
$items[] = array( |
| 294 |
'label' => esc_html__( 'Shipping', 'hyperpay-gateways' ), |
| 295 |
'amount' => (float) ( $shipping ), |
| 296 |
); |
| 297 |
} |
| 298 |
|
| 299 |
if (WC()->cart->has_discount()) { |
| 300 |
$items[] = array( |
| 301 |
'label' => esc_html__( 'Discount', 'hyperpay-gateways' ), |
| 302 |
'amount' => (float) ( $discounts ), |
| 303 |
); |
| 304 |
} |
| 305 |
|
| 306 |
if (version_compare(WC_VERSION, '3.2', '<')) { |
| 307 |
$cart_fees = WC()->cart->fees; |
| 308 |
} else { |
| 309 |
$cart_fees = WC()->cart->get_fees(); |
| 310 |
} |
| 311 |
|
| 312 |
// Include fees and taxes as display items. |
| 313 |
foreach ($cart_fees as $key => $fee) { |
| 314 |
$items[] = [ |
| 315 |
'label' => $fee->name, |
| 316 |
'amount' => (float)($fee->amount), |
| 317 |
]; |
| 318 |
} |
| 319 |
|
| 320 |
return [ |
| 321 |
'displayItems' => $items, |
| 322 |
'total' => [ |
| 323 |
'label' => "By HyperPay", |
| 324 |
'amount' => max(0, apply_filters('woocommerce_stripe_calculated_total', (float)($order_total), $order_total, WC()->cart)), |
| 325 |
'pending' => false, |
| 326 |
], |
| 327 |
]; |
| 328 |
} |
| 329 |
|
| 330 |
|
| 331 |
|
| 332 |
/** |
| 333 |
* Calculate and set shipping method. |
| 334 |
* |
| 335 |
* @param array $address Shipping address. |
| 336 |
* |
| 337 |
* @since 3.1.0 |
| 338 |
* @version 5.0.0 |
| 339 |
*/ |
| 340 |
protected function calculate_shipping($address = []) |
| 341 |
{ |
| 342 |
$country = $address['country']; |
| 343 |
$state = $address['state']; |
| 344 |
$postcode = $address['postcode']; |
| 345 |
$city = $address['city']; |
| 346 |
$address_1 = $address['address']; |
| 347 |
$address_2 = $address['address_2']; |
| 348 |
|
| 349 |
// Normalizes state to calculate shipping zones. |
| 350 |
|
| 351 |
// Normalizes postal code in case of redacted data from Apple Pay. |
| 352 |
|
| 353 |
WC()->shipping->reset_shipping(); |
| 354 |
|
| 355 |
if ($postcode && WC_Validation::is_postcode($postcode, $country)) { |
| 356 |
$postcode = wc_format_postcode($postcode, $country); |
| 357 |
} |
| 358 |
|
| 359 |
if ($country) { |
| 360 |
WC()->customer->set_location($country, $state, $postcode, $city); |
| 361 |
WC()->customer->set_shipping_location($country, $state, $postcode, $city); |
| 362 |
} else { |
| 363 |
WC()->customer->set_billing_address_to_base(); |
| 364 |
WC()->customer->set_shipping_address_to_base(); |
| 365 |
} |
| 366 |
|
| 367 |
WC()->customer->set_calculated_shipping(true); |
| 368 |
WC()->customer->save(); |
| 369 |
|
| 370 |
$packages = []; |
| 371 |
|
| 372 |
$packages[0]['contents'] = WC()->cart->get_cart(); |
| 373 |
$packages[0]['contents_cost'] = 0; |
| 374 |
$packages[0]['applied_coupons'] = WC()->cart->applied_coupons; |
| 375 |
$packages[0]['user']['ID'] = get_current_user_id(); |
| 376 |
$packages[0]['destination']['country'] = $country; |
| 377 |
$packages[0]['destination']['state'] = $state; |
| 378 |
$packages[0]['destination']['postcode'] = $postcode; |
| 379 |
$packages[0]['destination']['city'] = $city; |
| 380 |
$packages[0]['destination']['address'] = $address_1; |
| 381 |
$packages[0]['destination']['address_2'] = $address_2; |
| 382 |
|
| 383 |
foreach (WC()->cart->get_cart() as $item) { |
| 384 |
if ($item['data']->needs_shipping()) { |
| 385 |
if (isset($item['line_total'])) { |
| 386 |
$packages[0]['contents_cost'] += $item['line_total']; |
| 387 |
} |
| 388 |
} |
| 389 |
} |
| 390 |
|
| 391 |
$packages = apply_filters('woocommerce_cart_shipping_packages', $packages); |
| 392 |
|
| 393 |
WC()->shipping->calculate_shipping($packages); |
| 394 |
} |
| 395 |
|
| 396 |
/** |
| 397 |
* Create order. Security is handled by WC. |
| 398 |
* |
| 399 |
* @since 3.1.0 |
| 400 |
* @version 5.1.0 |
| 401 |
*/ |
| 402 |
public function process_checkout() |
| 403 |
{ |
| 404 |
if (WC()->cart->is_empty()) { |
| 405 |
wp_send_json_error(__('Empty cart', 'hyperpay-gateways')); |
| 406 |
} |
| 407 |
|
| 408 |
if (!isset($_POST['checkoutId']) || !isset($_POST['hyperpay_nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['hyperpay_nonce'])), 'hyperpay_process_checkout')) { |
| 409 |
wp_send_json_error(__('Invalid request.', 'hyperpay-gateways')); |
| 410 |
} |
| 411 |
|
| 412 |
if (!defined('WOOCOMMERCE_CHECKOUT')) { |
| 413 |
define('WOOCOMMERCE_CHECKOUT', true); |
| 414 |
} |
| 415 |
|
| 416 |
|
| 417 |
$this->legacy = false; |
| 418 |
|
| 419 |
WC()->checkout()->process_checkout(); |
| 420 |
|
| 421 |
die(0); |
| 422 |
} |
| 423 |
|
| 424 |
|
| 425 |
public function process_payment($order_id) |
| 426 |
{ |
| 427 |
// that mean the request comes from checkout blocks |
| 428 |
if (isset($_POST['checkoutId'], $_POST['hyperpay_nonce']) && wp_verify_nonce(sanitize_key(wp_unslash($_POST['hyperpay_nonce'])), 'hyperpay_process_checkout')) { |
| 429 |
|
| 430 |
$checkoutId = sanitize_text_field(wp_unslash($_POST['checkoutId'])); |
| 431 |
$url = $this->token_url . "/$checkoutId"; |
| 432 |
|
| 433 |
|
| 434 |
$checkout = $this->getCheckoutData($order_id); |
| 435 |
$checkout['data']['body']['shopperResultUrl'] = $checkout['postBackURL']; |
| 436 |
|
| 437 |
// HTTP Request to oppwa to get checkout |
| 438 |
$response = Http::post($url, $checkout['data']); |
| 439 |
|
| 440 |
if (!\preg_match("/^(000\.200)/", $response['result']['code'] ?? '')) { |
| 441 |
return wp_send_json_error($response['result']['description'] ?? 'unable to update checkout'); |
| 442 |
} |
| 443 |
|
| 444 |
return wp_send_json(["result" => "SUCCESS"]); |
| 445 |
} |
| 446 |
|
| 447 |
// legacy checkout |
| 448 |
return parent::process_payment($order_id); |
| 449 |
} |
| 450 |
} |
| 451 |
|