id = 'integration-agechecker'; $this->method_title = 'AgeChecker.Net'; $product_categories = get_terms(array( 'taxonomy' => 'product_cat', 'hide_empty' => false, )); if (!function_exists('get_editable_roles')) { require_once(ABSPATH . '/wp-admin/includes/user.php'); } $roles = array(); foreach (get_editable_roles() as $role_name => $role_info) { $roles[] = $role_name; } $this->init_form_fields(); $this->init_settings(); add_action('wp_head', array( $this, 'add_script_head' ), 1); add_action('wp_footer', array( $this, 'add_script_footer' ), 1); add_action('woocommerce_update_options_integration_integration-agechecker', array( $this, 'process_admin_options' )); add_action('woocommerce_checkout_process', array( $this, 'validate_order_shortcode' )); // Add hook for WooCommerce Block Checkout add_action('woocommerce_store_api_checkout_update_order_from_request', array( $this, 'validate_order_blocks' ), 10, 2); add_action( 'woocommerce_new_order', array( $this, 'new_order' )); add_action( 'rest_api_init', array( $this, 'establish_api' )); $this->key = $this->get_option('key'); $this->secret = $this->get_option('secret'); $this->store_name = $this->get_option('store_name'); $this->element = $this->get_option('element'); $this->workflow_type = $this->get_option('workflow_type'); $this->set_status_pending = $this->get_option('set_status_pending'); $this->set_status_accepted = $this->get_option('set_status_accepted'); $this->set_status_denied = $this->get_option('set_status_denied'); $this->set_status_disabled = $this->get_option('set_status_disabled'); $this->set_status_blocked = $this->get_option('set_status_blocked'); $this->excluded_categories = $this->get_option('excluded_categories'); $this->categories_mode = $this->get_option('categories_mode'); $this->excluded_groups = $this->get_option('excluded_groups'); $this->groups_mode = $this->get_option('groups_mode'); $this->excluded_shipping = $this->get_option('excluded_shipping'); $this->excluded_payment = $this->get_option('excluded_payment'); $this->min_total = $this->get_option('min_total'); $this->form_trigger_name = $this->get_option('form_trigger_name'); $this->form_trigger_value = $this->get_option('form_trigger_value'); $this->form_trigger_mode = $this->get_option('form_trigger_mode'); $this->page = $this->get_option('page'); $this->enable_all = $this->get_option('enable_all'); $this->enable_noscript = $this->get_option('enable_noscript'); $this->script_location = $this->get_option('script_location'); $this->category_apikeys = $this->get_option('category_apikeys'); $this->apikeys_list = $this->get_option('apikeys_list'); $this->client_config = wp_specialchars_decode($this->get_option('client_config'), 'double'); $this->before_script = wp_specialchars_decode($this->get_option('before_script'), 'double'); $this->load_script = wp_specialchars_decode($this->get_option('load_script'), 'double'); $this->tag_order = $this->get_option('tag_order'); $this->set_customer_role = $this->get_option('set_customer_role'); $this->role_find_account = $this->get_option('role_find_account'); $this->customer_info = $this->get_option('customer_info'); $this->trigger_afterpayment_on_failed = $this->get_option('trigger_afterpayment_on_failed'); $this->trigger_afterpayment_on_completed = $this->get_option('trigger_afterpayment_on_completed'); $this->exclude_statuses = $this->get_option('exclude_statuses'); if($this->get_option('tag_order_setting_updated') != 'true') { if ($this->get_option('tag_order') == 'tag_none') { $this->update_option('tag_order', 'tag_notes'); } $this->update_option('tag_order_setting_updated', 'true'); } // Set settings to version without entity codes. $this->settings['client_config'] = $this->client_config; $this->settings['before_script'] = $this->before_script; if ($this->workflow_type == "after_payment") { add_filter('manage_woocommerce_page_wc-orders_columns', array($this, 'add_order_status_column_header')); // WC 7.1+ add_action('manage_woocommerce_page_wc-orders_custom_column', array($this, 'add_order_status_column_content'), 10, 2); // WC 7.1+ add_filter('manage_edit-shop_order_columns', array($this, 'add_order_status_column_header')); add_action('manage_shop_order_posts_custom_column', array($this, 'add_order_status_column_content'), 10, 2); add_action('admin_print_styles', array($this, 'add_order_status_column_style')); add_action('woocommerce_admin_order_data_after_order_details', array($this, 'add_verification_status_order_page')); } } public function init_form_fields() { $this->form_fields = array( 'settings_hider' => array( 'type' => 'settingshider', 'desc_tip' => false, ), 'workflow_type' => array( 'title' => 'Verification Workflow Type', 'type' => 'radio', 'description' => 'Specify where in the order process you want age verification to occur.', 'default' => 'before_payment', 'desc_tip' => false, ), 'key' => array( 'title' => '* API Key', 'type' => 'text', 'description' => 'The API key for this domain. Find it in the websites tab of your AgeChecker.Net account.', 'default' => '', 'desc_tip' => false, ), 'secret' => array( 'title' => '* Account Secret', 'type' => 'text', 'description' => 'The Account Secret for your AgeChecker account. Find it in the websites tab of your AgeChecker.Net account.', 'default' => '', 'desc_tip' => false, ), 'store_name' => array( 'title' => 'Store Name', 'type' => 'text', 'description' => 'Display name of your store in the popup.', 'default' => get_bloginfo('name'), 'desc_tip' => false, ), 'page' => array( 'title' => 'Page', 'type' => 'selectbox', 'description' => 'Page to show AgeChecker.Net on.', 'default' => 'checkout', 'list' => 'pages', 'desc_tip' => false, ), 'enable_all' => array( 'title' => 'Enable on All Pages', 'type' => 'radio', 'description' => 'This option only effects the "Before Payment" workflow.
NOTE: This should only be used if "Page" box above is not working. Try setting the page name in the "Page" input box above first before using this.', 'default' => 'dont_enable_all', 'desc_tip' => false, ), 'element' => array( 'title' => 'Element', 'type' => 'text', 'description' => 'Element selector to attach the popup event to when using the "Before Payment" workflow. You may need to change this if you use a custom theme/template. Please contact us if the popup is not showing when the element is clicked. The default element is the final checkout button.', 'default' => '#place_order,.wc-block-components-checkout-place-order-button', 'desc_tip' => false, ), 'trigger_afterpayment_on_failed' => array( 'title' => 'Trigger Popup on Order Failed', 'type' => 'radio', 'description' => 'Determines whether or not to trigger the verification popup if order status is failed.', 'default' => 'disable', 'desc_tip' => false, ), 'trigger_afterpayment_on_completed' => array( 'title' => 'Trigger Popup on Order Completed', 'type' => 'radio', 'description' => 'Determines whether or not to trigger the verification popup if order status is completed, and no verification has been performed yet.', 'default' => 'enable', 'desc_tip' => false, ), 'customer_info' => array( 'title' => 'Customer Information', 'type' => 'radio', 'description' => 'Specify which customer data set to use for age verification.', 'default' => 'billing', 'desc_tip' => false, ), 'set_status_pending' => array( 'title' => 'Set Order Status when Verification is Pending', 'type' => 'selectbox', 'description' => 'Change the status of an order when a verification is pending. (Such as waiting for photo ID submission or e-signature, or no verification has been performed at all yet) AgeChecker will not set the order status if set to "None".', 'default' => 'do_not_set_status', 'list' => 'order_status_pending', 'desc_tip' => false, ), 'set_status_accepted' => array( 'title' => 'Set Order Status when Verification is Accepted', 'type' => 'selectbox', 'description' => 'Change the status of an order when a verification is accepted. AgeChecker will not set the order status if set to "None".', 'default' => 'do_not_set_status', 'list' => 'order_status_accepted', 'desc_tip' => false, ), 'set_status_denied' => array( 'title' => 'Set Order Status when Verification is Denied or Failed', 'type' => 'selectbox', 'description' => 'Change the status of an order when a verification is denied, user is banned, or when an error occured while running the verification. AgeChecker will not set the order status if set to "None".', 'default' => 'do_not_set_status', 'list' => 'order_status_denied', 'desc_tip' => false, ), 'set_status_disabled' => array( 'title' => 'Set Order Status when Verification is Disabled at Customer\'s Location', 'type' => 'selectbox', 'description' => 'Change the status of an order when a verification is disabled and not needed at customer\'s location. AgeChecker will not set the order status if set to "None".', 'default' => 'do_not_set_status', 'list' => 'order_status_disabled', 'desc_tip' => false, ), 'set_status_blocked' => array( 'title' => 'Set Order Status when Verification is Blocked at Customer\'s Location', 'type' => 'selectbox', 'description' => 'Change the status of an order when a verification is blocked due to customer\'s location. AgeChecker will not set the order status if set to "None".', 'default' => 'do_not_set_status', 'list' => 'order_status_blocked', 'desc_tip' => false, ), 'exclude_statuses' => array( 'title' => 'Exclude Order Statuses', 'type' => 'listbox', 'description' => 'Prevent AgeChecker from triggering if the current order status is one of these selected statuses.', 'default' => '', 'list' => 'exclude_statuses', 'desc_tip' => false, ), 'excluded_categories' => array( 'title' => 'Exclude/Include Categories', 'type' => 'listbox', 'description' => 'NOTE: To enable age verification on all product categories, the list above should be empty and "Exclude listed roles from age verification" selected.', 'default' => '', 'list' => 'categories', 'desc_tip' => false, ), 'categories_mode' => array( 'type' => 'radio', 'default' => 'exclude', 'list' => 'categories', 'desc_tip' => false, ), 'excluded_groups' => array( 'title' => 'Exclude/Include Groups', 'type' => 'listbox', 'default' => '', 'list' => 'roles', 'desc_tip' => false, ), 'groups_mode' => array( 'type' => 'radio', 'default' => 'exclude', 'list' => 'roles', 'desc_tip' => false, ), 'excluded_shipping' => array( 'title' => 'Exclude Shipping Methods', 'type' => 'listbox', 'default' => '', 'list' => 'shipping_methods', 'desc_tip' => false, ), 'excluded_payment' => array( 'title' => 'Exclude Payment Methods', 'type' => 'listbox', 'default' => '', 'list' => 'payment_methods', 'desc_tip' => false, ), 'min_total' => array( 'title' => 'Minimum Total', 'type' => 'text', 'description' => 'Specify a minimum payment total that triggers age verification. (A value of 0 would age verify any payment total) E.g. A minimum total of 10 means any order with a payment total under $10 will not be age checked, while any total $10 or above will be age checked.', 'default' => '0.00', 'desc_tip' => false, ), 'form_trigger_name' => array( 'title' => 'Custom Form Trigger (Advanced)', 'type' => 'titled_text', 'header' => 'Element name', 'description' => '', 'default' => '', 'desc_tip' => false, ), 'form_trigger_value' => array( 'type' => 'titled_text', 'header' => 'Element value', 'default' => '', 'desc_tip' => false, ), 'form_trigger_mode' => array( 'type' => 'radio', 'description' => 'This option only effects the "Before Payment" workflow.
This setting can be used to run (or stop) age verification based on the value of an element from the checkout form.', 'default' => 'run', 'desc_tip' => false, ), 'enable_noscript' => array( 'title' => 'Redirect users if Javascript is disabled', 'type' => 'radio', 'description' => 'Since AgeChecker requires Javascript to load, users will be redirected to a page letting them know to enable Javascript if it\'s disabled. Only disable this if you have another way put in place that alerts users to re-enable Javascript if disabled.', 'default' => 'enable_noscript', 'desc_tip' => false, ), 'script_location' => array( 'title' => 'Script Location', 'type' => 'radio', 'description' => 'This option only effects the "Before Payment" workflow.
Location of script in site source', 'default' => 'head', 'desc_tip' => false, ), 'category_apikeys' => array( 'title' => 'Category Specific API Keys (Advanced)', 'type' => 'apikeys_listbox', 'default' => '{}', 'desc_tip' => false, ), 'apikeys_list' => array( 'type' => 'ordered_listbox', 'default' => 'default_api_key', 'list' => 'apikeys', 'description' => 'Move API keys with the arrow buttons to set priority of the key. Keys with a higher position on the list will take priority to be used than ones below it. (E.g. If you have a product in a cart with a category specific API key with age settings set to 21+, you would most likely want this to override another product in the cart with a category specific API key with 18+ age settings)
Default API Key = API key set in the "API Key" input box at the top of the settings page', 'desc_tip' => false, ), 'client_config' => array( 'title' => 'Additional Config (Advanced)', 'type' => 'textarea', 'description' => 'Other additional configuration options from our client API.', 'desc_tip' => false, 'css' => 'width: 400px' ), 'before_script' => array( 'title' => 'Before Load Script (Advanced)', 'type' => 'textarea', 'description' => 'Custom code to run before the script is loaded. Use "return" to prevent loading.', 'desc_tip' => false, 'css' => 'width: 400px' ), 'load_script' => array( 'title' => 'Custom Script Loader (Advanced)', 'type' => 'textarea', 'description' => 'If filled, this will overwrite the default code for the section that starts with "w.AgeCheckerConfig" and ends with "h.insertBefore(a,h.firstChild);".', 'desc_tip' => false, 'css' => 'width: 400px' ), 'tag_order' => array( 'title' => 'Tag Orders', 'type' => 'radio', 'description' => 'This option only effects the "Before Payment" workflow. "After Payment" workflow will always add a verification status and UUID to the order fields, ignoring this setting.', 'default' => 'tag_none', 'desc_tip' => false, ), 'set_customer_role' => array( 'title' => 'Set Customer Role', 'type' => 'selectbox', 'description' => 'Add a role to a logged-in or newly created customer\'s list of user roles upon accepted verification. (Customers must be logged in to their account for the role to be added)', 'default' => 'do_not_set_role', 'list' => 'customer_role', 'desc_tip' => false, ), 'role_find_account' => array( 'type' => 'radio', 'description' => 'If first option is enabled, a user\'s account will be attempted to be found based on a guest order\'s email and billing details. If matching, the role will be applied to the found user account.', 'default' => 'false', 'desc_tip' => false, ), 'enable_noscript' => array( 'title' => 'Redirect users if Javascript is disabled', 'type' => 'radio', 'description' => 'Since AgeChecker requires Javascript to load, users will be redirected to a page letting them know to enable Javascript if it\'s disabled. Only disable this if you have another way put in place that alerts users to re-enable Javascript if disabled.', 'default' => 'enable_noscript', 'desc_tip' => false, ), ); } public function generate_selectbox_html($key, $data) { $field = $this->plugin_id . $this->id . '_' . $key; $array = array(); $default = ""; $selected = $this->get_option($key); if($key == "set_customer_role") { $default = "do_not_set_role"; foreach (get_editable_roles() as $role_name => $role_info) { $array[$role_name] = $role_name; } } else if(substr($key, 0, strlen("set_status_")) === "set_status_") { $default = "do_not_set_status"; $order_statuses = wc_get_order_statuses(); foreach ($order_statuses as $status_tag => $status_name) { $array[$status_tag] = $status_name; } } else if ($key == "page") { $pages = get_pages(); $current_found = false; $alt_selected = null; foreach ($pages as $page) { $slug = $page->post_name; if(empty($slug)) { continue; } if($slug === $selected) { $current_found = true; } if(strpos($slug, 'checkout') !== false) { $alt_selected = $slug; } $array[$slug] = $page->post_title; } // If current saved page was not found, then set the page slug that contains "checkout" to be the selected value if (!$current_found && $alt_selected != null) { $selected = $alt_selected; } } ob_start(); ?> get_tooltip_html($data); ?>

get_description_html($data); ?>
plugin_id . $this->id . '_' . $key; $excluded = true; if($this->get_option($key) == 'include') $excluded = false; ob_start(); ?> get_tooltip_html($data); ?>

get_option($key); ?> > Tag Order Notes with age verification UUID
> Tag Order Custom Fields with age verification UUID
> Tag Order Notes & Custom Fields with age verification UUID
> Don't tag order with age verification UUID
get_option($key); ?> > Load only on page specified in "Page" input box
> Enable AgeChecker script to all pages
get_option($key); ?> > Head
> Footer
get_option($key); ?> > Attempt to find user account based on guest order details
> Only use user accounts associated with orders
get_option($key); ?> onclick="changeWorkflow(this.value);"> Before Payment - Customers will be prompted with AgeChecker before completing their order on the checkout page. Order will be stopped if user fails verification.
onclick="changeWorkflow(this.value);"> After Payment - Customers will be prompted with AgeChecker after completing their order. All orders will be submitted, with a status of the verification added to the order custom fields.
get_option($key); ?> > Enable Redirection
> Disable Redirection
get_option($key); ?> > Enable popup if order status is failed
> Disable popup if order status is failed
get_option($key); ?> > Enable popup if order status is completed
> Disable popup if order status is completed
get_option($key); ?>

Trigger Result:

> Run age verification
> Stop age verification
get_option($key); ?> > Use billing information
> Use shipping information
> Exclude listed from age verification
> Include listed for age verification

get_description_html($data); ?>
term_id == $id) { return $c->name; } } return "[DELETED CATEGORY]"; } else { return $value; } } public function generate_listbox_html($key, $data) { $field = $this->plugin_id . $this->id . '_' . $key; $product_categories = get_terms(array( 'taxonomy' => 'product_cat', 'hide_empty' => false, )); $roles = array(); foreach (get_editable_roles() as $role_name => $role_info) { $roles[] = $role_name; } $shipping_methods = array(); foreach(WC_Shipping_Zones::get_zones() as $zone_key => $zone ) { foreach($zone['shipping_methods'] as $method_key => $method ) { $str = "(".$zone['zone_name'].") ".$method->title; $str = str_replace('"', "", $str); $str = str_replace("'", "", $str); $str = str_replace("`", "", $str); $str = str_replace(",", "", $str); $shipping_methods[] = $str; } } $exclude_statuses = wc_get_order_statuses(); $payment_methods = WC()->payment_gateways->payment_gateways(); ob_start(); ?> get_tooltip_html($data); ?>

get_description_html($data); ?>
plugin_id . $this->id . '_' . $key; $product_categories = get_terms(array( 'taxonomy' => 'product_cat', 'hide_empty' => false, )); ob_start(); ?> get_tooltip_html($data); ?>

Select a product category in the list above.

get_description_html($data); ?>
plugin_id . $this->id . '_' . $key; ob_start(); ?> get_tooltip_html($data); ?>

Add and order API keys:

get_description_html($data); ?>
plugin_id . $this->id . '_' . $key; ob_start(); ?> get_tooltip_html($data); ?>

get_description_html($data); ?>
$column_info) { $new_columns[$column_name] = $column_info; if ('order_status' === $column_name) { $new_columns['order_agechecker'] = __('AgeChecker Status', 'Verified'); } } return $new_columns; } function get_style($order) { $status = $order->get_meta('AgeChecker Status'); $status = strip_tags(trim(substr($status, 0, strpos($status, "-")))); $styles = array( "Accepted" => "agechecker_status--accepted", "Denied" => "agechecker_status--denied", "Photo ID Required" => "agechecker_status--pending", "Signature Required" => "agechecker_status--pending", "Location Blocked" => "agechecker_status--denied", "Buyer Banned" => "agechecker_status--denied", "Error" => "agechecker_status--denied", ); return isset($styles[$status]) ? $styles[$status] : "agechecker_status--void"; } function add_order_status_column_content($column, $post_id) { if ( 'order_agechecker' === $column ) { $order = wc_get_order($post_id); if(!$order) { return; } $status = $order->get_meta('AgeChecker Status'); $status = strip_tags(trim(substr($status, 0, strpos($status, "-")))); $style = $this->get_style($order); if(!empty($status)) { ?>
get_style($order); ?>

Age Verification Status

get_meta('AgeChecker Status') ? strip_tags($order->get_meta('AgeChecker Status')) : "Customer has not performed verification for this order."; ?>

get_meta('AgeChecker UUID') ? ("Verification UUID: " . strip_tags($order->get_meta('AgeChecker UUID'))) : ""; ?>

get_payment_method(); } if ($this->is_before_excluded($chosenPayment)) { return true; } $uuid = ""; if(isset($request)) { $body = $request->get_body(); if(isset($body)) { $post = json_decode($body); if(isset($post->extensions)) { $extensions = $post->extensions; if(isset($extensions->agecheckernet_checkout) && isset($extensions->agecheckernet_checkout->agechecker_uuid)) { $uuid = $extensions->agecheckernet_checkout->agechecker_uuid; } } } } $this->validate_order($uuid); $this->add_verified_notes($order, $uuid); } public function validate_order_shortcode() { if ($this->is_before_excluded($_POST["payment_method"])) { return true; } $this->validate_order($_POST["agechecker_uuid"]); } public function validate_order($uuid) { // Check custom form trigger if (!empty($this->get_option("form_trigger_name")) && !empty($this->get_option("form_trigger_value"))) { $mode = $this->get_option("form_trigger_mode"); if ($mode === "run" && strtolower($_POST[$this->get_option("form_trigger_name")]) !== strtolower($this->get_option("form_trigger_value"))) { return true; } if ($mode === "stop" && strtolower($_POST[$this->get_option("form_trigger_name")]) === strtolower($this->get_option("form_trigger_value"))) { return true; } } // In order to work around Google Pay or Apple Pay from the WooCommerce Square plugin, we check if // 'wc-square-digital-wallet-type' is included in the order data, and if so then set a cookie // to trigger the AgeChecker window on the user's end so it shows after closing the Google Pay or // Apple Pay window, but catching it before it submits the order and finalizing the payment. // If agechecker_uuid is included in the order data, then the user already went through, so continue. if(!isset($uuid) && isset($_POST['wc-square-digital-wallet-type'])) { $order_data = array( "ac_perform_id" => substr(str_shuffle('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'),1,24), "action" => $_POST["action"], "billing_address_1" => $_POST["billing_address_1"], "billing_address_2" => $_POST["billing_address_2"], "billing_city" => $_POST["billing_city"], "billing_company" => $_POST["billing_company"], "billing_country" => $_POST["billing_country"], "billing_email" => $_POST["billing_email"], "billing_first_name" => $_POST["billing_first_name"], "billing_last_name" => $_POST["billing_last_name"], "billing_phone" => $_POST["billing_phone"], "billing_postcode" => $_POST["billing_postcode"], "billing_state" => $_POST["billing_state"], "order_comments" => $_POST["order_comments"], "payment_method" => $_POST["payment_method"], "ship_to_different_address" => intval($_POST["ship_to_different_address"]), "shipping_address_1" => $_POST["shipping_address_1"], "shipping_address_2" => $_POST["shipping_address_2"], "shipping_city" => $_POST["shipping_city"], "shipping_company" => $_POST["shipping_company"], "shipping_country" => $_POST["shipping_country"], "shipping_first_name" => $_POST["shipping_first_name"], "shipping_last_name" => $_POST["shipping_last_name"], "shipping_method" => $_POST["shipping_method"], "shipping_postcode" => $_POST["shipping_postcode"], "shipping_state" => $_POST["shipping_state"], "terms" => $_POST["terms"], "wc-square-credit-card-exp-month" => intval($_POST["wc-square-credit-card-exp-month"]), "wc-square-credit-card-exp-year" => intval($_POST["wc-square-credit-card-exp-year"]), "wc-square-credit-card-last-four" => $_POST["wc-square-credit-card-last-four"], "wc-square-credit-card-payment-nonce" => $_POST["wc-square-credit-card-payment-nonce"], "wc-square-credit-card-exp-month" => $_POST["wc-square-credit-card-exp-month"], "wc-square-credit-card-payment-postcode" => $_POST["wc-square-credit-card-payment-postcode"], "wc-square-digital-wallet-type" => $_POST["wc-square-digital-wallet-type"], "_wpnonce" => $_POST["_wpnonce"] ); if(isset($_POST['wc-square-credit-card-buyer-verification-token'])) { $order_data['wc-square-credit-card-buyer-verification-token'] = $_POST['wc-square-credit-card-buyer-verification-token']; } setcookie("perform_agechecker_dw", json_encode($order_data)); wc_add_notice('Age verification required.', 'error'); } else { if (!isset($uuid) || strlen($uuid) != 32) { throw new Exception('You must complete the age verification process! Need help? Contact help@agechecker.net or try using another device or browser. (Error WP201)'); } else { $req = json_encode(array( 'key' => $this->key, 'uuid' => $uuid ), JSON_FORCE_OBJECT); $post = wp_remote_post("https://api.agechecker.net/v1/validate", array( 'method' => 'POST', 'timeout' => 20, 'httpversion' => '1.1', 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => $req )); if (is_wp_error($post)) { $error_body = json_encode(array( 'message' => 'Woo: WP203 error during validation', 'error' => $post->get_error_message() ), JSON_FORCE_OBJECT); $post = wp_remote_post("https://api.agechecker.net/v1/error_console", array( 'method' => 'POST', 'timeout' => 20, 'httpversion' => '1.1', 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => $error_body )); throw new Exception('The age verification service could not verify you at this time. Need help? Contact help@agechecker.net. (Error WP203)'); } else { $res = json_decode($post['body']); if ($res->status == "accepted") { return true; } else { throw new Exception('You have not been approved by the age verification process! Need help? Contact help@agechecker.net. (Error WP205)'); } } } } return false; } public function is_before_excluded($chosenPayment) { if ($this->workflow_type != "before_payment" || $this->is_excluded(false) || $this->is_total_excluded(false) || $this->is_shipping_excluded(null) || $this->is_payment_excluded($chosenPayment)) { return true; } else { return false; } } public function new_order( $order_id ) { $order = new WC_Order( $order_id ); if(!isset($_POST['agechecker_uuid'])) return; $uuid = $_POST['agechecker_uuid']; $this->add_verified_notes($order, $uuid); } public function add_verified_notes( $order, $uuid ) { if(!isset($uuid)) return; // Not order tagging logic, but we set this here for before_payment logic // so users that are created after an order is created are correctly set // with the role. if($this->workflow_type == "before_payment") { $this->set_verified_role($order); } if(isset($this->tag_order)) { if($this->tag_order == "tag_notes" || $this->tag_order == "tag_both") { $note = __("Age Verified order. \n\nAgeChecker UUID: ". $uuid); $order->add_order_note( $note ); } if($this->tag_order == "tag_fields" || $this->tag_order == "tag_both") { $order->update_meta_data( 'Age Verified', 'AgeChecker UUID: '. $uuid ); } } $order->save(); } public function validate_workflow_type_field($key) { $value = $_POST[ $this->plugin_id . $this->id . '_' . $key ]; $accountSecretValue = $_POST[ $this->plugin_id . $this->id . '_secret' ]; if ($value === "after_payment" && empty($accountSecretValue)) { ?>

Account secret invalid! Account secret must be filled out when using the after-payment workflow. Use the account secret from the website tab in your AgeChecker.Net account area.

get_option($key); } return $value; } public function validate_excluded_categories_field($key) { $value = $_POST["categoriesListData"]; if (!isset($value)) { ?>

Invalid categories.

get_option($key); } return $value; } public function validate_excluded_groups_field($key) { $value = $_POST["rolesListData"]; if (!isset($value)) { ?>

Invalid roles.

get_option($key); } return $value; } public function validate_excluded_shipping_field($key) { $value = $_POST["shipping_methodsListData"]; if (!isset($value)) { ?>

Invalid shipping fields.

get_option($key); } return $value; } public function validate_exclude_statuses_field($key) { $value = $_POST["exclude_statusesListData"]; if (!isset($value)) { ?>

Invalid block status updates fields.

get_option($key); } return $value; } public function validate_excluded_payment_field($key) { $value = $_POST["payment_methodsListData"]; if (!isset($value)) { ?>

Invalid payment fields.

get_option($key); } return $value; } public function validate_min_total_field($key) { $value = $_POST[ $this->plugin_id . $this->id . '_' . $key ]; if (!isset($value) || (isset($value) && !is_numeric($value))) { ?>

Minimum Total invalid! Total must be a valid numeric value.

get_option($key); } return $value; } public function validate_titled_text_field($key) { return $_POST[ $this->plugin_id . $this->id . '_' . $key ]; } public function validate_category_apikeys_field($key) { $value = $_POST["categoryApiKeysListData"]; if (!isset($value)) { ?>

Invalid category api keys.

get_option($key); } return $value; } public function validate_apikeys_list_field($key) { $value = $_POST["apikeysListData"]; if (!isset($value)) { ?>

Invalid api keys ordered list.

get_option($key); } return $value; } public function validate_key_field($key) { $value = $_POST[ $this->plugin_id . $this->id . '_' . $key ]; if (isset($value) && strlen($value) != 32) { ?>

API key invalid! Use the 32-character domain key from the website tab in your AgeChecker.Net account area.'

get_option($key); } return $value; } public function validate_set_customer_role_field($key) { $value = $_POST["customer_roleInput"]; if (!isset($value)) { ?>

Invalid customer role.

get_option($key); } return $value; } public function validate_page_field($key) { $value = $_POST["pagesInput"]; if (!isset($value)) { ?>

Invalid page.

get_option($key); } return $value; } public function validate_set_status_pending_field($key) { $value = $_POST["order_status_pendingInput"]; if (!isset($value)) { ?>

Invalid order status for "Set Order Status When Verification is Pending".

get_option($key); } return $value; } public function validate_set_status_accepted_field($key) { $value = $_POST["order_status_acceptedInput"]; if (!isset($value)) { ?>

Invalid order status for "Set Order Status When Verification is Accepted".

get_option($key); } return $value; } public function validate_set_status_denied_field($key) { $value = $_POST["order_status_deniedInput"]; if (!isset($value)) { ?>

Invalid order status for "Set Order Status When Verification is Denied".

get_option($key); } return $value; } public function validate_set_status_disabled_field($key) { $value = $_POST["order_status_disabledInput"]; if (!isset($value)) { ?>

Invalid order status for "Set Order Status when Verification is Disabled at Customer's Location".

get_option($key); } return $value; } public function validate_set_status_blocked_field($key) { $value = $_POST["order_status_blockedInput"]; if (!isset($value)) { ?>

Invalid order status for "Set Order Status when Verification is Blocked at Customer's Location".

get_option($key); } return $value; } function get_api_key($order_items) { // If no category specific keys are set, return default api key if($this->category_apikeys === "{}" || $this->category_apikeys === "[]") { return $this->key; } $category_keys = json_decode(stripslashes($this->category_apikeys)); if (is_array($category_keys)) { return $this->key; } $priority_list = explode(",", $this->apikeys_list); $default_index = array_search('default_api_key', $priority_list); $api_key = null; $priority_index = null; $cart_items = $order_items; if($order_items === false && !is_null(WC()->cart)) { $cart_items = WC()->cart->get_cart(); } foreach ($cart_items as $cart_item_key => $cart_item) { $prod_id = isset($cart_item['product_id']) ? $cart_item['product_id'] : $cart_item['data']->id; $cats = wp_get_post_terms($prod_id, 'product_cat'); foreach ($cats as $cat_key => $cat) { $cat_name = $cat->name; $cat_apikey = property_exists($category_keys, $cat_name) ? $category_keys->$cat_name : null; // If category's api key is null, then it's using // the default api key. Check priority before setting. if($cat_apikey === null && ($priority_index === null || $default_index < $priority_index)) { $priority_index = $default_index; $api_key = $this->key; continue; } // If category's api key is not null, then get priority // index for that api key. Compare against current priority_index // and update current priority_index if less than. $index = 0; foreach ($priority_list as $key) { if($priority_index !== null && $priority_index < $index) break; if($key == $cat_apikey) { $priority_index = $index; $api_key = $key; break; } $index++; } } } if(empty($api_key)) { $api_key = $this->key; } return $api_key; } function print_script_dw() { if(!file_exists(stream_resolve_include_path(ABSPATH . 'wp-content/plugins/woocommerce-square/woocommerce-square.php'))) return; include_once(ABSPATH . 'wp-content/plugins/woocommerce-square/woocommerce-square.php'); if(!in_array('woocommerce-square/woocommerce-square.php', apply_filters('active_plugins', get_option('active_plugins'))) || $this->workflow_type != "before_payment") return; ?> workflow_type != "before_payment") return; if($this->enable_noscript == "enable_noscript") { ?> workflow_type != "after_payment") return; $order = new WC_Order($order_id); $orderStatus = $order->get_status(); $excludedOrderStatuses = explode(",", $this->exclude_statuses); $normalizedExcludedStatuses = array_map(function($status) { return str_replace('wc-', '', trim($status)); }, $excludedOrderStatuses); if (!(sizeof($excludedOrderStatuses) == 1 && $excludedOrderStatuses[0] == "")) { if (in_array($orderStatus, $normalizedExcludedStatuses, true)) { return; }; } if($orderStatus == "failed" && $this->trigger_afterpayment_on_failed == "disable") return; if($orderStatus == "completed" && $this->trigger_afterpayment_on_completed == "disable") return; $status = $order->get_meta('AgeChecker Status'); if(!empty($status)) { $status = strip_tags(trim(substr($status, 0, strpos($status, "-")))); if($status != "Denied" && $status != "Photo ID Required" && $status != "Signature Required") { return; } } if($this->enable_noscript == "enable_noscript") { ?> workflow_type == "after_payment" && is_checkout() && is_wc_endpoint_url('order-received')) { global $wp; $order_id = absint($wp->query_vars['order-received']); if ($order_id) { $this->add_script_afterpayment($order_id); } } if ($this->script_location == 'footer' || $this->is_excluded(false) || (!is_page($this->page) && $this->enable_all == 'dont_enable_all')) { return; } $this->print_script_beforepayment(); } public function add_script_footer() { $this->print_script_dw(); if ($this->script_location == 'head' || $this->is_excluded(false) || (!is_page($this->page) && $this->enable_all == 'dont_enable_all')) { return; } $this->print_script_beforepayment(); } public function add_script_afterpayment($order_id) { if (empty(wc_get_order($order_id))) return; $order = new WC_Order($order_id); $shipping_methods = array(); foreach ( $order->get_shipping_methods() as $shipping_method ) { $shipping_methods[] = $shipping_method->get_method_id() . ":" . $shipping_method->get_instance_id(); } if ($this->is_excluded($order->get_items()) || $this->is_total_excluded($order->get_total()) || $this->is_shipping_excluded($shipping_methods) || $this->is_payment_excluded($order->get_payment_method())) { return; } // Set pending order status for after payment workflow is available // Check if pending status has already been set. // We also check to make sure there hasn't already been an age verification performed. (Implying this is an order that was placed before the pending status setting was set) $pendingMeta = $order->get_meta('agechecker_pending_status'); $status = $order->get_meta('AgeChecker Status'); if($this->workflow_type == "after_payment" && $this->set_status_pending != "do_not_set_status" && empty($pendingMeta) && empty($status)) { $order->update_status($this->set_status_pending); // Set this so the plugin doesn't set the order status multiple times (e.g. Customer reloads the Thank You page or comes back to it later) $order->update_meta_data('agechecker_pending_status', 'set'); $order->save(); } $this->print_script_afterpayment($order_id); } public function is_shipping_excluded($chosen_methods) { $excluded_shipping_methods = explode(",", $this->excluded_shipping); if (!(sizeof($excluded_shipping_methods) == 1 && $excluded_shipping_methods[0] == "")) { if($chosen_methods === null) { $chosen_methods = WC()->session->get('chosen_shipping_methods'); } $all_shipping_methods = array(); foreach(WC_Shipping_Zones::get_zones() as $zone_key => $zone ) { foreach($zone['shipping_methods'] as $method_key => $method ) { $str = "(".$zone['zone_name'].") ".$method->title; $str = str_replace('"', "", $str); $str = str_replace("'", "", $str); $str = str_replace("`", "", $str); $str = str_replace(",", "", $str); $all_shipping_methods[$method->instance_id] = $str; } } if($chosen_methods) { foreach ($chosen_methods as $shipping_method_key => $shipping_method) { $title = $all_shipping_methods[substr(strstr($shipping_method, ":"), 1)]; foreach ($excluded_shipping_methods as $c) { if (strpos($title, $c) !== false) { return true; } } } } } return false; } public function is_payment_excluded($chosen_method) { $excluded_payment_methods = explode(",", $this->excluded_payment); if (!(sizeof($excluded_payment_methods) == 1 && $excluded_payment_methods[0] == "")) { if($chosen_method === null) { $chosen_method = WC()->session->get('chosen_payment_method'); } if($chosen_method) { foreach ($excluded_payment_methods as $c) { if ($chosen_method == $c) { return true; } } } } return false; } public function is_total_excluded($order_total) { $min_total = floatval($this->min_total); if($min_total == 0) { return false; } $total = $order_total; if($order_total === false && !is_null(WC()->cart)) { $total = WC()->cart->total; } return $total < $min_total; } public function is_excluded($order_items) { $current_user = wp_get_current_user(); if (!($current_user instanceof WP_User)) { return true; } $roles = $current_user->roles; $user_role = implode($roles); $excluded = true; $categories_excluded = strpos($this->categories_mode, "exclude"); $categories = explode(",", $this->excluded_categories); $product_categories = get_terms(array( 'taxonomy' => 'product_cat', 'hide_empty' => false, )); if (!(sizeof($categories) == 1 && $categories[0] == "")) { $cart_items = $order_items; if($order_items === false && !is_null(WC()->cart)) { $cart_items = WC()->cart->get_cart(); } if($categories_excluded !== false) { foreach ($cart_items as $cart_item_key => $cart_item) { $prod_id = isset($cart_item['product_id']) ? $cart_item['product_id'] : $cart_item['data']->id; $noExclude = true; foreach ($categories as $c) { $cat = $this->product_name_from_value($c, $product_categories); if (has_term($cat, 'product_cat', $prod_id)) { $noExclude = false; break; } } if ($noExclude) { $excluded = false; } } } else { foreach ($cart_items as $cart_item_key => $cart_item) { $prod_id = isset($cart_item['product_id']) ? $cart_item['product_id'] : $cart_item['data']->id; $noExclude = false; foreach ($categories as $c) { $cat = $this->product_name_from_value($c, $product_categories); if (has_term($cat, 'product_cat', $prod_id)) { $noExclude = true; break; } } if ($noExclude) { $excluded = false; } } } } else { $excluded = false; } $groups_excluded = strpos($this->groups_mode, "exclude"); $groups = explode(",", $this->excluded_groups); if (!(sizeof($groups) == 1 && $groups[0] == "")) { if($groups_excluded !== false) { foreach ($groups as $c) { if (strpos($user_role, $c) !== false || current_user_can($c)) { $excluded = true; break; } } } else if(!$excluded) { $excluded = true; foreach ($groups as $c) { if (strpos($user_role, $c) !== false || current_user_can($c)) { $excluded = false; break; } } } } return $excluded; } public function set_verified_role($order) { if($this->set_customer_role == 'do_not_set_role') return; $user = $order->get_user(); // Add user role if defined in settings, and user's logged in // Else, try to find a user account if setting is enabled if($user) { $user->add_role($this->set_customer_role); } else if($this->role_find_account == 'true') { $user = get_user_by('email', $order->get_billing_email()); if($user) { if($order->get_billing_first_name() == $user->billing_first_name && $order->get_billing_last_name() == $user->billing_last_name && $order->get_billing_address_1() == $user->billing_address_1 && $order->get_billing_city() == $user->billing_city && $order->get_billing_state() == $user->billing_state && $order->get_billing_postcode() == $user->billing_postcode && $order->get_billing_country() == $user->billing_country) { $user->add_role($this->set_customer_role); } } } } public function get_status_wording($body) { if(isset($body['blocked'])) { return "Buyer Banned - This customer is currently banned from performing verifications."; } else if(isset($body['error'])) { return "Error - Error performing the verification. If you believe this is a mistake, please contact AgeChecker support."; } $status = $body['status']; $denyReasons = array( 'invalid_id' => "The image uploaded was not a valid ID", 'info_missing' => "The name, DOB, or expiration was not visible in the image", 'info_mismatch' => "The name or date of birth on the ID did not match the information submitted", 'blocked' => "The user was banned", 'fake_id' => "The ID was a fake, sample, or blocked ID", 'blank_id' => "The image uploaded was blank or corrupted", 'expired' => "The ID was expired", 'ip_blocked' => "Buyer\'s IP address was blocked", 'location_blocked' => "Buyer\'s location was blocked", 'selfie_mismatch' => "The selfie does not match the face on the ID", 'sms_failed' => "Incorrect phone validation code", 'selfie_id_missing' => "ID is missing or unclear in the selfie image", 'selfie_not_provided' => "A selfie image with matching ID was not provided", 'location_disabled' => "The customer ordered from a location that doesn\'t require age verification according to your age settings.", 'verification_disabled' => "The customer ordered from a location that doesn\'t require age verification according to your age settings.", 'both_sides_needed' => "Both sides of the ID are needed to see the required information", 'id_validation_failed' => "Identity Validation failed", 'id_number_missing' => "License # not found", 'dropped_verification' => "The image could not be processed" ); switch($status) { case 'accepted': return 'Accepted - Customer successfully age verified.'; case 'denied': $denyReason = $denyReasons[$body['reason']]; if(isset($denyReason)) { return 'Denied - Customer failed the age verification for the following reason: '. $denyReasons[$body['reason']]; } return 'Denied - Customer failed the age verification.'; case 'photo_id': return 'Photo ID Required - Customer was prompted with photo ID, and has not submitted one yet. The customer is either still in the process of submitting, or closed the window without finishing the verification.'; case 'signature': return 'Signature Required - Customer was prompted to submit a signature, and has not submitted one yet. The customer is either still in the process of submitting, or closed the window without finishing the verification.'; case 'not_created': $reason = $body['reason']; if($reason == 'location_blocked') { return 'Location Blocked - The customer ordered from a location that is blocked according to your age settings.'; } else if($reason == 'verification_disabled') { return 'Location Disabled - The customer ordered from a location that doesn\'t require age verification according to your age settings.'; } break; default: return $status; } return $status; } public function update_order_verification($order_id, $body) { // Do not do anything to the order if the call to v1/create or v1/status // returned that AgeChecker is currently in maintenance mode if ($body['code'] && $body['code'] === 503) return; $order = wc_get_order($order_id); if(!$order) { return; } $uuid = $body['uuid']; $status = $body['status']; $order->update_meta_data('AgeChecker Status', $this->get_status_wording($body) ); if(isset($uuid)) { $order->update_meta_data('AgeChecker UUID', $uuid ); } $order->save(); if(isset($body['blocked']) || isset($body['error'])) { if($this->set_status_denied != "do_not_set_status") { $order->update_status($this->set_status_denied); } } if(isset($status)) { $orderStatus = $order->get_status(); if($status == "accepted") { if($this->set_status_accepted != "do_not_set_status") { $order->update_status($this->set_status_accepted); } $this->set_verified_role($order); } else if($status == "denied") { if($this->set_status_denied != "do_not_set_status") { $order->update_status($this->set_status_denied); } } else if($status == "photo_id" || $status == "signature") { if($this->set_status_pending != "do_not_set_status") { $order->update_status($this->set_status_pending); } } else if($status == "not_created") { $reason = $body['reason']; if($reason == 'location_blocked') { if($this->set_status_blocked != "do_not_set_status") { $order->update_status($this->set_status_blocked); } } else if($reason == 'verification_disabled') { if($this->set_status_disabled != "do_not_set_status") { $order->update_status($this->set_status_disabled); } } } } } public function initCors($value) { header('Access-Control-Allow-Origin: ' . esc_url_raw( site_url() )); return $value; } public function establish_api() { remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ); add_filter( 'rest_pre_serve_request', array($this, 'initCors')); register_rest_route( 'agechecker-net/v1', '/verify', array( 'methods' => 'POST', 'callback' => array( $this, 'handle_verification_v1' ), 'permission_callback' => '__return_true' ) ); register_rest_route( 'agechecker-net/v1', '/callback', array( 'methods' => 'POST', 'callback' => array( $this, 'handle_callback_v1' ), 'permission_callback' => '__return_true' ) ); } public function handle_verification_v1($req) { if ($this->workflow_type !== "after_payment") { return new WP_REST_Response(array('error' => 'Not found'), 404); } if(!$this->secret || strlen($this->secret) !== 16) { // Return with code/message so popup displays invalid config screen return new WP_REST_Response(array('error' => array('code' => 'invalid_secret', 'message' => 'Invalid secret.')), 503); } if(!isset($req['key']) || !isset($req['data']) || !isset($req['orderKey']) || empty(wc_get_order_id_by_order_key($req['orderKey']))) { return new WP_REST_Response(array('error' => 'Data is not valid'), 400); } $reqData = $req->get_param('data'); $orderId = wc_get_order_id_by_order_key($req['orderKey']); $order = wc_get_order($orderId); if ($order) { $customerEmail = $order->get_billing_email(); if ($customerEmail) $reqData['email'] = $customerEmail; } else { return new WP_REST_Response(array('error' => 'Invalid order'), 400); } $url = "https://api.agechecker.net/v1/create"; $data = [ 'key' => $req['key'], 'secret' => $this->secret, 'data' => $reqData, 'require' => $req['require'], 'options' => [ "callback_url" => esc_url_raw( site_url() ) ."/wp-json/agechecker-net/v1/callback", "metadata" => [ "orderId" => strval($orderId), "callbackpost_83qfbg84" => "true" // If changed or removed, check uses in core ] ] ]; if($_SERVER['REMOTE_ADDR'] != "::1" && $_SERVER['REMOTE_ADDR'] != "127.0.0.1" && $_SERVER['REMOTE_ADDR'] != "localhost") { $data['options']['customer_ip'] = $_SERVER['REMOTE_ADDR']; } $fields = json_encode($data); $ch = curl_init(); curl_setopt($ch,CURLOPT_URL, $url); curl_setopt($ch,CURLOPT_POST, true); curl_setopt($ch,CURLOPT_POSTFIELDS, $fields); curl_setopt($ch,CURLOPT_HTTPHEADER, array('Content-Type: application/json')); curl_setopt($ch,CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); if ($result === false) { throw new Exception(curl_error($ch), curl_errno($ch)); } $result = json_decode($result, true); $this->update_order_verification($orderId, $result); return new WP_REST_Response($result, 200); } public function handle_callback_v1($req) { if ($this->workflow_type !== "after_payment") { return new WP_REST_Response(array('error' => 'Not found'), 404); } $sig = $req->get_header('X-AgeChecker-Signature'); if(!$this->secret || strlen($this->secret) !== 16 || !$sig) { return new WP_REST_Response(array('error' => 'Unauthorized request'), 403); } $encoded = base64_encode(hash_hmac('sha1', $req->get_body(), $this->secret, true)); if(!hash_equals($encoded, $sig)) { return new WP_REST_Response(array('error' => 'Unauthorized request'), 403); } $body = json_decode($req->get_body(), true); if (!$body['uuid'] || !is_string($body['uuid']) || strlen($body['uuid']) !== 32) { return new WP_REST_Response(array('error' => 'Invalid verification uuid.'), 400); } $ch = curl_init(); $url = "https://api.agechecker.net/v1/status/". $body['uuid']; curl_setopt($ch,CURLOPT_URL, $url); curl_setopt($ch,CURLOPT_HTTPHEADER, array('X-AgeChecker-Secret: '. $this->secret)); curl_setopt($ch,CURLOPT_RETURNTRANSFER, true); $verification_info = curl_exec($ch); if ($verification_info === false) { throw new Exception(curl_error($ch), curl_errno($ch)); } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode !== 200) { return new WP_REST_Response(array('error' => 'Error retrieving verification.'), 400); } $verification_info = json_decode($verification_info, true); if (!$verification_info['metadata'] || !$verification_info['metadata']['orderid']) { return new WP_REST_Response(array('error' => 'Invalid verification.'), 400); } $this->update_order_verification($verification_info['metadata']['orderid'], $body); return new WP_REST_Response(array("success" => true), 200); } } endif;