PluginProbe
Route ‑ Shipping Protection / 2.4.9
Route ‑ Shipping Protection v2.4.9
2.4.17 2.4.16 2.4.15 2.4.14 2.4.13 2.4.12 2.4.11 2.4.10 2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.2.4 2.2.5 2.2.6 2.2.8 2.2.9 2.3.0 2.3.1 2.3.2 2.3.3 All 151 releases
routeapp / includes / class-routeapp-setup.php

class-routeapp-setup.php in Route ‑ Shipping Protection 2.4.9, at includes/class-routeapp-setup.php

957 lines 32.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * A Route Magento Extension that adds secure shipping
4 * insurance to your orders
5 *
6 * Php version 7.0^
7 *
8 * @category
9 * @package Route_Route
10 * @author Route Development Team <dev@routeapp.io>
11 * @copyright 2019 Route App Inc. Copyright (c) https://www.routeapp.io/
12 * @license https://www.routeapp.io/merchant-terms-of-use Proprietary License
13 * @link https://magento.routeapp.io/magento2/index.html
14 */
15
16
17 class Route_Setup
18 {
19
20 const WOOCOMMERCE = "woocommerce";
21
22 const REGISTRATION_FIXED = '0';
23 const REGISTRATION_STEP_USER_LOGIN_SUCCESS = '1-200';
24 const FAILED_REGISTRATION_STEP_USER = "1";
25 const FAILED_REGISTRATION_STEP_USER_DUPLICATED = '1-409';
26 const FAILED_REGISTRATION_STEP_USER_LOGIN_FAILED = '1-401';
27 const FAILED_REGISTRATION_STEP_USER_ACTIVATION = '2';
28 const FAILED_REGISTRATION_STEP_MERCHANT = '3';
29 const FAILED_REGISTRATION_STEP_MERCHANT_DUPLICATED = '3-409';
30
31 const FAILED_REGISTRATION = 'routeapp_failed_registration';
32 const USER_TOKEN_OPTION = 'routeapp_user_token';
33 const USER_ID_OPTION = 'routeapp_user_id';
34 const SECRET_TOKEN_OPTION = 'routeapp_secret_token';
35 const PUBLIC_TOKEN_OPTION = 'routeapp_public_token';
36 const USER_ID = 'routeapp_user_id';
37 const REGISTRATION_STEP = 'routeapp_route_registration_step';
38 const DASHBOARD = 'https://dashboard.route.com/login?redirect=onboarding';
39 const DASHBOARD_STAGE = 'https://dashboard-stage.route.com/login?redirect=onboarding';
40 const REDIRECTED = '2';
41 const MODULE_INSTALLED = '1';
42 /**
43 * After duplicate-user / login-conflict notice auto-redirect runs once, show manual link only (avoids redirect loops).
44 */
45 const USER_CONFLICT_AUTO_REDIRECT_DONE_OPTION = 'routeapp_user_conflict_auto_redirect_done';
46 const ACTIVATION_LINK = 'activation_link';
47 const SETUP_CHECK_WIDGET_INSTALLATION_DATE = 'route_setup_check_widget_installation_date';
48 const SETUP_CHECK_WIDGET_API_CALL = 'route_setup_check_widget_api_call';
49 const SETUP_CHECK_WIDGET_PHP = 'route_setup_check_widget_php';
50 const SETUP_CHECK_WIDGET_JS = 'route_setup_check_widget_js';
51 const ACTIVE = 'Active';
52
53 /**
54 * Stable per-site identifier for ownership / OTP callbacks (used in the ownership-resolution URL path).
55 *
56 * @var string
57 */
58 const STORE_HASH_OPTION = 'routeapp_store_hash';
59
60 /**
61 * Short alphanumeric id length (10–12) for /app/ownership-resolution/{hash}.
62 */
63 const STORE_HASH_LENGTH = 12;
64
65 /**
66 * Register rewrite, query vars, and front controller for /app/ownership-resolution/{storeHash}.
67 */
68 public static function init_ownership_routing() {
69 add_action( 'init', array( __CLASS__, 'register_ownership_rewrite_rule' ), 5 );
70 add_filter( 'query_vars', array( __CLASS__, 'ownership_query_vars' ) );
71 add_action( 'template_redirect', array( __CLASS__, 'template_redirect_ownership_resolution' ), 1 );
72 }
73
74 /**
75 * @param array $vars Query vars.
76 * @return array
77 */
78 public static function ownership_query_vars( $vars ) {
79 $vars[] = 'routeapp_ownership';
80 $vars[] = 'routeapp_store_hash';
81 return $vars;
82 }
83
84 /**
85 * Pretty URL: {site}/app/ownership-resolution/{storeHash} on the store origin.
86 */
87 public static function register_ownership_rewrite_rule() {
88 add_rewrite_rule(
89 '^app/ownership-resolution/([a-zA-Z0-9]{10,12})/?$',
90 'index.php?routeapp_ownership=1&routeapp_store_hash=$matches[1]',
91 'top'
92 );
93 }
94
95 /**
96 * Handle GET /app/ownership-resolution/{storeHash}?token= / ?oneTimeToken=
97 */
98 public static function template_redirect_ownership_resolution() {
99 if ( ! (int) get_query_var( 'routeapp_ownership' ) ) {
100 return;
101 }
102 $path_hash = get_query_var( 'routeapp_store_hash' );
103 $token = '';
104 if ( isset( $_GET['oneTimeToken'] ) ) {
105 $token = sanitize_text_field( wp_unslash( $_GET['oneTimeToken'] ) );
106 }
107 if ( $token === '' && isset( $_GET['token'] ) ) {
108 $token = sanitize_text_field( wp_unslash( $_GET['token'] ) );
109 }
110 self::ownership_resolution_handle( $path_hash, $token );
111 }
112
113 public static function init()
114 {
115 self::ensure_store_hash();
116
117 if ( ! self::get_route_public_instance() ) {
118 return;
119 }
120
121 if ( ! self::is_fresh_new_installation() ) {
122 self::update_merchant_status();
123 return;
124 }
125
126 self::create_user();
127 }
128
129 /**
130 * True when activation should run create_user() / merchant onboarding against the Route API.
131 *
132 * Gated only on missing valid merchant credentials. Requiring an empty routeapp_route_registration_step
133 * used to block re-onboarding after tokens were cleared while the step option stayed set.
134 *
135 * @return bool
136 */
137 public static function is_fresh_new_installation()
138 {
139 return ! self::has_valid_merchant_tokens();
140 }
141
142 /**
143 * Public/secret missing but we have prior onboarding state — user should use Dashboard login + OTP or POST user_login.
144 *
145 * @return bool
146 */
147 public static function should_prompt_merchant_token_recovery()
148 {
149 if ( self::has_merchant_tokens() ) {
150 return false;
151 }
152 return ! empty( get_option( self::USER_TOKEN_OPTION ) )
153 || ! empty( self::registration_step() )
154 || ! empty( get_option( 'routeapp_merchant_id' ) );
155 }
156
157 /**
158 * Create and persist a unique store hash for this WordPress installation (used in ownership-resolution URL).
159 *
160 * @return string
161 */
162 /**
163 * Whether the stored hash matches the short format (10–12 alnum).
164 *
165 * @param string $hash Hash.
166 * @return bool
167 */
168 private static function is_valid_store_hash_format( $hash ) {
169 return is_string( $hash ) && (bool) preg_match( '/^[a-zA-Z0-9]{10,12}$/', $hash );
170 }
171
172 public static function ensure_store_hash() {
173 $hash = get_option( self::STORE_HASH_OPTION );
174 if ( self::is_valid_store_hash_format( $hash ) ) {
175 return $hash;
176 }
177 if ( function_exists( 'wp_generate_password' ) ) {
178 $hash = wp_generate_password( self::STORE_HASH_LENGTH, false, false );
179 } else {
180 $hash = substr( bin2hex( random_bytes( 8 ) ), 0, self::STORE_HASH_LENGTH );
181 }
182 update_option( self::STORE_HASH_OPTION, $hash, false );
183 return $hash;
184 }
185
186 /**
187 * Stored store hash (must exist before building platformUrl for Route Dashboard).
188 *
189 * @return string
190 */
191 public static function get_store_hash() {
192 return (string) get_option( self::STORE_HASH_OPTION, '' );
193 }
194
195 /**
196 * Callback URL for Route Dashboard platformUrl: {store-web-url}/app/ownership-resolution/{storeHash}
197 * (avoids wp-json / rest_route encoding on the merchant origin).
198 *
199 * @return string
200 */
201 public static function get_ownership_resolution_platform_url() {
202 $store_hash = self::ensure_store_hash();
203 return home_url( '/app/ownership-resolution/' . $store_hash );
204 }
205
206 /**
207 * Prepare user data, call user create request
208 * and handle response
209 *
210 * @return bool
211 */
212 public static function create_user()
213 {
214 $routeapp_public = self::get_route_public_instance();
215 $response = $routeapp_public->routeapp_create_user(self::get_user_data());
216
217 try{
218 if ($routeapp_public->routeapp_last_request_has_failed()) {
219 self::set_registration_failed_as(
220 $routeapp_public->routeapp_last_request_has_conflicted() ?
221 self::FAILED_REGISTRATION_STEP_USER_DUPLICATED:
222 self::FAILED_REGISTRATION_STEP_USER
223 );
224 throw new Exception($routeapp_public->routeapp_last_request_has_conflicted() ?
225 'FAILED_REGISTRATION_STEP_USER_DUPLICATED':
226 'FAILED_REGISTRATION_STEP_USER');
227 }
228 }catch (Exception $exception) {
229 $extraData = array(
230 'params' => self::get_user_data(),
231 'method' => 'POST',
232 'endpoint' => 'users'
233 );
234 $routeapp_public->routeapp_log($exception, $extraData);
235 return false;
236 }
237
238 if (self::register_user($response)) {
239 self::set_registration_failed_as(self::REGISTRATION_FIXED);
240 self::clear_user_conflict_auto_redirect_flag();
241 self::set_as_installed();
242
243 return true;
244 }
245 }
246
247 /**
248 * Prepare user data, call user create request
249 * and handle response
250 *
251 * @return bool
252 */
253 public static function register_user_login($username, $password)
254 {
255 $routeapp_public = self::get_route_public_instance();
256 $response = $routeapp_public->routeapp_user_login($username, $password);
257
258 try{
259 if ($routeapp_public->routeapp_last_request_has_failed()) {
260 self::set_registration_failed_as(self::FAILED_REGISTRATION_STEP_USER_LOGIN_FAILED);
261 throw new Exception('FAILED_REGISTRATION_STEP_USER_LOGIN_FAILED');
262 }
263 }catch (Exception $exception) {
264 $extraData = array(
265 'params' => array('username' => $username, 'password' => 'XXXXX'),
266 'method' => 'POST',
267 'endpoint' => 'login'
268 );
269 $routeapp_public->routeapp_log($exception, $extraData);
270 return false;
271 }
272
273 if (self::register_user($response, true)) {
274 self::set_registration_failed_as(self::REGISTRATION_STEP_USER_LOGIN_SUCCESS);
275 self::clear_user_conflict_auto_redirect_flag();
276
277 self::set_as_installed();
278
279 return true;
280 }
281 }
282
283 /**
284 * @param $response
285 * @param $is_active
286 *
287 * @return bool
288 */
289 public static function register_user($response, $is_active = false)
290 {
291
292 self::set_user_key($response->token);
293 self::set_user_id($response->id);
294
295 if (!$is_active) {
296 self::active_account();
297 }
298
299 return self::create_merchant();
300 }
301
302 /**
303 * Activate User Account
304 *
305 * @return bool
306 */
307 public static function active_account()
308 {
309 $routeapp_public = self::get_route_public_instance();
310 try{
311 $response = $routeapp_public->routeapp_activate_account(self::get_current_email());
312
313 try{
314 if(!$response){
315 self::set_registration_failed_as(self::FAILED_REGISTRATION_STEP_USER_ACTIVATION);
316 throw new Exception('FAILED_REGISTRATION_STEP_USER_ACTIVATION');
317 }
318 } catch (Exception $exception) {
319 $extraData = array(
320 'params' => self::get_current_email(),
321 'method' => 'POST',
322 'endpoint' => 'activate_account'
323 );
324 $routeapp_public->routeapp_log($exception, $extraData);
325 return false;
326 }
327 self::set_activation_link($response->set_password_url);
328 }catch (Exception $e){
329 self::set_registration_failed_as(self::FAILED_REGISTRATION_STEP_USER_ACTIVATION);
330 $extraData = array(
331 'params' => self::get_current_email(),
332 'method' => 'POST',
333 'endpoint' => 'activate_account'
334 );
335 $routeapp_public->routeapp_log($e, $extraData);
336 return false;
337 }
338
339 self::set_registration_failed_as(self::REGISTRATION_FIXED);
340
341 return true;
342
343 }
344
345 public static function has_valid_merchant_tokens(){
346 $routeapp_public = self::get_route_public_instance();
347 if ( ! $routeapp_public ) {
348 return false;
349 }
350 return self::has_merchant_tokens() &&
351 ! empty( $routeapp_public->routeapp_api_client->get_merchant() );
352 }
353
354 public static function has_merchant_tokens(){
355 $routeapp_public = self::get_route_public_instance();
356 if ( ! $routeapp_public ) {
357 return false;
358 }
359 return
360 ! empty( $routeapp_public->routeapp_get_public_token() ) &&
361 ! empty( $routeapp_public->routeapp_get_secret_token() );
362 }
363
364 public static function get_route_public_instance(){
365 global $routeapp_public;
366 return $routeapp_public;
367 }
368
369 /**
370 * @param $store_domain
371 * @param $merchant_store_domain
372 * @return bool
373 */
374 public static function is_same_domain($store_domain, $merchant_store_domain)
375 {
376 if(strpos($store_domain,'www.') === 0 || strpos($merchant_store_domain,'www.') === 0){
377 return strpos($store_domain, $merchant_store_domain) !== false || strpos($merchant_store_domain, $store_domain) !== false;
378 }
379
380 return $merchant_store_domain === $store_domain;
381 }
382
383 /**
384 * Prepare merchant data, call merchant create request
385 * and handle response
386 *
387 * @return bool
388 */
389 public static function create_merchant()
390 {
391 $created_domains = [];
392 $responses = [];
393
394 foreach (self::get_all_sites() as $site){
395
396 //Avoid duplicated merchant to the same user account
397 if (in_array($site->domain, $created_domains)) {
398 continue;
399 }
400
401 $associated = false;
402 $responses = [];
403
404 $created_domains[$site->blog_id] = $site->domain;
405 $merchant_data = self::get_merchant_data($site);
406 $routeapp_public = self::get_route_public_instance();
407 $merchant_creation_response = $routeapp_public->routeapp_create_merchant($merchant_data);
408
409 if($merchant_creation_response){
410 $responses[$site->blog_id] = $merchant_creation_response;
411 }
412
413 try{
414 if ($routeapp_public->routeapp_last_request_has_failed()) {
415
416
417 $merchants_by_user = $routeapp_public->routeapp_get_merchants();
418
419 if(!empty($merchants_by_user) && is_array($merchants_by_user)){
420 foreach ($merchants_by_user as $merchant) {
421 if(self::is_same_domain($merchant->store_domain, $site->domain)){
422 $responses[$site->blog_id] = $merchant;
423 $associated = true;
424 }
425 }
426 }
427
428 if (empty($responses)) {
429 self::set_registration_failed_as(
430 $routeapp_public->routeapp_last_request_has_conflicted() ?
431 self::FAILED_REGISTRATION_STEP_MERCHANT_DUPLICATED:
432 self::FAILED_REGISTRATION_STEP_MERCHANT
433 );
434 throw new Exception($routeapp_public->routeapp_last_request_has_conflicted() ?
435 'FAILED_REGISTRATION_STEP_MERCHANT_DUPLICATED':
436 'FAILED_REGISTRATION_STEP_MERCHANT');
437 }
438 }
439 } catch (Exception $exception) {
440 $routeapp_public->routeapp_log($exception);
441 return false;
442 }
443
444
445
446 //TODO Currently when we try to create merchant that already exists it's returning 200 http code success
447 //TODO So we need try to update it with current platform
448 if (!$associated && $routeapp_public->routeapp_last_request_has_success()) {
449
450 try{
451 if (!self::merchant_can_be_updated($responses[$site->blog_id])) {
452 self::set_registration_failed_as(self::FAILED_REGISTRATION_STEP_MERCHANT);
453 throw new Exception('FAILED_REGISTRATION_STEP_MERCHANT');
454 }
455 } catch (Exception $exception) {
456 $routeapp_public->routeapp_log($exception);
457 return false;
458 }
459
460 //TODO This is happening when we try to update merchant that user doesn't own
461 //TODO So we need try to recreate it with additional slash at the store's domain end
462 $merchant_data['store_domain'] = $merchant_data['store_domain'] . '/';
463 $responses[$site->blog_id] = $routeapp_public->routeapp_create_merchant($merchant_data);
464
465 try{
466 if ($routeapp_public->routeapp_last_request_has_failed()) {
467 self::set_registration_failed_as(
468 $routeapp_public->routeapp_last_request_has_conflicted() ?
469 self::FAILED_REGISTRATION_STEP_MERCHANT_DUPLICATED :
470 self::FAILED_REGISTRATION_STEP_MERCHANT);
471 throw new Exception($routeapp_public->routeapp_last_request_has_conflicted() ?
472 'FAILED_REGISTRATION_STEP_MERCHANT_DUPLICATED' :
473 'FAILED_REGISTRATION_STEP_MERCHANT');
474 }
475 } catch (Exception $exception) {
476 $routeapp_public->routeapp_log($exception);
477 return false;
478 }
479 }
480
481 }
482
483 foreach ($responses as $blog_id => $response){
484 self::set_public_key($response->public_api_key, $blog_id);
485 self::set_secret_key($response->prod_api_secret, $blog_id);
486 }
487
488 return true;
489 }
490
491 /**
492 * @param $response
493 * @return bool
494 */
495 public static function merchant_can_be_updated($response)
496 {
497 return (isset($response->platform_id) && strtolower($response->platform_id) == 'email') ||
498 (isset($response->status) && strtolower($response->status) != 'active');
499 }
500
501 /**
502 * @param $data
503 * @return int Total of orders in last month
504 */
505 public static function get_calculate_dealsize()
506 {
507 $created_at_min = date( 'Y-m-d', strtotime( '-1 month' ) );
508
509 $ordersCollection = wc_get_orders(
510 array(
511 'limit' => -1,
512 'date_after' => $created_at_min,
513 'return' => 'ids',
514 )
515 );
516
517 return strval( count( $ordersCollection ) );
518 }
519
520 /**
521 * @param $data
522 * @return array
523 */
524 private static function get_user_data()
525 {
526 $userData = [];
527 $user = self::get_current_user();
528 $userData['name'] = $user->user_firstname;
529 $userData['password'] = self::generate_temp_pass();
530 $userData['platform_id'] = self::WOOCOMMERCE;
531 $userData['phone'] = '';
532 $userData['primary_email'] = self::get_current_email();
533 return $userData;
534 }
535
536 /**
537 * @return string
538 */
539 private static function get_current_email()
540 {
541 return get_bloginfo('admin_email');
542 }
543
544 /**
545 * @param $data
546 * @return array
547 */
548 private static function get_merchant_data($site)
549 {
550 $merchantData = [];
551 $merchantData['platform_id'] = self::WOOCOMMERCE;
552 $merchantData['store_domain'] = $site->domain;
553 $merchantData['store_name'] = self::get_blog_name($site);
554 $merchantData['deal_size_order_count'] = self::get_calculate_dealsize();
555 $merchantData['country'] = self::get_store_country($site);
556 $merchantData['currency'] = self::get_currency($site);
557 $merchantData['source'] = self::WOOCOMMERCE;
558 $merchantData['status'] = self::ACTIVE;
559 return $merchantData;
560 }
561
562 /**
563 * Prepare Merchant Data
564 * @param $store
565 * @return mixed
566 */
567 public function prepare_compatibility_data()
568 {
569 $widgetPhpFlag = $this->get_setup_check_widget(self::SETUP_CHECK_WIDGET_PHP);
570 $widgetJsFlag = $this->get_setup_check_widget(self::SETUP_CHECK_WIDGET_JS);
571 $creationDate = $this->get_setup_check_widget(self::SETUP_CHECK_WIDGET_INSTALLATION_DATE);
572
573 $successInstallation = $widgetPhpFlag && $widgetJsFlag;
574 $data = [];
575 $data['php_flag'] = $widgetPhpFlag ? $widgetPhpFlag : 0;
576 $data['js_flag'] = $widgetJsFlag ? $widgetJsFlag : 0;
577 $data['success_installation'] = $successInstallation;
578 $data['install_date'] = $creationDate;
579 $data['store_domain'] = parse_url(get_site_url(get_current_blog_id()), PHP_URL_HOST);
580 $data['platform_id'] = self::WOOCOMMERCE;
581 // If get_plugins() isn't available, require it
582 if ( ! function_exists( 'get_plugins' ) )
583 require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
584 $data['modules'] = get_plugins();
585 $data['subject'] = $successInstallation ? 'Success Installation' : 'Installation Issues';
586 return $data;
587 }
588
589 public function can_send_compatibility_report(){
590 $apiCalled = $this->get_setup_check_widget(self::SETUP_CHECK_WIDGET_API_CALL);
591 $creationDate = $this->get_setup_check_widget(self::SETUP_CHECK_WIDGET_INSTALLATION_DATE);
592
593 return (time() > $creationDate && !$apiCalled);
594 }
595
596
597 /**
598 * @return array|
599 */
600 public static function get_all_sites()
601 {
602 if(is_multisite()) {
603 return get_sites();
604 }
605 $site = new stdClass();
606 $site->domain = parse_url(get_site_url(), PHP_URL_HOST);
607 $site->blog_id = 1;
608 return [$site];
609 }
610
611 /**
612 * Retrieve the blog name for a given site.
613 *
614 * Falls back to the site domain if the blog name is empty
615 * to prevent merchant creation failures on the Route API.
616 *
617 * @param $site
618 * @return string The blog name or domain as fallback.
619 */
620 private static function get_blog_name($site)
621 {
622 $name = is_multisite()
623 ? get_blog_option($site->blog_id, 'blogname')
624 : get_option('blogname');
625
626 return empty($name) ? $site->domain : $name;
627 }
628
629 /**
630 * @param $site
631 * @return mixed
632 */
633 private static function get_currency($site)
634 {
635 if(is_multisite())
636 return get_blog_option($site->blog_id, 'woocommerce_currency');
637 return get_woocommerce_currency();
638 }
639
640 /**
641 * @param $site
642 * @return mixed|string
643 */
644 private static function get_store_country($site){
645 if(is_multisite()) {
646 $country = get_blog_option($site->blog_id, 'woocommerce_default_country');
647 if (strpos($country, ':') > 0) {
648 $countryState = explode(':', $country);
649 return isset($countryState[0]) ? $countryState[0] : $country;
650 }
651 return $country;
652 }
653 return get_woocommerce_currency();
654 }
655
656 private static function generate_temp_pass()
657 {
658 return "pass" . substr(hash('sha256', rand()), 0, 10);
659 }
660
661 private static function set_registration_failed_as($step){
662 update_option(self::FAILED_REGISTRATION , $step);
663 }
664
665 /**
666 * Reset so the next duplicate-user / login-conflict flow can auto-redirect once again.
667 */
668 public static function clear_user_conflict_auto_redirect_flag() {
669 delete_option( self::USER_CONFLICT_AUTO_REDIRECT_DONE_OPTION );
670 }
671
672 private static function get_registration_failed_as(){
673 return get_option(self::FAILED_REGISTRATION);
674 }
675
676 private static function set_secret_key($token, $blog_id = null){
677 if (isset($blog_id) && is_multisite()) {
678 return update_blog_option($blog_id, self::SECRET_TOKEN_OPTION, $token);
679 }
680 return update_option(self::SECRET_TOKEN_OPTION , $token);
681 }
682
683 private static function set_user_key($token, $blog_id = null){
684 if (isset($blog_id) && is_multisite()) {
685 return update_blog_option($blog_id, self::USER_TOKEN_OPTION, $token);
686 }
687 return update_option(self::USER_TOKEN_OPTION , $token);
688 }
689
690 private static function set_user_id($userId, $blog_id = null){
691 if (isset($blog_id) && is_multisite()) {
692 return update_blog_option($blog_id, self::USER_ID_OPTION, $userId);
693 }
694 return update_option(self::USER_ID_OPTION , $userId);
695 }
696
697 private static function set_public_key($token, $blog_id = null){
698 if (isset($blog_id) && is_multisite()) {
699 return update_blog_option($blog_id, self::PUBLIC_TOKEN_OPTION, $token);
700 }
701 return update_option(self::PUBLIC_TOKEN_OPTION , $token);
702 }
703
704 private static function get_activation_link(){
705 return get_option(self::ACTIVATION_LINK);
706 }
707
708 private static function set_as_installed(){
709 update_option(self::REGISTRATION_STEP, self::MODULE_INSTALLED);
710 self::set_setup_check_widget(self::SETUP_CHECK_WIDGET_INSTALLATION_DATE, time());
711 }
712
713 private static function set_activation_link($activation_link){
714 update_option(self::ACTIVATION_LINK, $activation_link);
715 }
716
717 private static function registration_step(){
718 return get_option(self::REGISTRATION_STEP);
719 }
720
721 public static function is_installed(){
722 return self::registration_step() == self::MODULE_INSTALLED;
723 }
724
725 public static function has_user_login_succeed(){
726 return self::get_registration_failed_as() == self::REGISTRATION_STEP_USER_LOGIN_SUCCESS;
727 }
728
729 /**
730 * Set setup check widget
731 *
732 * @param $config
733 * @param bool $value
734 */
735 public static function set_setup_check_widget($config, $value = false)
736 {
737 if (!get_option($config)) {
738 $value = $value ? $value : 1;
739 update_option($config, $value);
740 }
741 }
742
743 /**
744 * Get setup check widget config
745 *
746 * @param $config
747 * @return mixed
748 */
749 public function get_setup_check_widget($config)
750 {
751 return get_option($config);
752 }
753
754 /**
755 * @return WP_User
756 */
757 private static function get_current_user()
758 {
759 return wp_get_current_user();
760 }
761
762
763 public static function retry_user(){
764 self::create_user();
765 wp_redirect( self_admin_url( "plugins.php" ) );
766 die();
767 }
768
769 public static function user_login() {
770 self::register_user_login($_POST['username'],$_POST['password']);
771 wp_redirect( self_admin_url( "plugins.php" ) );
772 die();
773 }
774
775 public static function retry_merchant(){
776 self::create_merchant();
777 wp_redirect( self_admin_url( "plugins.php" ) );
778 die();
779 }
780
781 public static function get_route_redirect(){
782 if(self::has_user_login_succeed()){
783 $custom_env = getenv('ROUTEAPP_ENVIRONMENT_ENDPOINT');
784 if (is_null($custom_env) || !$custom_env) {
785 $custom_env = isset($_SERVER['ROUTEAPP_ENVIRONMENT_ENDPOINT']) ? $_SERVER['ROUTEAPP_ENVIRONMENT_ENDPOINT'] : '';
786 }
787 if ($custom_env == 'stage') {
788 return self::DASHBOARD_STAGE;
789 }
790 return self::DASHBOARD ;
791 }
792 return self::get_activation_link();
793 }
794
795 private static function update_merchant_status(){
796 $routeapp_public = self::get_route_public_instance();
797 $merchant = $routeapp_public->routeapp_api_client->get_merchant();
798 if (empty($merchant)) {
799 return;
800 }
801
802 if (property_exists($merchant, 'status')) {
803 $routeapp_public->routeapp_api_client->update_merchant_status('Active');
804 }
805 }
806
807 /**
808 * Route Dashboard base URL (no path), respecting stage env like other helpers.
809 *
810 * @return string
811 */
812 public static function get_dashboard_root_url() {
813 $custom_env = getenv( 'ROUTEAPP_ENVIRONMENT_ENDPOINT' );
814 if ( is_null( $custom_env ) || ! $custom_env ) {
815 $custom_env = isset( $_SERVER['ROUTEAPP_ENVIRONMENT_ENDPOINT'] ) ? $_SERVER['ROUTEAPP_ENVIRONMENT_ENDPOINT'] : '';
816 }
817 return ( 'stage' === $custom_env ) ? 'https://dashboard-stage.route.com' : 'https://dashboard.route.com';
818 }
819
820 /**
821 * Route Dashboard login URL with platformUrl pointing at this site's OTP callback.
822 *
823 * @return string
824 */
825 public static function get_dashboard_ownership_login_url() {
826 $platform_url = self::get_ownership_resolution_platform_url();
827 return self::get_dashboard_root_url() . '/login?platformUrl=' . rawurlencode( $platform_url );
828 }
829
830 /**
831 * Dashboard login URL for Auth0 / post-activation flows (not legacy create-account).
832 *
833 * @return string
834 */
835 public static function get_dashboard_login_url() {
836 return self::get_dashboard_root_url() . '/login';
837 }
838
839 /**
840 * Whether the OTP merchant record matches this WordPress site's host.
841 *
842 * @param string $shop_domain Host from get_site_url().
843 * @param array $merchant Result from verify_otp.
844 * @return bool
845 */
846 public static function merchant_store_domain_matches_otp_merchant( $shop_domain, array $merchant ) {
847 if ( empty( $merchant['store_domain'] ) || ! is_string( $merchant['store_domain'] ) ) {
848 return false;
849 }
850 $m_domain = $merchant['store_domain'];
851 if ( 0 === strpos( $shop_domain, 'www.' ) ) {
852 return false !== strpos( $shop_domain, $m_domain ) || false !== strpos( $m_domain, $shop_domain );
853 }
854 return $m_domain === $shop_domain;
855 }
856
857 /**
858 * Persist merchant credentials after successful OTP and mark onboarding complete.
859 *
860 * @param array $merchant Result from Routeapp_API_Client::verify_otp.
861 */
862 public static function apply_ownership_verified_merchant( array $merchant ) {
863 $blog_id = is_multisite() ? get_current_blog_id() : null;
864 self::set_public_key( $merchant['public_api_key'], $blog_id );
865 self::set_secret_key( $merchant['prod_api_secret'], $blog_id );
866 $client = Routeapp_API_Client::getInstance();
867 $client->set_public_token( $merchant['public_api_key'] );
868 $client->set_secret_token( $merchant['prod_api_secret'] );
869 $client->set_merchant_id( $merchant['id'], $blog_id );
870 self::set_registration_failed_as( self::REGISTRATION_FIXED );
871 self::clear_user_conflict_auto_redirect_flag();
872 self::set_as_installed();
873
874 global $routeapp_public;
875 if ( $routeapp_public && isset( $routeapp_public->routeapp_api_client ) ) {
876 $routeapp_public->routeapp_api_client = new Routeapp_API_Client( $merchant['public_api_key'], $merchant['prod_api_secret'] );
877 $routeapp_public->routeapp_api_client->set_merchant_id( $merchant['id'], $blog_id );
878 $m = $routeapp_public->routeapp_api_client->get_merchant();
879 if ( $m && is_object( $m ) && property_exists( $m, 'status' ) ) {
880 $routeapp_public->routeapp_api_client->update_merchant_status( 'Active' );
881 }
882 }
883 }
884
885 /**
886 * Shared handler: path {storeHash} must match DB; verifies OTP and links merchant.
887 *
888 * @param string $path_hash Segment from URL.
889 * @param string $token OTP from ?token= or ?oneTimeToken=.
890 */
891 public static function ownership_resolution_handle( $path_hash, $token ) {
892 $expected = self::get_store_hash();
893 if ( $expected === '' ) {
894 self::ensure_store_hash();
895 $expected = self::get_store_hash();
896 }
897 if ( empty( $path_hash ) || $path_hash !== $expected ) {
898 wp_safe_redirect(
899 add_query_arg(
900 'route_ownership_error',
901 rawurlencode( 'invalid_store' ),
902 self::get_dashboard_login_url()
903 )
904 );
905 exit;
906 }
907
908 if ( empty( $token ) ) {
909 wp_safe_redirect(
910 add_query_arg(
911 'route_ownership_error',
912 rawurlencode( 'missing_token' ),
913 self::get_dashboard_login_url()
914 )
915 );
916 exit;
917 }
918
919 $merchant = Routeapp_API_Client::getInstance()->verify_otp( $token );
920 if ( is_wp_error( $merchant ) ) {
921 wp_safe_redirect(
922 add_query_arg(
923 'route_ownership_error',
924 rawurlencode( 'otp_verify_failed' ),
925 self::get_dashboard_login_url()
926 )
927 );
928 exit;
929 }
930
931 $shop_host = parse_url( get_site_url(), PHP_URL_HOST );
932 if ( ! self::merchant_store_domain_matches_otp_merchant( $shop_host, $merchant ) ) {
933 wp_safe_redirect(
934 add_query_arg(
935 'route_ownership_error',
936 rawurlencode( 'domain_mismatch' ),
937 self::get_dashboard_login_url()
938 )
939 );
940 exit;
941 }
942
943 self::apply_ownership_verified_merchant( $merchant );
944
945 if ( ! class_exists( 'Routeapp_Webhooks', false ) ) {
946 require_once dirname( dirname( __FILE__ ) ) . '/admin/class-routeapp-webhooks.php';
947 }
948 if ( class_exists( 'Routeapp_Webhooks' ) ) {
949 $webhooks = new Routeapp_Webhooks();
950 $webhooks->upsert_webhooks();
951 }
952
953 wp_safe_redirect( self::get_dashboard_root_url() );
954 exit;
955 }
956 }
957