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();
?>
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.
Invalid categories.
Invalid roles.
Invalid shipping fields.
Invalid block status updates fields.
Invalid payment fields.
Minimum Total invalid! Total must be a valid numeric value.
Invalid category api keys.
Invalid api keys ordered list.
API key invalid! Use the 32-character domain key from the website tab in your AgeChecker.Net account area.'
Invalid customer role.
Invalid page.
Invalid order status for "Set Order Status When Verification is Pending".
Invalid order status for "Set Order Status When Verification is Accepted".
Invalid order status for "Set Order Status When Verification is Denied".
Invalid order status for "Set Order Status when Verification is Disabled at Customer's Location".
Invalid order status for "Set Order Status when Verification is Blocked at Customer's Location".